Compare commits

...
232 Commits
Author SHA1 Message Date
Dan Luhring 407769e88c Merge pull request #314 from anchore/issue-291-java-parent-ref
Ensure java parent pkg ref isn't nil when looking for parent matches
2021-01-27 11:51:40 -05:00
Dan Luhring 1416e3cb7a Invert if statement to reduce nesting in archive parser
Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2021-01-27 10:34:52 -05:00
Dan Luhring 0ccfee03f1 Fix dependency resolution issue in test image
Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2021-01-27 10:30:49 -05:00
Alex Goodman bb1facbf81 ensure java parent pkg ref isnt nil when looking for parent matches
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2021-01-27 10:12:41 -05:00
Dan Luhring ae7cd6bbb7 Add test for Java cataloging with no main package
Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2021-01-27 10:12:41 -05:00
Dan Luhring 7bcdafe745 Merge pull request #312 from anchore/omit-empty-packages
Omit empty packages
2021-01-22 16:10:06 -05:00
Dan Luhring 4576c081b9 Improve output for test case
Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2021-01-22 15:20:42 -05:00
Dan Luhring d5779a9822 Clarify debug message for package.json omissions
Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2021-01-22 15:20:24 -05:00
Dan Luhring 9ec3ad58c8 Update regression test expected value and pin deps
Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2021-01-22 14:03:50 -05:00
Dan Luhring 176dfdd9c1 Don't create packages unless package.json has name and version
Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2021-01-22 13:56:54 -05:00
Alex Goodman 0f6288881b Merge pull request #308 from anchore/prefer-real-paths-on-glob-results
Prefer real paths over those with links for glob results
2021-01-05 21:32:24 -05:00
Alex Goodman 6a49717314 add VirtualPath to source.Location
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2021-01-05 21:16:42 -05:00
Alex Goodman 06641cfda2 prefer real paths for glob results
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2021-01-05 18:48:28 -05:00
Alex Goodman a5537943fa keep original dpkg md5sum location
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2021-01-05 14:30:05 -05:00
Alex Goodman 0030880e74 Merge pull request #307 from anchore/dup-readers-on-bulk-fetch
Duplicate reference readers for duplicate location resolutions
2021-01-05 14:12:42 -05:00
Alex Goodman fc8b431ea6 duplicate reference readers for duplicate location resolutions
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2021-01-05 13:54:39 -05:00
Alex Goodman bb70b0b43e Merge pull request #305 from anchore/add-has-path-to-resolver
Add HasPath() to Resolver interface for existence check
2021-01-04 19:45:03 -05:00
Alex Goodman ee0a02621a Merge pull request #306 from anchore/update-gemspec-glob
Update gemspec glob to include named nested specification directories
2021-01-04 19:42:26 -05:00
Alex Goodman 33c27c4f3d add HasPath() to Resolver interface
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2021-01-04 19:39:49 -05:00
Alex Goodman 133d180eec update gemspec glob to include named spec dirs
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2021-01-04 19:33:52 -05:00
Alex Goodman 37b96a241b ensure acceptance tests use existing snapshot dir
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2021-01-04 16:50:35 -05:00
Alex Goodman 7f4e8ab97d Fix symlink resolutions for constituent paths (#304)
* bump stereoscope to pull in content API refactors

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* incorporate symlink fixes

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* with filetree.File() adjustments

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* regress all-layers scope to not include dead-links + default tests to squashed scope

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* restore all layers resolver glob behavior (custom + lazy link resolution)

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* incorporate filetree link resolution options and restore no-follow dead link option for resolvers

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* removed path from lower-level FileTree.File() calls

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* bump stereoscope to pull in latest link resolution fixes

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* bump doublestar to v2 for directory resolver

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2021-01-04 16:41:45 -05:00
Dan Luhring 76446abd7d Merge pull request #299 from anchore/with-content-api-refactor
Bump stereoscope to pull in content API refactors
2021-01-04 14:41:00 -05:00
Dan Luhring c2799b35d8 Fix mock resolver interface implementation
Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2021-01-04 14:18:51 -05:00
Alex Goodman d475e6280a bump stereoscope to pull in content API refactors
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2021-01-04 14:07:14 -05:00
Dan Luhring e299a5355f Merge pull request #303 from anchore/fix-site-packages-dir
Handle site packages based on which egg file is parsed
2020-12-23 13:23:01 -05:00
Dan Luhring 359212e8ee Disable lint rule prealloc
Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2020-12-23 11:35:49 -05:00
Dan Luhring 52e719dcb8 Create MockResolver and use to improve python cataloger tests
Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2020-12-23 11:24:49 -05:00
Dan Luhring 13c289eb7e Add tests for determining site packages root
Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2020-12-23 08:22:31 -05:00
Dan Luhring c1fa701602 Apply lint fix
Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2020-12-22 17:46:33 -05:00
Dan Luhring 183b8f79d0 Handle site packages based on which egg file is parsed
Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2020-12-22 17:41:27 -05:00
Alex Goodman 558781eed6 Merge pull request #302 from anchore/add-manual-cache-buster
add manual cache buster to the pipeline
2020-12-22 16:32:59 -05:00
Alex Goodman 60f525af18 add manual cache buster to the pipeline
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-12-22 16:22:42 -05:00
Alfredo Deza 8dbb095fc6 Merge pull request #301 from anchore/issue-300
catalogers: Python runtime is not a Python package itself, ignore it
2020-12-22 15:18:56 -05:00
Alfredo Deza d0d7e849c9 catalogers: Python runtime is not a Python package itself, ignore it
Signed-off-by: Alfredo Deza <adeza@anchore.com>
2020-12-22 10:59:43 -05:00
Alex Goodman 6aaf9ee712 Incorporate import changes + add image overwrite option (#294)
* incorporate import changes + add image overwrite option

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* update import tests to account for arbitrary json shape

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-12-18 16:59:30 -05:00
Alex Goodman 75d89293ce fix acceptance tests
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-12-18 14:32:29 -05:00
7962002f81 Split dpk source into name and version (#297)
* Split dpk source into name and version

Signed-off-by: Zach Hill <zach@anchore.com>

* update dpkg status source name parsing

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

Co-authored-by: Dan Luhring <luhring@users.noreply.github.com>
Co-authored-by: Alex Goodman <alex.goodman@anchore.com>
2020-12-18 14:08:19 -05:00
Alex GoodmanandDan Luhring ea162f87f4 update dpkg license to include single-word entries (#298)
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

Co-authored-by: Dan Luhring <luhring@users.noreply.github.com>
2020-12-18 12:54:09 -05:00
Zach HillandDan Luhring 9adb57bcb5 Adds globs for .egg-info file for python detection as well as tests (#296)
* Adds globs for .egg-info file for python detection as well as tests

Signed-off-by: Zach Hill <zach@anchore.com>

* Fix lint error

Signed-off-by: Dan Luhring <dan.luhring@anchore.com>

Co-authored-by: Dan Luhring <dan.luhring@anchore.com>
2020-12-18 12:36:25 -05:00
Alex Goodman 0891faa756 Merge pull request #293 from anchore/revert-288-syft_docker_image
Revert "Add the ability to run syft from a scratch image."
2020-12-16 16:55:26 -05:00
Alex Goodman a56292e2e0 Revert "Add the ability to run syft from a scratch image."
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2020-12-16 16:54:34 -05:00
Toure Dunnon 688aa2e832 Merge pull request #288 from anchore/syft_docker_image
Add the ability to run syft from a scratch image.
2020-12-16 10:39:30 -05:00
Toure Dunnon 2c90ec84b9 Merge branch 'main' into syft_docker_image 2020-12-15 19:52:55 -05:00
Alex Goodman d1d7471f2f Merge pull request #290 from anchore/improve-python-cataloger
Improve performance of the python cataloger
2020-12-15 12:41:58 -05:00
Toure Dunnon a19496b846 added: Docker login github action to publish new images
Signed-off-by: Toure Dunnon <toure.dunnon@anchore.com>
2020-12-15 11:07:14 -05:00
Alex Goodman d94d7a7d80 add tests for content requester object
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-12-15 10:59:15 -05:00
Alex Goodman 45fed7c69b break out packageEntry into a separate file
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-12-15 10:59:14 -05:00
Alex Goodman e4a3e433b6 add content requested and refactor python cataloger to use it
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-12-15 10:59:14 -05:00
Alex Goodman 82c8a8e17b add mem profile option and refactor python cataloger for batch requests
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-12-15 10:59:14 -05:00
Alex Goodman be5917a058 add profiler dev option
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-12-15 10:59:13 -05:00
Toure Dunnon 9365625fc3 added: corrected request from review.
Signed-off-by: Toure Dunnon <toure.dunnon@anchore.com>
2020-12-15 09:27:09 -05:00
Toure Dunnon c626cb1c60 added: update to README.md to indicate on how to use the new feature.
Signed-off-by: Toure Dunnon <toure.dunnon@anchore.com>
2020-12-14 08:10:46 -05:00
Toure Dunnon 07f2c2f702 Add the ability to run syft from a scratch image.
This change will allow endusers or CI to run syft from a
minimum image which will simplify CI deployment.

Signed-off-by: Toure Dunnon <toure.dunnon@anchore.com>
2020-12-14 08:10:46 -05:00
Dan Luhring 737a81c38c Sort generated CPEs by specificity (#289)
Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2020-12-11 12:08:39 -05:00
Alex Goodman 52bac6e2fd Add enterprise upload capability (#285)
* add support to upload results to enterprise

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* add package sbom upload

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* add dockerfile support

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* add manifest, index, and dockerfile import functions

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* schema version to json output + enhance json schema generation

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* modify package SBOM shape to be entire syft document + add etui updates

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* add import image config and manifest support

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* add config options for import to enterprise

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* incorporate final stereoscope and client-go deps

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-12-09 22:20:53 -05:00
Toure Dunnon 2d0c127419 Merge pull request #282 from anchore/issue_270
Corrected syft cyclonedx generated bom to adhere to the specifications found in CycloneDX 1.2
2020-12-04 13:48:55 -05:00
Toure Dunnon 1a124bd77b added: regenerated new test fixtures to reflect change in specification.
Signed-off-by: Toure Dunnon <toure.dunnon@anchore.com>
2020-12-04 11:48:20 -05:00
Toure Dunnon a5fd83b21d added: correct the bom descriptor to meet the cyclonedx 1.2
Signed-off-by: Toure Dunnon <toure.dunnon@anchore.com>
2020-12-04 11:48:20 -05:00
Alex Goodman f87c59b4eb Merge pull request #286 from anchore/rm-tree-catalog-sync-check
Bump stereoscope to remove tree-catalog syft check
2020-12-03 16:32:24 -05:00
Alex Goodman 3ce7eabc98 bump stereoscope to remove tree-catalog syft check
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-12-03 16:20:17 -05:00
Alex Goodman 6f9ded60ed Merge pull request #279 from anchore/enhance-java-cpe-by-group-id
Include CPEs with elements from POM GroupId fields
2020-12-02 07:50:31 -05:00
Dan Luhring 65cbacd135 Clarify python wheel parsing process (#281)
Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2020-12-01 16:30:30 -05:00
Alex Goodman 2989d3d975 include CPEs with elementds from POM GroupId fields
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-12-01 08:00:49 -05:00
Dan Luhring ae71b8832d Update stereoscope version to fix opaque directory merge issue (#278)
Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2020-11-30 11:37:44 -05:00
Alex Goodman c42b036f46 Merge pull request #276 from anchore/generate-json-schema
Generate json schema from struct definitions
2020-11-23 11:22:33 -05:00
Alex Goodman 8a17bfb69f generate json schema from struct definitions
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-11-20 15:47:10 -05:00
Alex Goodman 0ed30138c4 Merge pull request #271 from anchore/cpe-generation
Move package PURL and CPEs to Package definition
2020-11-19 12:38:20 -05:00
Alex Goodman 3aaa0e5566 move package purl and cpes (identities) to pkg.Package
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-11-19 11:25:51 -05:00
Alex Goodman 71939557e6 Merge pull request #266 from anchore/document-import
Add JSON document import
2020-11-17 13:21:57 -05:00
Alex Goodman 030427bb33 rename json artifact to package + update resolver integrity checks
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-11-17 12:37:13 -05:00
Alex Goodman 569a598df7 minimize pointer usage & order return types consistently
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-11-17 12:37:13 -05:00
Dan Luhring 0ad8c53ec2 Fix cyclonedx test fixture usage of dynamic digest value
Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2020-11-17 12:37:13 -05:00
Alex Goodman c892c3609e improve doc comments
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-11-17 12:37:13 -05:00
Alex Goodman 62b03f3a91 dont export structs used for unmarshaling
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-11-17 12:37:12 -05:00
Alex Goodman 4b45c42f5a make cyclonedx presenter generally reusable (for grype)
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-11-17 12:37:12 -05:00
Alex Goodman f46de19c6b migrate scope option to image metadata (from source)
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-11-17 12:37:12 -05:00
Alex Goodman 91baabe5a1 add image metadata as catalogFromJSON return
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-11-17 12:37:12 -05:00
Alex Goodman 6f7a4fd3e4 move source metadata upstream and fix tests
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-11-17 12:37:12 -05:00
Alex Goodman aa0d444fd4 fix tests to use location instead of file.Reference
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-11-17 12:37:12 -05:00
Alex Goodman b694dacb21 add source.Location + reorient Resolvers to use it
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-11-17 12:37:11 -05:00
Alex Goodman 9668341a14 rename scope to source
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-11-17 12:37:11 -05:00
Alex Goodman 495fb0a45f add sbom document import lib helper function
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-11-17 12:36:51 -05:00
Dan Luhring a640a2c4cd Merge pull request #267 from anchore/improve-java-version-recognition
Improve Java version recognition
2020-11-17 10:59:54 -05:00
Dan Luhring 3e8bca6911 Rework Java archive name and version detection and clean up tests
Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2020-11-17 08:35:10 -05:00
Dan Luhring 5afdd574a8 Update existing archive test cases to correct names and versions
Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2020-11-17 08:35:10 -05:00
Dan Luhring 0ebe791acd Add archive filename test case for failing example from #255
Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2020-11-17 08:35:10 -05:00
Dan Luhring a5b72405dd Merge pull request #265 from anchore/package-json-license-objects
Improve package.json license parsing
2020-11-14 13:06:24 -05:00
Dan Luhring 4861f69d7c Fix spelling in logger setup
Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2020-11-14 11:35:55 -05:00
Dan Luhring 6bde075cd3 Remove unused argument value
Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2020-11-14 11:20:06 -05:00
Dan Luhring f7be73dbcc Fix package.json parsing for no licenses
Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2020-11-14 11:12:13 -05:00
Dan Luhring fc99538d78 Fix package.json parsing for multiple licenses objects
Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2020-11-14 11:12:13 -05:00
Dan Luhring 4d4e242251 Fix package.json parsing for license object
Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2020-11-14 11:12:12 -05:00
Dan Luhring 416afa015e Add test for package.json with no licenses
Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2020-11-14 11:11:46 -05:00
Dan Luhring bd4c784a64 Add test for package.json multiple license objects
Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2020-11-14 11:01:59 -05:00
Dan Luhring 624a090631 Add test for package.json license object
Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2020-11-14 09:50:17 -05:00
Alex Goodman dad1d9691c Merge pull request #263 from anchore/add-source-to-rpmdb-cataloger
Add source to packages found by RPMdb cataloger
2020-11-12 09:36:20 -05:00
Alex Goodman 7964c27bd8 add source to packages found by rpmdb cataloger
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-11-12 09:27:08 -05:00
Alex Goodman 9581a0309b Merge pull request #262 from anchore/fix-release
Fix release process
2020-11-11 15:15:12 -05:00
Alex Goodman 7e270bf76c restore the checksum file during release
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-11-11 15:11:51 -05:00
Alex Goodman fde37b4b56 provide goreleaser asset version via env var
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-11-11 14:36:24 -05:00
Dan Luhring 8627ea88ce Fix usage of goreleaser's artifact pipeline
Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2020-11-11 14:29:55 -05:00
Alex Goodman 2c82c7c0f0 Merge pull request #257 from anchore/enhance-deb-support
Enhance dpkg support
2020-11-11 13:12:29 -05:00
Dan Luhring 4c38ea595d Merge pull request #261 from anchore/fix-brew
Add signed and notarized ZIP release asset
2020-11-11 13:09:45 -05:00
Dan Luhring 6fe5ad0739 Avoid Finder window flash during install
Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2020-11-11 12:38:11 -05:00
Dan Luhring 7bced775c4 Add zip to gon outputs
Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2020-11-11 12:30:29 -05:00
Alex Goodman db98fba3b4 Merge pull request #260 from anchore/add-warning-levels
Add warning log level
2020-11-11 11:13:51 -05:00
Alfredo Deza 29d464c38a Merge pull request #258 from anchore/distro-like
Report the ID_LIKE value in the JSON presenter
2020-11-11 11:11:13 -05:00
Alex Goodman 2a329002b8 enhance dpkg support by parsing md5sum and copyright file sources
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-11-11 11:06:34 -05:00
Alex Goodman a6365886ad add warning log level
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-11-10 15:09:41 -05:00
Alfredo Deza 6ae3b47959 tests: update all tests to pass the IDLike value
Signed-off-by: Alfredo Deza <adeza@anchore.com>
2020-11-10 12:40:50 -05:00
Alfredo Deza 10b55311df presenter: provide ID_LIKE information in json
Signed-off-by: Alfredo Deza <adeza@anchore.com>
2020-11-10 12:40:17 -05:00
Alfredo Deza 64d5554144 include ID_LIKE in distro detection
Signed-off-by: Alfredo Deza <adeza@anchore.com>
2020-11-10 12:37:35 -05:00
Alfredo Deza b3098f3423 Merge pull request #250 from anchore/issue-169
Add distro information to JSON presenter
2020-11-09 14:18:48 -05:00
Alfredo Deza c2cf4eb7b0 update tests for the new distro information
Signed-off-by: Alfredo Deza <adeza@anchore.com>
2020-11-09 12:16:00 -05:00
Alfredo Deza 1e79986188 json: update the document to include distro information
Signed-off-by: Alfredo Deza <adeza@anchore.com>
2020-11-09 12:16:00 -05:00
Dan Luhring 3699a917fd Merge pull request #254 from anchore/updates-to-mac-releases
Updates for macOS release process
2020-11-06 13:49:54 -05:00
Dan Luhring 501870f89f Update README for secure macOS releases
Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2020-11-06 13:40:31 -05:00
Dan Luhring c878cc361c Update install.sh to handle new macOS releases
Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2020-11-06 13:39:26 -05:00
Dan Luhring 8fb5b17dbd Use .Version in place of .Tag for release asset name
Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2020-11-06 13:36:02 -05:00
Alex Goodman b6eb589b78 Merge pull request #251 from anchore/add-rpm-file-info
Add RPM file info sourced from the RPM DB
2020-11-06 11:06:34 -05:00
Alex Goodman 6aba2f48d4 split out rpmdb file processing to helper + lint fixes
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-11-06 10:44:27 -05:00
Alex Goodman 0205e72be9 ensure resolvers ignore directories for "FilesBy*" methods
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-11-06 07:21:38 -05:00
Alex Goodman 8095cd9980 add rpmdb file info to cataloger
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-11-06 07:21:23 -05:00
Dan Luhring ecfc471ce5 Resolve security warning for macOS users (#249)
* Add support for macOS signing and notarization

Signed-off-by: Dan Luhring <dan.luhring@anchore.com>

* Use Docker to run the changelog generator locally

Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2020-11-04 15:47:55 -05:00
Alex Goodman a52750bdd3 enhance alpine file discovery (#248)
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-11-04 09:56:52 -05:00
Alex Goodman 773581704c bump threshold for inline compare for jenkins image
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-10-30 15:14:32 -04:00
Alex Goodman 6e98752c6b Merge pull request #247 from anchore/syft-java-cataloger-integration
General Java cataloger enhancements
2020-10-30 13:55:16 -04:00
Alex Goodman e2593cd6b7 remove extra fields from the compare script metadata namedtuple
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-10-30 10:59:53 -04:00
Alex Goodman 232cd13035 update tests for enhanced java pkg pairings
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-10-30 10:44:45 -04:00
Alex Goodman 2532928afa bump java compare testing thresholds
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-10-30 10:06:47 -04:00
Alex Goodman 03dbfb8dfb improve java name and version extraction as well as parent pkg pairing
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-10-30 08:12:25 -04:00
Alex Goodman a5cba13ddf enable more flexible java manifest structure (closer to the spec)
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-10-29 16:26:04 -04:00
Alex Goodman a4f22e65fc expand compare testing images
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-10-29 13:52:51 -04:00
Alex Goodman ab45be98b8 append java nested package names to the virtual path
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-10-29 13:52:34 -04:00
Alex Goodman fc991bc62e partial java comparison with extra metadata
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-10-29 12:40:49 -04:00
Alex Goodman 1230650771 allow for java manifest data to be optional
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-10-29 11:28:27 -04:00
Alex Goodman 452426d1d6 Merge pull request #245 from anchore/update-java-cataloger-for-engine
Java cataloger miscellaneous fixes
2020-10-28 17:36:44 -04:00
Alex Goodman 2675891110 remove parent java package from json && add java manifest section parsing
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-10-28 17:19:06 -04:00
Dan Luhring bffc4713a7 Update zip archive handling to match globs as expected (#244)
* Refactor zip file tests

Signed-off-by: Dan Luhring <dan.luhring@anchore.com>

* Add glob support for leading slashes

Signed-off-by: Dan Luhring <dan.luhring@anchore.com>

* Update zip testing to account for glob matching

Signed-off-by: Dan Luhring <dan.luhring@anchore.com>

* Ignore .DS_STORE

Signed-off-by: Dan Luhring <dan.luhring@anchore.com>

* Improve normalization of zip entry names

Signed-off-by: Dan Luhring <dan.luhring@anchore.com>

* Rename zip test helpers file

Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2020-10-28 16:50:40 -04:00
Alex Goodman 62f6146c37 do not include optional fields
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-10-28 15:58:54 -04:00
Alex Goodman 8bcba7d830 update json schema and test for java virtualPath
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-10-28 15:25:30 -04:00
Alex Goodman 76c8f24090 add java virtual path
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-10-28 13:53:44 -04:00
Alex Goodman cc466e47da bump python version for acceptance tests
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-10-27 14:53:52 -04:00
Alex Goodman f9407d0ce4 Add java/npm to inline comparison (#235)
* Adds java and npm package comparison
* Adds probable matching of extra packages syft found and missing packages that syft did not find (but inline did). This way there is a section of output that fuzzy-matches the package names to get a better sense of "real" problems (actual missing packages) vs slightly mismatched metadata during troubleshooting.
* Adds a set or probable missing packages to the report based on the probable matches (again, to aid in troubleshooting)
* Fixes image reference clean function to support references with registries
* Only shows metadata differences when the package was found by both inline and syft
* Splits the inline-compare code into more manageable pieces

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-10-27 13:43:36 -04:00
Dan Luhring f19cb03aa0 Update doublestar to include fix for open dirs issue (#240)
Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2020-10-26 11:19:25 -04:00
Toure Dunnon 076454d7a8 Merge pull request #233 from anchore/javascript_parser_fix_author
Javascript parser fix author
2020-10-26 09:30:11 -04:00
Toure Dunnon 7c42a7441c Merge branch 'javascript_parser_fix_author' of https://github.com/anchore/syft into javascript_parser_fix_author 2020-10-23 16:49:51 -04:00
Toure Dunnon cbb7e00a52 Cleaned up go.mod import.
Signed-off-by: Toure Dunnon <toure.dunnon@anchore.com>
2020-10-23 16:44:07 -04:00
Alex Goodman 6eb36bc3c8 allow for optional fields in npm metadata
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-10-23 15:00:55 -04:00
Toure Dunnon 94ffc2caa8 Merge branch 'main' into javascript_parser_fix_author
Signed-off-by: Toure Dunnon <toure.dunnon@anchore.com>
2020-10-23 12:05:30 -04:00
Alex Goodman 15e2e32103 Merge pull request #237 from anchore/update-future-plans
Remove CycloneDX from future plans
2020-10-23 11:06:51 -04:00
Alex Goodman 1cdf2b9151 remove cyclonedx from future plans (already implemented)
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-10-23 11:05:28 -04:00
Alex Goodman fd65f363ce Merge pull request #236 from anchore/fix-acceptance-test-pipeline
Bootstrap cached dependencies and CI dependencies separately for acceptance tests
2020-10-23 10:55:24 -04:00
Alex Goodman 103f0617f5 bootstrap cached deps and ci deps separately for acceptance tests
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-10-23 10:42:57 -04:00
Alex Goodman de2e6a13b8 Merge pull request #234 from anchore/split-python-cataloger-with-cataloger-addition
Split python cataloger by image/directory scanning + add more metadata
2020-10-23 10:37:01 -04:00
Alex Goodman 7d55bca0a0 allow for python metadata fields to be optional
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-10-23 10:29:52 -04:00
Toure 48c7dee9da included additional support for older npm spec.
Signed-off-by: Toure <tdunnon@gmail.com>
2020-10-23 08:50:38 -04:00
Toure 94e448a818 Merge commit '15379d1' into javascript_parser_fix_author
Signed-off-by: Toure <tdunnon@gmail.com>
2020-10-22 14:08:33 -04:00
Toure Dunnon 15379d1075 Fix for errors+failures parsing package.json
closes: #230

Signed-off-by: Toure Dunnon <toure.dunnon@anchore.com>
2020-10-22 13:58:18 -04:00
Alex Goodman 2e5ff4a995 add top_level.txt processing to python package cataloger
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-10-22 12:39:03 -04:00
Alex Goodman 1414d1fbc3 add test coverage for python pacakge cataloger and update catalog interface
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-10-22 09:51:29 -04:00
Alex Goodman 7fc926d40d add python author and emil fields + add metadata type
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-10-22 09:51:29 -04:00
Toure Dunnon 5b08616e47 Fix for errors+failures parsing package.json
closes: #230

Signed-off-by: Toure Dunnon <toure.dunnon@anchore.com>
2020-10-21 14:11:35 -04:00
Alex Goodman 0ce8701e73 split python package catalogers by image vs directory
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-10-21 11:48:26 -04:00
Alex Goodman beb6afff36 Merge pull request #232 from anchore/cataloger-interface-upgrade
Upgrade cataloger interface to use full resolver
2020-10-21 11:47:52 -04:00
Alex Goodman d4ca0ab167 expand the resolver to include content requests for a single reference
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-10-21 11:40:14 -04:00
Alex Goodman 6cd9c2b771 upgrade cataloger interface to use full resolver (remove SelectFiles function)
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-10-21 10:49:33 -04:00
Alex Goodman 931c796158 add dynamic package.json parsing of author field
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-10-20 15:23:04 -04:00
Alex Goodman bb14f3b45b reduce number of open files while processing nested java archives (#227)
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-10-16 17:22:14 -04:00
Toure Dunnon c0b9d7854b Merge pull request #224 from anchore/add_package_json
Add support for package.json #200
2020-10-16 11:43:27 -04:00
Toure Dunnon 27c62e34f2 Add support for package.json #200
Signed-off-by: Toure Dunnon <toure.dunnon@anchore.com>
2020-10-16 11:28:54 -04:00
Alex Goodman 4c751cb1d4 Merge pull request #222 from VinodAnandan/main
Enable CodeQL Security Scan
2020-10-15 23:11:01 -04:00
Vinod Anandan 0f92f16eb6 Enable CodeQL Security Scan
https://github.blog/2020-09-30-code-scanning-is-now-available/

remove java from codeql scan

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
Signed-off-by: Vinod Anandan <vinod.anandan@jpmorgan.com>
2020-10-16 03:02:09 +01:00
Alex Goodman 1bf1e643eb restore original release token
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-10-15 14:41:28 -04:00
Alex Goodman 3ba67a6bcc update json schema to account for optional gemspec fields (#225)
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-10-15 13:56:59 -04:00
Alex Goodman 4a8ee15e87 fix syft version verbiage
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-10-15 11:50:23 -04:00
Alex Goodman dcb5d6d08e remove greeter action since it is broken for forked PRs (#223)
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-10-15 10:20:09 -04:00
Alex Goodman b3387807c1 allow for gemspec metadata fields to be optional (#218)
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-10-15 09:57:58 -04:00
Alex Goodman 24c4c025e1 Merge pull request #217 from jhujasonw/main
Fixes #212 by increasing buffer size for scanner
2020-10-14 07:29:00 -04:00
Alex Goodman 5d0909f31e add regression test to cover issue #212
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-10-14 07:22:19 -04:00
Jason Williams 1f64b3cd49 Fixes #212 by increasing buffer size for scanner
Signed-off-by: Jason Williams <jasonw@jhu.edu>
2020-10-13 11:00:55 -04:00
Toure Dunnon 8d25d44ea9 Add homepage field as output to the gemspec metadata (#215)
https://github.com/anchore/syft/issues/214

Signed-off-by: Toure <tdunnon@gmail.com>
2020-10-12 18:59:56 -04:00
Alex Goodman 1fc46291a6 update install script arguments for proper argument processing (#211)
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-10-09 11:04:53 -04:00
Alex Goodman 2b932f8d65 run tests on fork PRs (#210)
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-10-09 07:45:05 -04:00
Alex Goodman 0d3e153959 replace circle ci badge with GHA (#209)
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-10-08 15:45:26 -04:00
587589bfe3 Add first issue/PR welcome message action (#185)
* Add first issue/PR welcome message action

Signed-off-by: Robert Prince <robert.prince@anchore.com>

* update first-pr-issue message with a simple greeting

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

Co-authored-by: Alex Goodman <alex.goodman@anchore.com>
Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
2020-10-08 15:42:46 -04:00
Alfredo Deza 4ff330e396 Merge pull request #208 from anchore/non-prerelase
docs: update the readme to remove pre-release note
2020-10-08 15:34:06 -04:00
Alfredo Deza 949bf7bc91 docs: update the readme to remove pre-release note
Signed-off-by: Alfredo Deza <adeza@anchore.com>
2020-10-08 15:30:50 -04:00
Alex Goodman 956af57d3b update release token name
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-10-08 14:37:06 -04:00
Alex Goodman 262a0888a8 fix release pipeline to wait for GHA check names
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-10-08 13:57:43 -04:00
Alex Goodman 335a6b022f add acceptance test to cover gem cataloger + fix gem cataloger parent dir (#207)
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-10-08 13:13:31 -04:00
Alex Goodman da0eb6f20f fix acceptance tests & add notification upon failures (#204)
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-10-08 11:07:00 -04:00
Toure Dunnon 1be5f5756a Merge pull request #203 from anchore/syft_bundler_wip
Add gemspec support
2020-10-08 11:03:30 -04:00
Alex Goodman 9edbc65bce move unicode regex to static space
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-10-08 10:55:57 -04:00
Alex Goodman 398d8903e7 finalize gemspec parser + update json schema to match
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-10-08 10:52:14 -04:00
Alex Goodman 46c74865e5 add render unicode in gemspec parser
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-10-08 10:52:14 -04:00
Alex Goodman 1f0f6fa3e5 more gemspec tests
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-10-08 10:52:03 -04:00
Alex Goodman abdd00cd24 rename gem parsers and catalogers
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-10-08 10:23:24 -04:00
Alex Goodman 10b44f5311 split catalogers into two sets, one for images another for directory scans
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-10-08 10:23:24 -04:00
Alex Goodman 1c320a8382 defer to GenericCataloger instances for pkg catalogers
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-10-08 10:23:24 -04:00
Toure 7a8a5419b8 adding ruby gemspec support.
Signed-off-by: Toure Dunnon <toure.dunnon@anchore.com>
2020-10-08 10:19:33 -04:00
Alex Goodman 16b23e7994 add gha pipeline to replace circlei pipeline (#202)
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-10-07 12:20:51 -04:00
Alex Goodman e6b71486b0 modify version command to match grype (#196)
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-10-06 13:13:52 -04:00
Dan Luhring 5adfce19e0 Set prerelease to auto (#189)
Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2020-09-29 16:33:40 -04:00
Dan Luhring b6dfdf16b6 Remove and update TODOs (#194)
* Remove and update TODOs

Signed-off-by: Dan Luhring <dan.luhring@anchore.com>

* Update TODO with link

Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2020-09-29 11:53:19 -04:00
Alex Goodman b5ce187ba7 replace modfile with stdlib (#187)
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-09-28 17:25:04 -04:00
Alex Goodman 26855a2a9e ignore apk xattr file checksum + remove log.Errorf error wraps (#192)
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-09-28 17:22:17 -04:00
Alex Goodman 4b78d9a1c0 support home dir expansion (#188)
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-09-28 15:02:21 -04:00
Alex Goodman 2844b9878f add release notification
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-09-25 23:21:16 -04:00
Alex Goodman 8b81c87d18 remove unreleased tags and exclude size labels
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-09-25 22:59:19 -04:00
Alex Goodman 9bd9dad76c remove unrelease changelog option
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-09-25 18:11:02 -04:00
Alex Goodman 6d9f9a9b3b pin the two tags used for release autochangelog
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-09-25 17:57:51 -04:00
Alex Goodman 50cb5612d0 fix github releaser changelog intake
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-09-25 17:20:27 -04:00
Alex Goodman 080f010746 remove tty requirement from release process
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-09-25 17:07:59 -04:00
Alex Goodman f4502fc824 Add notifications around the release process (#184)
* add pipeline notification upon release

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* add pending notification to release pipeline

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-09-25 16:58:56 -04:00
Alex Goodman 03378e976c always return a cleanup function from scope (#183)
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-09-25 16:20:06 -04:00
Alex Goodman 576fbc898b rm stereoscope from testutils (#181)
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-09-25 14:53:05 -04:00
Alex Goodman 49800b6747 Normalize the json image/dir source (#180)
* normalize the json image/dir source

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* update json image presenter golden file

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-09-25 14:07:38 -04:00
Alex Goodman c46d004a3b remove duplicate rows from the summary table (#179)
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-09-25 10:32:37 -04:00
Alex Goodman 3d91a66536 Add OCI support + use URI schemes for user image input (#178)
* add oci support + update image schemes

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* update docs to reflect OCI image sources + URI scheme change

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* update to oci-dir

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* bump upstream stereoscope pin

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-09-25 10:24:24 -04:00
Alex Goodman 45b5cab49a unpin skopeo during mac acceptance tests (#176)
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2020-09-18 13:54:40 -04:00
Sam Dacanay 1866949eff Merge pull request #175 from anchore/fix-json-keys
Change kebab case to camelCase in json keys throughout app
2020-09-18 09:19:37 -07:00
Samuel Dacanay 0567206b38 Change kebab case to camelCase in json keys throughout app
Signed-off-by: Samuel Dacanay <sam.dacanay@anchore.com>
2020-09-18 08:55:17 -07:00
Alex GoodmanandAlfredo Deza f3076c3905 Add docs around the main execution path (#172)
* add docs around the main execution path of syft

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* Update syft/lib.go

Co-authored-by: Alfredo Deza <adeza@anchore.com>
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* Update syft/lib.go

Co-authored-by: Alfredo Deza <adeza@anchore.com>
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* wrap line

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

Co-authored-by: Alfredo Deza <adeza@anchore.com>
2020-09-17 06:02:51 -04:00
Samuel Dacanay b467964044 port over shell completion w/ cobra from grype, find/replace, etc.
Signed-off-by: Samuel Dacanay <sam.dacanay@anchore.com>
2020-09-15 09:07:17 -07:00
Praveen Mylavarapu e26627d8b6 remove accessing tag when not available (#174)
Signed-off-by: praveenmylavarapu <saipraveenmylavarapu@gmail.com>
2020-09-15 07:55:08 -04:00
Sam Dacanay 80ee18d20b Merge pull request #171 from anchore/version-output-json
Add support for JSON version output
2020-09-14 13:51:59 -07:00
Samuel Dacanay ef64a6f206 Add support for JSON version output
Signed-off-by: Samuel Dacanay <sam.dacanay@anchore.com>

Make json version output always verbose, cleanup struct tag

Signed-off-by: Samuel Dacanay <sam.dacanay@anchore.com>

Use camel case for json

Signed-off-by: Samuel Dacanay <sam.dacanay@anchore.com>
2020-09-14 13:48:27 -07:00
Sam Dacanay 22dfcb2d0b Merge pull request #170 from anchore/add-distro-mappings-arch-and-opensuseleap
Add support for Arch (which doesnt return a version) and Open Suse Leap
2020-09-14 09:02:28 -07:00
Samuel Dacanay 329b080a37 Add support for Arch (which doesnt return a version) and Open Suse Leap
Signed-off-by: Samuel Dacanay <sam.dacanay@anchore.com>

remove bad-version test fixture (no longer relevant since it is now an expected case) and add cases for arch and opensuse-leap

Signed-off-by: Samuel Dacanay <sam.dacanay@anchore.com>

Reduce nesting, simplify empty version logic

Signed-off-by: Samuel Dacanay <sam.dacanay@anchore.com>
2020-09-13 13:50:56 -07:00
Sam Dacanay 11334b632e Expand matching of requirements.txt file to include any prefixes or suffixes (#168)
* Expand matching of requirements.txt file to include any prefixes or suffixes

Signed-off-by: Samuel Dacanay <sam.dacanay@anchore.com>

* Add some test cases to integration test (ensure syft can pick up multiple requirements files)

Signed-off-by: Samuel Dacanay <sam.dacanay@anchore.com>

* Run lint-fix

Signed-off-by: Samuel Dacanay <sam.dacanay@anchore.com>
2020-09-08 10:23:37 -07:00
297 changed files with 15339 additions and 9175 deletions
-129
View File
@@ -1,129 +0,0 @@
version: 2.1
jobs:
run-static-analysis:
parameters:
version:
type: string
docker:
- image: circleci/golang:<< parameters.version >>
environment:
GO111MODULE: "on"
# work around for recent circle CI breaking change
# Error: "Error response from daemon: client version 1.39 is too new. Maximum supported API version is 1.38"
DOCKER_API_VERSION: "1.38"
# 2CPU / 4GB RAM
resource_class: medium
steps:
- checkout
- restore_cache:
keys:
- go-<< parameters.version >>-{{ checksum "go.sum" }}-{{ checksum "Makefile" }}
- run: make ci-bootstrap
- save_cache:
key: go-<< parameters.version >>-{{ checksum "go.sum" }}-{{ checksum "Makefile" }}
paths:
- "/go/pkg/mod"
- ".tmp"
- run:
name: run static analysis
command: make static-analysis
run-tests:
parameters:
version:
type: string
docker:
- image: circleci/golang:<< parameters.version >>
environment:
GO111MODULE: "on"
# work around for recent circle CI breaking change
# Error: "Error response from daemon: client version 1.39 is too new. Maximum supported API version is 1.38"
DOCKER_API_VERSION: "1.38"
# 2CPU / 4GB RAM
resource_class: medium
steps:
- checkout
- restore_cache:
keys:
- go-<< parameters.version >>-{{ checksum "go.sum" }}-{{ checksum "Makefile" }}
- run: make ci-bootstrap
- save_cache:
key: go-<< parameters.version >>-{{ checksum "go.sum" }}-{{ checksum "Makefile" }}
paths:
- "/go/pkg/mod"
- ".tmp"
- setup_remote_docker:
version: 18.06.0-ce
- run:
name: enable docker client
command: |
# all of this to enable "circleci local execute ..." cli commands for /var/run/docker.sock
mkdir -p ${HOME}/.local/bin
cat \<< EOF > ${HOME}/.local/bin/docker
#!/bin/bash
set -xue
sudo -E ${HOME}/.local/bin/docker.bin \$@
EOF
sudo mv /usr/bin/docker ${HOME}/.local/bin/docker.bin
chmod 755 ${HOME}/.local/bin/docker
- run:
name: build cache key for java test-fixture blobs
command: make java-packages-fingerprint
- restore_cache:
keys:
- syft-unit-test-java-cache-{{ checksum "syft/cataloger/java/test-fixtures/java-builds/packages.fingerprint" }}
- run:
name: run unit tests
command: make unit
- save_cache:
key: syft-unit-test-java-cache-{{ checksum "syft/cataloger/java/test-fixtures/java-builds/packages.fingerprint" }}
paths:
- "syft/cataloger/java/test-fixtures/java-builds/packages"
- run:
name: validate syft output against the CycloneDX schema
command: make validate-cyclonedx-schema
- run:
name: build hash key for integration test-fixtures blobs
command: make integration-fingerprint
- restore_cache:
keys:
- syft-integration-test-tar-cache-{{ checksum "test/integration/test-fixtures/tar-cache.fingerprint" }}
- run:
name: run integration tests
command: make integration
- save_cache:
key: syft-integration-test-tar-cache-{{ checksum "test/integration/test-fixtures/tar-cache.fingerprint" }}
paths:
- "test/integration/test-fixtures/tar-cache"
workflows:
# Note: changing this workflow name requires making the same update in the .github/workflows/release.yaml pipeline
"Static Analysis + Unit + Integration":
jobs:
- run-static-analysis:
name: "Static Analysis"
version: "1.14"
- run-tests:
name: "Unit & Integration Tests (go-1.13)"
version: "1.13"
- run-tests:
name: "Unit & Integration Tests (go-latest)"
version: "latest"
+1 -1
View File
@@ -16,5 +16,5 @@ assignees: ''
**Anything else we need to know?**:
**Environment**:
- syft version (use `syft version -v`):
- Output of `syft version`:
- OS (e.g: `cat /etc/os-release` or similar):
+31
View File
@@ -0,0 +1,31 @@
#!/usr/bin/env bash
set -eu
CI_HOME="/Users/runner"
if [[ "${HOME}" != "${CI_HOME}" ]]; then
printf "WARNING! It looks like this isn't the CI environment. This script modifies the macOS Keychain setup in ways you probably wouldn't want for your own machine. It also requires an Apple Developer ID Certificate that you shouldn't have outside of the CI environment.\n\nExiting early to make sure nothing bad happens.\n"
exit 1
fi
# Install gon (see https://github.com/mitchellh/gon for details).
brew tap mitchellh/gon
brew install mitchellh/gon/gon
# Write signing certificate to disk from environment variable.
CERT_FILE="$HOME/developer_id_certificate.p12"
echo -n "$APPLE_DEVELOPER_ID_CERT" | base64 --decode > "$CERT_FILE"
# In order to have all keychain interactions avoid an interactive user prompt, we need to control the password for the keychain in question, which means we need to create a new keychain into which we'll import the signing certificate and from which we'll later access this certificate during code signing.
EPHEMERAL_KEYCHAIN="ci-ephemeral-keychain"
EPHEMERAL_KEYCHAIN_PASSWORD="$(openssl rand -base64 100)"
security create-keychain -p "${EPHEMERAL_KEYCHAIN_PASSWORD}" "${EPHEMERAL_KEYCHAIN}"
# Import signing certificate into the keychain. (This is a pre-requisite for gon, which is invoked via goreleaser.)
EPHEMERAL_KEYCHAIN_FULL_PATH="$HOME/Library/Keychains/${EPHEMERAL_KEYCHAIN}-db"
security import "${CERT_FILE}" -k "${EPHEMERAL_KEYCHAIN_FULL_PATH}" -P "${APPLE_DEVELOPER_ID_CERT_PASS}" -T "$(command -v codesign)"
# Setting the partition list for this certificate's private key to include "apple-tool:" and "apple:" allows the codesign command to access this keychain item without an interactive user prompt. (codesign is invoked by gon.)
security set-key-partition-list -S "apple-tool:,apple:" -s -k "${EPHEMERAL_KEYCHAIN_PASSWORD}" "${EPHEMERAL_KEYCHAIN_FULL_PATH}"
# Make this new keychain the user's default keychain, so that codesign will be able to find this certificate when we specify it during signing.
security default-keychain -d "user" -s "${EPHEMERAL_KEYCHAIN_FULL_PATH}"
+19
View File
@@ -0,0 +1,19 @@
#!/usr/bin/env bash
set -eu
IS_SNAPSHOT="$1" # e.g. "true", "false"
if [[ "${IS_SNAPSHOT}" == "true" ]]; then
# This is a snapshot build —— skipping signing and notarization...
exit 0
fi
GON_CONFIG="$2" # e.g. "gon.hcl"
NEW_NAME_WITHOUT_EXTENSION="$3" # e.g. "./dist/syft-0.1.0"
ORIGINAL_NAME_WITHOUT_EXTENSION="./dist/output" # This should match dmg and zip output_path in the gon config file, without the extension.
gon "${GON_CONFIG}"
# Rename outputs with specified desired name
mv -v "${ORIGINAL_NAME_WITHOUT_EXTENSION}.dmg" "${NEW_NAME_WITHOUT_EXTENSION}.dmg"
mv -v "${ORIGINAL_NAME_WITHOUT_EXTENSION}.zip" "${NEW_NAME_WITHOUT_EXTENSION}.zip"
+4 -9
View File
@@ -15,14 +15,9 @@ fi
echo "creating and publishing version file"
# create a version file for version-update checks
echo "${VERSION}" | tee ${DISTDIR}/VERSION
VERSION_FILE="${DISTDIR}/VERSION"
echo "${VERSION}" | tee "${VERSION_FILE}"
# upload the version file that supports the application version update check
docker run --rm \
-i \
-e AWS_DEFAULT_REGION=us-west-2 \
-e AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} \
-e AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} \
-v $(pwd)/${DISTDIR}/:/distmount \
amazon/aws-cli \
s3 cp /distmount/VERSION s3://toolbox-data.anchore.io/${BIN}/releases/latest/VERSION
export AWS_DEFAULT_REGION=us-west-2
aws s3 cp "${VERSION_FILE}" s3://toolbox-data.anchore.io/${BIN}/releases/latest/VERSION
+45 -7
View File
@@ -14,7 +14,7 @@ env:
jobs:
Build-Snapshot-Artifacts:
runs-on: ubuntu-latest
runs-on: macos-latest # We're creating these snapshot builds on macOS to be consistent with our release workflow's build process, which also takes place on macOS (due to code signing requirements).
steps:
- uses: actions/setup-go@v2
@@ -35,9 +35,9 @@ jobs:
${{ runner.os }}-go-${{ env.GO_VERSION }}-${{ hashFiles('**/go.sum') }}-
${{ runner.os }}-go-${{ env.GO_VERSION }}-
- name: Bootstrap dependencies
if: steps.cache.outputs.cache-hit != 'true'
run: make ci-bootstrap
- name: Bootstrap project dependencies
if: steps.bootstrap-cache.outputs.cache-hit != 'true'
run: make bootstrap
- name: Import GPG key
id: import_gpg
@@ -64,10 +64,20 @@ jobs:
name: artifacts
path: snapshot/**/*
- uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
fields: repo,workflow,job,commit,message,author
text: The syft acceptance tests have failed tragically!
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_TOOLBOX_WEBHOOK_URL }}
if: ${{ failure() }}
# Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline
Acceptance-Linux:
needs: [ Build-Snapshot-Artifacts ]
runs-on: ubuntu-latest
# come Nov 30 2020 ubuntu-latest will be ubuntu-20.04, until then it needs to be explicitly referenced due to python 3.7 specific features being used
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
@@ -80,6 +90,15 @@ jobs:
- name: Run Acceptance Tests (Linux)
run: make acceptance-linux
- uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
fields: repo,workflow,job,commit,message,author
text: The syft acceptance tests have failed tragically!
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_TOOLBOX_WEBHOOK_URL }}
if: ${{ failure() }}
# Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline
Acceptance-Mac:
needs: [ Build-Snapshot-Artifacts ]
@@ -96,10 +115,20 @@ jobs:
- name: Run Acceptance Tests (Mac)
run: make acceptance-mac
- uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
fields: repo,workflow,job,commit,message,author
text: The syft acceptance tests have failed tragically!
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_TOOLBOX_WEBHOOK_URL }}
if: ${{ failure() }}
# Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline
Inline-Compare:
needs: [ Build-Snapshot-Artifacts ]
runs-on: ubuntu-latest
# come Nov 30 2020 ubuntu-latest will be ubuntu-20.04, until then it needs to be explicitly referenced due to python 3.7 specific features being used
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
@@ -120,4 +149,13 @@ jobs:
path: snapshot
- name: Compare Anchore inline-scan results against snapshot build output
run: make compare-snapshot
run: make compare-snapshot
- uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
fields: repo,workflow,job,commit,message,author
text: The syft acceptance tests have failed tragically!
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_TOOLBOX_WEBHOOK_URL }}
if: ${{ failure() }}
+71
View File
@@ -0,0 +1,71 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
name: "CodeQL Security Scan"
on:
push:
branches: [main]
pull_request:
# The branches below must be a subset of the branches above
branches: [main]
schedule:
- cron: '0 0 * * 3'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# Override automatic language detection by changing the below list
# Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
language: ['go', 'python']
# Learn more...
# https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
steps:
- name: Checkout repository
uses: actions/checkout@v2
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
fetch-depth: 2
# If this run was triggered by a pull request event, then checkout
# the head of the pull request instead of the merge commit.
- run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
+32 -12
View File
@@ -13,7 +13,7 @@ env:
jobs:
wait-for-checks:
runs-on: ubuntu-latest
runs-on: ubuntu-latest # This OS choice is arbitrary. None of the steps in this job are specific to either Linux or macOS.
steps:
- uses: actions/checkout@v2
@@ -25,13 +25,22 @@ jobs:
git fetch origin main
git merge-base --is-ancestor ${GITHUB_REF##*/} origin/main && echo "${GITHUB_REF##*/} is a commit on main!"
- name: Check static anaylysis, unit, and integration test results
- name: Check static analysis results
uses: fountainhead/action-wait-for-check@v1.0.0
id: sa-unit-int
id: static-analysis
with:
token: ${{ secrets.GITHUB_TOKEN }}
# This check name is defined as the circle-ci workflow name (in .circleci/config.yaml)
checkName: "Static Analysis + Unit + Integration"
checkName: "Static-Analysis (1.x, ubuntu-latest)"
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Check unit + integration results (latest go version)
uses: fountainhead/action-wait-for-check@v1.0.0
id: unit-integration
with:
token: ${{ secrets.GITHUB_TOKEN }}
# This check name is defined as the circle-ci workflow name (in .circleci/config.yaml)
checkName: "Tests (1.x, ubuntu-latest)"
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Check acceptance test results (linux)
@@ -62,9 +71,10 @@ jobs:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Quality gate
if: steps.sa-unit-int.outputs.conclusion != 'success' || steps.inline-compare.outputs.conclusion != 'success' || steps.acceptance-linux.outputs.conclusion != 'success' || steps.acceptance-mac.outputs.conclusion != 'success'
if: steps.static-analysis.outputs.conclusion != 'success' || steps.unit-integration.outputs.conclusion != 'success' || steps.inline-compare.outputs.conclusion != 'success' || steps.acceptance-linux.outputs.conclusion != 'success' || steps.acceptance-mac.outputs.conclusion != 'success'
run: |
echo "Static/Unit/Integration Status: ${{ steps.sa-unit-int.outputs.conclusion }}"
echo "Static Analysis Status: ${{ steps.static-analysis.conclusion }}"
echo "Unit & Integration Test Status: ${{ steps.unit-integration.outputs.conclusion }}"
echo "Acceptance Test (Linux) Status: ${{ steps.acceptance-linux.outputs.conclusion }}"
echo "Acceptance Test (Mac) Status: ${{ steps.acceptance-mac.outputs.conclusion }}"
echo "Inline Compare Status: ${{ steps.inline-compare.outputs.conclusion }}"
@@ -72,7 +82,7 @@ jobs:
release:
needs: [ wait-for-checks ]
runs-on: ubuntu-latest
runs-on: macos-latest # Due to our code signing process, it's vital that we run our release steps on macOS.
steps:
- uses: actions/setup-go@v2
@@ -83,6 +93,7 @@ jobs:
with:
fetch-depth: 0
# We are expecting this cache to have been created during the "Build-Snapshot-Artifacts" job in the "Acceptance" workflow.
- name: Restore bootstrap cache
id: cache
uses: actions/cache@v2
@@ -95,10 +106,6 @@ jobs:
${{ runner.os }}-go-${{ env.GO_VERSION }}-${{ hashFiles('**/go.sum') }}-
${{ runner.os }}-go-${{ env.GO_VERSION }}-
- name: Bootstrap dependencies
if: steps.cache.outputs.cache-hit != 'true'
run: make ci-bootstrap
- name: Import GPG key
id: import_gpg
uses: crazy-max/ghaction-import-gpg@v2
@@ -122,8 +129,21 @@ jobs:
SIGNING_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }}
AWS_ACCESS_KEY_ID: ${{ secrets.TOOLBOX_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.TOOLBOX_AWS_SECRET_ACCESS_KEY }}
APPLE_DEVELOPER_ID_CERT: ${{ secrets.APPLE_DEVELOPER_ID_CERT }} # Used during macOS code signing.
APPLE_DEVELOPER_ID_CERT_PASS: ${{ secrets.APPLE_DEVELOPER_ID_CERT_PASS }} # Used during macOS code signing.
AC_USERNAME: ${{ secrets.ENG_CI_APPLE_ID }} # Used during macOS notarization.
AC_PASSWORD: ${{ secrets.ENG_CI_APPLE_ID_PASS }} # Used during macOS notarization.
- uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
fields: repo,workflow,action,eventName
text: "A new Syft release is ready to be manually published: https://github.com/anchore/syft/releases"
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_TOOLBOX_WEBHOOK_URL }}
if: ${{ success() }}
- uses: actions/upload-artifact@v2
with:
name: artifacts
path: dist/**/*
path: dist/**/*
@@ -0,0 +1,104 @@
name: 'Static Analysis + Unit + Integration'
on:
workflow_dispatch:
push:
pull_request:
jobs:
Static-Analysis:
strategy:
matrix:
go-version: [1.x]
platform: [ubuntu-latest]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
- uses: actions/checkout@v2
- name: Restore bootstrap cache
id: bootstrap-cache
uses: actions/cache@v2
with:
path: |
~/go/pkg/mod
${{ github.workspace }}/.tmp
key: ${{ runner.os }}-go-${{ matrix.go-version }}-${{ hashFiles('**/go.sum') }}-${{ hashFiles('Makefile') }}
restore-keys: |
${{ runner.os }}-go-${{ matrix.go-version }}-${{ hashFiles('**/go.sum') }}-
${{ runner.os }}-go-${{ matrix.go-version }}-
- name: Bootstrap project dependencies
if: steps.bootstrap-cache.outputs.cache-hit != 'true'
run: make bootstrap
- name: Bootstrap CI dependencies
run: make ci-bootstrap
- name: Run static analysis
run: make static-analysis
Tests:
strategy:
matrix:
# test the lower bounds of support, and the latest available
go-version: [1.13.x, 1.x]
platform: [ubuntu-latest]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
- uses: actions/checkout@v2
- name: Restore bootstrap cache
id: bootstrap-cache
uses: actions/cache@v2
with:
path: |
~/go/pkg/mod
${{ github.workspace }}/.tmp
key: ${{ runner.os }}-go-${{ matrix.go-version }}-${{ hashFiles('**/go.sum') }}-${{ hashFiles('Makefile') }}
restore-keys: |
${{ runner.os }}-go-${{ matrix.go-version }}-${{ hashFiles('**/go.sum') }}-
${{ runner.os }}-go-${{ matrix.go-version }}-
- name: Bootstrap project dependencies
if: steps.bootstrap-cache.outputs.cache-hit != 'true'
run: make bootstrap
- name: Bootstrap CI dependencies
run: make ci-bootstrap
- name: Build cache key for java test-fixture blobs (for unit tests)
run: make java-packages-fingerprint
- name: Restore Java test-fixture cache
id: unit-java-cache
uses: actions/cache@v2
with:
path: syft/cataloger/java/test-fixtures/java-builds/packages
key: ${{ runner.os }}-unit-java-cache-${{ hashFiles( 'syft/cataloger/java/test-fixtures/java-builds/packages.fingerprint' ) }}
- name: Run unit tests
run: make unit
- name: Validate syft output against the CycloneDX schema
run: make validate-cyclonedx-schema
- name: Build key for tar cache
run: make integration-fingerprint
- name: Restore integration test cache
uses: actions/cache@v2
with:
path: ${{ github.workspace }}/integration/test-fixtures/cache
key: ${{ runner.os }}-integration-test-cache-${{ hashFiles('integration/test-fixtures/cache.fingerprint') }}
- name: Run integration tests
run: make integration
+6
View File
@@ -3,6 +3,7 @@ CHANGELOG.md
/snapshot
.server/
.vscode/
.history/
*.fingerprint
*.tar
*.jar
@@ -29,3 +30,8 @@ coverage.txt
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# macOS Finder metadata
.DS_STORE
*.profile
+2 -2
View File
@@ -31,7 +31,6 @@ linters:
- misspell
- nakedret
- nolintlint
- prealloc
- rowserrcheck
- scopelint
- staticcheck
@@ -55,5 +54,6 @@ linters:
# - lll # without a way to specify per-line exception cases, this is not usable
# - maligned # this is an excellent linter, but tricky to optimize and we are not sensitive to memory layout optimizations
# - nestif
# - prealloc # following this rule isn't consistently a good idea, as it sometimes forces unnecessary allocations that result in less idiomatic code
# - testpackage
# - wsl
# - wsl
+48 -12
View File
@@ -1,23 +1,38 @@
release:
# If set to auto, will mark the release as not ready for production in case there is an indicator for this in the
# tag e.g. v1.0.0-rc1 .If set to true, will mark the release as not ready for production.
prerelease: false
prerelease: auto
# If set to true, will not auto-publish the release. This is done to allow us to review the changelog before publishing.
draft: true
signs:
- artifacts: checksum
args: ["--output", "${signature}", "--detach-sign", "${artifact}"]
builds:
- binary: syft
id: syft
env:
- CGO_ENABLED=0
goos:
# windows not supported yet (due to jotframe)
# - windows
- linux
goarch:
- amd64
# Set the modified timestamp on the output binary to the git timestamp (to ensure a reproducible build)
mod_timestamp: '{{ .CommitTimestamp }}'
ldflags: |
-w
-s
-extldflags '-static'
-X github.com/anchore/syft/internal/version.version={{.Version}}
-X github.com/anchore/syft/internal/version.gitCommit={{.Commit}}
-X github.com/anchore/syft/internal/version.buildDate={{.Date}}
-X github.com/anchore/syft/internal/version.gitTreeState={{.Env.BUILD_GIT_TREE_STATE}}
# For more info on this macOS build, see: https://github.com/mitchellh/gon#usage-with-goreleaser
- binary: syft
id: syft-macos
env:
- CGO_ENABLED=0
goos:
- darwin
goarch:
- amd64
@@ -32,6 +47,34 @@ builds:
-X github.com/anchore/syft/internal/version.buildDate={{.Date}}
-X github.com/anchore/syft/internal/version.gitTreeState={{.Env.BUILD_GIT_TREE_STATE}}
archives:
- format: tar.gz
builds:
- syft # i.e. Linux only
- format: zip # This is a hack! We don't actually intend to use _this_ ZIP file, we just need goreleaser to consider the ZIP file produced by gon (which will have the same file name) to be an artifact so we can use it downstream in publishing (e.g. to a homebrew tap)
id: syft-zip
builds:
- syft-macos
signs:
- artifacts: checksum
cmd: sh
args:
- '-c'
# we should not include the zip artifact, as the artifact is mutated throughout the next macOS notarization step
# note: sed -i is not portable
- 'sed "/.*\.zip/d" ${artifact} > tmpfile && mv tmpfile ${artifact} && gpg --output ${signature} --detach-sign ${artifact}'
- id: syft-macos-signing
ids:
- syft-macos
cmd: ./.github/scripts/mac-sign-and-notarize.sh
signature: "syft_${VERSION}_darwin_amd64.dmg" # This is somewhat unintuitive. This gets the DMG file recognized as an artifact. In fact, both a DMG and a ZIP file are being produced by this signing step.
args:
- "{{ .IsSnapshot }}"
- "gon.hcl"
- "./dist/syft_{{ .Version }}_darwin_amd64"
artifacts: all
nfpms:
- license: "Apache 2.0"
maintainer: "Anchore, Inc"
@@ -47,10 +90,3 @@ brews:
name: homebrew-syft
homepage: *website
description: *description
archives:
- format: tar.gz
format_overrides:
- goos: windows
format: zip
+67 -47
View File
@@ -15,7 +15,10 @@ RESET := $(shell tput -T linux sgr0)
TITLE := $(BOLD)$(PURPLE)
SUCCESS := $(BOLD)$(GREEN)
# the quality gate lower threshold for unit test total % coverage (by function statements)
COVERAGE_THRESHOLD := 72
COVERAGE_THRESHOLD := 68
# CI cache busting values; change these if you want CI to not use previous stored cache
COMPARE_CACHE_BUSTER="f7e689d76a9"
INTEGRATION_CACHE_BUSTER="789bacdf"
## Build variables
DISTDIR=./dist
@@ -27,6 +30,10 @@ ifeq "$(strip $(VERSION))" ""
override VERSION = $(shell git describe --always --tags --dirty)
endif
# used to generate the changelog from the second to last tag to the current tag (used in the release pipeline when the release tag is in place)
LAST_TAG := $(shell git describe --abbrev=0 --tags $(shell git rev-list --tags --max-count=1))
SECOND_TO_LAST_TAG := $(shell git describe --abbrev=0 --tags $(shell git rev-list --tags --skip=1 --max-count=1))
## Variable assertions
ifndef TEMPDIR
@@ -67,9 +74,13 @@ help:
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "$(BOLD)$(CYAN)%-25s$(RESET)%s\n", $$1, $$2}'
.PHONY: ci-bootstrap
ci-bootstrap: bootstrap
ci-bootstrap:
DEBIAN_FRONTEND=noninteractive sudo apt update && sudo -E apt install -y bc jq libxml2-utils
.PHONY:
ci-bootstrap-mac:
github_changelog_generator --version || sudo gem install github_changelog_generator
.PHONY: bootstrap
bootstrap: ## Download and install all go dependencies (+ prep tooling in the ./tmp dir)
$(call title,Bootstrapping dependencies)
@@ -118,7 +129,7 @@ validate-cyclonedx-schema:
.PHONY: unit
unit: fixtures ## Run unit tests (with coverage)
$(call title,Running unit tests)
go test -coverprofile $(COVER_REPORT) ./...
go test -coverprofile $(COVER_REPORT) $(shell go list ./... | grep -v anchore/syft/test)
@go tool cover -func $(COVER_REPORT) | grep total | awk '{print substr($$3, 1, length($$3)-1)}' > $(COVER_TOTAL)
@echo "Coverage: $$(cat $(COVER_TOTAL))"
@if [ $$(echo "$$(cat $(COVER_TOTAL)) >= $(COVERAGE_THRESHOLD)" | bc -l) -ne 1 ]; then echo "$(RED)$(BOLD)Failed coverage quality gate (> $(COVERAGE_THRESHOLD)%)$(RESET)" && false; fi
@@ -126,11 +137,13 @@ unit: fixtures ## Run unit tests (with coverage)
.PHONY: integration
integration: ## Run integration tests
$(call title,Running integration tests)
go test -v -tags=integration ./test/integration
go test -v ./test/integration
# note: this is used by CI to determine if the integration test fixture cache (docker image tars) should be busted
integration-fingerprint:
find test/integration/test-fixtures/image-* -type f -exec md5sum {} + | awk '{print $1}' | sort | md5sum | tee test/integration/test-fixtures/tar-cache.fingerprint
find test/integration/test-fixtures/image-* -type f -exec md5sum {} + | awk '{print $1}' | sort | md5sum | tee test/integration/test-fixtures/cache.fingerprint && echo "$(INTEGRATION_CACHE_BUSTER)" >> test/integration/test-fixtures/cache.fingerprint
.PHONY: java-packages-fingerprint
java-packages-fingerprint:
@@ -143,31 +156,12 @@ fixtures:
cd syft/cataloger/java/test-fixtures/java-builds && make
.PHONY: generate-json-schema
generate-json-schema: clean-json-schema-examples integration ## Generate a new json schema for the json presenter, derived from integration test cases
docker run \
-i \
--rm \
-v $(shell pwd)/schema/json:/work \
-w /work \
python:3.8 \
bash -x -c "\
pip install -r requirements.txt && \
python generate.py \
"
generate-json-schema: ## Generate a new json schema
cd schema/json && go run generate.go
.PHONY: clear-test-cache
clear-test-cache: ## Delete all test cache (built docker image tars)
find . -type f -wholename "**/test-fixtures/tar-cache/*.tar" -delete
.PHONY: check-pipeline
check-pipeline: ## Run local CircleCI pipeline locally (sanity check)
$(call title,Check pipeline)
# note: this is meant for local development & testing of the pipeline, NOT to be run in CI
mkdir -p $(TEMPDIR)
circleci config process .circleci/config.yml > .tmp/circleci.yml
circleci local execute -c .tmp/circleci.yml --job "Static Analysis"
circleci local execute -c .tmp/circleci.yml --job "Unit & Integration Tests (go-latest)"
@printf '$(SUCCESS)Pipeline checks pass!$(RESET)\n'
find . -type f -wholename "**/test-fixtures/cache/*.tar" -delete
.PHONY: build
build: $(SNAPSHOTDIR) ## Build release snapshot binaries and packages
@@ -182,6 +176,7 @@ $(SNAPSHOTDIR): ## Build snapshot release binaries and packages
BUILD_GIT_TREE_STATE=$(GITTREESTATE) \
$(TEMPDIR)/goreleaser release --skip-publish --rm-dist --snapshot --config $(TEMPDIR)/goreleaser.yaml
# note: we cannot clean the snapshot directory since the pipeline builds the snapshot separately
.PHONY: acceptance-mac
acceptance-mac: $(SNAPSHOTDIR) ## Run acceptance tests on build snapshot binaries and packages (Mac)
$(call title,Running acceptance test: Run on Mac)
@@ -191,13 +186,14 @@ acceptance-mac: $(SNAPSHOTDIR) ## Run acceptance tests on build snapshot binarie
$(ACC_TEST_IMAGE) \
$(RESULTSDIR)
# note: we cannot clean the snapshot directory since the pipeline builds the snapshot separately
.PHONY: acceptance-linux
acceptance-linux: acceptance-test-deb-package-install acceptance-test-rpm-package-install ## Run acceptance tests on build snapshot binaries and packages (Linux)
# note: this is used by CI to determine if the inline-scan report cache should be busted for the inline-compare tests
.PHONY: compare-fingerprint
compare-fingerprint:
find test/inline-compare/* -type f -exec md5sum {} + | grep -v '\-reports' | grep -v 'fingerprint' | awk '{print $1}' | sort | md5sum | tee test/inline-compare/inline-compare.fingerprint
find test/inline-compare/* -type f -exec md5sum {} + | grep -v '\-reports' | grep -v 'fingerprint' | awk '{print $1}' | sort | md5sum | tee test/inline-compare/inline-compare.fingerprint && echo "$(COMPARE_CACHE_BUSTER)" >> test/inline-compare/inline-compare.fingerprint
.PHONY: compare-snapshot
compare-snapshot: $(SNAPSHOTDIR) ## Compare the reports of a run of a snapshot build of syft against inline-scan
@@ -226,32 +222,60 @@ acceptance-test-rpm-package-install: $(SNAPSHOTDIR)
$(ACC_TEST_IMAGE) \
$(RESULTSDIR)
.PHONY: changlog
changelog:
mkdir -p $(DISTDIR)
@docker run -it --rm \
-v "$(shell pwd)":/usr/local/src/your-app ferrarimarco/github-changelog-generator \
.PHONY: changlog-release
changelog-release:
@echo "Last tag: $(SECOND_TO_LAST_TAG)"
@echo "Current tag: $(VERSION)"
@github_changelog_generator \
--user anchore \
--project $(BIN) \
-t ${GITHUB_TOKEN} \
--exclude-labels 'duplicate,question,invalid,wontfix,size:small,size:medium,size:large,size:x-large' \
--no-pr-wo-labels \
--no-issues-wo-labels \
--unreleased-only \
--future-release $(VERSION)
--since-tag $(SECOND_TO_LAST_TAG)
@printf '\n$(BOLD)$(CYAN)Release $(VERSION) Changelog$(RESET)\n\n'
@cat CHANGELOG.md
.PHONY: changelog-unreleased
changelog-unreleased: ## show the current changelog that will be produced on the next release (note: requires GITHUB_TOKEN set)
@docker run -it --rm \
-v "$(shell pwd)":/usr/local/src/your-app \
ferrarimarco/github-changelog-generator \
--user anchore \
--project $(BIN) \
-t ${GITHUB_TOKEN} \
--exclude-labels 'duplicate,question,invalid,wontfix,size:small,size:medium,size:large,size:x-large' \
--since-tag $(LAST_TAG)
@printf '\n$(BOLD)$(CYAN)Unreleased Changes (closed PRs and issues will not be in the final changelog)$(RESET)\n'
@docker run -it --rm \
-v $(shell pwd)/CHANGELOG.md:/CHANGELOG.md \
rawkode/mdv \
-t 748.5989 \
/CHANGELOG.md
.PHONY: release
release: clean-dist changelog ## Build and publish final binaries and packages
release: clean-dist ci-bootstrap-mac changelog-release ## Build and publish final binaries and packages. Intended to be run only on macOS.
$(call title,Publishing release artifacts)
# Prepare for macOS-specific signing process
.github/scripts/mac-prepare-for-signing.sh
# create a config with the dist dir overridden
echo "dist: $(DISTDIR)" > $(TEMPDIR)/goreleaser.yaml
cat .goreleaser.yaml >> $(TEMPDIR)/goreleaser.yaml
# release
BUILD_GIT_TREE_STATE=$(GITTREESTATE) \
$(TEMPDIR)/goreleaser \
--rm-dist \
--config $(TEMPDIR)/goreleaser.yaml \
--release-notes <(cat CHANGELOG.md)
# release (note the version transformation from v0.7.0 --> 0.7.0)
bash -c "\
BUILD_GIT_TREE_STATE=$(GITTREESTATE) \
VERSION=$(VERSION:v%=%) \
$(TEMPDIR)/goreleaser \
--rm-dist \
--config $(TEMPDIR)/goreleaser.yaml \
--release-notes <(cat CHANGELOG.md)"
# verify checksum signatures
.github/scripts/verify-signature.sh "$(DISTDIR)"
@@ -260,7 +284,7 @@ release: clean-dist changelog ## Build and publish final binaries and packages
.github/scripts/update-version-file.sh "$(DISTDIR)" "$(VERSION)"
.PHONY: clean
clean: clean-dist clean-snapshot clean-json-schema-examples ## Remove previous builds and result reports
clean: clean-dist clean-snapshot ## Remove previous builds and result reports
rm -rf $(RESULTSDIR)/*
.PHONY: clean-snapshot
@@ -270,7 +294,3 @@ clean-snapshot:
.PHONY: clean-dist
clean-dist:
rm -rf $(DISTDIR) $(TEMPDIR)/goreleaser.yaml
.PHONY: clean-json-schema-examples
clean-json-schema-examples:
rm -f schema/json/examples/*
+56 -27
View File
@@ -1,6 +1,7 @@
# syft
[![CircleCI](https://circleci.com/gh/anchore/syft.svg?style=svg)](https://circleci.com/gh/anchore/syft)
[![Static Analysis + Unit + Integration](https://github.com/anchore/syft/workflows/Static%20Analysis%20+%20Unit%20+%20Integration/badge.svg)](https://github.com/anchore/syft/actions?query=workflow%3A%22Static+Analysis+%2B+Unit+%2B+Integration%22)
[![Acceptance](https://github.com/anchore/syft/workflows/Acceptance/badge.svg)](https://github.com/anchore/syft/actions?query=workflow%3AAcceptance)
[![Go Report Card](https://goreportcard.com/badge/github.com/anchore/syft)](https://goreportcard.com/report/github.com/anchore/syft)
[![GitHub release](https://img.shields.io/github/release/anchore/syft.svg)](https://github.com/anchore/syft/releases/latest)
[![License: Apache-2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/anchore/syft/blob/main/LICENSE)
@@ -13,12 +14,13 @@ A CLI tool and go library for generating a Software Bill of Materials (SBOM) fro
- Catalog container images and filesystems to discover packages and libraries.
- Supports packages and libraries from various ecosystems (APK, DEB, RPM, Ruby Bundles, Python Wheel/Egg/requirements.txt, JavaScript NPM/Yarn, Java JAR/EAR/WAR, Jenkins plugins JPI/HPI, Go modules)
- Linux distribution identification (supports Alpine, BusyBox, CentOS/RedHat, Debian/Ubuntu flavored distributions)
- Supports Docker and OCI image formats
> :warning: **This is pre-release software** and it may not work as expected. If you encounter an issue, please [let us know using the issue tracker](https://github.com/anchore/syft/issues).
If you encounter an issue, please [let us know using the issue tracker](https://github.com/anchore/syft/issues).
## Getting started
To generate an SBOM for an image:
To generate an SBOM for a Docker or OCI image:
```
syft <image>
```
@@ -32,41 +34,41 @@ syft <image> --scope all-layers
Syft can generate a SBOM from a variety of sources:
```
# catalog a docker image tar (from the result of "docker image save ... -o image.tar" command)
syft docker-archive://path/to/image.tar
# catalog a container image archive (from the result of `docker image save ...`, `podman save ...`, or `skopeo copy` commands)
syft path/to/image.tar
# catalog a directory
syft dir://path/to/dir
syft path/to/dir
```
By default Syft shows a summary table, however, more detailed `text` and `json` formats are also available.
The output format for Syft is configurable as well:
```
syft <image> -o json
syft <image> -o text
syft <image> -o <format>
```
Where the `format`s available are:
- `json`: Use this to get as much information out of Syft as possible!
- `text`: A row-oriented, human-and-machine-friendly output.
- `cyclonedx`: A XML report conforming to the [CycloneDX 1.2](https://cyclonedx.org/) specification.
- `table`: A columnar summary (default).
## Installation
**Recommended**
**Recommended (macOS and Linux)**
```bash
# install the latest version to /usr/local/bin
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
# install a specific version into a specific dir
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s <RELEASE_VERSION> -b <SOME_BIN_PATH>
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b <SOME_BIN_PATH> <RELEASE_VERSION>
```
**macOS**
**Homebrew (macOS)**
```bash
brew tap anchore/syft
brew install syft
```
You may experience a "macOS cannot verify app is free from malware" error upon running Syft because it is not yet signed and notarized. You can override this using `xattr`.
```bash
xattr -rd com.apple.quarantine syft
```
## Configuration
Configuration search paths:
@@ -79,31 +81,58 @@ Configuration search paths:
Configuration options (example values are the default):
```yaml
# same as -o ; the output format of the SBOM report (options: table, text, json)
# the output format of the SBOM report (options: table, text, json)
# same as -o ; SYFT_OUTPUT env var
output: "table"
# same as -s ; the search space to look for packages (options: all-layers, squashed)
# the search space to look for packages (options: all-layers, squashed)
# same as -s ; SYFT_SCOPE env var
scope: "squashed"
# same as -q ; suppress all output (except for the SBOM report)
# suppress all output (except for the SBOM report)
# same as -q ; SYFT_QUIET env var
quiet: false
# enable/disable checking for application updates on startup
# same as SYFT_CHECK_FOR_APP_UPDATE env var
check-for-app-update: true
log:
# use structured logging
# same as SYFT_LOG_STRUCTURED env var
structured: false
# the log level; note: detailed logging suppress the ETUI
# same as SYFT_LOG_LEVEL env var
level: "error"
# location to write the log file (default is not to have a log file)
# same as SYFT_LOG_FILE env var
file: ""
# enable/disable checking for application updates on startup
check-for-app-update: true
```
anchore:
# (feature-preview) enable uploading of results to Anchore Enterprise automatically (supported on Enterprise 3.0+)
# same as SYFT_ANCHORE_UPLOAD_ENABLED env var
upload-enabled: false
## Future plans
# (feature-preview) the Anchore Enterprise Host or URL to upload results to (supported on Enterprise 3.0+)
# same as -H ; SYFT_ANCHORE_HOST env var
host: ""
The following areas of potential development are currently being investigated:
- Add CycloneDX to list of output formats
- Establish a stable interchange format w/Grype
# (feature-preview) the path after the host to the Anchore External API (supported on Enterprise 3.0+)
# same as SYFT_ANCHORE_PATH env var
path: ""
# (feature-preview) the username to authenticate against Anchore Enterprise (supported on Enterprise 3.0+)
# same as -u ; SYFT_ANCHORE_USERNAME env var
username: ""
# (feature-preview) the password to authenticate against Anchore Enterprise (supported on Enterprise 3.0+)
# same as -p ; SYFT_ANCHORE_PASSWORD env var
password: ""
# (feature-preview) path to dockerfile to be uploaded with the syft results to Anchore Enterprise (supported on Enterprise 3.0+)
# same as -d ; SYFT_ANCHORE_DOCKERFILE env var
dockerfile: ""
```
+70 -19
View File
@@ -4,20 +4,17 @@ import (
"fmt"
"os"
"github.com/spf13/cobra"
"github.com/anchore/syft/syft/presenter"
"github.com/anchore/syft/syft/scope"
"github.com/anchore/stereoscope"
"github.com/anchore/syft/internal/config"
"github.com/anchore/syft/internal/format"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/logger"
"github.com/anchore/syft/syft"
"github.com/anchore/syft/syft/presenter"
"github.com/anchore/syft/syft/source"
"github.com/gookit/color"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/wagoodman/go-partybus"
"gopkg.in/yaml.v2"
)
var appConfig *config.Application
@@ -49,15 +46,20 @@ func setGlobalCliOptions() {
// scan options
flag := "scope"
rootCmd.Flags().StringP(
"scope", "s", scope.SquashedScope.String(),
fmt.Sprintf("selection of layers to catalog, options=%v", scope.Options))
"scope", "s", source.SquashedScope.String(),
fmt.Sprintf("selection of layers to catalog, options=%v", source.AllScopes))
if err := viper.BindPFlag(flag, rootCmd.Flags().Lookup(flag)); err != nil {
fmt.Printf("unable to bind flag '%s': %+v", flag, err)
os.Exit(1)
}
setGlobalFormatOptions()
setGlobalUploadOptions()
}
func setGlobalFormatOptions() {
// output & formatting options
flag = "output"
flag := "output"
rootCmd.Flags().StringP(
flag, "o", string(presenter.TablePresenter),
fmt.Sprintf("report output formatter, options=%v", presenter.Options),
@@ -80,12 +82,67 @@ func setGlobalCliOptions() {
rootCmd.Flags().CountVarP(&cliOpts.Verbosity, "verbose", "v", "increase verbosity (-v = info, -vv = debug)")
}
func setGlobalUploadOptions() {
flag := "host"
rootCmd.Flags().StringP(
flag, "H", "",
"the hostname or URL of the Anchore Enterprise instance to upload to",
)
if err := viper.BindPFlag("anchore.host", rootCmd.Flags().Lookup(flag)); err != nil {
fmt.Printf("unable to bind flag '%s': %+v", flag, err)
os.Exit(1)
}
flag = "username"
rootCmd.Flags().StringP(
flag, "u", "",
"the username to authenticate against Anchore Enterprise",
)
if err := viper.BindPFlag("anchore.username", rootCmd.Flags().Lookup(flag)); err != nil {
fmt.Printf("unable to bind flag '%s': %+v", flag, err)
os.Exit(1)
}
flag = "password"
rootCmd.Flags().StringP(
flag, "p", "",
"the password to authenticate against Anchore Enterprise",
)
if err := viper.BindPFlag("anchore.password", rootCmd.Flags().Lookup(flag)); err != nil {
fmt.Printf("unable to bind flag '%s': %+v", flag, err)
os.Exit(1)
}
flag = "dockerfile"
rootCmd.Flags().StringP(
flag, "d", "",
"include dockerfile for upload to Anchore Enterprise",
)
if err := viper.BindPFlag("anchore.dockerfile", rootCmd.Flags().Lookup(flag)); err != nil {
fmt.Printf("unable to bind flag '#{flag}': #{err}")
os.Exit(1)
}
flag = "overwrite-existing-image"
rootCmd.Flags().Bool(
flag, false,
"overwrite an existing image during the upload to Anchore Enterprise",
)
if err := viper.BindPFlag("anchore.overwrite-existing-image", rootCmd.Flags().Lookup(flag)); err != nil {
fmt.Printf("unable to bind flag '#{flag}': #{err}")
os.Exit(1)
}
}
func initAppConfig() {
cfg, err := config.LoadConfigFromFile(viper.GetViper(), &cliOpts)
cfgVehicle := viper.GetViper()
wasHostnameSet := rootCmd.Flags().Changed("host")
cfg, err := config.LoadApplicationConfig(cfgVehicle, cliOpts, wasHostnameSet)
if err != nil {
fmt.Printf("failed to load application config: \n\t%+v\n", err)
os.Exit(1)
}
appConfig = cfg
}
@@ -101,18 +158,12 @@ func initLogging() {
logWrapper := logger.NewLogrusLogger(cfg)
syft.SetLogger(logWrapper)
stereoscope.SetLogger(&logger.LogrusNestedLogger{
Logger: logWrapper.Logger.WithField("from-lib", "steroscope"),
Logger: logWrapper.Logger.WithField("from-lib", "stereoscope"),
})
}
func logAppConfig() {
appCfgStr, err := yaml.Marshal(&appConfig)
if err != nil {
log.Debugf("Could not display application config: %+v", err)
} else {
log.Debugf("Application config:\n%+v", format.Magenta.Format(string(appCfgStr)))
}
log.Debugf("Application config:\n%+v", color.Magenta.Sprint(appConfig.String()))
}
func initEventBus() {
+65
View File
@@ -0,0 +1,65 @@
package cmd
import (
"os"
"github.com/spf13/cobra"
)
// completionCmd represents the completion command
var completionCmd = &cobra.Command{
Use: "completion [bash|zsh|fish]",
Short: "Generate a shell completion for Syft (listing local docker images)",
Long: `To load completions (docker image list):
Bash:
$ source <(syft completion bash)
# To load completions for each session, execute once:
Linux:
$ syft completion bash > /etc/bash_completion.d/syft
MacOS:
$ syft completion bash > /usr/local/etc/bash_completion.d/syft
Zsh:
# If shell completion is not already enabled in your environment you will need
# to enable it. You can execute the following once:
$ echo "autoload -U compinit; compinit" >> ~/.zshrc
# To load completions for each session, execute once:
$ syft completion zsh > "${fpath[1]}/_syft"
# You will need to start a new shell for this setup to take effect.
Fish:
$ syft completion fish | source
# To load completions for each session, execute once:
$ syft completion fish > ~/.config/fish/completions/syft.fish
`,
DisableFlagsInUseLine: true,
ValidArgs: []string{"bash", "zsh", "fish"},
Args: cobra.ExactValidArgs(1),
Run: func(cmd *cobra.Command, args []string) {
var err error
switch args[0] {
case "bash":
err = cmd.Root().GenBashCompletion(os.Stdout)
case "zsh":
err = cmd.Root().GenZshCompletion(os.Stdout)
case "fish":
err = cmd.Root().GenFishCompletion(os.Stdout, true)
}
if err != nil {
panic(err)
}
},
}
func init() {
rootCmd.AddCommand(completionCmd)
}
+143 -7
View File
@@ -1,30 +1,47 @@
package cmd
import (
"context"
"fmt"
"io/ioutil"
"os"
"strings"
"github.com/pkg/profile"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/anchore"
"github.com/anchore/syft/internal/bus"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/ui"
"github.com/anchore/syft/internal/version"
"github.com/anchore/syft/syft"
"github.com/anchore/syft/syft/distro"
"github.com/anchore/syft/syft/event"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/presenter"
"github.com/anchore/syft/syft/source"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/client"
"github.com/spf13/cobra"
"github.com/wagoodman/go-partybus"
)
var rootCmd = &cobra.Command{
Use: fmt.Sprintf("%s [SOURCE]", internal.ApplicationName),
Short: "A tool for generating a Software Bill Of Materials (SBOM) from container images and filesystems",
Short: "A tool for generating a Software Bill Of Materials (PackageSBOM) from container images and filesystems",
Long: internal.Tprintf(`
Supports the following image sources:
{{.appName}} yourrepo/yourimage:tag defaults to using images from a docker daemon
{{.appName}} docker://yourrepo/yourimage:tag explicitly use the docker daemon
{{.appName}} tar://path/to/yourimage.tar use a tarball from disk
{{.appName}} dir://path/to/yourproject read directly from a path in disk
{{.appName}} yourrepo/yourimage:tag defaults to using images from a Docker daemon
{{.appName}} path/to/yourproject a Docker tar, OCI tar, OCI directory, or generic filesystem directory
You can also explicitly specify the scheme to use:
{{.appName}} docker:yourrepo/yourimage:tag explicitly use the Docker daemon
{{.appName}} docker-archive:path/to/yourimage.tar use a tarball from disk for archives created from "docker save"
{{.appName}} oci-archive:path/to/yourimage.tar use a tarball from disk for OCI archives (from Podman or otherwise)
{{.appName}} oci-dir:path/to/yourimage read directly from a path on disk for OCI layout directories (from Skopeo or otherwise)
{{.appName}} dir:path/to/yourproject read directly from a path on disk (any directory)
`, map[string]interface{}{
"appName": internal.ApplicationName,
}),
@@ -38,12 +55,39 @@ Supports the following image sources:
}
os.Exit(1)
}
if appConfig.Dev.ProfileCPU && appConfig.Dev.ProfileMem {
log.Errorf("cannot profile CPU and memory simultaneously")
os.Exit(1)
}
if appConfig.Dev.ProfileCPU {
defer profile.Start(profile.CPUProfile).Stop()
} else if appConfig.Dev.ProfileMem {
defer profile.Start(profile.MemProfile).Stop()
}
err := doRunCmd(cmd, args)
if err != nil {
log.Errorf(err.Error())
os.Exit(1)
}
},
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
// Since we use ValidArgsFunction, Cobra will call this AFTER having parsed all flags and arguments provided
dockerImageRepoTags, err := ListLocalDockerImages(toComplete)
if err != nil {
// Indicates that an error occurred and completions should be ignored
return []string{"completion failed"}, cobra.ShellCompDirectiveError
}
if len(dockerImageRepoTags) == 0 {
return []string{"no docker images found"}, cobra.ShellCompDirectiveError
}
// ShellCompDirectiveDefault indicates that the shell will perform its default behavior after completions have
// been provided (without implying other possible directives)
return dockerImageRepoTags, cobra.ShellCompDirectiveDefault
},
}
func startWorker(userInput string) <-chan error {
@@ -68,23 +112,115 @@ func startWorker(userInput string) <-chan error {
}
}
catalog, scope, distro, err := syft.Catalog(userInput, appConfig.ScopeOpt)
src, catalog, distro, err := syft.Catalog(userInput, appConfig.ScopeOpt)
if err != nil {
errs <- fmt.Errorf("failed to catalog input: %+v", err)
return
}
if appConfig.Anchore.UploadEnabled {
if err := doImport(src, src.Metadata, catalog, distro); err != nil {
errs <- err
return
}
}
bus.Publish(partybus.Event{
Type: event.CatalogerFinished,
Value: presenter.GetPresenter(appConfig.PresenterOpt, *scope, catalog, distro),
Value: presenter.GetPresenter(appConfig.PresenterOpt, src.Metadata, catalog, distro),
})
}()
return errs
}
func doImport(src source.Source, s source.Metadata, catalog *pkg.Catalog, d *distro.Distro) error {
// TODO: ETUI element for this
log.Infof("uploading results to %s", appConfig.Anchore.Host)
if src.Metadata.Scheme != source.ImageScheme {
return fmt.Errorf("unable to upload results: only images are supported")
}
var dockerfileContents []byte
if appConfig.Anchore.Dockerfile != "" {
if _, err := os.Stat(appConfig.Anchore.Dockerfile); os.IsNotExist(err) {
return fmt.Errorf("unable to read dockerfile=%q: %w", appConfig.Anchore.Dockerfile, err)
}
fh, err := os.Open(appConfig.Anchore.Dockerfile)
if err != nil {
return fmt.Errorf("unable to open dockerfile=%q: %w", appConfig.Anchore.Dockerfile, err)
}
dockerfileContents, err = ioutil.ReadAll(fh)
if err != nil {
return fmt.Errorf("unable to read dockerfile=%q: %w", appConfig.Anchore.Dockerfile, err)
}
}
var scheme string
var hostname = appConfig.Anchore.Host
urlFields := strings.Split(hostname, "://")
if len(urlFields) > 1 {
scheme = urlFields[0]
hostname = urlFields[1]
}
c, err := anchore.NewClient(anchore.Configuration{
Hostname: hostname,
Username: appConfig.Anchore.Username,
Password: appConfig.Anchore.Password,
Scheme: scheme,
})
if err != nil {
return fmt.Errorf("failed to create anchore client: %+v", err)
}
importCfg := anchore.ImportConfig{
ImageMetadata: src.Image.Metadata,
SourceMetadata: s,
Catalog: catalog,
Distro: d,
Dockerfile: dockerfileContents,
OverwriteExistingUpload: appConfig.Anchore.OverwriteExistingImage,
}
if err := c.Import(context.Background(), importCfg); err != nil {
return fmt.Errorf("failed to upload results to host=%s: %+v", appConfig.Anchore.Host, err)
}
return nil
}
func doRunCmd(_ *cobra.Command, args []string) error {
userInput := args[0]
errs := startWorker(userInput)
ux := ui.Select(appConfig.CliOptions.Verbosity > 0, appConfig.Quiet)
return ux(errs, eventSubscription)
}
func ListLocalDockerImages(prefix string) ([]string, error) {
var repoTags = make([]string, 0)
ctx := context.Background()
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil {
return repoTags, err
}
// Only want to return tagged images
imageListArgs := filters.NewArgs()
imageListArgs.Add("dangling", "false")
images, err := cli.ImageList(ctx, types.ImageListOptions{All: false, Filters: imageListArgs})
if err != nil {
return repoTags, err
}
for _, image := range images {
// image may have multiple tags
for _, tag := range image.RepoTags {
if strings.HasPrefix(tag, prefix) {
repoTags = append(repoTags, tag)
}
}
}
return repoTags, nil
}
+28 -6
View File
@@ -1,14 +1,18 @@
package cmd
import (
"encoding/json"
"fmt"
"os"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/version"
"github.com/anchore/syft/syft/presenter"
"github.com/spf13/cobra"
)
var showVerboseVersionInfo bool
var outputFormat string
var versionCmd = &cobra.Command{
Use: "version",
@@ -17,14 +21,15 @@ var versionCmd = &cobra.Command{
}
func init() {
versionCmd.Flags().BoolVarP(&showVerboseVersionInfo, "verbose", "v", false, "show additional version information")
versionCmd.Flags().StringVarP(&outputFormat, "output", "o", string(presenter.TextPresenter), "format to show version information (available=[text, json])")
rootCmd.AddCommand(versionCmd)
}
func printVersion(_ *cobra.Command, _ []string) {
versionInfo := version.FromBuild()
if showVerboseVersionInfo {
switch outputFormat {
case "text":
fmt.Println("Application: ", internal.ApplicationName)
fmt.Println("Version: ", versionInfo.Version)
fmt.Println("BuildDate: ", versionInfo.BuildDate)
@@ -33,7 +38,24 @@ func printVersion(_ *cobra.Command, _ []string) {
fmt.Println("Platform: ", versionInfo.Platform)
fmt.Println("GoVersion: ", versionInfo.GoVersion)
fmt.Println("Compiler: ", versionInfo.Compiler)
} else {
fmt.Printf("%s %s\n", internal.ApplicationName, versionInfo.Version)
case "json":
enc := json.NewEncoder(os.Stdout)
enc.SetEscapeHTML(false)
enc.SetIndent("", " ")
err := enc.Encode(&struct {
version.Version
Application string `json:"application"`
}{
Version: versionInfo,
Application: internal.ApplicationName,
})
if err != nil {
fmt.Printf("failed to show version information: %+v\n", err)
os.Exit(1)
}
default:
fmt.Printf("unsupported output format: %s\n", outputFormat)
os.Exit(1)
}
}
+15 -15
View File
@@ -4,18 +4,20 @@ go 1.14
require (
github.com/adrg/xdg v0.2.1
github.com/anchore/go-rpmdb v0.0.0-20200811175839-cbc751c28e8e
github.com/anchore/go-testutils v0.0.0-20200624184116-66aa578126db
github.com/alecthomas/jsonschema v0.0.0-20200530073317-71f438968921
github.com/anchore/client-go v0.0.0-20201216213038-a486b838e238
github.com/anchore/go-rpmdb v0.0.0-20201106153645-0043963c2e12
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b
github.com/anchore/stereoscope v0.0.0-20200813152757-548b22c8a0b3
github.com/bmatcuk/doublestar v1.3.1
github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe // indirect
github.com/anchore/stereoscope v0.0.0-20210105001222-7beea73cb7e5
github.com/antihax/optional v1.0.0
github.com/bmatcuk/doublestar/v2 v2.0.4
github.com/docker/docker v17.12.0-ce-rc1.0.20200309214505-aa6a9891b09c+incompatible
github.com/dustin/go-humanize v1.0.0
github.com/go-test/deep v1.0.6
github.com/google/go-containerregistry v0.1.1 // indirect
github.com/facebookincubator/nvdtools v0.1.4
github.com/go-test/deep v1.0.7
github.com/google/uuid v1.1.1
github.com/gookit/color v1.2.7
github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99 // indirect
github.com/hashicorp/go-multierror v1.1.0
github.com/hashicorp/go-version v1.2.0
github.com/mitchellh/go-homedir v1.1.0
@@ -23,10 +25,12 @@ require (
github.com/olekukonko/tablewriter v0.0.4
github.com/package-url/packageurl-go v0.1.0
github.com/pelletier/go-toml v1.8.0
github.com/rogpeppe/go-internal v1.5.2
github.com/pkg/profile v1.5.0
github.com/scylladb/go-set v1.0.2
github.com/sergi/go-diff v1.1.0
github.com/sirupsen/logrus v1.6.0
github.com/spf13/cobra v1.0.0
github.com/spf13/afero v1.2.2
github.com/spf13/cobra v1.0.1-0.20200909172742-8a63648dd905
github.com/spf13/viper v1.7.0
github.com/wagoodman/go-partybus v0.0.0-20200526224238-eb215533f07d
github.com/wagoodman/go-progress v0.0.0-20200731105512-1020f39e6240
@@ -34,10 +38,6 @@ require (
github.com/x-cray/logrus-prefixed-formatter v0.5.2
github.com/xeipuuv/gojsonschema v1.2.0
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
golang.org/x/net v0.0.0-20200625001655-4c5254603344 // indirect
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 // indirect
golang.org/x/sys v0.0.0-20200610111108-226ff32320da // indirect
google.golang.org/genproto v0.0.0-20200615140333-fd031eab31e7 // indirect
gopkg.in/ini.v1 v1.57.0 // indirect
golang.org/x/mod v0.3.0
gopkg.in/yaml.v2 v2.3.0
)
+52 -43
View File
@@ -104,10 +104,10 @@ github.com/Masterminds/semver/v3 v3.1.0 h1:Y2lUDsFKVRSYGojLJ1yLxSXdMmMYTYls0rCvo
github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU=
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5 h1:ygIc8M6trr62pF5DucadTWGdEB4mEyvzi0e2nbcmcyA=
github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=
github.com/Microsoft/hcsshim v0.8.9 h1:VrfodqvztU8YSOvygU+DN1BGaSGxmrNfqOv5oOuX2Bk=
github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8=
github.com/Microsoft/go-winio v0.4.15-0.20200908182639-5b44b70ab3ab h1:9pygWVFqbY9lPxM0peffumuVDyMuIMzNLyO9uFjJuQo=
github.com/Microsoft/go-winio v0.4.15-0.20200908182639-5b44b70ab3ab/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=
github.com/Microsoft/hcsshim v0.8.10 h1:k5wTrpnVU2/xv8ZuzGkbXVd3js5zJ8RnumPo5RxiIxU=
github.com/Microsoft/hcsshim v0.8.10/go.mod h1:g5uw8EV2mAlzqe94tfNBNdr89fnbD/n3HV0OhsddkmM=
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/OpenPeeDeeP/depguard v1.0.1 h1:VlW4R6jmBIv3/u1JNlawEvJMM4J+dPORPaZasQee8Us=
@@ -119,21 +119,30 @@ github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdko
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/adrg/xdg v0.2.1 h1:VSVdnH7cQ7V+B33qSJHTCRlNgra1607Q8PzEmnvb2Ic=
github.com/adrg/xdg v0.2.1/go.mod h1:ZuOshBmzV4Ta+s23hdfFZnBsdzmoR3US0d7ErpqSbTQ=
github.com/alecthomas/jsonschema v0.0.0-20200530073317-71f438968921 h1:T3+cD5fYvuH36h7EZq+TDpm+d8a6FSD4pQsbmuGGQ8o=
github.com/alecthomas/jsonschema v0.0.0-20200530073317-71f438968921/go.mod h1:/n6+1/DWPltRLWL/VKyUxg6tzsl5kHUCcraimt4vr60=
github.com/alecthomas/kingpin v2.2.6+incompatible/go.mod h1:59OFYbFVLKQKq+mqrL6Rw5bR0c3ACQaawgXx0QYndlE=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/anchore/go-rpmdb v0.0.0-20200811175839-cbc751c28e8e h1:kty6r0R2JeaNPeWKSYDC+HW3hkqwFh4PP5TQ8pUPYFw=
github.com/anchore/go-rpmdb v0.0.0-20200811175839-cbc751c28e8e/go.mod h1:iYuIG0Nai8dR0ri3LhZQKUyO1loxUWAGvoWhXDmjy1A=
github.com/anchore/go-testutils v0.0.0-20200624184116-66aa578126db h1:LWKezJnFTFxNkZ4MzajVf+YWvJS0+7hwFr59u6SS7cw=
github.com/anchore/go-testutils v0.0.0-20200624184116-66aa578126db/go.mod h1:D3rc2L/q4Hcp9eeX6AIJH4Q+kPjOtJCFhG9za90j+nU=
github.com/anchore/client-go v0.0.0-20201216213038-a486b838e238 h1:/iI+1cj1a27ow0wj378pPJIm8sCSy6I21Tz6oLbLDQY=
github.com/anchore/client-go v0.0.0-20201216213038-a486b838e238/go.mod h1:FaODhIA06mxO1E6R32JE0TL1JWZZkmjRIAd4ULvHUKk=
github.com/anchore/go-rpmdb v0.0.0-20201106153645-0043963c2e12 h1:xbeIbn5F52JVx3RUIajxCj8b0y+9lywspql4sFhcxWQ=
github.com/anchore/go-rpmdb v0.0.0-20201106153645-0043963c2e12/go.mod h1:juoyWXIj7sJ1IDl4E/KIfyLtovbs5XQVSIdaQifFQT8=
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04 h1:VzprUTpc0vW0nnNKJfJieyH/TZ9UYAnTZs5/gHTdAe8=
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04/go.mod h1:6dK64g27Qi1qGQZ67gFmBFvEHScy0/C8qhQhNe5B5pQ=
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b h1:e1bmaoJfZVsCYMrIZBpFxwV26CbsuoEh5muXD5I1Ods=
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b/go.mod h1:Bkc+JYWjMCF8OyZ340IMSIi2Ebf3uwByOk6ho4wne1E=
github.com/anchore/stereoscope v0.0.0-20200520221116-025e07f1c93e/go.mod h1:bkyLl5VITnrmgErv4S1vDfVz/TGAZ5il6161IQo7w2g=
github.com/anchore/stereoscope v0.0.0-20200813152757-548b22c8a0b3 h1:pl+txuYlhK8Mmio4d+4zQI/1xg8X6BtNErTASrx23Wk=
github.com/anchore/stereoscope v0.0.0-20200813152757-548b22c8a0b3/go.mod h1:WntReQTI/I27FOQ87UgLVVzWgku6+ZsqfOTLxpIZFCs=
github.com/anchore/stereoscope v0.0.0-20210104203718-4c1d1bd9a255 h1:Ng7BDr9PQTCztANogjfEdEjjWUylhlPyZPhtarIGo00=
github.com/anchore/stereoscope v0.0.0-20210104203718-4c1d1bd9a255/go.mod h1:BMdPL0QEIYfpjQ3M7sHYZvuh6+vcomqF3TMHL8gr6Vw=
github.com/anchore/stereoscope v0.0.0-20210105000809-428eda0b2ec6 h1:JWpsV/8x1fuCYjJmNjT43cVFblLTpO/ISDnePukiTNw=
github.com/anchore/stereoscope v0.0.0-20210105000809-428eda0b2ec6/go.mod h1:BMdPL0QEIYfpjQ3M7sHYZvuh6+vcomqF3TMHL8gr6Vw=
github.com/anchore/stereoscope v0.0.0-20210105001222-7beea73cb7e5 h1:NGRfS6BZKElgiMbqdoH9iQn+6oxT7CJdZYrqgwvGkWY=
github.com/anchore/stereoscope v0.0.0-20210105001222-7beea73cb7e5/go.mod h1:BMdPL0QEIYfpjQ3M7sHYZvuh6+vcomqF3TMHL8gr6Vw=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/antihax/optional v1.0.0 h1:xK2lYat7ZLaVVcIuj82J8kIro4V6kDe0AUDFboUCwcg=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/apex/log v1.1.4/go.mod h1:AlpoD9aScyQfJDVHmLMEcx4oU6LqzkWp4Mg9GdAcEvQ=
github.com/apex/log v1.3.0 h1:1fyfbPvUwD10nMoh3hY6MXzvZShJQn9/ck7ATgAt5pA=
github.com/apex/log v1.3.0/go.mod h1:jd8Vpsr46WAe3EZSQ/IUMs2qQD/GOycT5rPWCO1yGcs=
@@ -161,8 +170,8 @@ github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJm
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4=
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=
github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/bmatcuk/doublestar v1.3.1 h1:rT8rxDPsavp9G+4ZULzqhhUSaI/OPsTZNG88Z3i0xvY=
github.com/bmatcuk/doublestar v1.3.1/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE=
github.com/bmatcuk/doublestar/v2 v2.0.4 h1:6I6oUiT/sU27eE2OFcWqBhL1SwjyvQuOssxT4a1yidI=
github.com/bmatcuk/doublestar/v2 v2.0.4/go.mod h1:QMmcs3H2AUQICWhfzLXz+IYln8lRQmTZRptLie8RgRw=
github.com/bombsimon/wsl/v2 v2.0.0/go.mod h1:mf25kr/SqFEPhhcxW1+7pxzGlW+hIl/hYTKY95VwV8U=
github.com/bombsimon/wsl/v2 v2.2.0/go.mod h1:Azh8c3XGEJl9LyX0/sFC+CKMc7Ssgua0g+6abzXN4Pg=
github.com/bombsimon/wsl/v3 v3.0.0/go.mod h1:st10JtZYLE4D5sC7b8xV4zTKZwAQjCH/Hy2Pm1FNZIc=
@@ -181,10 +190,11 @@ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghf
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko=
github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM=
github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw=
github.com/containerd/containerd v1.3.0 h1:xjvXQWABwS2uiv3TWgQt5Uth60Gu86LTGZXMJkjc7rY=
github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
@@ -193,7 +203,6 @@ github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMX
github.com/containerd/containerd v1.3.4 h1:3o0smo5SKY7H6AJCmJhsnCjR2/V2T8VmiHt7seN2/kI=
github.com/containerd/containerd v1.3.4/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
github.com/containerd/continuity v0.0.0-20200413184840-d3ef23f19fbb/go.mod h1:Dq467ZllaHgAtVp4p1xUQWBrFXR9s/wyoTpG8zOJGkY=
github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe h1:PEmIrUvwG9Yyv+0WKZqjXfSFDeZjs/q15g0m08BYS9k=
github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe/go.mod h1:cECdGN1O8G9bgKTlLhuPJimka6Xb/Gg7vYzCTNVxhvo=
github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI=
@@ -209,10 +218,12 @@ github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
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.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
@@ -253,9 +264,13 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/facebookincubator/nvdtools v0.1.4 h1:x1Ucw9+bSkMd8DJJN4jNQ1Lk4PSFlJarGOxp9D6WUMo=
github.com/facebookincubator/nvdtools v0.1.4/go.mod h1:0/FIVnSEl9YHXLq3tKBPpKaI0iUceDhdSHPlIwIX44Y=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/fatih/set v0.2.1 h1:nn2CaJyknWE/6txyUDGwysr3G5QC6xWB/PtVjPBbeaA=
github.com/fatih/set v0.2.1/go.mod h1:+RKtMCH+favT2+3YecHGxcc0b4KyVWA1QWWJUs4E0CI=
github.com/fortytw2/leaktest v1.2.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
@@ -294,8 +309,8 @@ github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-test/deep v1.0.6 h1:UHSEyLZUwX9Qoi99vVwvewiMC8mM2bf7XEM2nqvzEn8=
github.com/go-test/deep v1.0.6/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8=
github.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M=
github.com/go-test/deep v1.0.7/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8=
github.com/go-toolsmith/astcast v1.0.0 h1:JojxlmI6STnFVG9yOImLeGREv8W2ocNUM+iOhR6jE7g=
github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4=
github.com/go-toolsmith/astcopy v1.0.0 h1:OMgl1b1MEpjFQ1m5ztEO06rz5CUd3oBv9RF7+DyvdG8=
@@ -321,7 +336,7 @@ github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b h1:khEcpUM4yFcxg4
github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofrs/flock v0.0.0-20190320160742-5135e617513b h1:ekuhfTjngPhisSjOJ0QWKpPQE8/rbknHaes6WVJj5Hw=
github.com/gofrs/flock v0.0.0-20190320160742-5135e617513b/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
@@ -399,11 +414,8 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1 h1:/exdXoGamhu5ONeUJH0deniYLWYvQwW66yvlfiiKTu0=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-containerregistry v0.0.0-20200430153450-5cbd060f5c92/go.mod h1:pD1UFYs7MCAx+ZLShBdttcaOSbyc8F9Na/9IZLNwJeA=
github.com/google/go-containerregistry v0.1.0 h1:hL5mVw7cTX3SBr64Arpv+cJH93L+Z9Q6WjckImYLB3g=
github.com/google/go-containerregistry v0.1.0/go.mod h1:npTSyywOeILcgWqd+rvtzGWflIPPcBQhYoOONaY4ltM=
github.com/google/go-containerregistry v0.1.1 h1:AG8FSAfXglim2l5qSrqp5VK2Xl03PiBf25NiTGGamws=
github.com/google/go-containerregistry v0.1.1/go.mod h1:npTSyywOeILcgWqd+rvtzGWflIPPcBQhYoOONaY4ltM=
github.com/google/go-github/v28 v28.1.1 h1:kORf5ekX5qwXO2mGzXXOjMe/g6ap8ahVe0sBEulhSxo=
github.com/google/go-github/v28 v28.1.1/go.mod h1:bsqJWQX05omyWVmc00nEUql9mhQyv38lDZ8kPZcQVoM=
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
@@ -444,9 +456,8 @@ github.com/gookit/color v1.2.5/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1K
github.com/gookit/color v1.2.7 h1:4qePMNWZhrmbfYJDix+J4V2l0iVW+6jQGjicELlN14E=
github.com/gookit/color v1.2.7/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg=
github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99 h1:twflg0XRTjwKpxb/jFExr4HGq6on2dEOmnL6FV+fgPw=
github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/goreleaser/goreleaser v0.136.0 h1:Z+7XPrfGK11s/Sp+a06sx2FzGuCjTBdxN2ubpGvQbjY=
github.com/goreleaser/goreleaser v0.136.0/go.mod h1:wiKrPUeSNh6Wu8nUHxZydSOVQ/OZvOaO7DTtFqie904=
github.com/goreleaser/nfpm v1.2.1/go.mod h1:TtWrABZozuLOttX2uDlYyECfQX7x5XYkVxhjYcR6G9w=
@@ -502,6 +513,8 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 h1:i462o439ZjprVSFSZLZxcsoAe592sZB1rci2Z8j4wdk=
github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
@@ -667,7 +680,7 @@ github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zM
github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y=
github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/package-url/packageurl-go v0.1.0 h1:efWBc98O/dBZRg1pw2xiDzovnlMjCa9NPnfaiBduh8I=
github.com/package-url/packageurl-go v0.1.0/go.mod h1:C/ApiuWpmbpni4DIOECf6WCjFUZV7O1Fx7VAzrZHgBw=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
@@ -683,6 +696,8 @@ github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/profile v1.5.0 h1:042Buzk+NhDI+DeSAA62RwJL8VAuZUMQZUjCsRz1Mug=
github.com/pkg/profile v1.5.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18=
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -721,6 +736,8 @@ github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb
github.com/sassoftware/go-rpmutils v0.0.0-20190420191620-a8f1baeba37b/go.mod h1:am+Fp8Bt506lA3Rk3QCmSqmYmLMnPDhdDUcosQCAx+I=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U=
github.com/scylladb/go-set v1.0.2 h1:SkvlMCKhP0wyyct6j+0IHJkBkSZL+TDzZ4E7f7BCcRE=
github.com/scylladb/go-set v1.0.2/go.mod h1:DkpGd78rljTxKAnTDPFqXSGxvETQnJyuSOQwsHycqfs=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/securego/gosec v0.0.0-20200103095621-79fbf3af8d83/go.mod h1:vvbZ2Ae7AzSq3/kywjUDxSNq2SJ27RxCz2un0H3ePqE=
github.com/securego/gosec v0.0.0-20200401082031-e946c8c39989 h1:rq2/kILQnPtq5oL4+IAjgVOjh5e2yj2aaCYi7squEvI=
@@ -740,7 +757,6 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx
github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo=
github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
@@ -766,6 +782,8 @@ github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8=
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
github.com/spf13/cobra v1.0.1-0.20200909172742-8a63648dd905 h1:Xn3y6St5vlEgvxekK6JuArarc79GKAj/jbcsMRCaRH4=
github.com/spf13/cobra v1.0.1-0.20200909172742-8a63648dd905/go.mod h1:jCpPr/pv3OLY1D1nS9lN5HIdIpmOO/WTYJcAmnPq1nU=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
@@ -787,7 +805,9 @@ github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoH
github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.3.1-0.20190311161405-34c6fa2dc709/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.0 h1:jlIyCplCJFULU/01vCkhKuTyc3OorI3bJFuw6obfgho=
github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -802,6 +822,7 @@ github.com/tetafro/godot v0.4.2/go.mod h1:/7NLHhv08H1+8DNj0MElpAACw1ajsCuf3TKNQx
github.com/timakin/bodyclose v0.0.0-20190930140734-f7f2e9bca95e/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk=
github.com/timakin/bodyclose v0.0.0-20200424151742-cb6215831a94 h1:ig99OeTyDwQWhPe2iw9lwfQVF1KB3Q4fpP3X7/2VBG8=
github.com/timakin/bodyclose v0.0.0-20200424151742-cb6215831a94/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk=
github.com/tj/assert v0.0.0-20171129193455-018094318fb0 h1:Rw8kxzWo1mr6FSaYXjQELRe88y2KdfynXdnK72rdjtA=
github.com/tj/assert v0.0.0-20171129193455-018094318fb0/go.mod h1:mZ9/Rh9oLWpLLDRpvE+3b7gP/C2YyLFYxNmcLnPTMe0=
github.com/tj/go-elastic v0.0.0-20171221160941-36157cbbebc2/go.mod h1:WjeM0Oo1eNAjXGDx2yma7uG2XoyRZTq1uv3M/o7imD0=
github.com/tj/go-kinesis v0.0.0-20171128231115-08b17f58cb1b/go.mod h1:/yhzCV0xPfx6jb1bBgRFjl5lytqVqZXEaeqWP8lTEao=
@@ -820,8 +841,8 @@ github.com/ultraware/funlen v0.0.2 h1:Av96YVBwwNSe4MLR7iI/BIa3VyI7/djnto/pK3Uxbd
github.com/ultraware/funlen v0.0.2/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA=
github.com/ultraware/whitespace v0.0.4 h1:If7Va4cM03mpgrNH9k49/VOicWpGoG70XPBFFODYDsg=
github.com/ultraware/whitespace v0.0.4/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA=
github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/uudashr/gocognit v1.0.1 h1:MoG2fZ0b/Eo7NXoIwCVFLG5JED3qgQz5/NEE+rOsjPs=
github.com/uudashr/gocognit v1.0.1/go.mod h1:j44Ayx2KW4+oB6SWMv8KsmHzZrOInQav7D3cQMJ5JUM=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
@@ -959,11 +980,10 @@ golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2 h1:eDrdRpKgkcCqKZQwyZRyeFZgfqt37SL7Kv3tok06cKE=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200602114024-627f9648deb9 h1:pNX+40auqi2JqRfOP1akLGtYcn15TUbkhwuCO3foqqM=
golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -980,8 +1000,6 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -1000,7 +1018,6 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190620070143-6f217b454f45/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1012,7 +1029,7 @@ golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1020,6 +1037,7 @@ golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1028,14 +1046,11 @@ golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121 h1:rITEj+UZHYC927n8GT97eC3zrpzXdb/voyeOuVKS46o=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980 h1:OjiUf46hAmXblsZdnoSXsEUSKU8r1UEzcL5RVZ4gO9Y=
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200610111108-226ff32320da h1:bGb80FudwxpeucJUjPYJXuJ8Hk91vNtfvrymzwiei38=
golang.org/x/sys v0.0.0-20200610111108-226ff32320da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
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=
@@ -1098,7 +1113,6 @@ golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapK
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204192400-7124308813f3/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200210192313-1ace956b0e17/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
@@ -1174,20 +1188,17 @@ google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfG
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200519141106-08726f379972/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece h1:1YM0uhfumvoDu9sx8+RyWwTI63zoCQvI23IYFRlvte0=
google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200604104852-0b0486081ffb h1:ek2py5bOqzR7MR/6obzk0rXUgYCLmjyLnaO9ssT+l6w=
google.golang.org/genproto v0.0.0-20200604104852-0b0486081ffb/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200615140333-fd031eab31e7 h1:1N7l1PuXZwEK7OhHdmKQROOM75PnUjABGwvVRbLBgFk=
google.golang.org/genproto v0.0.0-20200615140333-fd031eab31e7/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
@@ -1223,8 +1234,6 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.56.0 h1:DPMeDvGTM54DXbPkVIZsp19fp/I2K7zwA/itHYHKo8Y=
gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww=
gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
+15
View File
@@ -0,0 +1,15 @@
source = ["./dist/syft-macos_darwin_amd64/syft"] # The 'dist' directory path should ideally reference an env var, where the source of truth is the Makefile. I wasn't able to figure out how to solve this.
bundle_id = "com.anchore.toolbox.syft"
sign {
application_identity = "Developer ID Application: ANCHORE, INC. (9MJHKYX5AT)"
}
dmg {
output_path = "./dist/output.dmg"
volume_name = "Syft"
}
zip {
output_path = "./dist/output.zip"
}
+27 -13
View File
@@ -1,6 +1,6 @@
#!/bin/sh
set -e
# Code generated by godownloader on 2020-08-10T20:55:46Z. DO NOT EDIT.
# Code generated by godownloader on 2020-08-10T20:55:46Z.
#
usage() {
@@ -45,11 +45,16 @@ parse_args() {
execute() {
tmpdir=$(mktemp -d)
log_debug "downloading files into ${tmpdir}"
http_download "${tmpdir}/${TARBALL}" "${TARBALL_URL}"
http_download "${tmpdir}/${ARCHIVE}" "${ARCHIVE_URL}"
http_download "${tmpdir}/${CHECKSUM}" "${CHECKSUM_URL}"
hash_sha256_verify "${tmpdir}/${TARBALL}" "${tmpdir}/${CHECKSUM}"
# macOS has its own secure verification mechanism, and checksums.txt is not used.
if [ "$OS" != "darwin" ]; then
hash_sha256_verify "${tmpdir}/${ARCHIVE}" "${tmpdir}/${CHECKSUM}"
fi
srcdir="${tmpdir}"
(cd "${tmpdir}" && untar "${TARBALL}")
(cd "${tmpdir}" && unpack "${ARCHIVE}")
test ! -d "${BINDIR}" && install -d "${BINDIR}"
for binexe in $BINARIES; do
if [ "$OS" = "windows" ]; then
@@ -89,6 +94,7 @@ tag_to_version() {
adjust_format() {
# change format (tar.gz or zip) based on OS
case ${OS} in
darwin) FORMAT=dmg ;;
windows) FORMAT=zip ;;
esac
true
@@ -221,18 +227,26 @@ uname_arch_check() {
log_crit "uname_arch_check '$(uname -m)' got converted to '$arch' which is not a GOARCH value. Please file bug report at https://github.com/client9/shlib"
return 1
}
untar() {
tarball=$1
case "${tarball}" in
*.tar.gz | *.tgz) tar --no-same-owner -xzf "${tarball}" ;;
*.tar) tar --no-same-owner -xf "${tarball}" ;;
*.zip) unzip "${tarball}" ;;
unpack() {
archive=$1
case "${archive}" in
*.tar.gz | *.tgz) tar --no-same-owner -xzf "${archive}" ;;
*.tar) tar --no-same-owner -xf "${archive}" ;;
*.zip) unzip "${archive}" ;;
*.dmg) extract_from_dmg "${archive}" ;;
*)
log_err "untar unknown archive format for ${tarball}"
log_err "unpack unknown archive format for ${archive}"
return 1
;;
esac
}
extract_from_dmg() {
dmg_file=$1
mount_point="/Volumes/tmp-dmg"
hdiutil attach -quiet -nobrowse -mountpoint "${mount_point}" "${dmg_file}"
cp -fR "${mount_point}/" ./
hdiutil detach -quiet -force "${mount_point}"
}
http_download_curl() {
local_file=$1
source_url=$2
@@ -366,8 +380,8 @@ adjust_arch
log_info "found version: ${VERSION} for ${TAG}/${OS}/${ARCH}"
NAME=${PROJECT_NAME}_${VERSION}_${OS}_${ARCH}
TARBALL=${NAME}.${FORMAT}
TARBALL_URL=${GITHUB_DOWNLOAD}/${TAG}/${TARBALL}
ARCHIVE=${NAME}.${FORMAT}
ARCHIVE_URL=${GITHUB_DOWNLOAD}/${TAG}/${ARCHIVE}
CHECKSUM=${PROJECT_NAME}_${VERSION}_checksums.txt
CHECKSUM_URL=${GITHUB_DOWNLOAD}/${TAG}/${CHECKSUM}
+58
View File
@@ -0,0 +1,58 @@
package anchore
import (
"context"
"fmt"
"github.com/anchore/client-go/pkg/external"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/version"
)
type Configuration struct {
Hostname string
Username string
Password string
UserAgent string
Scheme string
}
type Client struct {
config Configuration
client *external.APIClient
}
func NewClient(cfg Configuration) (*Client, error) {
if cfg.UserAgent == "" {
versionInfo := version.FromBuild()
// format: product / product-version comment
cfg.UserAgent = fmt.Sprintf("%s / %s %s", internal.ApplicationName, versionInfo.Version, versionInfo.Platform)
}
if cfg.Scheme == "" {
cfg.Scheme = "https"
}
return &Client{
config: cfg,
client: external.NewAPIClient(&external.Configuration{
Host: cfg.Hostname,
UserAgent: cfg.UserAgent,
Scheme: cfg.Scheme,
}),
}, nil
}
func (c *Client) newRequestContext(parentContext context.Context) context.Context {
if parentContext == nil {
parentContext = context.Background()
}
return context.WithValue(
parentContext,
external.ContextBasicAuth,
external.BasicAuth{
UserName: c.config.Username,
Password: c.config.Password,
},
)
}
+143
View File
@@ -0,0 +1,143 @@
package anchore
import (
"context"
"errors"
"fmt"
"time"
"github.com/antihax/optional"
"github.com/anchore/client-go/pkg/external"
"github.com/anchore/stereoscope/pkg/image"
"github.com/anchore/syft/internal/bus"
"github.com/anchore/syft/syft/distro"
"github.com/anchore/syft/syft/event"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/source"
"github.com/wagoodman/go-partybus"
"github.com/wagoodman/go-progress"
)
type ImportConfig struct {
ImageMetadata image.Metadata
SourceMetadata source.Metadata
Catalog *pkg.Catalog
Distro *distro.Distro
Dockerfile []byte
OverwriteExistingUpload bool
}
func importProgress(source string) (*progress.Stage, *progress.Manual) {
stage := &progress.Stage{}
prog := &progress.Manual{
// this is the number of stages to expect; start + individual endpoints + stop
Total: 6,
}
bus.Publish(partybus.Event{
Type: event.ImportStarted,
Source: source,
Value: progress.StagedProgressable(&struct {
progress.Stager
progress.Progressable
}{
Stager: progress.Stager(stage),
Progressable: prog,
}),
})
return stage, prog
}
// nolint:funlen
func (c *Client) Import(ctx context.Context, cfg ImportConfig) error {
stage, prog := importProgress(c.config.Hostname)
ctxWithTimeout, cancel := context.WithTimeout(ctx, time.Second*30)
defer cancel()
authedCtx := c.newRequestContext(ctxWithTimeout)
stage.Current = "starting session"
startOperation, _, err := c.client.ImportsApi.CreateOperation(authedCtx)
if err != nil {
var detail = "no details given"
var openAPIErr external.GenericOpenAPIError
if errors.As(err, &openAPIErr) {
detail = string(openAPIErr.Body())
}
return fmt.Errorf("unable to start import session: %w: %s", err, detail)
}
prog.N++
sessionID := startOperation.Uuid
packageDigest, err := importPackageSBOM(authedCtx, c.client.ImportsApi, sessionID, cfg.SourceMetadata, cfg.Catalog, cfg.Distro, stage)
if err != nil {
return fmt.Errorf("failed to import Package SBOM: %w", err)
}
prog.N++
manifestDigest, err := importManifest(authedCtx, c.client.ImportsApi, sessionID, cfg.ImageMetadata.RawManifest, stage)
if err != nil {
return fmt.Errorf("failed to import Manifest: %w", err)
}
prog.N++
configDigest, err := importConfig(authedCtx, c.client.ImportsApi, sessionID, cfg.ImageMetadata.RawConfig, stage)
if err != nil {
return fmt.Errorf("failed to import Config: %w", err)
}
prog.N++
dockerfileDigest, err := importDockerfile(authedCtx, c.client.ImportsApi, sessionID, cfg.Dockerfile, stage)
if err != nil {
return fmt.Errorf("failed to import Dockerfile: %w", err)
}
prog.N++
stage.Current = "finalizing"
imageModel := addImageModel(cfg.ImageMetadata, packageDigest, manifestDigest, dockerfileDigest, configDigest, sessionID)
opts := external.AddImageOpts{
Force: optional.NewBool(cfg.OverwriteExistingUpload),
}
_, _, err = c.client.ImagesApi.AddImage(authedCtx, imageModel, &opts)
if err != nil {
var detail = "no details given"
var openAPIErr external.GenericOpenAPIError
if errors.As(err, &openAPIErr) {
detail = string(openAPIErr.Body())
}
return fmt.Errorf("unable to complete import session=%q: %w: %s", sessionID, err, detail)
}
prog.N++
stage.Current = ""
prog.SetCompleted()
return nil
}
func addImageModel(imageMetadata image.Metadata, packageDigest, manifestDigest, dockerfileDigest, configDigest, sessionID string) external.ImageAnalysisRequest {
var tags = make([]string, len(imageMetadata.Tags))
for i, t := range imageMetadata.Tags {
tags[i] = t.String()
}
return external.ImageAnalysisRequest{
Source: external.ImageSource{
Import: &external.ImageImportManifest{
Contents: external.ImportContentDigests{
Packages: packageDigest,
Manifest: manifestDigest,
Dockerfile: dockerfileDigest,
ImageConfig: configDigest,
},
Tags: tags,
Digest: imageMetadata.ManifestDigest,
LocalImageId: imageMetadata.ID,
OperationUuid: sessionID,
},
},
}
}
+50
View File
@@ -0,0 +1,50 @@
// nolint:dupl
package anchore
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"github.com/wagoodman/go-progress"
"github.com/anchore/client-go/pkg/external"
"github.com/anchore/syft/internal/log"
)
type configImportAPI interface {
ImportImageConfig(ctx context.Context, sessionID string, contents interface{}) (external.ImageImportContentResponse, *http.Response, error)
}
func importConfig(ctx context.Context, api configImportAPI, sessionID string, config []byte, stage *progress.Stage) (string, error) {
if len(config) > 0 {
log.Debug("importing image config")
stage.Current = "image config"
// API requires an object, but we do not verify the shape of this object locally
var sender map[string]interface{}
if err := json.Unmarshal(config, &sender); err != nil {
return "", err
}
response, httpResponse, err := api.ImportImageConfig(ctx, sessionID, sender)
if err != nil {
var openAPIErr external.GenericOpenAPIError
if errors.As(err, &openAPIErr) {
log.Errorf("api response: %+v", string(openAPIErr.Body()))
}
return "", fmt.Errorf("unable to import Config: %w", err)
}
defer httpResponse.Body.Close()
if httpResponse.StatusCode != 200 {
return "", fmt.Errorf("unable to import Config: %s", httpResponse.Status)
}
return response.Digest, nil
}
return "", nil
}
+129
View File
@@ -0,0 +1,129 @@
package anchore
import (
"context"
"encoding/json"
"fmt"
"net/http"
"strings"
"testing"
"github.com/wagoodman/go-progress"
"github.com/anchore/client-go/pkg/external"
"github.com/docker/docker/pkg/ioutils"
"github.com/go-test/deep"
)
type mockConfigImportAPI struct {
sessionID string
model interface{}
httpResponse *http.Response
err error
ctx context.Context
responseDigest string
wasCalled bool
}
func (m *mockConfigImportAPI) ImportImageConfig(ctx context.Context, sessionID string, contents interface{}) (external.ImageImportContentResponse, *http.Response, error) {
m.wasCalled = true
m.model = contents
m.sessionID = sessionID
m.ctx = ctx
if m.httpResponse == nil {
m.httpResponse = &http.Response{}
}
m.httpResponse.Body = ioutils.NewReadCloserWrapper(strings.NewReader(""), func() error { return nil })
return external.ImageImportContentResponse{Digest: m.responseDigest}, m.httpResponse, m.err
}
func TestConfigImport(t *testing.T) {
sessionID := "my-session"
tests := []struct {
name string
manifestJSONStr string
api *mockConfigImportAPI
expectsError bool
expectsCall bool
}{
{
name: "Go case: import works",
manifestJSONStr: `{ "key": "the-manifest-contents!" }`,
api: &mockConfigImportAPI{
httpResponse: &http.Response{StatusCode: 200},
responseDigest: "digest!",
},
expectsCall: true,
},
{
name: "No manifest provided",
manifestJSONStr: "",
api: &mockConfigImportAPI{},
expectsCall: false,
},
{
name: "API returns an error",
manifestJSONStr: `{ "key": "the-manifest-contents!" }`,
api: &mockConfigImportAPI{
err: fmt.Errorf("api error, something went wrong"),
},
expectsError: true,
expectsCall: true,
},
{
name: "API HTTP-level error",
manifestJSONStr: `{ "key": "the-manifest-contents!" }`,
api: &mockConfigImportAPI{
httpResponse: &http.Response{StatusCode: 404},
},
expectsError: true,
expectsCall: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
digest, err := importConfig(context.TODO(), test.api, sessionID, []byte(test.manifestJSONStr), &progress.Stage{})
// validate error handling
if err != nil && !test.expectsError {
t.Fatalf("did not expect an error, but got: %+v", err)
} else if err == nil && test.expectsError {
t.Fatalf("did expect an error, but got none")
}
if !test.api.wasCalled && test.expectsCall {
t.Fatalf("was not called!")
} else if test.api.wasCalled && !test.expectsCall {
t.Fatalf("should not have been called")
}
if !test.expectsCall {
return
}
if digest != test.api.responseDigest {
t.Errorf("unexpected content digest: %q != %q", digest, test.api.responseDigest)
}
// validating that the mock got the right parameters
if test.api.sessionID != sessionID {
t.Errorf("different session ID: %s != %s", test.api.sessionID, sessionID)
}
var expected map[string]interface{}
if err := json.Unmarshal([]byte(test.manifestJSONStr), &expected); err != nil {
t.Fatalf("could not unmarshal expected results")
}
for _, d := range deep.Equal(test.api.model, expected) {
t.Errorf("model difference: %s", d)
}
})
}
}
+43
View File
@@ -0,0 +1,43 @@
package anchore
import (
"context"
"errors"
"fmt"
"net/http"
"github.com/wagoodman/go-progress"
"github.com/anchore/syft/internal/log"
"github.com/anchore/client-go/pkg/external"
)
type dockerfileImportAPI interface {
ImportImageDockerfile(ctx context.Context, sessionID string, contents string) (external.ImageImportContentResponse, *http.Response, error)
}
func importDockerfile(ctx context.Context, api dockerfileImportAPI, sessionID string, dockerfile []byte, stage *progress.Stage) (string, error) {
if len(dockerfile) > 0 {
log.Debug("importing dockerfile")
stage.Current = "dockerfile"
response, httpResponse, err := api.ImportImageDockerfile(ctx, sessionID, string(dockerfile))
if err != nil {
var openAPIErr external.GenericOpenAPIError
if errors.As(err, &openAPIErr) {
log.Errorf("api response: %+v", string(openAPIErr.Body()))
}
return "", fmt.Errorf("unable to import Dockerfile: %w", err)
}
defer httpResponse.Body.Close()
if httpResponse.StatusCode != 200 {
return "", fmt.Errorf("unable to import Dockerfile: %s", httpResponse.Status)
}
return response.Digest, nil
}
return "", nil
}
+124
View File
@@ -0,0 +1,124 @@
package anchore
import (
"context"
"fmt"
"net/http"
"strings"
"testing"
"github.com/wagoodman/go-progress"
"github.com/docker/docker/pkg/ioutils"
"github.com/anchore/client-go/pkg/external"
"github.com/go-test/deep"
)
type mockDockerfileImportAPI struct {
sessionID string
model string
httpResponse *http.Response
err error
ctx context.Context
responseDigest string
wasCalled bool
}
func (m *mockDockerfileImportAPI) ImportImageDockerfile(ctx context.Context, sessionID string, contents string) (external.ImageImportContentResponse, *http.Response, error) {
m.wasCalled = true
m.model = contents
m.sessionID = sessionID
m.ctx = ctx
if m.httpResponse == nil {
m.httpResponse = &http.Response{}
}
m.httpResponse.Body = ioutils.NewReadCloserWrapper(strings.NewReader(""), func() error { return nil })
return external.ImageImportContentResponse{Digest: m.responseDigest}, m.httpResponse, m.err
}
func TestDockerfileImport(t *testing.T) {
sessionID := "my-session"
tests := []struct {
name string
dockerfile string
api *mockDockerfileImportAPI
expectsError bool
expectsCall bool
}{
{
name: "Go case: import works",
dockerfile: "the-manifest-contents!",
api: &mockDockerfileImportAPI{
httpResponse: &http.Response{StatusCode: 200},
responseDigest: "digest!",
},
expectsCall: true,
},
{
name: "No manifest provided",
dockerfile: "",
api: &mockDockerfileImportAPI{},
expectsCall: false,
},
{
name: "API returns an error",
dockerfile: "the-manifest-contents!",
api: &mockDockerfileImportAPI{
err: fmt.Errorf("api error, something went wrong"),
},
expectsError: true,
expectsCall: true,
},
{
name: "API HTTP-level error",
dockerfile: "the-manifest-contents!",
api: &mockDockerfileImportAPI{
httpResponse: &http.Response{StatusCode: 404},
},
expectsError: true,
expectsCall: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
digest, err := importDockerfile(context.TODO(), test.api, sessionID, []byte(test.dockerfile), &progress.Stage{})
// validate error handling
if err != nil && !test.expectsError {
t.Fatalf("did not expect an error, but got: %+v", err)
} else if err == nil && test.expectsError {
t.Fatalf("did expect an error, but got none")
}
if !test.api.wasCalled && test.expectsCall {
t.Fatalf("was not called!")
} else if test.api.wasCalled && !test.expectsCall {
t.Fatalf("should not have been called")
}
if !test.expectsCall {
return
}
if digest != test.api.responseDigest {
t.Errorf("unexpected content digest: %q != %q", digest, test.api.responseDigest)
}
// validating that the mock got the right parameters
if test.api.sessionID != sessionID {
t.Errorf("different session ID: %s != %s", test.api.sessionID, sessionID)
}
for _, d := range deep.Equal(test.api.model, test.dockerfile) {
t.Errorf("model difference: %s", d)
}
})
}
}
+50
View File
@@ -0,0 +1,50 @@
// nolint: dupl
package anchore
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"github.com/wagoodman/go-progress"
"github.com/anchore/client-go/pkg/external"
"github.com/anchore/syft/internal/log"
)
type manifestImportAPI interface {
ImportImageManifest(ctx context.Context, sessionID string, contents interface{}) (external.ImageImportContentResponse, *http.Response, error)
}
func importManifest(ctx context.Context, api manifestImportAPI, sessionID string, manifest []byte, stage *progress.Stage) (string, error) {
if len(manifest) > 0 {
log.Debug("importing image manifest")
stage.Current = "image manifest"
// API requires an object, but we do not verify the shape of this object locally
var sender map[string]interface{}
if err := json.Unmarshal(manifest, &sender); err != nil {
return "", err
}
response, httpResponse, err := api.ImportImageManifest(ctx, sessionID, sender)
if err != nil {
var openAPIErr external.GenericOpenAPIError
if errors.As(err, &openAPIErr) {
log.Errorf("api response: %+v", string(openAPIErr.Body()))
}
return "", fmt.Errorf("unable to import Manifest: %w", err)
}
defer httpResponse.Body.Close()
if httpResponse.StatusCode != 200 {
return "", fmt.Errorf("unable to import Manifest: %s", httpResponse.Status)
}
return response.Digest, nil
}
return "", nil
}
+129
View File
@@ -0,0 +1,129 @@
package anchore
import (
"context"
"encoding/json"
"fmt"
"net/http"
"strings"
"testing"
"github.com/wagoodman/go-progress"
"github.com/anchore/client-go/pkg/external"
"github.com/docker/docker/pkg/ioutils"
"github.com/go-test/deep"
)
type mockManifestImportAPI struct {
sessionID string
model interface{}
httpResponse *http.Response
err error
ctx context.Context
responseDigest string
wasCalled bool
}
func (m *mockManifestImportAPI) ImportImageManifest(ctx context.Context, sessionID string, contents interface{}) (external.ImageImportContentResponse, *http.Response, error) {
m.wasCalled = true
m.model = contents
m.sessionID = sessionID
m.ctx = ctx
if m.httpResponse == nil {
m.httpResponse = &http.Response{}
}
m.httpResponse.Body = ioutils.NewReadCloserWrapper(strings.NewReader(""), func() error { return nil })
return external.ImageImportContentResponse{Digest: m.responseDigest}, m.httpResponse, m.err
}
func TestManifestImport(t *testing.T) {
sessionID := "my-session"
tests := []struct {
name string
manifest string
api *mockManifestImportAPI
expectsError bool
expectsCall bool
}{
{
name: "Go case: import works",
manifest: `{ "key": "the-config-contents!" }`,
api: &mockManifestImportAPI{
httpResponse: &http.Response{StatusCode: 200},
responseDigest: "digest!",
},
expectsCall: true,
},
{
name: "No manifest provided",
manifest: "",
api: &mockManifestImportAPI{},
expectsCall: false,
},
{
name: "API returns an error",
manifest: `{ "key": "the-config-contents!" }`,
api: &mockManifestImportAPI{
err: fmt.Errorf("api error, something went wrong"),
},
expectsError: true,
expectsCall: true,
},
{
name: "API HTTP-level error",
manifest: `{ "key": "the-config-contents!" }`,
api: &mockManifestImportAPI{
httpResponse: &http.Response{StatusCode: 404},
},
expectsError: true,
expectsCall: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
digest, err := importManifest(context.TODO(), test.api, sessionID, []byte(test.manifest), &progress.Stage{})
// validate error handling
if err != nil && !test.expectsError {
t.Fatalf("did not expect an error, but got: %+v", err)
} else if err == nil && test.expectsError {
t.Fatalf("did expect an error, but got none")
}
if !test.api.wasCalled && test.expectsCall {
t.Fatalf("was not called!")
} else if test.api.wasCalled && !test.expectsCall {
t.Fatalf("should not have been called")
}
if !test.expectsCall {
return
}
if digest != test.api.responseDigest {
t.Errorf("unexpected content digest: %q != %q", digest, test.api.responseDigest)
}
// validating that the mock got the right parameters
if test.api.sessionID != sessionID {
t.Errorf("different session ID: %s != %s", test.api.sessionID, sessionID)
}
var expected map[string]interface{}
if err := json.Unmarshal([]byte(test.manifest), &expected); err != nil {
t.Fatalf("could not unmarshal expected results")
}
for _, d := range deep.Equal(test.api.model, expected) {
t.Errorf("model difference: %s", d)
}
})
}
}
+69
View File
@@ -0,0 +1,69 @@
package anchore
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"github.com/wagoodman/go-progress"
jsonPresenter "github.com/anchore/syft/syft/presenter/json"
"github.com/anchore/syft/syft/distro"
"github.com/anchore/syft/syft/source"
"github.com/anchore/client-go/pkg/external"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/pkg"
)
type packageSBOMImportAPI interface {
ImportImagePackages(context.Context, string, external.ImagePackageManifest) (external.ImageImportContentResponse, *http.Response, error)
}
func packageSbomModel(s source.Metadata, catalog *pkg.Catalog, d *distro.Distro) (*external.ImagePackageManifest, error) {
var buf bytes.Buffer
pres := jsonPresenter.NewPresenter(catalog, s, d)
err := pres.Present(&buf)
if err != nil {
return nil, fmt.Errorf("unable to serialize results: %w", err)
}
// the model is 1:1 the JSON output of today. As the schema changes, this will need to be converted into individual mappings.
var model external.ImagePackageManifest
if err = json.Unmarshal(buf.Bytes(), &model); err != nil {
return nil, fmt.Errorf("unable to convert JSON presenter output to import model: %w", err)
}
return &model, nil
}
func importPackageSBOM(ctx context.Context, api packageSBOMImportAPI, sessionID string, s source.Metadata, catalog *pkg.Catalog, d *distro.Distro, stage *progress.Stage) (string, error) {
log.Debug("importing package SBOM")
stage.Current = "package SBOM"
model, err := packageSbomModel(s, catalog, d)
if err != nil {
return "", fmt.Errorf("unable to create PackageSBOM model: %w", err)
}
response, httpResponse, err := api.ImportImagePackages(ctx, sessionID, *model)
if err != nil {
var openAPIErr external.GenericOpenAPIError
if errors.As(err, &openAPIErr) {
log.Errorf("api response: %+v", string(openAPIErr.Body()))
}
return "", fmt.Errorf("unable to import PackageSBOM: %w", err)
}
defer httpResponse.Body.Close()
if httpResponse.StatusCode != 200 {
return "", fmt.Errorf("unable to import PackageSBOM: %s", httpResponse.Status)
}
return response.Digest, nil
}
@@ -0,0 +1,254 @@
package anchore
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"strings"
"testing"
"github.com/wagoodman/go-progress"
jsonPresenter "github.com/anchore/syft/syft/presenter/json"
"github.com/anchore/syft/syft/distro"
"github.com/docker/docker/pkg/ioutils"
"github.com/anchore/client-go/pkg/external"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/source"
"github.com/go-test/deep"
)
func must(c pkg.CPE, e error) pkg.CPE {
if e != nil {
panic(e)
}
return c
}
// this test is tailored towards the assumption that the import doc shape and the syft json shape are the same.
// TODO: replace this as the document shapes diverge.
func TestPackageSbomToModel(t *testing.T) {
m := source.Metadata{
Scheme: source.ImageScheme,
ImageMetadata: source.ImageMetadata{
UserInput: "user-in",
Scope: "scope!",
Layers: []source.LayerMetadata{
{
MediaType: "layer-metadata-type!",
Digest: "layer-digest",
Size: 20,
},
},
Size: 10,
ManifestDigest: "sha256:digest!",
MediaType: "mediatype!",
Tags: nil,
},
}
d, _ := distro.NewDistro(distro.CentOS, "8.0", "")
p := pkg.Package{
Name: "name",
Version: "version",
FoundBy: "foundBy",
Locations: []source.Location{
{
RealPath: "path",
FileSystemID: "layerID",
},
},
Licenses: []string{"license"},
Language: pkg.Python,
Type: pkg.PythonPkg,
CPEs: []pkg.CPE{
must(pkg.NewCPE("cpe:2.3:*:some:package:1:*:*:*:*:*:*:*")),
},
PURL: "purl",
}
c := pkg.NewCatalog(p)
model, err := packageSbomModel(m, c, &d)
if err != nil {
t.Fatalf("unable to generate model from source material: %+v", err)
}
var modelJSON []byte
modelJSON, err = json.Marshal(&model)
if err != nil {
t.Fatalf("unable to marshal model: %+v", err)
}
var buf bytes.Buffer
pres := jsonPresenter.NewPresenter(c, m, &d)
if err := pres.Present(&buf); err != nil {
t.Fatalf("unable to get expected json: %+v", err)
}
// unmarshal expected result
var expectedDoc jsonPresenter.Document
if err := json.Unmarshal(buf.Bytes(), &expectedDoc); err != nil {
t.Fatalf("unable to parse json doc: %+v", err)
}
// unmarshal actual result
var actualDoc jsonPresenter.Document
if err := json.Unmarshal(modelJSON, &actualDoc); err != nil {
t.Fatalf("unable to parse json doc: %+v", err)
}
for _, d := range deep.Equal(actualDoc, expectedDoc) {
t.Errorf("diff: %+v", d)
}
}
type mockPackageSBOMImportAPI struct {
sessionID string
model external.ImagePackageManifest
httpResponse *http.Response
err error
ctx context.Context
responseDigest string
}
func (m *mockPackageSBOMImportAPI) ImportImagePackages(ctx context.Context, sessionID string, model external.ImagePackageManifest) (external.ImageImportContentResponse, *http.Response, error) {
m.model = model
m.sessionID = sessionID
m.ctx = ctx
if m.httpResponse == nil {
m.httpResponse = &http.Response{}
}
m.httpResponse.Body = ioutils.NewReadCloserWrapper(strings.NewReader(""), func() error { return nil })
return external.ImageImportContentResponse{Digest: m.responseDigest}, m.httpResponse, m.err
}
func TestPackageSbomImport(t *testing.T) {
catalog := pkg.NewCatalog(pkg.Package{
Name: "name",
Version: "version",
FoundBy: "foundBy",
Locations: []source.Location{
{
RealPath: "path",
FileSystemID: "layerID",
},
},
Licenses: []string{"license"},
Language: pkg.Python,
Type: pkg.PythonPkg,
CPEs: []pkg.CPE{
must(pkg.NewCPE("cpe:2.3:*:some:package:1:*:*:*:*:*:*:*")),
},
PURL: "purl",
MetadataType: pkg.PythonPackageMetadataType,
Metadata: pkg.PythonPackageMetadata{
Name: "p-name",
Version: "p-version",
License: "p-license",
Author: "p-author",
AuthorEmail: "p-email",
Platform: "p-platform",
Files: []pkg.PythonFileRecord{
{
Path: "p-path",
Digest: &pkg.PythonFileDigest{
Algorithm: "p-alg",
Value: "p-digest",
},
Size: "p-size",
},
},
SitePackagesRootPath: "p-site-packages-root",
TopLevelPackages: []string{"top-level"},
},
})
m := source.Metadata{
Scheme: "a-schema",
ImageMetadata: source.ImageMetadata{
UserInput: "user-in",
Scope: "scope!",
Layers: nil,
Size: 10,
ManifestDigest: "sha256:digest!",
MediaType: "mediatype!",
Tags: nil,
},
}
d, _ := distro.NewDistro(distro.CentOS, "8.0", "")
theModel, err := packageSbomModel(m, catalog, &d)
if err != nil {
t.Fatalf("could not get sbom model: %+v", err)
}
sessionID := "my-session"
tests := []struct {
name string
api *mockPackageSBOMImportAPI
expectsError bool
}{
{
name: "Go case: import works",
api: &mockPackageSBOMImportAPI{
httpResponse: &http.Response{StatusCode: 200},
responseDigest: "digest!",
},
},
{
name: "API returns an error",
api: &mockPackageSBOMImportAPI{
err: fmt.Errorf("API error, something went wrong."),
},
expectsError: true,
},
{
name: "API HTTP-level error",
api: &mockPackageSBOMImportAPI{
httpResponse: &http.Response{StatusCode: 404},
},
expectsError: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
digest, err := importPackageSBOM(context.TODO(), test.api, sessionID, m, catalog, &d, &progress.Stage{})
// validate error handling
if err != nil && !test.expectsError {
t.Fatalf("did not expect an error, but got: %+v", err)
} else if err == nil && test.expectsError {
t.Fatalf("did expect an error, but got none")
}
if digest != test.api.responseDigest {
t.Errorf("unexpected content digest: %q != %q", digest, test.api.responseDigest)
}
// validating that the mock got the right parameters (api.ImportImagePackages)
if test.api.sessionID != sessionID {
t.Errorf("different session ID: %s != %s", test.api.sessionID, sessionID)
}
for _, d := range deep.Equal(&test.api.model, theModel) {
t.Errorf("model difference: %s", d)
}
})
}
}
+93 -41
View File
@@ -8,70 +8,82 @@ import (
"github.com/adrg/xdg"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/syft/presenter"
"github.com/anchore/syft/syft/scope"
"github.com/anchore/syft/syft/source"
"github.com/mitchellh/go-homedir"
"github.com/sirupsen/logrus"
"github.com/spf13/viper"
"gopkg.in/yaml.v2"
)
type CliOnlyOptions struct {
ConfigPath string
Verbosity int
}
// Application is the main syft application configuration.
type Application struct {
ConfigPath string
PresenterOpt presenter.Option
Output string `mapstructure:"output"`
ScopeOpt scope.Option
Scope string `mapstructure:"scope"`
Quiet bool `mapstructure:"quiet"`
Log Logging `mapstructure:"log"`
CliOptions CliOnlyOptions
CheckForAppUpdate bool `mapstructure:"check-for-app-update"`
ConfigPath string `yaml:",omitempty"` // the location where the application config was read from (either from -c or discovered while loading)
PresenterOpt presenter.Option `yaml:"-"` // -o, the native Presenter.Option to use for report formatting
Output string `yaml:"output" mapstructure:"output"` // -o, the Presenter hint string to use for report formatting
ScopeOpt source.Scope `yaml:"-"` // -s, the native source.Scope option to use for how to catalog the container image
Scope string `yaml:"scope" mapstructure:"scope"` // -s, the source.Scope string hint for how to catalog the container image
Quiet bool `yaml:"quiet" mapstructure:"quiet"` // -q, indicates to not show any status output to stderr (ETUI or logging UI)
Log logging `yaml:"log" mapstructure:"log"` // all logging-related options
CliOptions CliOnlyOptions `yaml:"-"` // all options only available through the CLI (not via env vars or config)
Dev Development `mapstructure:"dev"`
CheckForAppUpdate bool `yaml:"check-for-app-update" mapstructure:"check-for-app-update"` // whether to check for an application update on start up or not
Anchore anchore `yaml:"anchore" mapstructure:"anchore"` // options for interacting with Anchore Engine/Enterprise
}
type Logging struct {
Structured bool `mapstructure:"structured"`
LevelOpt logrus.Level
Level string `mapstructure:"level"`
FileLocation string `mapstructure:"file"`
// CliOnlyOptions are options that are in the application config in memory, but are only exposed via CLI switches (not from unmarshaling a config file)
type CliOnlyOptions struct {
ConfigPath string // -c. where the read config is on disk
Verbosity int // -v or -vv , controlling which UI (ETUI vs logging) and what the log level should be
}
func setNonCliDefaultValues(v *viper.Viper) {
v.SetDefault("log.level", "")
v.SetDefault("log.file", "")
v.SetDefault("log.structured", false)
v.SetDefault("check-for-app-update", true)
// logging contains all logging-related configuration options available to the user via the application config.
type logging struct {
Structured bool `yaml:"structured" mapstructure:"structured"` // show all log entries as JSON formatted strings
LevelOpt logrus.Level `yaml:"level"` // the native log level object used by the logger
Level string `yaml:"-" mapstructure:"level"` // the log level string hint
FileLocation string `yaml:"file" mapstructure:"file"` // the file path to write logs to
}
func LoadConfigFromFile(v *viper.Viper, cliOpts *CliOnlyOptions) (*Application, error) {
type anchore struct {
// upload options
UploadEnabled bool `yaml:"upload-enabled" mapstructure:"upload-enabled"` // whether to upload results to Anchore Engine/Enterprise (defaults to "false" unless there is the presence of -h CLI option)
Host string `yaml:"host" mapstructure:"host"` // -H , hostname of the engine/enterprise instance to upload to
Path string `yaml:"path" mapstructure:"path"` // override the engine/enterprise API upload path
Username string `yaml:"username" mapstructure:"username"` // -u , username to authenticate upload
Password string `yaml:"password" mapstructure:"password"` // -p , password to authenticate upload
Dockerfile string `yaml:"dockerfile" mapstructure:"dockerfile"` // -d , dockerfile to attach for upload
OverwriteExistingImage bool `yaml:"overwrite-existing-image" mapstructure:"overwrite-existing-image"` // --overwrite-existing-image , if any of the SBOM components have already been uploaded this flag will ensure they are overwritten with the current upload
}
type Development struct {
ProfileCPU bool `mapstructure:"profile-cpu"`
ProfileMem bool `mapstructure:"profile-mem"`
}
// LoadApplicationConfig populates the given viper object with application configuration discovered on disk
func LoadApplicationConfig(v *viper.Viper, cliOpts CliOnlyOptions, wasHostnameSet bool) (*Application, error) {
// the user may not have a config, and this is OK, we can use the default config + default cobra cli values instead
setNonCliDefaultValues(v)
if cliOpts != nil {
_ = readConfig(v, cliOpts.ConfigPath)
} else {
_ = readConfig(v, "")
}
_ = readConfig(v, cliOpts.ConfigPath)
config := &Application{
CliOptions: *cliOpts,
CliOptions: cliOpts,
}
err := v.Unmarshal(config)
if err != nil {
if err := v.Unmarshal(config); err != nil {
return nil, fmt.Errorf("unable to parse config: %w", err)
}
config.ConfigPath = v.ConfigFileUsed()
err = config.Build()
if err != nil {
if err := config.build(v, wasHostnameSet); err != nil {
return nil, fmt.Errorf("invalid config: %w", err)
}
return config, nil
}
func (cfg *Application) Build() error {
// build inflates simple config values into syft native objects (or other complex objects) after the config is fully read in.
func (cfg *Application) build(v *viper.Viper, wasHostnameSet bool) error {
// set the presenter
presenterOption := presenter.ParseOption(cfg.Output)
if presenterOption == presenter.UnknownPresenter {
@@ -79,9 +91,9 @@ func (cfg *Application) Build() error {
}
cfg.PresenterOpt = presenterOption
// set the scope
scopeOption := scope.ParseOption(cfg.Scope)
if scopeOption == scope.UnknownScope {
// set the source
scopeOption := source.ParseScope(cfg.Scope)
if scopeOption == source.UnknownScope {
return fmt.Errorf("bad --scope value '%s'", cfg.Scope)
}
cfg.ScopeOpt = scopeOption
@@ -111,14 +123,44 @@ func (cfg *Application) Build() error {
case v >= 2:
cfg.Log.LevelOpt = logrus.DebugLevel
default:
cfg.Log.LevelOpt = logrus.ErrorLevel
cfg.Log.LevelOpt = logrus.WarnLevel
}
}
}
// check if upload should be done relative to the CLI and config behavior
if !v.IsSet("anchore.upload-enabled") && wasHostnameSet {
// we know the user didn't specify to upload in the config file and a --hostname option was provided (so set upload)
cfg.Anchore.UploadEnabled = true
}
if !cfg.Anchore.UploadEnabled && cfg.Anchore.Dockerfile != "" {
return fmt.Errorf("cannot provide dockerfile option without enabling upload")
}
return nil
}
func (cfg Application) String() string {
// redact sensitive information
if cfg.Anchore.Username != "" {
cfg.Anchore.Username = "********"
}
if cfg.Anchore.Password != "" {
cfg.Anchore.Password = "********"
}
// yaml is pretty human friendly (at least when compared to json)
appCfgStr, err := yaml.Marshal(&cfg)
if err != nil {
return err.Error()
}
return string(appCfgStr)
}
// readConfig attempts to read the given config path from disk or discover an alternate store location
func readConfig(v *viper.Viper, configPath string) error {
v.AutomaticEnv()
v.SetEnvPrefix(internal.ApplicationName)
@@ -174,3 +216,13 @@ func readConfig(v *viper.Viper, configPath string) error {
return fmt.Errorf("application config not found")
}
// setNonCliDefaultValues ensures that there are sane defaults for values that do not have CLI equivalent options (where there would already be a default value)
func setNonCliDefaultValues(v *viper.Viper) {
v.SetDefault("log.level", "")
v.SetDefault("log.file", "")
v.SetDefault("log.structured", false)
v.SetDefault("check-for-app-update", true)
v.SetDefault("dev.profile-cpu", false)
v.SetDefault("dev.profile-mem", false)
}
+8 -2
View File
@@ -1,4 +1,10 @@
package internal
// note: do not change this
const ApplicationName = "syft"
const (
// ApplicationName is the non-capitalized name of the application (do not change this)
ApplicationName = "syft"
// JSONSchemaVersion is the current schema version output by the JSON presenter
// 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 = "1.0.1"
)
+4
View File
@@ -0,0 +1,4 @@
/*
Package internal contains miscellaneous functions and objects useful within syft but should not be used externally.
*/
package internal
+1
View File
@@ -1,5 +1,6 @@
package file
// GlobMatch evaluates the given glob pattern against the given "name" string, indicating if there is a match or not.
// Source: https://research.swtch.com/glob.go
func GlobMatch(pattern, name string) bool {
px := 0
+16
View File
@@ -0,0 +1,16 @@
package file
import (
"io"
"os"
)
// Opener is an object that stores a path to later be opened as a file.
type Opener struct {
path string
}
// Open the stored path as a io.ReadCloser.
func (o Opener) Open() (io.ReadCloser, error) {
return os.Open(o.path)
}
@@ -1,4 +1,7 @@
#!/usr/bin/env bash
set -eux
zip -r "$1" zip-source
# $1 —— absolute path to destination file, should end with .zip, ideally
# $2 —— absolute path to directory from which to add entries to the archive
pushd "$2" && find . -print | zip "$1" -@ && popd
+131
View File
@@ -0,0 +1,131 @@
package file
import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"path"
"path/filepath"
"syscall"
"testing"
)
var expectedZipArchiveEntries = []string{
"some-dir" + string(os.PathSeparator),
filepath.Join("some-dir", "a-file.txt"),
"b-file.txt",
"nested.zip",
}
// fatalIfError calls the supplied function. If the function returns a non-nil error, t.Fatal(err) is called.
func fatalIfError(t *testing.T, fn func() error) {
t.Helper()
if fn == nil {
return
}
err := fn()
if err != nil {
t.Fatal(err)
}
}
// createZipArchive creates a new ZIP archive file at destinationArchivePath based on the directory found at
// sourceDirPath.
func createZipArchive(t *testing.T, sourceDirPath, destinationArchivePath string) error {
t.Helper()
cwd, err := os.Getwd()
if err != nil {
return fmt.Errorf("unable to get cwd: %+v", err)
}
cmd := exec.Command("./generate-zip-fixture.sh", destinationArchivePath, path.Base(sourceDirPath))
cmd.Dir = filepath.Join(cwd, "test-fixtures")
if err := cmd.Start(); err != nil {
return fmt.Errorf("unable to start generate zip fixture script: %+v", err)
}
if err := cmd.Wait(); err != nil {
if exiterr, ok := err.(*exec.ExitError); ok {
// The program has exited with an exit code != 0
// This works on both Unix and Windows. Although package
// syscall is generally platform dependent, WaitStatus is
// defined for both Unix and Windows and in both cases has
// an ExitStatus() method with the same signature.
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
if status.ExitStatus() != 0 {
return fmt.Errorf("failed to generate fixture: rc=%d", status.ExitStatus())
}
}
} else {
return fmt.Errorf("unable to get generate fixture script result: %+v", err)
}
}
return nil
}
// setupZipFileTest encapsulates common test setup work for zip file tests. It returns a cleanup function,
// which should be called (typically deferred) by the caller, the path of the created zip archive, and an error,
// which should trigger a fatal test failure in the consuming test. The returned cleanup function will never be nil
// (even if there's an error), and it should always be called.
func setupZipFileTest(t *testing.T, sourceDirPath string) (func() error, string, error) {
t.Helper()
// Keep track of any needed cleanup work as we go
var cleanupFns []func() error
cleanup := func(fns []func() error) func() error {
return func() error {
for _, fn := range fns {
err := fn()
if err != nil {
return err
}
}
return nil
}
}
archivePrefix, err := ioutil.TempFile("", "syft-ziputil-archive-TEST-")
if err != nil {
return cleanup(cleanupFns), "", fmt.Errorf("unable to create tempfile: %+v", err)
}
cleanupFns = append(cleanupFns, func() error { return os.Remove(archivePrefix.Name()) })
destinationArchiveFilePath := archivePrefix.Name() + ".zip"
t.Logf("archive path: %s", destinationArchiveFilePath)
err = createZipArchive(t, sourceDirPath, destinationArchiveFilePath)
cleanupFns = append(cleanupFns, func() error { return os.Remove(destinationArchiveFilePath) })
if err != nil {
return cleanup(cleanupFns), "", err
}
cwd, err := os.Getwd()
if err != nil {
return cleanup(cleanupFns), "", fmt.Errorf("unable to get cwd: %+v", err)
}
t.Logf("running from: %s", cwd)
return cleanup(cleanupFns), destinationArchiveFilePath, nil
}
// TODO: Consider moving any non-git asset generation to a task (e.g. make) that's run ahead of running go tests.
func ensureNestedZipExists(t *testing.T, sourceDirPath string) error {
t.Helper()
nestedArchiveFilePath := path.Join(sourceDirPath, "nested.zip")
err := createZipArchive(t, sourceDirPath, nestedArchiveFilePath)
if err != nil {
return fmt.Errorf("unable to create nested archive for test fixture: %+v", err)
}
return nil
}
+21 -2
View File
@@ -5,28 +5,37 @@ import (
"fmt"
"os"
"sort"
"strings"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/log"
)
// ZipFileManifest is a collection of paths and their file metadata.
type ZipFileManifest map[string]os.FileInfo
// newZipManifest creates an empty ZipFileManifest.
func newZipManifest() ZipFileManifest {
return make(ZipFileManifest)
}
// Add a new path and it's file metadata to the collection.
func (z ZipFileManifest) Add(entry string, info os.FileInfo) {
z[entry] = info
}
// GlobMatch returns the path keys that match the given value(s).
func (z ZipFileManifest) GlobMatch(patterns ...string) []string {
uniqueMatches := internal.NewStringSet()
for _, pattern := range patterns {
for entry := range z {
if GlobMatch(pattern, entry) {
// We want to match globs as if entries begin with a leading slash (akin to an absolute path)
// so that glob logic is consistent inside and outside of ZIP archives
normalizedEntry := normalizeZipEntryName(entry)
if GlobMatch(pattern, normalizedEntry) {
uniqueMatches.Add(entry)
}
}
@@ -38,6 +47,7 @@ func (z ZipFileManifest) GlobMatch(patterns ...string) []string {
return results
}
// NewZipFileManifest creates and returns a new ZipFileManifest populated with path and metadata from the given zip archive path.
func NewZipFileManifest(archivePath string) (ZipFileManifest, error) {
zipReader, err := zip.OpenReader(archivePath)
manifest := newZipManifest()
@@ -47,7 +57,7 @@ func NewZipFileManifest(archivePath string) (ZipFileManifest, error) {
defer func() {
err = zipReader.Close()
if err != nil {
log.Errorf("unable to close zip archive (%s): %w", archivePath, err)
log.Errorf("unable to close zip archive (%s): %+v", archivePath, err)
}
}()
@@ -56,3 +66,12 @@ func NewZipFileManifest(archivePath string) (ZipFileManifest, error) {
}
return manifest, nil
}
// normalizeZipEntryName takes the given path entry and ensures it is prefixed with "/".
func normalizeZipEntryName(entry string) string {
if !strings.HasPrefix(entry, "/") {
return "/" + entry
}
return entry
}
+109
View File
@@ -0,0 +1,109 @@
package file
import (
"encoding/json"
"os"
"path"
"testing"
)
func TestNewZipFileManifest(t *testing.T) {
cwd, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
sourceDirPath := path.Join(cwd, "test-fixtures", "zip-source")
err = ensureNestedZipExists(t, sourceDirPath)
if err != nil {
t.Fatal(err)
}
cleanup, archiveFilePath, err := setupZipFileTest(t, sourceDirPath)
defer fatalIfError(t, cleanup)
if err != nil {
t.Fatal(err)
}
actual, err := NewZipFileManifest(archiveFilePath)
if err != nil {
t.Fatalf("unable to extract from unzip archive: %+v", err)
}
if len(expectedZipArchiveEntries) != len(actual) {
t.Fatalf("mismatched manifest: %d != %d", len(actual), len(expectedZipArchiveEntries))
}
for _, e := range expectedZipArchiveEntries {
_, ok := actual[e]
if !ok {
t.Errorf("missing path: %s", e)
}
}
if t.Failed() {
b, err := json.MarshalIndent(actual, "", " ")
if err != nil {
t.Fatalf("can't show results: %+v", err)
}
t.Errorf("full result: %s", string(b))
}
}
func TestZipFileManifest_GlobMatch(t *testing.T) {
cwd, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
sourceDirPath := path.Join(cwd, "test-fixtures", "zip-source")
err = ensureNestedZipExists(t, sourceDirPath)
if err != nil {
t.Fatal(err)
}
cleanup, archiveFilePath, err := setupZipFileTest(t, sourceDirPath)
//goland:noinspection GoNilness
defer fatalIfError(t, cleanup)
if err != nil {
t.Fatal(err)
}
z, err := NewZipFileManifest(archiveFilePath)
if err != nil {
t.Fatalf("unable to extract from unzip archive: %+v", err)
}
cases := []struct {
glob string
expected string
}{
{
"/b*",
"b-file.txt",
},
{
"*/a-file.txt",
"some-dir/a-file.txt",
},
{
"**/*.zip",
"nested.zip",
},
}
for _, tc := range cases {
t.Run(tc.glob, func(t *testing.T) {
glob := tc.glob
results := z.GlobMatch(glob)
if len(results) == 1 && results[0] == tc.expected {
return
}
t.Errorf("unexpected results for glob '%s': %+v", glob, results)
})
}
}
+19 -8
View File
@@ -15,6 +15,7 @@ import (
)
const (
// represents the order of bytes
_ = iota
KB = 1 << (10 * iota)
MB
@@ -33,6 +34,7 @@ func newZipTraverseRequest(paths ...string) zipTraversalRequest {
return results
}
// TraverseFilesInZip enumerates all paths stored within a zip archive using the visitor pattern.
func TraverseFilesInZip(archivePath string, visitor func(*zip.File) error, paths ...string) error {
request := newZipTraverseRequest(paths...)
@@ -43,7 +45,7 @@ func TraverseFilesInZip(archivePath string, visitor func(*zip.File) error, paths
defer func() {
err = zipReader.Close()
if err != nil {
log.Errorf("unable to close zip archive (%s): %w", archivePath, err)
log.Errorf("unable to close zip archive (%s): %+v", archivePath, err)
}
}()
@@ -63,8 +65,9 @@ func TraverseFilesInZip(archivePath string, visitor func(*zip.File) error, paths
return nil
}
func ExtractFromZipToUniqueTempFile(archivePath, dir string, paths ...string) (map[string]io.Reader, error) {
results := make(map[string]io.Reader)
// ExtractFromZipToUniqueTempFile extracts select paths for the given archive to a temporary directory, returning file openers for each file extracted.
func ExtractFromZipToUniqueTempFile(archivePath, dir string, paths ...string) (map[string]Opener, error) {
results := make(map[string]Opener)
// don't allow for full traversal, only select traversal from given paths
if len(paths) == 0 {
@@ -78,11 +81,21 @@ func ExtractFromZipToUniqueTempFile(archivePath, dir string, paths ...string) (m
if err != nil {
return fmt.Errorf("unable to create temp file: %w", err)
}
// we shouldn't try and keep the tempfile open as the returned result may have several files, which takes up
// resources (leading to "too many open files"). Instead we'll return a file opener to the caller which
// provides a ReadCloser. It is up to the caller to handle closing the file explicitly.
defer tempFile.Close()
zippedFile, err := file.Open()
if err != nil {
return fmt.Errorf("unable to read file=%q from zip=%q: %w", file.Name, archivePath, err)
}
defer func() {
err := zippedFile.Close()
if err != nil {
log.Errorf("unable to close source file=%q from zip=%q: %+v", file.Name, archivePath, err)
}
}()
if file.FileInfo().IsDir() {
return fmt.Errorf("unable to extract directories, only files: %s", file.Name)
@@ -103,18 +116,15 @@ func ExtractFromZipToUniqueTempFile(archivePath, dir string, paths ...string) (m
return fmt.Errorf("unable to reset file pointer (%s): %w", tempFile.Name(), err)
}
results[file.Name] = tempFile
results[file.Name] = Opener{path: tempFile.Name()}
err = zippedFile.Close()
if err != nil {
return fmt.Errorf("unable to close source file=%q from zip=%q: %w", file.Name, archivePath, err)
}
return nil
}
return results, TraverseFilesInZip(archivePath, visitor, paths...)
}
// ContentsFromZip extracts select paths for the given archive and returns a set of string contents for each path.
func ContentsFromZip(archivePath string, paths ...string) (map[string]string, error) {
results := make(map[string]string)
@@ -156,6 +166,7 @@ func ContentsFromZip(archivePath string, paths ...string) (map[string]string, er
return results, TraverseFilesInZip(archivePath, visitor, paths...)
}
// UnzipToDir extracts a zip archive to a target directory.
func UnzipToDir(archivePath, targetDir string) error {
visitor := func(file *zip.File) error {
// the zip-slip attack protection is still being erroneously detected
+31 -122
View File
@@ -6,47 +6,14 @@ import (
"io"
"io/ioutil"
"os"
"os/exec"
"path"
"path/filepath"
"strings"
"syscall"
"testing"
"github.com/go-test/deep"
)
func generateFixture(t *testing.T, archivePath string) {
cwd, err := os.Getwd()
if err != nil {
t.Errorf("unable to get cwd: %+v", err)
}
cmd := exec.Command("./generate-zip-fixture.sh", archivePath)
cmd.Dir = filepath.Join(cwd, "test-fixtures")
if err := cmd.Start(); err != nil {
t.Fatalf("unable to start generate zip fixture script: %+v", err)
}
if err := cmd.Wait(); err != nil {
if exiterr, ok := err.(*exec.ExitError); ok {
// The program has exited with an exit code != 0
// This works on both Unix and Windows. Although package
// syscall is generally platform dependent, WaitStatus is
// defined for both Unix and Windows and in both cases has
// an ExitStatus() method with the same signature.
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
if status.ExitStatus() != 0 {
t.Fatalf("failed to generate fixture: rc=%d", status.ExitStatus())
}
}
} else {
t.Fatalf("unable to get generate fixture script result: %+v", err)
}
}
}
func equal(r1, r2 io.Reader) (bool, error) {
w1 := sha256.New()
w2 := sha256.New()
@@ -67,54 +34,50 @@ func equal(r1, r2 io.Reader) (bool, error) {
}
func TestUnzipToDir(t *testing.T) {
archivePrefix, err := ioutil.TempFile("", "syft-ziputil-archive-TEST-")
cwd, err := os.Getwd()
if err != nil {
t.Fatalf("unable to create tempfile: %+v", err)
t.Fatal(err)
}
defer os.Remove(archivePrefix.Name())
// the zip utility will add ".zip" to the end of the given name
archivePath := archivePrefix.Name() + ".zip"
defer os.Remove(archivePath)
t.Logf("archive path: %s", archivePath)
generateFixture(t, archivePrefix.Name())
goldenRootDir := filepath.Join(cwd, "test-fixtures")
sourceDirPath := path.Join(goldenRootDir, "zip-source")
cleanup, archiveFilePath, err := setupZipFileTest(t, sourceDirPath)
defer fatalIfError(t, cleanup)
if err != nil {
t.Fatal(err)
}
contentsDir, err := ioutil.TempDir("", "syft-ziputil-contents-TEST-")
unzipDestinationDir, err := ioutil.TempDir("", "syft-ziputil-contents-TEST-")
defer os.RemoveAll(unzipDestinationDir)
if err != nil {
t.Fatalf("unable to create tempdir: %+v", err)
}
defer os.RemoveAll(contentsDir)
t.Logf("content path: %s", contentsDir)
t.Logf("content path: %s", unzipDestinationDir)
cwd, err := os.Getwd()
if err != nil {
t.Errorf("unable to get cwd: %+v", err)
}
t.Logf("running from: %s", cwd)
// note: zip utility already includes "zip-source" as a parent dir for all contained files
goldenRootDir := filepath.Join(cwd, "test-fixtures")
expectedPaths := 4
expectedPaths := len(expectedZipArchiveEntries)
observedPaths := 0
err = UnzipToDir(archivePath, contentsDir)
err = UnzipToDir(archiveFilePath, unzipDestinationDir)
if err != nil {
t.Fatalf("unable to unzip archive: %+v", err)
}
// compare the source dir tree and the unzipped tree
err = filepath.Walk(filepath.Join(contentsDir, "zip-source"),
err = filepath.Walk(unzipDestinationDir,
func(path string, info os.FileInfo, err error) error {
t.Logf("unzipped path: %s", path)
observedPaths++
// We don't unzip the root archive dir, since there's no archive entry for it
if path != unzipDestinationDir {
t.Logf("unzipped path: %s", path)
observedPaths++
}
if err != nil {
t.Fatalf("this should not happen")
return err
}
goldenPath := filepath.Join(goldenRootDir, strings.TrimPrefix(path, contentsDir))
goldenPath := filepath.Join(sourceDirPath, strings.TrimPrefix(path, unzipDestinationDir))
if info.IsDir() {
i, err := os.Stat(goldenPath)
@@ -156,12 +119,11 @@ func TestUnzipToDir(t *testing.T) {
}
if observedPaths != expectedPaths {
t.Errorf("missed test paths: %d!=%d", observedPaths, expectedPaths)
t.Errorf("missed test paths: %d != %d", observedPaths, expectedPaths)
}
}
func TestExtractFilesFromZipFile(t *testing.T) {
func TestContentsFromZip(t *testing.T) {
archivePrefix, err := ioutil.TempFile("", "syft-ziputil-archive-TEST-")
if err != nil {
t.Fatalf("unable to create tempfile: %+v", err)
@@ -172,7 +134,10 @@ func TestExtractFilesFromZipFile(t *testing.T) {
defer os.Remove(archivePath)
t.Logf("archive path: %s", archivePath)
generateFixture(t, archivePrefix.Name())
err = createZipArchive(t, "zip-source", archivePrefix.Name())
if err != nil {
t.Fatal(err)
}
cwd, err := os.Getwd()
if err != nil {
@@ -181,8 +146,8 @@ func TestExtractFilesFromZipFile(t *testing.T) {
t.Logf("running from: %s", cwd)
aFilePath := filepath.Join("zip-source", "some-dir", "a-file.txt")
bFilePath := filepath.Join("zip-source", "b-file.txt")
aFilePath := filepath.Join("some-dir", "a-file.txt")
bFilePath := filepath.Join("b-file.txt")
expected := map[string]string{
aFilePath: "A file! nice!",
@@ -207,60 +172,4 @@ func TestExtractFilesFromZipFile(t *testing.T) {
t.Errorf("full result: %s", string(b))
}
}
func TestZipFileManifest(t *testing.T) {
archivePrefix, err := ioutil.TempFile("", "syft-ziputil-archive-TEST-")
if err != nil {
t.Fatalf("unable to create tempfile: %+v", err)
}
defer os.Remove(archivePrefix.Name())
// the zip utility will add ".zip" to the end of the given name
archivePath := archivePrefix.Name() + ".zip"
defer os.Remove(archivePath)
t.Logf("archive path: %s", archivePath)
generateFixture(t, archivePrefix.Name())
cwd, err := os.Getwd()
if err != nil {
t.Errorf("unable to get cwd: %+v", err)
}
t.Logf("running from: %s", cwd)
expected := []string{
filepath.Join("zip-source") + string(os.PathSeparator),
filepath.Join("zip-source", "some-dir") + string(os.PathSeparator),
filepath.Join("zip-source", "some-dir", "a-file.txt"),
filepath.Join("zip-source", "b-file.txt"),
}
actual, err := NewZipFileManifest(archivePath)
if err != nil {
t.Fatalf("unable to extract from unzip archive: %+v", err)
}
if len(expected) != len(actual) {
t.Fatalf("mismatched manifest: %d != %d", len(actual), len(expected))
}
for _, e := range expected {
_, ok := actual[e]
if !ok {
t.Errorf("missing path: %s", e)
}
}
if t.Failed() {
b, err := json.MarshalIndent(actual, "", " ")
if err != nil {
t.Fatalf("can't show results: %+v", err)
}
t.Errorf("full result: %s", string(b))
}
}
-21
View File
@@ -1,21 +0,0 @@
package format
import "fmt"
const (
DefaultColor Color = iota + 30
Red
Green
Yellow
Blue
Magenta
Cyan
White
)
type Color uint8
// TODO: not cross platform (windows...)
func (c Color) Format(s string) string {
return fmt.Sprintf("\x1b[%dm%s\x1b[0m", c, s)
}
+24
View File
@@ -1,25 +1,49 @@
/*
Package log contains the singleton object and helper functions for facilitating logging within the syft library.
*/
package log
import "github.com/anchore/syft/syft/logger"
// Log is the singleton used to facilitate logging internally within syft
var Log logger.Logger = &nopLogger{}
// Errorf takes a formatted template string and template arguments for the error logging level.
func Errorf(format string, args ...interface{}) {
Log.Errorf(format, args...)
}
// Error logs the given arguments at the error logging level.
func Error(args ...interface{}) {
Log.Error(args...)
}
// Warnf takes a formatted template string and template arguments for the warning logging level.
func Warnf(format string, args ...interface{}) {
Log.Warnf(format, args...)
}
// Warn logs the given arguments at the warning logging level.
func Warn(args ...interface{}) {
Log.Warn(args...)
}
// Infof takes a formatted template string and template arguments for the info logging level.
func Infof(format string, args ...interface{}) {
Log.Infof(format, args...)
}
// Info logs the given arguments at the info logging level.
func Info(args ...interface{}) {
Log.Info(args...)
}
// Debugf takes a formatted template string and template arguments for the debug logging level.
func Debugf(format string, args ...interface{}) {
Log.Debugf(format, args...)
}
// Debug logs the given arguments at the debug logging level.
func Debug(args ...interface{}) {
Log.Debug(args...)
}
+3
View File
@@ -3,6 +3,9 @@ package log
type nopLogger struct{}
func (l *nopLogger) Errorf(format string, args ...interface{}) {}
func (l *nopLogger) Error(args ...interface{}) {}
func (l *nopLogger) Warnf(format string, args ...interface{}) {}
func (l *nopLogger) Warn(args ...interface{}) {}
func (l *nopLogger) Infof(format string, args ...interface{}) {}
func (l *nopLogger) Info(args ...interface{}) {}
func (l *nopLogger) Debugf(format string, args ...interface{}) {}
+4
View File
@@ -0,0 +1,4 @@
/*
Package logger contains implementations for the syft.logger.Logger interface.
*/
package logger
+52 -8
View File
@@ -10,6 +10,7 @@ import (
prefixed "github.com/x-cray/logrus-prefixed-formatter"
)
// LogrusConfig contains all configurable values for the Logrus logger
type LogrusConfig struct {
EnableConsole bool
EnableFile bool
@@ -18,16 +19,19 @@ type LogrusConfig struct {
FileLocation string
}
// LogrusLogger contains all runtime values for using Logrus with the configured output target and input configuration values.
type LogrusLogger struct {
Config LogrusConfig
Logger *logrus.Logger
Output io.Writer
}
// LogrusNestedLogger is a wrapper for Logrus to enable nested logging configuration (loggers that always attach key-value pairs to all log entries)
type LogrusNestedLogger struct {
Logger *logrus.Entry
}
// NewLogrusLogger creates a new LogrusLogger with the given configuration
func NewLogrusLogger(cfg LogrusConfig) *LogrusLogger {
appLogger := logrus.New()
@@ -76,42 +80,82 @@ func NewLogrusLogger(cfg LogrusConfig) *LogrusLogger {
}
}
// Debugf takes a formatted template string and template arguments for the debug logging level.
func (l *LogrusLogger) Debugf(format string, args ...interface{}) {
l.Logger.Debugf(format, args...)
}
// Infof takes a formatted template string and template arguments for the info logging level.
func (l *LogrusLogger) Infof(format string, args ...interface{}) {
l.Logger.Infof(format, args...)
}
func (l *LogrusLogger) Debug(args ...interface{}) {
l.Logger.Debug(args...)
}
func (l *LogrusLogger) Info(args ...interface{}) {
l.Logger.Info(args...)
// Warnf takes a formatted template string and template arguments for the warning logging level.
func (l *LogrusLogger) Warnf(format string, args ...interface{}) {
l.Logger.Warnf(format, args...)
}
// Errorf takes a formatted template string and template arguments for the error logging level.
func (l *LogrusLogger) Errorf(format string, args ...interface{}) {
l.Logger.Errorf(format, args...)
}
// Debug logs the given arguments at the debug logging level.
func (l *LogrusLogger) Debug(args ...interface{}) {
l.Logger.Debug(args...)
}
// Info logs the given arguments at the info logging level.
func (l *LogrusLogger) Info(args ...interface{}) {
l.Logger.Info(args...)
}
// Warn logs the given arguments at the warning logging level.
func (l *LogrusLogger) Warn(args ...interface{}) {
l.Logger.Warn(args...)
}
// Error logs the given arguments at the error logging level.
func (l *LogrusLogger) Error(args ...interface{}) {
l.Logger.Error(args...)
}
// Debugf takes a formatted template string and template arguments for the debug logging level.
func (l *LogrusNestedLogger) Debugf(format string, args ...interface{}) {
l.Logger.Debugf(format, args...)
}
// Infof takes a formatted template string and template arguments for the info logging level.
func (l *LogrusNestedLogger) Infof(format string, args ...interface{}) {
l.Logger.Infof(format, args...)
}
// Warnf takes a formatted template string and template arguments for the warning logging level.
func (l *LogrusNestedLogger) Warnf(format string, args ...interface{}) {
l.Logger.Warnf(format, args...)
}
// Errorf takes a formatted template string and template arguments for the error logging level.
func (l *LogrusNestedLogger) Errorf(format string, args ...interface{}) {
l.Logger.Errorf(format, args...)
}
// Debug logs the given arguments at the debug logging level.
func (l *LogrusNestedLogger) Debug(args ...interface{}) {
l.Logger.Debug(args...)
}
// Info logs the given arguments at the info logging level.
func (l *LogrusNestedLogger) Info(args ...interface{}) {
l.Logger.Info(args...)
}
func (l *LogrusNestedLogger) Errorf(format string, args ...interface{}) {
l.Logger.Errorf(format, args...)
// Warn logs the given arguments at the warning logging level.
func (l *LogrusNestedLogger) Warn(args ...interface{}) {
l.Logger.Warn(args...)
}
// Error logs the given arguments at the error logging level.
func (l *LogrusNestedLogger) Error(args ...interface{}) {
l.Logger.Error(args...)
}
+15
View File
@@ -0,0 +1,15 @@
package internal
import "regexp"
// MatchCaptureGroups takes a regular expression and string and returns all of the named capture group results in a map.
func MatchCaptureGroups(regEx *regexp.Regexp, str string) map[string]string {
match := regEx.FindStringSubmatch(str)
results := make(map[string]string)
for i, name := range regEx.SubexpNames() {
if i > 0 && i <= len(match) {
results[name] = match[i]
}
}
return results
}
+10
View File
@@ -1,11 +1,16 @@
package internal
import "sort"
// StringSet represents a set of string types.
type StringSet map[string]struct{}
// NewStringSet creates a new empty StringSet.
func NewStringSet() StringSet {
return make(StringSet)
}
// NewStringSetFromSlice creates a StringSet populated with values from the given slice.
func NewStringSetFromSlice(start []string) StringSet {
ret := make(StringSet)
for _, s := range start {
@@ -14,19 +19,23 @@ func NewStringSetFromSlice(start []string) StringSet {
return ret
}
// Add a string to the set.
func (s StringSet) Add(i string) {
s[i] = struct{}{}
}
// Remove a string from the set.
func (s StringSet) Remove(i string) {
delete(s, i)
}
// Contains indicates if the given string is contained within the set.
func (s StringSet) Contains(i string) bool {
_, ok := s[i]
return ok
}
// ToSlice returns a sorted slice of strings that are contained within the set.
func (s StringSet) ToSlice() []string {
ret := make([]string, len(s))
idx := 0
@@ -34,5 +43,6 @@ func (s StringSet) ToSlice() []string {
ret[idx] = v
idx++
}
sort.Strings(ret)
return ret
}
+10 -7
View File
@@ -1,3 +1,6 @@
/*
Package version contains all build time metadata (version, build time, git commit, etc).
*/
package version
import (
@@ -16,13 +19,13 @@ var platform = fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH)
// Version defines the application version details (generally from build information)
type Version struct {
Version string // application semantic version
GitCommit string // git SHA at build-time
GitTreeState string // indication of git tree (either "clean" or "dirty") at build-time
BuildDate string // date of the build
GoVersion string // go runtime version at build-time
Compiler string // compiler used at build-time
Platform string // GOOS and GOARCH at build-time
Version string `json:"version"` // application semantic version
GitCommit string `json:"gitCommit"` // git SHA at build-time
GitTreeState string `json:"gitTreeState"` // indication of git tree (either "clean" or "dirty") at build-time
BuildDate string `json:"buildDate"` // date of the build
GoVersion string `json:"goVersion"` // go runtime version at build-time
Compiler string `json:"compiler"` // compiler used at build-time
Platform string `json:"platform"` // GOOS and GOARCH at build-time
}
// FromBuild provides all version details
+1
View File
@@ -18,6 +18,7 @@ var latestAppVersionURL = struct {
path: fmt.Sprintf("/%s/releases/latest/VERSION", internal.ApplicationName),
}
// IsUpdateAvailable indicates if there is a newer application version available, and if so, what the new version is.
func IsUpdateAvailable() (bool, string, error) {
currentVersionStr := FromBuild().Version
currentVersion, err := hashiVersion.NewVersion(currentVersionStr)
-5
View File
@@ -1,5 +0,0 @@
#!/usr/bin/env bash
# Source this file in your shell sessions in order to use this shell completion.
complete -W "$(docker images --format '{{.Repository}}:{{.Tag}}')" syft
-183
View File
@@ -1,183 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
CycloneDX BOM Descriptor Extension
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:vc="http://www.w3.org/2007/XMLSchema-versioning"
xmlns:bom="http://cyclonedx.org/schema/bom/1.1"
xmlns:bd="http://cyclonedx.org/schema/ext/bom-descriptor/1.0"
elementFormDefault="qualified"
targetNamespace="http://cyclonedx.org/schema/ext/bom-descriptor/1.0"
vc:minVersion="1.0"
vc:maxVersion="1.1"
version="1.0">
<xs:annotation>
<xs:documentation>
<name>CycloneDX BOM Descriptor Extension</name>
<url>https://cyclonedx.org/ext/bom-descriptor</url>
<license uri="http://www.apache.org/licenses/LICENSE-2.0"
version="2.0">Apache License, Version 2.0</license>
<authors>
<author>Steve Springett</author>
</authors>
</xs:documentation>
</xs:annotation>
<xs:import namespace="http://cyclonedx.org/schema/bom/1.1" schemaLocation="http://cyclonedx.org/schema/bom/1.1"/>
<xs:complexType name="metadata">
<xs:sequence minOccurs="0" maxOccurs="1">
<xs:element name="timestamp" type="xs:dateTime" minOccurs="0">
<xs:annotation>
<xs:documentation>The date and time (timestamp) when the document was created.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="tool" minOccurs="0" type="bd:toolType">
<xs:annotation>
<xs:documentation>The tool used to create the BOM.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="authors" minOccurs="0" maxOccurs="1">
<xs:annotation>
<xs:documentation>The person(s) who created the BOM. Authors are common in BOMs created through
manual processes. BOMs created through automated means may not have authors.</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence minOccurs="0" maxOccurs="unbounded">
<xs:element name="author" type="bd:organizationalPerson"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="component" type="bom:component" minOccurs="0">
<xs:annotation>
<xs:documentation>The component that the BOM describes.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="manufacture" type="bd:organizationalEntity" minOccurs="0" maxOccurs="unbounded">
<xs:annotation>
<xs:documentation>The organization that manufactured the component that the BOM describes.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="supplier" type="bd:organizationalEntity" minOccurs="0" maxOccurs="unbounded">
<xs:annotation>
<xs:documentation>The organization that supplied the component that the BOM describes. The
supplier may often be the manufacture, but may also be a distributor or repackager.</xs:documentation>
</xs:annotation>
</xs:element>
</xs:sequence>
<xs:anyAttribute namespace="##other" processContents="lax">
<xs:annotation>
<xs:documentation>User-defined attributes may be used on this element as long as they
do not have the same name as an existing attribute used by the schema.</xs:documentation>
</xs:annotation>
</xs:anyAttribute>
</xs:complexType>
<xs:complexType name="organizationalEntity">
<xs:sequence minOccurs="0" maxOccurs="1">
<xs:element name="name" type="xs:normalizedString" minOccurs="0" maxOccurs="1">
<xs:annotation>
<xs:documentation>The name of the organization</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="url" type="xs:anyURI" minOccurs="0" maxOccurs="unbounded">
<xs:annotation>
<xs:documentation>The URL of the organization. Multiple URLs are allowed.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="contact" type="bd:organizationalPerson" minOccurs="0" maxOccurs="unbounded">
<xs:annotation>
<xs:documentation>A contact person at the organization. Multiple contacts are allowed.</xs:documentation>
</xs:annotation>
</xs:element>
</xs:sequence>
<xs:anyAttribute namespace="##other" processContents="lax">
<xs:annotation>
<xs:documentation>User-defined attributes may be used on this element as long as they
do not have the same name as an existing attribute used by the schema.</xs:documentation>
</xs:annotation>
</xs:anyAttribute>
</xs:complexType>
<xs:complexType name="toolType">
<xs:annotation>
<xs:documentation>Specifies a tool (manual or automated).</xs:documentation>
</xs:annotation>
<xs:sequence minOccurs="0" maxOccurs="1">
<xs:element name="vendor" minOccurs="0" maxOccurs="1" type="xs:normalizedString">
<xs:annotation>
<xs:documentation>The vendor of the tool used to create the BOM.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="name" minOccurs="0" maxOccurs="1" type="xs:normalizedString">
<xs:annotation>
<xs:documentation>The name of the tool used to create the BOM.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="version" minOccurs="0" maxOccurs="1" type="xs:normalizedString">
<xs:annotation>
<xs:documentation>The version of the tool used to create the BOM.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="hashes" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:sequence minOccurs="0" maxOccurs="unbounded">
<xs:element name="hash" type="bom:hashType"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
<xs:anyAttribute namespace="##other" processContents="lax">
<xs:annotation>
<xs:documentation>User-defined attributes may be used on this element as long as they
do not have the same name as an existing attribute used by the schema.</xs:documentation>
</xs:annotation>
</xs:anyAttribute>
</xs:complexType>
<xs:complexType name="organizationalPerson">
<xs:sequence minOccurs="0" maxOccurs="1">
<xs:element name="name" type="xs:normalizedString" minOccurs="0" maxOccurs="1">
<xs:annotation>
<xs:documentation>The name of the person</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="email" type="xs:normalizedString" minOccurs="0" maxOccurs="unbounded">
<xs:annotation>
<xs:documentation>The email address of the person. Multiple email addresses are allowed.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="phone" type="xs:normalizedString" minOccurs="0" maxOccurs="unbounded">
<xs:annotation>
<xs:documentation>The phone number of the person. Multiple phone numbers are allowed.</xs:documentation>
</xs:annotation>
</xs:element>
</xs:sequence>
<xs:anyAttribute namespace="##other" processContents="lax">
<xs:annotation>
<xs:documentation>User-defined attributes may be used on this element as long as they
do not have the same name as an existing attribute used by the schema.</xs:documentation>
</xs:annotation>
</xs:anyAttribute>
</xs:complexType>
<xs:element name="metadata" type="bd:metadata">
<xs:annotation>
<xs:documentation>Provides additional information about a BOM.</xs:documentation>
</xs:annotation>
</xs:element>
</xs:schema>
-1
View File
@@ -1 +0,0 @@
examples/
+39
View File
@@ -0,0 +1,39 @@
# JSON Schema
This is the JSON schema for output from the JSON presenter (`syft <img> -o json`). The required inputs for defining the JSON schema are as follows:
- the value of `internal.JSONSchemaVersion` that governs the schema filename
- the `Document` struct definition within `syft/presenters/json/document.go` that governs the overall document shape
- the `metadataContainer` struct definition within `schema/json/generate.go` that governs the allowable shapes of `pkg.Package.Metadata`
With regard to testing the JSON schema, integration test cases provided by the developer are used as examples to validate that JSON output from Syft is always valid relative to the `schema/json/schema-$VERSION.json` file.
## Versioning
Versioning the JSON schema must be done manually by changing the `JSONSchemaVersion` constant within `internal/constants.go`.
This schema is being versioned based off of the "SchemaVer" guidelines, which slightly diverges from Semantic Versioning to tailor for the purposes of data models.
Given a version number format `MODEL.REVISION.ADDITION`:
- `MODEL`: increment when you make a breaking schema change which will prevent interaction with any historical data
- `REVISION`: increment when you make a schema change which may prevent interaction with some historical data
- `ADDITION`: increment when you make a schema change that is compatible with all historical data
## Adding a New `pkg.*Metadata` Type
When adding a new `pkg.*Metadata` that is assigned to the `pkg.Package.Metadata` struct field it is important that a few things
are done:
- a new integration test case is added to `test/integration/pkg_cases_test.go` that exercises the new package type with the new metadata
- the new metadata struct is added to the `metadataContainer` struct within `schema/json/generate.go`
## Generating a New Schema
Create the new schema by running `cd schema/json && go run generate.go` (note you must be in the `schema/json` dir while running this):
- If there is **not** an existing schema for the given version, then the new schema file will be written to `schema/json/schema-$VERSION.json`
- If there is an existing schema for the given version and the new schema matches the existing schema, no action is taken
- If there is an existing schema for the given version and the new schema **does not** match the existing schema, an error is shown indicating to increment the version appropriately (see the "Versioning" section)
***Note: never delete a JSON schema and never change an existing JSON schema once it has been published in a release!*** Only add new schemas with a newly incremented version. All previous schema files must be stored in the `schema/json/` directory.
+125
View File
@@ -0,0 +1,125 @@
package main
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"sort"
"strings"
"github.com/alecthomas/jsonschema"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/syft/pkg"
jsonPresenter "github.com/anchore/syft/syft/presenter/json"
)
/*
This method of creating the JSON schema only captures strongly typed fields for the purpose of integrations between syft
JSON output and integrations. The downside to this approach is that any values and types used on weakly typed fields
are not captured (empty interfaces). This means that pkg.Package.Metadata is not validated at this time. This approach
can be extended to include specific package metadata struct shapes in the future.
*/
// This should represent all possible metadatas represented in the pkg.Package.Metadata field (an interface{}).
// When a new package metadata definition is created it will need to be manually added here. The variable name does
// not matter as long as it is exported.
type metadataContainer struct {
Apk pkg.ApkMetadata
Dpkg pkg.DpkgMetadata
Gem pkg.GemMetadata
Java pkg.JavaMetadata
Npm pkg.NpmPackageJSONMetadata
Python pkg.PythonPackageMetadata
Rpm pkg.RpmdbMetadata
}
// nolint:funlen
func main() {
metadataSchema := jsonschema.Reflect(&metadataContainer{})
documentSchema := jsonschema.Reflect(&jsonPresenter.Document{})
// TODO: inject source definitions
// inject the definitions of all metadatas into the schema definitions
var metadataNames []string
for name, definition := range metadataSchema.Definitions {
if name == "metadataContainer" {
// ignore the definition for the fake container
continue
}
documentSchema.Definitions[name] = definition
if strings.HasSuffix(name, "Metadata") {
metadataNames = append(metadataNames, name)
}
}
// ensure the generated list of names is stable between runs
sort.Strings(metadataNames)
var metadataTypes = []map[string]string{
// allow for no metadata to be provided
{"type": "null"},
}
for _, name := range metadataNames {
metadataTypes = append(metadataTypes, map[string]string{
"$ref": fmt.Sprintf("#/definitions/%s", name),
})
}
// set the "anyOf" field for Package.Metadata to be a conjunction of several types
documentSchema.Definitions["Package"].Properties.Set("metadata", map[string][]map[string]string{
"anyOf": metadataTypes,
})
filename := fmt.Sprintf("schema-%s.json", internal.JSONSchemaVersion)
var newSchemaBuffer = new(bytes.Buffer)
enc := json.NewEncoder(newSchemaBuffer)
// prevent > and < from being escaped in the payload
enc.SetEscapeHTML(false)
enc.SetIndent("", " ")
err := enc.Encode(&documentSchema)
if err != nil {
panic(err)
}
if _, err := os.Stat(filename); !os.IsNotExist(err) {
// check if the schema is the same...
existingFh, err := os.Open(filename)
if err != nil {
panic(err)
}
existingSchemaBytes, err := ioutil.ReadAll(existingFh)
if err != nil {
panic(err)
}
if bytes.Equal(existingSchemaBytes, newSchemaBuffer.Bytes()) {
// the generated schema is the same, bail with no error :)
fmt.Println("No change to the existing schema!")
os.Exit(0)
}
// the generated schema is different, bail with error :(
fmt.Printf("Cowardly refusing to overwrite existing schema (%s)!\nSee the scheam/json/README.md for how to increment\n", filename)
os.Exit(1)
}
fh, err := os.Create(filename)
if err != nil {
panic(err)
}
_, err = fh.Write(newSchemaBuffer.Bytes())
if err != nil {
panic(err)
}
defer fh.Close()
fmt.Printf("wrote new schema to %q\n", filename)
}
-30
View File
@@ -1,30 +0,0 @@
#!/usr/env/bin python3
import os
import glob
import json
from genson import SchemaBuilder
EXAMPLES_DIR = "examples/"
OUTPUT = "schema.json"
def main():
builder = SchemaBuilder()
print("Generating new Syft json schema...")
for filepath in glob.glob(os.path.join(EXAMPLES_DIR, '*.json')):
with open(filepath, 'r') as f:
print(f" adding {filepath}")
builder.add_object(json.loads(f.read()))
print("Building schema...")
new_schema = builder.to_schema()
with open(OUTPUT, 'w') as f:
f.write(json.dumps(new_schema, sort_keys=True, indent=4))
print(f"New schema written to '{OUTPUT}'")
if __name__ == "__main__":
main()
-1
View File
@@ -1 +0,0 @@
genson
+678
View File
@@ -0,0 +1,678 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"$ref": "#/definitions/Document",
"definitions": {
"ApkFileRecord": {
"required": [
"path"
],
"properties": {
"path": {
"type": "string"
},
"ownerUid": {
"type": "string"
},
"ownerGid": {
"type": "string"
},
"permissions": {
"type": "string"
},
"checksum": {
"type": "string"
}
},
"additionalProperties": false,
"type": "object"
},
"ApkMetadata": {
"required": [
"package",
"originPackage",
"maintainer",
"version",
"license",
"architecture",
"url",
"description",
"size",
"installedSize",
"pullDependencies",
"pullChecksum",
"gitCommitOfApkPort",
"files"
],
"properties": {
"package": {
"type": "string"
},
"originPackage": {
"type": "string"
},
"maintainer": {
"type": "string"
},
"version": {
"type": "string"
},
"license": {
"type": "string"
},
"architecture": {
"type": "string"
},
"url": {
"type": "string"
},
"description": {
"type": "string"
},
"size": {
"type": "integer"
},
"installedSize": {
"type": "integer"
},
"pullDependencies": {
"type": "string"
},
"pullChecksum": {
"type": "string"
},
"gitCommitOfApkPort": {
"type": "string"
},
"files": {
"items": {
"$schema": "http://json-schema.org/draft-04/schema#",
"$ref": "#/definitions/ApkFileRecord"
},
"type": "array"
}
},
"additionalProperties": false,
"type": "object"
},
"Descriptor": {
"required": [
"name",
"version"
],
"properties": {
"name": {
"type": "string"
},
"version": {
"type": "string"
}
},
"additionalProperties": false,
"type": "object"
},
"Distribution": {
"required": [
"name",
"version",
"idLike"
],
"properties": {
"name": {
"type": "string"
},
"version": {
"type": "string"
},
"idLike": {
"type": "string"
}
},
"additionalProperties": false,
"type": "object"
},
"Document": {
"required": [
"artifacts",
"source",
"distro",
"descriptor",
"schema"
],
"properties": {
"artifacts": {
"items": {
"$schema": "http://json-schema.org/draft-04/schema#",
"$ref": "#/definitions/Package"
},
"type": "array"
},
"source": {
"$schema": "http://json-schema.org/draft-04/schema#",
"$ref": "#/definitions/Source"
},
"distro": {
"$schema": "http://json-schema.org/draft-04/schema#",
"$ref": "#/definitions/Distribution"
},
"descriptor": {
"$schema": "http://json-schema.org/draft-04/schema#",
"$ref": "#/definitions/Descriptor"
},
"schema": {
"$schema": "http://json-schema.org/draft-04/schema#",
"$ref": "#/definitions/Schema"
}
},
"additionalProperties": false,
"type": "object"
},
"DpkgFileRecord": {
"required": [
"path",
"md5"
],
"properties": {
"path": {
"type": "string"
},
"md5": {
"type": "string"
}
},
"additionalProperties": false,
"type": "object"
},
"DpkgMetadata": {
"required": [
"package",
"source",
"version",
"architecture",
"maintainer",
"installedSize",
"files"
],
"properties": {
"package": {
"type": "string"
},
"source": {
"type": "string"
},
"version": {
"type": "string"
},
"architecture": {
"type": "string"
},
"maintainer": {
"type": "string"
},
"installedSize": {
"type": "integer"
},
"files": {
"items": {
"$schema": "http://json-schema.org/draft-04/schema#",
"$ref": "#/definitions/DpkgFileRecord"
},
"type": "array"
}
},
"additionalProperties": false,
"type": "object"
},
"GemMetadata": {
"required": [
"name",
"version"
],
"properties": {
"name": {
"type": "string"
},
"version": {
"type": "string"
},
"files": {
"items": {
"type": "string"
},
"type": "array"
},
"authors": {
"items": {
"type": "string"
},
"type": "array"
},
"licenses": {
"items": {
"type": "string"
},
"type": "array"
},
"homepage": {
"type": "string"
}
},
"additionalProperties": false,
"type": "object"
},
"JavaManifest": {
"properties": {
"main": {
"patternProperties": {
".*": {
"type": "string"
}
},
"type": "object"
},
"namedSections": {
"patternProperties": {
".*": {
"patternProperties": {
".*": {
"type": "string"
}
},
"type": "object"
}
},
"type": "object"
}
},
"additionalProperties": false,
"type": "object"
},
"JavaMetadata": {
"required": [
"virtualPath"
],
"properties": {
"virtualPath": {
"type": "string"
},
"manifest": {
"$schema": "http://json-schema.org/draft-04/schema#",
"$ref": "#/definitions/JavaManifest"
},
"pomProperties": {
"$schema": "http://json-schema.org/draft-04/schema#",
"$ref": "#/definitions/PomProperties"
}
},
"additionalProperties": false,
"type": "object"
},
"Location": {
"required": [
"path"
],
"properties": {
"path": {
"type": "string"
},
"layerID": {
"type": "string"
}
},
"additionalProperties": false,
"type": "object"
},
"NpmPackageJSONMetadata": {
"required": [
"author",
"licenses",
"homepage",
"description",
"url"
],
"properties": {
"files": {
"items": {
"type": "string"
},
"type": "array"
},
"author": {
"type": "string"
},
"licenses": {
"items": {
"type": "string"
},
"type": "array"
},
"homepage": {
"type": "string"
},
"description": {
"type": "string"
},
"url": {
"type": "string"
}
},
"additionalProperties": false,
"type": "object"
},
"Package": {
"required": [
"name",
"version",
"type",
"foundBy",
"locations",
"licenses",
"language",
"cpes",
"purl",
"metadataType",
"metadata"
],
"properties": {
"name": {
"type": "string"
},
"version": {
"type": "string"
},
"type": {
"type": "string"
},
"foundBy": {
"type": "string"
},
"locations": {
"items": {
"$schema": "http://json-schema.org/draft-04/schema#",
"$ref": "#/definitions/Location"
},
"type": "array"
},
"licenses": {
"items": {
"type": "string"
},
"type": "array"
},
"language": {
"type": "string"
},
"cpes": {
"items": {
"type": "string"
},
"type": "array"
},
"purl": {
"type": "string"
},
"metadataType": {
"type": "string"
},
"metadata": {
"anyOf": [
{
"type": "null"
},
{
"$ref": "#/definitions/ApkMetadata"
},
{
"$ref": "#/definitions/DpkgMetadata"
},
{
"$ref": "#/definitions/GemMetadata"
},
{
"$ref": "#/definitions/JavaMetadata"
},
{
"$ref": "#/definitions/NpmPackageJSONMetadata"
},
{
"$ref": "#/definitions/PythonPackageMetadata"
},
{
"$ref": "#/definitions/RpmdbMetadata"
}
]
}
},
"additionalProperties": false,
"type": "object"
},
"PomProperties": {
"required": [
"path",
"name",
"groupId",
"artifactId",
"version",
"extraFields"
],
"properties": {
"path": {
"type": "string"
},
"name": {
"type": "string"
},
"groupId": {
"type": "string"
},
"artifactId": {
"type": "string"
},
"version": {
"type": "string"
},
"extraFields": {
"patternProperties": {
".*": {
"type": "string"
}
},
"type": "object"
}
},
"additionalProperties": false,
"type": "object"
},
"PythonFileDigest": {
"required": [
"algorithm",
"value"
],
"properties": {
"algorithm": {
"type": "string"
},
"value": {
"type": "string"
}
},
"additionalProperties": false,
"type": "object"
},
"PythonFileRecord": {
"required": [
"path"
],
"properties": {
"path": {
"type": "string"
},
"digest": {
"$schema": "http://json-schema.org/draft-04/schema#",
"$ref": "#/definitions/PythonFileDigest"
},
"size": {
"type": "string"
}
},
"additionalProperties": false,
"type": "object"
},
"PythonPackageMetadata": {
"required": [
"name",
"version",
"license",
"author",
"authorEmail",
"platform",
"sitePackagesRootPath"
],
"properties": {
"name": {
"type": "string"
},
"version": {
"type": "string"
},
"license": {
"type": "string"
},
"author": {
"type": "string"
},
"authorEmail": {
"type": "string"
},
"platform": {
"type": "string"
},
"files": {
"items": {
"$schema": "http://json-schema.org/draft-04/schema#",
"$ref": "#/definitions/PythonFileRecord"
},
"type": "array"
},
"sitePackagesRootPath": {
"type": "string"
},
"topLevelPackages": {
"items": {
"type": "string"
},
"type": "array"
}
},
"additionalProperties": false,
"type": "object"
},
"RpmdbFileRecord": {
"required": [
"path",
"mode",
"size",
"sha256"
],
"properties": {
"path": {
"type": "string"
},
"mode": {
"type": "integer"
},
"size": {
"type": "integer"
},
"sha256": {
"type": "string"
}
},
"additionalProperties": false,
"type": "object"
},
"RpmdbMetadata": {
"required": [
"name",
"version",
"epoch",
"architecture",
"release",
"sourceRpm",
"size",
"license",
"vendor",
"files"
],
"properties": {
"name": {
"type": "string"
},
"version": {
"type": "string"
},
"epoch": {
"type": "integer"
},
"architecture": {
"type": "string"
},
"release": {
"type": "string"
},
"sourceRpm": {
"type": "string"
},
"size": {
"type": "integer"
},
"license": {
"type": "string"
},
"vendor": {
"type": "string"
},
"files": {
"items": {
"$schema": "http://json-schema.org/draft-04/schema#",
"$ref": "#/definitions/RpmdbFileRecord"
},
"type": "array"
}
},
"additionalProperties": false,
"type": "object"
},
"Schema": {
"required": [
"version",
"url"
],
"properties": {
"version": {
"type": "string"
},
"url": {
"type": "string"
}
},
"additionalProperties": false,
"type": "object"
},
"Source": {
"required": [
"type",
"target"
],
"properties": {
"type": {
"type": "string"
},
"target": {
"additionalProperties": true
}
},
"additionalProperties": false,
"type": "object"
}
}
}
+682
View File
@@ -0,0 +1,682 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"$ref": "#/definitions/Document",
"definitions": {
"ApkFileRecord": {
"required": [
"path"
],
"properties": {
"path": {
"type": "string"
},
"ownerUid": {
"type": "string"
},
"ownerGid": {
"type": "string"
},
"permissions": {
"type": "string"
},
"checksum": {
"type": "string"
}
},
"additionalProperties": false,
"type": "object"
},
"ApkMetadata": {
"required": [
"package",
"originPackage",
"maintainer",
"version",
"license",
"architecture",
"url",
"description",
"size",
"installedSize",
"pullDependencies",
"pullChecksum",
"gitCommitOfApkPort",
"files"
],
"properties": {
"package": {
"type": "string"
},
"originPackage": {
"type": "string"
},
"maintainer": {
"type": "string"
},
"version": {
"type": "string"
},
"license": {
"type": "string"
},
"architecture": {
"type": "string"
},
"url": {
"type": "string"
},
"description": {
"type": "string"
},
"size": {
"type": "integer"
},
"installedSize": {
"type": "integer"
},
"pullDependencies": {
"type": "string"
},
"pullChecksum": {
"type": "string"
},
"gitCommitOfApkPort": {
"type": "string"
},
"files": {
"items": {
"$schema": "http://json-schema.org/draft-04/schema#",
"$ref": "#/definitions/ApkFileRecord"
},
"type": "array"
}
},
"additionalProperties": false,
"type": "object"
},
"Descriptor": {
"required": [
"name",
"version"
],
"properties": {
"name": {
"type": "string"
},
"version": {
"type": "string"
}
},
"additionalProperties": false,
"type": "object"
},
"Distribution": {
"required": [
"name",
"version",
"idLike"
],
"properties": {
"name": {
"type": "string"
},
"version": {
"type": "string"
},
"idLike": {
"type": "string"
}
},
"additionalProperties": false,
"type": "object"
},
"Document": {
"required": [
"artifacts",
"source",
"distro",
"descriptor",
"schema"
],
"properties": {
"artifacts": {
"items": {
"$schema": "http://json-schema.org/draft-04/schema#",
"$ref": "#/definitions/Package"
},
"type": "array"
},
"source": {
"$schema": "http://json-schema.org/draft-04/schema#",
"$ref": "#/definitions/Source"
},
"distro": {
"$schema": "http://json-schema.org/draft-04/schema#",
"$ref": "#/definitions/Distribution"
},
"descriptor": {
"$schema": "http://json-schema.org/draft-04/schema#",
"$ref": "#/definitions/Descriptor"
},
"schema": {
"$schema": "http://json-schema.org/draft-04/schema#",
"$ref": "#/definitions/Schema"
}
},
"additionalProperties": false,
"type": "object"
},
"DpkgFileRecord": {
"required": [
"path",
"md5"
],
"properties": {
"path": {
"type": "string"
},
"md5": {
"type": "string"
}
},
"additionalProperties": false,
"type": "object"
},
"DpkgMetadata": {
"required": [
"package",
"source",
"version",
"sourceVersion",
"architecture",
"maintainer",
"installedSize",
"files"
],
"properties": {
"package": {
"type": "string"
},
"source": {
"type": "string"
},
"version": {
"type": "string"
},
"sourceVersion": {
"type": "string"
},
"architecture": {
"type": "string"
},
"maintainer": {
"type": "string"
},
"installedSize": {
"type": "integer"
},
"files": {
"items": {
"$schema": "http://json-schema.org/draft-04/schema#",
"$ref": "#/definitions/DpkgFileRecord"
},
"type": "array"
}
},
"additionalProperties": false,
"type": "object"
},
"GemMetadata": {
"required": [
"name",
"version"
],
"properties": {
"name": {
"type": "string"
},
"version": {
"type": "string"
},
"files": {
"items": {
"type": "string"
},
"type": "array"
},
"authors": {
"items": {
"type": "string"
},
"type": "array"
},
"licenses": {
"items": {
"type": "string"
},
"type": "array"
},
"homepage": {
"type": "string"
}
},
"additionalProperties": false,
"type": "object"
},
"JavaManifest": {
"properties": {
"main": {
"patternProperties": {
".*": {
"type": "string"
}
},
"type": "object"
},
"namedSections": {
"patternProperties": {
".*": {
"patternProperties": {
".*": {
"type": "string"
}
},
"type": "object"
}
},
"type": "object"
}
},
"additionalProperties": false,
"type": "object"
},
"JavaMetadata": {
"required": [
"virtualPath"
],
"properties": {
"virtualPath": {
"type": "string"
},
"manifest": {
"$schema": "http://json-schema.org/draft-04/schema#",
"$ref": "#/definitions/JavaManifest"
},
"pomProperties": {
"$schema": "http://json-schema.org/draft-04/schema#",
"$ref": "#/definitions/PomProperties"
}
},
"additionalProperties": false,
"type": "object"
},
"Location": {
"required": [
"path"
],
"properties": {
"path": {
"type": "string"
},
"layerID": {
"type": "string"
}
},
"additionalProperties": false,
"type": "object"
},
"NpmPackageJSONMetadata": {
"required": [
"author",
"licenses",
"homepage",
"description",
"url"
],
"properties": {
"files": {
"items": {
"type": "string"
},
"type": "array"
},
"author": {
"type": "string"
},
"licenses": {
"items": {
"type": "string"
},
"type": "array"
},
"homepage": {
"type": "string"
},
"description": {
"type": "string"
},
"url": {
"type": "string"
}
},
"additionalProperties": false,
"type": "object"
},
"Package": {
"required": [
"name",
"version",
"type",
"foundBy",
"locations",
"licenses",
"language",
"cpes",
"purl",
"metadataType",
"metadata"
],
"properties": {
"name": {
"type": "string"
},
"version": {
"type": "string"
},
"type": {
"type": "string"
},
"foundBy": {
"type": "string"
},
"locations": {
"items": {
"$schema": "http://json-schema.org/draft-04/schema#",
"$ref": "#/definitions/Location"
},
"type": "array"
},
"licenses": {
"items": {
"type": "string"
},
"type": "array"
},
"language": {
"type": "string"
},
"cpes": {
"items": {
"type": "string"
},
"type": "array"
},
"purl": {
"type": "string"
},
"metadataType": {
"type": "string"
},
"metadata": {
"anyOf": [
{
"type": "null"
},
{
"$ref": "#/definitions/ApkMetadata"
},
{
"$ref": "#/definitions/DpkgMetadata"
},
{
"$ref": "#/definitions/GemMetadata"
},
{
"$ref": "#/definitions/JavaMetadata"
},
{
"$ref": "#/definitions/NpmPackageJSONMetadata"
},
{
"$ref": "#/definitions/PythonPackageMetadata"
},
{
"$ref": "#/definitions/RpmdbMetadata"
}
]
}
},
"additionalProperties": false,
"type": "object"
},
"PomProperties": {
"required": [
"path",
"name",
"groupId",
"artifactId",
"version",
"extraFields"
],
"properties": {
"path": {
"type": "string"
},
"name": {
"type": "string"
},
"groupId": {
"type": "string"
},
"artifactId": {
"type": "string"
},
"version": {
"type": "string"
},
"extraFields": {
"patternProperties": {
".*": {
"type": "string"
}
},
"type": "object"
}
},
"additionalProperties": false,
"type": "object"
},
"PythonFileDigest": {
"required": [
"algorithm",
"value"
],
"properties": {
"algorithm": {
"type": "string"
},
"value": {
"type": "string"
}
},
"additionalProperties": false,
"type": "object"
},
"PythonFileRecord": {
"required": [
"path"
],
"properties": {
"path": {
"type": "string"
},
"digest": {
"$schema": "http://json-schema.org/draft-04/schema#",
"$ref": "#/definitions/PythonFileDigest"
},
"size": {
"type": "string"
}
},
"additionalProperties": false,
"type": "object"
},
"PythonPackageMetadata": {
"required": [
"name",
"version",
"license",
"author",
"authorEmail",
"platform",
"sitePackagesRootPath"
],
"properties": {
"name": {
"type": "string"
},
"version": {
"type": "string"
},
"license": {
"type": "string"
},
"author": {
"type": "string"
},
"authorEmail": {
"type": "string"
},
"platform": {
"type": "string"
},
"files": {
"items": {
"$schema": "http://json-schema.org/draft-04/schema#",
"$ref": "#/definitions/PythonFileRecord"
},
"type": "array"
},
"sitePackagesRootPath": {
"type": "string"
},
"topLevelPackages": {
"items": {
"type": "string"
},
"type": "array"
}
},
"additionalProperties": false,
"type": "object"
},
"RpmdbFileRecord": {
"required": [
"path",
"mode",
"size",
"sha256"
],
"properties": {
"path": {
"type": "string"
},
"mode": {
"type": "integer"
},
"size": {
"type": "integer"
},
"sha256": {
"type": "string"
}
},
"additionalProperties": false,
"type": "object"
},
"RpmdbMetadata": {
"required": [
"name",
"version",
"epoch",
"architecture",
"release",
"sourceRpm",
"size",
"license",
"vendor",
"files"
],
"properties": {
"name": {
"type": "string"
},
"version": {
"type": "string"
},
"epoch": {
"type": "integer"
},
"architecture": {
"type": "string"
},
"release": {
"type": "string"
},
"sourceRpm": {
"type": "string"
},
"size": {
"type": "integer"
},
"license": {
"type": "string"
},
"vendor": {
"type": "string"
},
"files": {
"items": {
"$schema": "http://json-schema.org/draft-04/schema#",
"$ref": "#/definitions/RpmdbFileRecord"
},
"type": "array"
}
},
"additionalProperties": false,
"type": "object"
},
"Schema": {
"required": [
"version",
"url"
],
"properties": {
"version": {
"type": "string"
},
"url": {
"type": "string"
}
},
"additionalProperties": false,
"type": "object"
},
"Source": {
"required": [
"type",
"target"
],
"properties": {
"type": {
"type": "string"
},
"target": {
"additionalProperties": true
}
},
"additionalProperties": false,
"type": "object"
}
}
}
-534
View File
@@ -1,534 +0,0 @@
{
"$schema": "http://json-schema.org/schema#",
"properties": {
"artifacts": {
"items": {
"properties": {
"found-by": {
"items": {
"type": "string"
},
"type": "array"
},
"locations": {
"items": {
"anyOf": [
{
"type": "string"
},
{
"properties": {
"layer-index": {
"type": "integer"
},
"path": {
"type": "string"
}
},
"required": [
"layer-index",
"path"
],
"type": "object"
}
]
},
"type": "array"
},
"metadata": {
"properties": {
"architecture": {
"type": "string"
},
"description": {
"type": "string"
},
"epoch": {
"type": "integer"
},
"files": {
"items": {
"properties": {
"checksum": {
"type": "string"
},
"owner-gid": {
"type": "string"
},
"owner-uid": {
"type": "string"
},
"path": {
"type": "string"
},
"permissions": {
"type": "string"
}
},
"required": [
"checksum",
"owner-gid",
"owner-uid",
"path",
"permissions"
],
"type": "object"
},
"type": "array"
},
"git-commit-of-apk-port": {
"type": "string"
},
"installed-size": {
"type": "integer"
},
"license": {
"type": "string"
},
"maintainer": {
"type": "string"
},
"manifest": {
"anyOf": [
{
"type": "null"
},
{
"properties": {
"extra-fields": {
"properties": {
"Archiver-Version": {
"type": "string"
},
"Build-Jdk": {
"type": "string"
},
"Built-By": {
"type": "string"
},
"Created-By": {
"type": "string"
},
"Extension-Name": {
"type": "string"
},
"Group-Id": {
"type": "string"
},
"Hudson-Version": {
"type": "string"
},
"Jenkins-Version": {
"type": "string"
},
"Long-Name": {
"type": "string"
},
"Main-Class": {
"type": "string"
},
"Minimum-Java-Version": {
"type": "string"
},
"Plugin-Dependencies": {
"type": "string"
},
"Plugin-Developers": {
"type": "string"
},
"Plugin-License-Name": {
"type": "string"
},
"Plugin-License-Url": {
"type": "string"
},
"Plugin-ScmUrl": {
"type": "string"
},
"Plugin-Version": {
"type": "string"
},
"Short-Name": {
"type": "string"
}
},
"required": [
"Archiver-Version",
"Build-Jdk",
"Built-By",
"Created-By"
],
"type": "object"
},
"implementation-title": {
"type": "string"
},
"implementation-vendor": {
"type": "string"
},
"implementation-version": {
"type": "string"
},
"manifest-version": {
"type": "string"
},
"name": {
"type": "string"
},
"specification-title": {
"type": "string"
},
"specification-vendor": {
"type": "string"
},
"specification-version": {
"type": "string"
}
},
"required": [
"extra-fields",
"implementation-title",
"implementation-vendor",
"implementation-version",
"manifest-version",
"name",
"specification-title",
"specification-vendor",
"specification-version"
],
"type": "object"
}
]
},
"origin-package": {
"type": "string"
},
"package": {
"type": "string"
},
"parent-package": {
"anyOf": [
{
"type": "null"
},
{
"properties": {
"found-by": {
"type": "string"
},
"language": {
"type": "integer"
},
"licenses": {
"type": "null"
},
"manifest": {
"type": "string"
},
"metadata": {
"properties": {
"manifest": {
"properties": {
"extra-fields": {
"properties": {
"Archiver-Version": {
"type": "string"
},
"Build-Jdk": {
"type": "string"
},
"Built-By": {
"type": "string"
},
"Created-By": {
"type": "string"
},
"Extension-Name": {
"type": "string"
},
"Group-Id": {
"type": "string"
},
"Hudson-Version": {
"type": "string"
},
"Jenkins-Version": {
"type": "string"
},
"Long-Name": {
"type": "string"
},
"Main-Class": {
"type": "string"
},
"Minimum-Java-Version": {
"type": "string"
},
"Plugin-Dependencies": {
"type": "string"
},
"Plugin-Developers": {
"type": "string"
},
"Plugin-License-Name": {
"type": "string"
},
"Plugin-License-Url": {
"type": "string"
},
"Plugin-ScmUrl": {
"type": "string"
},
"Plugin-Version": {
"type": "string"
},
"Short-Name": {
"type": "string"
}
},
"required": [
"Archiver-Version",
"Build-Jdk",
"Built-By",
"Created-By"
],
"type": "object"
},
"implementation-title": {
"type": "string"
},
"implementation-vendor": {
"type": "string"
},
"implementation-version": {
"type": "string"
},
"manifest-version": {
"type": "string"
},
"name": {
"type": "string"
},
"specification-title": {
"type": "string"
},
"specification-vendor": {
"type": "string"
},
"specification-version": {
"type": "string"
}
},
"required": [
"extra-fields",
"implementation-title",
"implementation-vendor",
"implementation-version",
"manifest-version",
"name",
"specification-title",
"specification-vendor",
"specification-version"
],
"type": "object"
},
"parent-package": {
"type": "null"
},
"pom-properties": {
"properties": {
"Path": {
"type": "string"
},
"artifact-id": {
"type": "string"
},
"extra-fields": {
"type": "null"
},
"group-id": {
"type": "string"
},
"name": {
"type": "string"
},
"version": {
"type": "string"
}
},
"required": [
"Path",
"artifact-id",
"extra-fields",
"group-id",
"name",
"version"
],
"type": "object"
}
},
"required": [
"manifest",
"parent-package",
"pom-properties"
],
"type": "object"
},
"sources": {
"type": "null"
},
"type": {
"type": "string"
},
"version": {
"type": "string"
}
},
"required": [
"found-by",
"language",
"licenses",
"manifest",
"metadata",
"sources",
"type",
"version"
],
"type": "object"
}
]
},
"pom-properties": {
"properties": {
"Path": {
"type": "string"
},
"artifact-id": {
"type": "string"
},
"extra-fields": {
"type": "null"
},
"group-id": {
"type": "string"
},
"name": {
"type": "string"
},
"version": {
"type": "string"
}
},
"required": [
"Path",
"artifact-id",
"extra-fields",
"group-id",
"name",
"version"
],
"type": "object"
},
"pull-checksum": {
"type": "string"
},
"pull-dependencies": {
"type": "string"
},
"release": {
"type": "string"
},
"size": {
"type": "integer"
},
"source": {
"type": "string"
},
"url": {
"type": "string"
},
"version": {
"type": "string"
}
},
"type": "object"
},
"name": {
"type": "string"
},
"type": {
"type": "string"
},
"version": {
"type": "string"
}
},
"required": [
"found-by",
"locations",
"name",
"type",
"version"
],
"type": "object"
},
"type": "array"
},
"directory": {
"type": "string"
},
"image": {
"properties": {
"digest": {
"type": "string"
},
"layers": {
"items": {
"properties": {
"digest": {
"type": "string"
},
"media-type": {
"type": "string"
},
"size": {
"type": "integer"
}
},
"required": [
"digest",
"media-type",
"size"
],
"type": "object"
},
"type": "array"
},
"media-type": {
"type": "string"
},
"size": {
"type": "integer"
},
"tags": {
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"digest",
"layers",
"media-type",
"size",
"tags"
],
"type": "object"
}
},
"required": [
"artifacts"
],
"type": "object"
}
+3 -28
View File
@@ -4,39 +4,14 @@ Package apkdb provides a concrete Cataloger implementation for Alpine DB files.
package apkdb
import (
"github.com/anchore/stereoscope/pkg/file"
"github.com/anchore/syft/syft/cataloger/common"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/scope"
)
// Cataloger catalogs pkg.ApkPkg Package Types defined in Alpine DB files.
type Cataloger struct {
cataloger common.GenericCataloger
}
// New returns a new Alpine DB cataloger object.
func New() *Cataloger {
// NewApkdbCataloger returns a new Alpine DB cataloger object.
func NewApkdbCataloger() *common.GenericCataloger {
globParsers := map[string]common.ParserFn{
"**/lib/apk/db/installed": parseApkDB,
}
return &Cataloger{
cataloger: common.NewGenericCataloger(nil, globParsers),
}
}
// Name returns a string that uniquely describes this cataloger.
func (a *Cataloger) Name() string {
return "apkdb-cataloger"
}
// SelectFiles returns a set of discovered Alpine DB files from the user content source.
func (a *Cataloger) SelectFiles(resolver scope.FileResolver) []file.Reference {
return a.cataloger.SelectFiles(resolver)
}
// Catalog returns the Packages indexed from all Alpine DB files discovered.
func (a *Cataloger) Catalog(contents map[file.Reference]string) ([]pkg.Package, error) {
return a.cataloger.Catalog(contents, a.Name())
return common.NewGenericCataloger(nil, globParsers, "apkdb-cataloger")
}
+29 -12
View File
@@ -20,9 +20,14 @@ var _ common.ParserFn = parseApkDB
// parseApkDb parses individual packages from a given Alpine DB file. For more information on specific fields
// see https://wiki.alpinelinux.org/wiki/Apk_spec .
func parseApkDB(_ string, reader io.Reader) ([]pkg.Package, error) {
// larger capacity for the scanner.
const maxScannerCapacity = 1024 * 1024
// a new larger buffer for the scanner
bufScan := make([]byte, maxScannerCapacity)
packages := make([]pkg.Package, 0)
scanner := bufio.NewScanner(reader)
scanner.Buffer(bufScan, maxScannerCapacity)
onDoubleLF := func(data []byte, atEOF bool) (advance int, token []byte, err error) {
for i := 0; i < len(data); i++ {
if i > 0 && data[i-1] == '\n' && data[i] == '\n' {
@@ -44,11 +49,12 @@ func parseApkDB(_ string, reader io.Reader) ([]pkg.Package, error) {
}
if metadata != nil {
packages = append(packages, pkg.Package{
Name: metadata.Package,
Version: metadata.Version,
Licenses: strings.Split(metadata.License, " "),
Type: pkg.ApkPkg,
Metadata: *metadata,
Name: metadata.Package,
Version: metadata.Version,
Licenses: strings.Split(metadata.License, " "),
Type: pkg.ApkPkg,
MetadataType: pkg.ApkMetadataType,
Metadata: *metadata,
})
}
}
@@ -83,7 +89,16 @@ func parseApkDBEntry(reader io.Reader) (*pkg.ApkMetadata, error) {
switch key {
case "F":
lastFile = "/" + value
currentFile := "/" + value
newFileRecord := pkg.ApkFileRecord{
Path: currentFile,
}
files = append(files, newFileRecord)
fileRecord = &files[len(files)-1]
// future aux references are relative to previous "F" records
lastFile = currentFile
continue
case "R":
newFileRecord := pkg.ApkFileRecord{
@@ -91,22 +106,24 @@ func parseApkDBEntry(reader io.Reader) (*pkg.ApkMetadata, error) {
}
files = append(files, newFileRecord)
fileRecord = &files[len(files)-1]
case "a":
case "a", "M":
ownershipFields := strings.Split(value, ":")
if len(ownershipFields) != 3 {
log.Errorf("unexpected APK ownership field: %q", value)
if len(ownershipFields) < 3 {
log.Warnf("unexpected APK ownership field: %q", value)
continue
}
if fileRecord == nil {
log.Errorf("ownership field with no parent record: %q", value)
log.Warnf("ownership field with no parent record: %q", value)
continue
}
fileRecord.OwnerUID = ownershipFields[0]
fileRecord.OwnerGUI = ownershipFields[1]
fileRecord.OwnerGID = ownershipFields[1]
fileRecord.Permissions = ownershipFields[2]
// note: there are more optional fields available that we are not capturing, e.g.:
// "0:0:755:Q1JaDEHQHBbizhEzoWK1YxuraNU/4="
case "Z":
if fileRecord == nil {
log.Errorf("checksum field with no parent record: %q", value)
log.Warnf("checksum field with no parent record: %q", value)
continue
}
fileRecord.Checksum = value
+476 -23
View File
@@ -10,13 +10,78 @@ import (
"github.com/anchore/syft/syft/pkg"
)
func TestSinglePackage(t *testing.T) {
func TestExtraFileAttributes(t *testing.T) {
tests := []struct {
name string
expected pkg.ApkMetadata
}{
{
name: "Test Single Package",
name: "test extra file attributes (checksum) are ignored",
expected: pkg.ApkMetadata{
Files: []pkg.ApkFileRecord{
{
Path: "/usr",
},
{
Path: "/usr/lib",
},
{
Path: "/usr/lib/jvm",
},
{
Path: "/usr/lib/jvm/java-1.8-openjdk",
},
{
Path: "/usr/lib/jvm/java-1.8-openjdk/bin",
},
{
Path: "/usr/lib/jvm/java-1.8-openjdk/bin/policytool",
OwnerUID: "0",
OwnerGID: "0",
Permissions: "755",
Checksum: "Q1M0C9qfC/+kdRiOodeihG2GMRtkE=",
},
},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
file, err := os.Open("test-fixtures/extra-file-attributes")
if err != nil {
t.Fatal("Unable to read test-fixtures/extra-file-attributes: ", err)
}
defer func() {
err := file.Close()
if err != nil {
t.Fatal("closing file failed:", err)
}
}()
reader := bufio.NewReader(file)
entry, err := parseApkDBEntry(reader)
if err != nil {
t.Fatal("Unable to read file contents: ", err)
}
if diff := deep.Equal(entry.Files, test.expected.Files); diff != nil {
for _, d := range diff {
t.Errorf("diff: %+v", d)
}
}
})
}
}
func TestSinglePackageDetails(t *testing.T) {
tests := []struct {
fixture string
expected pkg.ApkMetadata
}{
{
fixture: "test-fixtures/single",
expected: pkg.ApkMetadata{
Package: "musl-utils",
OriginPackage: "musl",
@@ -32,51 +97,428 @@ func TestSinglePackage(t *testing.T) {
PullChecksum: "Q1bTtF5526tETKfL+lnigzIDvm+2o=",
GitCommitOfAport: "4024cc3b29ad4c65544ad068b8f59172b5494306",
Files: []pkg.ApkFileRecord{
{
Path: "/sbin",
},
{
Path: "/sbin/ldconfig",
OwnerUID: "0",
OwnerGUI: "0",
OwnerGID: "0",
Permissions: "755",
Checksum: "Q1Kja2+POZKxEkUOZqwSjC6kmaED4=",
},
{
Path: "/usr",
},
{
Path: "/usr/bin",
},
{
Path: "/usr/bin/iconv",
OwnerUID: "0",
OwnerGUI: "0",
OwnerGID: "0",
Permissions: "755",
Checksum: "Q1CVmFbdY+Hv6/jAHl1gec2Kbx1EY=",
},
{
Path: "/usr/bin/ldd",
OwnerUID: "0",
OwnerGUI: "0",
OwnerGID: "0",
Permissions: "755",
Checksum: "Q1yFAhGggmL7ERgbIA7KQxyTzf3ks=",
},
{
Path: "/usr/bin/getconf",
OwnerUID: "0",
OwnerGUI: "0",
OwnerGID: "0",
Permissions: "755",
Checksum: "Q1dAdYK8M/INibRQF5B3Rw7cmNDDA=",
},
{
Path: "/usr/bin/getent",
OwnerUID: "0",
OwnerGUI: "0",
OwnerGID: "0",
Permissions: "755",
Checksum: "Q1eR2Dz/WylabgbWMTkd2+hGmEya4=",
},
},
},
},
{
fixture: "test-fixtures/base",
expected: pkg.ApkMetadata{
Package: "alpine-baselayout",
OriginPackage: "alpine-baselayout",
Version: "3.2.0-r6",
Description: "Alpine base dir structure and init scripts",
Maintainer: "Natanael Copa <ncopa@alpinelinux.org>",
License: "GPL-2.0-only",
Architecture: "x86_64",
URL: "https://git.alpinelinux.org/cgit/aports/tree/main/alpine-baselayout",
Size: 19917,
InstalledSize: 409600,
PullDependencies: "/bin/sh so:libc.musl-x86_64.so.1",
PullChecksum: "Q1myMNfd7u5v5UTgNHeq1e31qTjZU=",
GitCommitOfAport: "e1c51734fa96fa4bac92e9f14a474324c67916fc",
Files: []pkg.ApkFileRecord{
{
Path: "/dev",
},
{
Path: "/dev/pts",
},
{
Path: "/dev/shm",
},
{
Path: "/etc",
},
{
Path: "/etc/fstab",
Checksum: "Q11Q7hNe8QpDS531guqCdrXBzoA/o=",
},
{
Path: "/etc/group",
Checksum: "Q1oJ16xWudgKOrXIEquEDzlF2Lsm4=",
},
{
Path: "/etc/hostname",
Checksum: "Q16nVwYVXP/tChvUPdukVD2ifXOmc=",
},
{
Path: "/etc/hosts",
Checksum: "Q1BD6zJKZTRWyqGnPi4tSfd3krsMU=",
},
{
Path: "/etc/inittab",
Checksum: "Q1TsthbhW7QzWRe1E/NKwTOuD4pHc=",
},
{
Path: "/etc/modules",
Checksum: "Q1toogjUipHGcMgECgPJX64SwUT1M=",
},
{
Path: "/etc/motd",
Checksum: "Q1XmduVVNURHQ27TvYp1Lr5TMtFcA=",
},
{
Path: "/etc/mtab",
OwnerUID: "0",
OwnerGID: "0",
Permissions: "777",
Checksum: "Q1kiljhXXH1LlQroHsEJIkPZg2eiw=",
},
{
Path: "/etc/passwd",
Checksum: "Q1TchuuLUfur0izvfZQZxgN/LJhB8=",
},
{
Path: "/etc/profile",
Checksum: "Q1KpFb8kl5LvwXWlY3e58FNsjrI34=",
},
{
Path: "/etc/protocols",
Checksum: "Q13FqXUnvuOpMDrH/6rehxuYAEE34=",
},
{
Path: "/etc/services",
Checksum: "Q1C6HJNgQvLWqt5VY+n7MZJ1rsDuY=",
},
{
Path: "/etc/shadow",
OwnerUID: "0",
OwnerGID: "42",
Permissions: "640",
Checksum: "Q1ltrPIAW2zHeDiajsex2Bdmq3uqA=",
},
{
Path: "/etc/shells",
Checksum: "Q1ojm2YdpCJ6B/apGDaZ/Sdb2xJkA=",
},
{
Path: "/etc/sysctl.conf",
Checksum: "Q14upz3tfnNxZkIEsUhWn7Xoiw96g=",
},
{
Path: "/etc/apk",
},
{
Path: "/etc/conf.d",
},
{
Path: "/etc/crontabs",
},
{
Path: "/etc/crontabs/root",
OwnerUID: "0",
OwnerGID: "0",
Permissions: "600",
Checksum: "Q1vfk1apUWI4yLJGhhNRd0kJixfvY=",
},
{
Path: "/etc/init.d",
},
{
Path: "/etc/modprobe.d",
},
{
Path: "/etc/modprobe.d/aliases.conf",
Checksum: "Q1WUbh6TBYNVK7e4Y+uUvLs/7viqk=",
},
{
Path: "/etc/modprobe.d/blacklist.conf",
Checksum: "Q1xxYGU6S6TLQvb7ervPrWWwAWqMg=",
},
{
Path: "/etc/modprobe.d/i386.conf",
Checksum: "Q1pnay/njn6ol9cCssL7KiZZ8etlc=",
},
{
Path: "/etc/modprobe.d/kms.conf",
Checksum: "Q1ynbLn3GYDpvajba/ldp1niayeog=",
},
{
Path: "/etc/modules-load.d",
},
{
Path: "/etc/network",
},
{
Path: "/etc/network/if-down.d",
},
{
Path: "/etc/network/if-post-down.d",
},
{
Path: "/etc/network/if-pre-up.d",
},
{
Path: "/etc/network/if-up.d",
},
{
Path: "/etc/opt",
},
{
Path: "/etc/periodic",
},
{
Path: "/etc/periodic/15min",
},
{
Path: "/etc/periodic/daily",
},
{
Path: "/etc/periodic/hourly",
},
{
Path: "/etc/periodic/monthly",
},
{
Path: "/etc/periodic/weekly",
},
{
Path: "/etc/profile.d",
},
{
Path: "/etc/profile.d/color_prompt",
Checksum: "Q10wL23GuSCVfumMRgakabUI6EsSk=",
},
{
Path: "/etc/profile.d/locale",
Checksum: "Q1R4bIEpnKxxOSrlnZy9AoawqZ5DU=",
},
{
Path: "/etc/sysctl.d",
},
{
Path: "/home",
},
{
Path: "/lib",
},
{
Path: "/lib/firmware",
},
{
Path: "/lib/mdev",
},
{
Path: "/lib/modules-load.d",
},
{
Path: "/lib/sysctl.d",
},
{
Path: "/lib/sysctl.d/00-alpine.conf",
Checksum: "Q1HpElzW1xEgmKfERtTy7oommnq6c=",
},
{
Path: "/media",
},
{
Path: "/media/cdrom",
},
{
Path: "/media/floppy",
},
{
Path: "/media/usb",
},
{
Path: "/mnt",
},
{
Path: "/opt",
},
{
Path: "/proc",
},
{
Path: "/root",
OwnerUID: "0",
OwnerGID: "0",
Permissions: "700",
},
{
Path: "/run",
},
{
Path: "/sbin",
},
{
Path: "/sbin/mkmntdirs",
OwnerUID: "0",
OwnerGID: "0",
Permissions: "755",
Checksum: "Q1YeuSmC7iDbEWrusPzA/zUQF6YSg=",
},
{
Path: "/srv",
},
{
Path: "/sys",
},
{
Path: "/tmp",
OwnerUID: "0",
OwnerGID: "0",
Permissions: "1777",
},
{
Path: "/usr",
},
{
Path: "/usr/lib",
},
{
Path: "/usr/lib/modules-load.d",
},
{
Path: "/usr/local",
},
{
Path: "/usr/local/bin",
},
{
Path: "/usr/local/lib",
},
{
Path: "/usr/local/share",
},
{
Path: "/usr/sbin",
},
{
Path: "/usr/share",
},
{
Path: "/usr/share/man",
},
{
Path: "/usr/share/misc",
},
{
Path: "/var",
},
{
Path: "/var/run",
OwnerUID: "0",
OwnerGID: "0",
Permissions: "777",
Checksum: "Q11/SNZz/8cK2dSKK+cJpVrZIuF4Q=",
},
{
Path: "/var/cache",
},
{
Path: "/var/cache/misc",
},
{
Path: "/var/empty",
OwnerUID: "0",
OwnerGID: "0",
Permissions: "555",
},
{
Path: "/var/lib",
},
{
Path: "/var/lib/misc",
},
{
Path: "/var/local",
},
{
Path: "/var/lock",
},
{
Path: "/var/lock/subsys",
},
{
Path: "/var/log",
},
{
Path: "/var/mail",
},
{
Path: "/var/opt",
},
{
Path: "/var/spool",
},
{
Path: "/var/spool/mail",
OwnerUID: "0",
OwnerGID: "0",
Permissions: "777",
Checksum: "Q1dzbdazYZA2nTzSIG3YyNw7d4Juc=",
},
{
Path: "/var/spool/cron",
},
{
Path: "/var/spool/cron/crontabs",
OwnerUID: "0",
OwnerGID: "0",
Permissions: "777",
Checksum: "Q1OFZt+ZMp7j0Gny0rqSKuWJyqYmA=",
},
{
Path: "/var/tmp",
OwnerUID: "0",
OwnerGID: "0",
Permissions: "1777",
},
},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
file, err := os.Open("test-fixtures/single")
t.Run(test.fixture, func(t *testing.T) {
file, err := os.Open(test.fixture)
if err != nil {
t.Fatal("Unable to read test_fixtures/single: ", err)
t.Fatal("Unable to read fixture: ", err)
}
defer func() {
err := file.Close()
@@ -110,10 +552,11 @@ func TestMultiplePackages(t *testing.T) {
fixture: "test-fixtures/multiple",
expected: []pkg.Package{
{
Name: "libc-utils",
Version: "0.7.2-r0",
Licenses: []string{"BSD"},
Type: pkg.ApkPkg,
Name: "libc-utils",
Version: "0.7.2-r0",
Licenses: []string{"BSD"},
Type: pkg.ApkPkg,
MetadataType: pkg.ApkMetadataType,
Metadata: pkg.ApkMetadata{
Package: "libc-utils",
OriginPackage: "libc-dev",
@@ -132,10 +575,11 @@ func TestMultiplePackages(t *testing.T) {
},
},
{
Name: "musl-utils",
Version: "1.1.24-r2",
Licenses: []string{"MIT", "BSD", "GPL2+"},
Type: pkg.ApkPkg,
Name: "musl-utils",
Version: "1.1.24-r2",
Licenses: []string{"MIT", "BSD", "GPL2+"},
Type: pkg.ApkPkg,
MetadataType: pkg.ApkMetadataType,
Metadata: pkg.ApkMetadata{
Package: "musl-utils",
OriginPackage: "musl",
@@ -151,38 +595,47 @@ func TestMultiplePackages(t *testing.T) {
PullChecksum: "Q1bTtF5526tETKfL+lnigzIDvm+2o=",
GitCommitOfAport: "4024cc3b29ad4c65544ad068b8f59172b5494306",
Files: []pkg.ApkFileRecord{
{
Path: "/sbin",
},
{
Path: "/sbin/ldconfig",
OwnerUID: "0",
OwnerGUI: "0",
OwnerGID: "0",
Permissions: "755",
Checksum: "Q1Kja2+POZKxEkUOZqwSjC6kmaED4=",
},
{
Path: "/usr",
},
{
Path: "/usr/bin",
},
{
Path: "/usr/bin/iconv",
OwnerUID: "0",
OwnerGUI: "0",
OwnerGID: "0",
Permissions: "755",
Checksum: "Q1CVmFbdY+Hv6/jAHl1gec2Kbx1EY=",
},
{
Path: "/usr/bin/ldd",
OwnerUID: "0",
OwnerGUI: "0",
OwnerGID: "0",
Permissions: "755",
Checksum: "Q1yFAhGggmL7ERgbIA7KQxyTzf3ks=",
},
{
Path: "/usr/bin/getconf",
OwnerUID: "0",
OwnerGUI: "0",
OwnerGID: "0",
Permissions: "755",
Checksum: "Q1dAdYK8M/INibRQF5B3Rw7cmNDDA=",
},
{
Path: "/usr/bin/getent",
OwnerUID: "0",
OwnerGUI: "0",
OwnerGID: "0",
Permissions: "755",
Checksum: "Q1eR2Dz/WylabgbWMTkd2+hGmEya4=",
},
+149
View File
@@ -0,0 +1,149 @@
C:Q1myMNfd7u5v5UTgNHeq1e31qTjZU=
P:alpine-baselayout
V:3.2.0-r6
A:x86_64
S:19917
I:409600
T:Alpine base dir structure and init scripts
U:https://git.alpinelinux.org/cgit/aports/tree/main/alpine-baselayout
L:GPL-2.0-only
o:alpine-baselayout
m:Natanael Copa <ncopa@alpinelinux.org>
t:1590679557
c:e1c51734fa96fa4bac92e9f14a474324c67916fc
D:/bin/sh so:libc.musl-x86_64.so.1
p:cmd:mkmntdirs
F:dev
F:dev/pts
F:dev/shm
F:etc
R:fstab
Z:Q11Q7hNe8QpDS531guqCdrXBzoA/o=
R:group
Z:Q1oJ16xWudgKOrXIEquEDzlF2Lsm4=
R:hostname
Z:Q16nVwYVXP/tChvUPdukVD2ifXOmc=
R:hosts
Z:Q1BD6zJKZTRWyqGnPi4tSfd3krsMU=
R:inittab
Z:Q1TsthbhW7QzWRe1E/NKwTOuD4pHc=
R:modules
Z:Q1toogjUipHGcMgECgPJX64SwUT1M=
R:motd
Z:Q1XmduVVNURHQ27TvYp1Lr5TMtFcA=
R:mtab
a:0:0:777
Z:Q1kiljhXXH1LlQroHsEJIkPZg2eiw=
R:passwd
Z:Q1TchuuLUfur0izvfZQZxgN/LJhB8=
R:profile
Z:Q1KpFb8kl5LvwXWlY3e58FNsjrI34=
R:protocols
Z:Q13FqXUnvuOpMDrH/6rehxuYAEE34=
R:services
Z:Q1C6HJNgQvLWqt5VY+n7MZJ1rsDuY=
R:shadow
a:0:42:640
Z:Q1ltrPIAW2zHeDiajsex2Bdmq3uqA=
R:shells
Z:Q1ojm2YdpCJ6B/apGDaZ/Sdb2xJkA=
R:sysctl.conf
Z:Q14upz3tfnNxZkIEsUhWn7Xoiw96g=
F:etc/apk
F:etc/conf.d
F:etc/crontabs
R:root
a:0:0:600
Z:Q1vfk1apUWI4yLJGhhNRd0kJixfvY=
F:etc/init.d
F:etc/modprobe.d
R:aliases.conf
Z:Q1WUbh6TBYNVK7e4Y+uUvLs/7viqk=
R:blacklist.conf
Z:Q1xxYGU6S6TLQvb7ervPrWWwAWqMg=
R:i386.conf
Z:Q1pnay/njn6ol9cCssL7KiZZ8etlc=
R:kms.conf
Z:Q1ynbLn3GYDpvajba/ldp1niayeog=
F:etc/modules-load.d
F:etc/network
F:etc/network/if-down.d
F:etc/network/if-post-down.d
F:etc/network/if-pre-up.d
F:etc/network/if-up.d
F:etc/opt
F:etc/periodic
F:etc/periodic/15min
F:etc/periodic/daily
F:etc/periodic/hourly
F:etc/periodic/monthly
F:etc/periodic/weekly
F:etc/profile.d
R:color_prompt
Z:Q10wL23GuSCVfumMRgakabUI6EsSk=
R:locale
Z:Q1R4bIEpnKxxOSrlnZy9AoawqZ5DU=
F:etc/sysctl.d
F:home
F:lib
F:lib/firmware
F:lib/mdev
F:lib/modules-load.d
F:lib/sysctl.d
R:00-alpine.conf
Z:Q1HpElzW1xEgmKfERtTy7oommnq6c=
F:media
F:media/cdrom
F:media/floppy
F:media/usb
F:mnt
F:opt
F:proc
F:root
M:0:0:700
F:run
F:sbin
R:mkmntdirs
a:0:0:755
Z:Q1YeuSmC7iDbEWrusPzA/zUQF6YSg=
F:srv
F:sys
F:tmp
M:0:0:1777
F:usr
F:usr/lib
F:usr/lib/modules-load.d
F:usr/local
F:usr/local/bin
F:usr/local/lib
F:usr/local/share
F:usr/sbin
F:usr/share
F:usr/share/man
F:usr/share/misc
F:var
R:run
a:0:0:777
Z:Q11/SNZz/8cK2dSKK+cJpVrZIuF4Q=
F:var/cache
F:var/cache/misc
F:var/empty
M:0:0:555
F:var/lib
F:var/lib/misc
F:var/local
F:var/lock
F:var/lock/subsys
F:var/log
F:var/mail
F:var/opt
F:var/spool
R:mail
a:0:0:777
Z:Q1dzbdazYZA2nTzSIG3YyNw7d4Juc=
F:var/spool/cron
R:crontabs
a:0:0:777
Z:Q1OFZt+ZMp7j0Gny0rqSKuWJyqYmA=
F:var/tmp
M:0:0:1777
@@ -0,0 +1,22 @@
P:openjdk8-jre
V:8.212.04-r0
A:x86_64
S:359029
I:970752
T:OpenJDK 8 Java Runtime
U:https://icedtea.classpath.org/
L:custom
o:openjdk8
m:Timo Teras <timo.teras@iki.fi>
t:1556993127
c:46badd1aabd3375ae4b41c7194bc448f74b2ea4a
D:java-cacerts nss so:libX11.so.6 so:libXcomposite.so.1 so:libXext.so.6 so:libXi.so.6 so:libXrender.so.1 so:libXtst.so.6 so:libasound.so.2 so:libc.musl-x86_64.so.1 so:libfreetype.so.6 so:libgcc_s.so.1 so:libgif.so.7 so:libjpeg.so.8 so:libpng16.so.16 so:libstdc++.so.6 so:openjdk8:libawt.so so:openjdk8:libjava.so so:openjdk8:libjli.so so:openjdk8:libjvm.so
p:so:openjdk8:libawt_xawt.so=0 so:openjdk8:libfontmanager.so=0 so:openjdk8:libjawt.so=0 so:openjdk8:libjsoundalsa.so=0 so:openjdk8:libsplashscreen.so=0
F:usr
F:usr/lib
F:usr/lib/jvm
F:usr/lib/jvm/java-1.8-openjdk
F:usr/lib/jvm/java-1.8-openjdk/bin
R:policytool
a:0:0:755:Q1JaDEHQHBbizhEzoWK1YxuraNU/4=
Z:Q1M0C9qfC/+kdRiOodeihG2GMRtkE=
-42
View File
@@ -1,42 +0,0 @@
/*
Package bundler provides a concrete Cataloger implementation for Ruby Gemfile.lock bundler files.
*/
package bundler
import (
"github.com/anchore/stereoscope/pkg/file"
"github.com/anchore/syft/syft/cataloger/common"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/scope"
)
// Cataloger catalogs pkg.GemPkg Package Types defined in Bundler Gemfile.lock files.
type Cataloger struct {
cataloger common.GenericCataloger
}
// New returns a new Bundler cataloger object.
func New() *Cataloger {
globParsers := map[string]common.ParserFn{
"**/Gemfile.lock": parseGemfileLockEntries,
}
return &Cataloger{
cataloger: common.NewGenericCataloger(nil, globParsers),
}
}
// Name returns a string that uniquely describes this cataloger.
func (a *Cataloger) Name() string {
return "bundler-cataloger"
}
// SelectFiles returns a set of discovered Gemfile.lock files from the user content source.
func (a *Cataloger) SelectFiles(resolver scope.FileResolver) []file.Reference {
return a.cataloger.SelectFiles(resolver)
}
// Catalog returns the Packages indexed from all Gemfile.lock files discovered.
func (a *Cataloger) Catalog(contents map[file.Reference]string) ([]pkg.Package, error) {
return a.cataloger.Catalog(contents, a.Name())
}
@@ -1,100 +0,0 @@
package bundler
import (
"os"
"testing"
"github.com/anchore/syft/syft/pkg"
)
var expected = map[string]string{
"actionmailer": "4.1.1",
"actionpack": "4.1.1",
"actionview": "4.1.1",
"activemodel": "4.1.1",
"activerecord": "4.1.1",
"activesupport": "4.1.1",
"arel": "5.0.1.20140414130214",
"bootstrap-sass": "3.1.1.1",
"builder": "3.2.2",
"coffee-rails": "4.0.1",
"coffee-script": "2.2.0",
"coffee-script-source": "1.7.0",
"erubis": "2.7.0",
"execjs": "2.0.2",
"hike": "1.2.3",
"i18n": "0.6.9",
"jbuilder": "2.0.7",
"jquery-rails": "3.1.0",
"json": "1.8.1",
"kgio": "2.9.2",
"libv8": "3.16.14.3",
"mail": "2.5.4",
"mime-types": "1.25.1",
"minitest": "5.3.4",
"multi_json": "1.10.1",
"mysql2": "0.3.16",
"polyglot": "0.3.4",
"rack": "1.5.2",
"rack-test": "0.6.2",
"rails": "4.1.1",
"railties": "4.1.1",
"raindrops": "0.13.0",
"rake": "10.3.2",
"rdoc": "4.1.1",
"ref": "1.0.5",
"sass": "3.2.19",
"sass-rails": "4.0.3",
"sdoc": "0.4.0",
"spring": "1.1.3",
"sprockets": "2.11.0",
"sprockets-rails": "2.1.3",
"sqlite3": "1.3.9",
"therubyracer": "0.12.1",
"thor": "0.19.1",
"thread_safe": "0.3.3",
"tilt": "1.4.1",
"treetop": "1.4.15",
"turbolinks": "2.2.2",
"tzinfo": "1.2.0",
"uglifier": "2.5.0",
"unicorn": "4.8.3",
}
func TestParseGemfileLockEntries(t *testing.T) {
fixture, err := os.Open("test-fixtures/Gemfile.lock")
if err != nil {
t.Fatalf("failed to open fixture: %+v", err)
}
actual, err := parseGemfileLockEntries(fixture.Name(), fixture)
if err != nil {
t.Fatalf("failed to parse gemfile lock: %+v", err)
}
if len(actual) != len(expected) {
for _, a := range actual {
t.Log(" ", a)
}
t.Fatalf("unexpected package count: %d!=%d", len(actual), len(expected))
}
for _, a := range actual {
expectedVersion, ok := expected[a.Name]
if !ok {
t.Errorf("unexpected package found: %s", a.Name)
}
if expectedVersion != a.Version {
t.Errorf("unexpected package version (pkg=%s): %s", a.Name, a.Version)
}
if a.Language != pkg.Ruby {
t.Errorf("bad language (pkg=%+v): %+v", a.Name, a.Language)
}
if a.Type != pkg.BundlerPkg {
t.Errorf("bad package type (pkg=%+v): %+v", a.Name, a.Type)
}
}
}
+18 -25
View File
@@ -1,12 +1,12 @@
package cataloger
import (
"github.com/anchore/stereoscope/pkg/file"
"github.com/anchore/syft/internal/bus"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/distro"
"github.com/anchore/syft/syft/event"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/scope"
"github.com/anchore/syft/syft/source"
"github.com/hashicorp/go-multierror"
"github.com/wagoodman/go-partybus"
"github.com/wagoodman/go-progress"
@@ -33,44 +33,37 @@ func newMonitor() (*progress.Manual, *progress.Manual) {
return &filesProcessed, &packagesDiscovered
}
// Catalog a given scope (container image or filesystem) with the given catalogers, returning all discovered packages.
// Catalog a given source (container image or filesystem) with the given catalogers, returning all discovered packages.
// In order to efficiently retrieve contents from a underlying container image the content fetch requests are
// done in bulk. Specifically, all files of interest are collected from each catalogers and accumulated into a single
// request.
func Catalog(resolver scope.Resolver, catalogers ...Cataloger) (*pkg.Catalog, error) {
func Catalog(resolver source.Resolver, theDistro *distro.Distro, catalogers ...Cataloger) (*pkg.Catalog, error) {
catalog := pkg.NewCatalog()
fileSelection := make([]file.Reference, 0)
filesProcessed, packagesDiscovered := newMonitor()
// ask catalogers for files to extract from the image tar
for _, a := range catalogers {
fileSelection = append(fileSelection, a.SelectFiles(resolver)...)
log.Debugf("cataloger '%s' selected '%d' files", a.Name(), len(fileSelection))
filesProcessed.N += int64(len(fileSelection))
}
// fetch contents for requested selection by catalogers
// TODO: we should consider refactoring to return a set of io.Readers instead of the full contents themselves (allow for optional buffering).
contents, err := resolver.MultipleFileContentsByRef(fileSelection...)
if err != nil {
return nil, err
}
// perform analysis, accumulating errors for each failed analysis
var errs error
for _, a := range catalogers {
// TODO: check for multiple rounds of analyses by Iterate error
packages, err := a.Catalog(contents)
for _, theCataloger := range catalogers {
// find packages from the underlying raw data
packages, err := theCataloger.Catalog(resolver)
if err != nil {
errs = multierror.Append(errs, err)
continue
}
log.Debugf("cataloger '%s' discovered '%d' packages", a.Name(), len(packages))
packagesDiscovered.N += int64(len(packages))
catalogedPackages := len(packages)
log.Debugf("cataloger '%s' discovered '%d' packages", theCataloger.Name(), catalogedPackages)
packagesDiscovered.N += int64(catalogedPackages)
for _, p := range packages {
// generate CPEs
p.CPEs = generatePackageCPEs(p)
// generate PURL
p.PURL = generatePackageURL(p, theDistro)
// add to catalog
catalog.Add(p)
}
}
+30 -20
View File
@@ -6,17 +6,16 @@ catalogers defined in child packages as well as the interface definition to impl
package cataloger
import (
"github.com/anchore/stereoscope/pkg/file"
"github.com/anchore/syft/syft/cataloger/apkdb"
"github.com/anchore/syft/syft/cataloger/bundler"
"github.com/anchore/syft/syft/cataloger/dpkg"
"github.com/anchore/syft/syft/cataloger/deb"
"github.com/anchore/syft/syft/cataloger/golang"
"github.com/anchore/syft/syft/cataloger/java"
"github.com/anchore/syft/syft/cataloger/javascript"
"github.com/anchore/syft/syft/cataloger/python"
"github.com/anchore/syft/syft/cataloger/rpmdb"
"github.com/anchore/syft/syft/cataloger/ruby"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/scope"
"github.com/anchore/syft/syft/source"
)
// Cataloger describes behavior for an object to participate in parsing container image or file system
@@ -25,24 +24,35 @@ import (
type Cataloger interface {
// Name returns a string that uniquely describes a cataloger
Name() string
// SelectFiles discovers and returns specific files that the cataloger would like to inspect the contents of.
SelectFiles(scope.FileResolver) []file.Reference
// Catalog is given the file contents and should return any discovered Packages after analyzing the contents.
Catalog(map[file.Reference]string) ([]pkg.Package, error)
// TODO: add "IterationNeeded" error to indicate to the driver to continue with another Select/Catalog pass
// TODO: we should consider refactoring to return a set of io.Readers instead of the full contents themselves (allow for optional buffering).
// Catalog is given an object to resolve file references and content, this function returns any discovered Packages after analyzing the catalog source.
Catalog(resolver source.Resolver) ([]pkg.Package, error)
}
// All returns a slice of all locally defined catalogers (defined in child packages).
func All() []Cataloger {
// ImageCatalogers returns a slice of locally implemented catalogers that are fit for detecting installations of packages.
func ImageCatalogers() []Cataloger {
return []Cataloger{
dpkg.New(),
bundler.New(),
python.New(),
rpmdb.New(),
java.New(),
apkdb.New(),
golang.New(),
javascript.New(),
ruby.NewGemSpecCataloger(),
python.NewPythonPackageCataloger(),
javascript.NewJavascriptPackageCataloger(),
deb.NewDpkgdbCataloger(),
rpmdb.NewRpmdbCataloger(),
java.NewJavaCataloger(),
apkdb.NewApkdbCataloger(),
golang.NewGoModCataloger(),
}
}
// DirectoryCatalogers returns a slice of locally implemented catalogers that are fit for detecting packages from index files (and select installations)
func DirectoryCatalogers() []Cataloger {
return []Cataloger{
ruby.NewGemFileLockCataloger(),
python.NewPythonIndexCataloger(),
python.NewPythonPackageCataloger(),
javascript.NewJavascriptLockCataloger(),
deb.NewDpkgdbCataloger(),
rpmdb.NewRpmdbCataloger(),
java.NewJavaCataloger(),
apkdb.NewApkdbCataloger(),
golang.NewGoModCataloger(),
}
}
+54 -38
View File
@@ -4,97 +4,113 @@ Package common provides generic utilities used by multiple catalogers.
package common
import (
"strings"
"io"
"github.com/anchore/stereoscope/pkg/file"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/scope"
"github.com/anchore/syft/syft/source"
)
// GenericCataloger implements the Catalog interface and is responsible for dispatching the proper parser function for
// a given path or glob pattern. This is intended to be reusable across many package cataloger types.
type GenericCataloger struct {
globParsers map[string]ParserFn
pathParsers map[string]ParserFn
selectedFiles []file.Reference
parsers map[file.Reference]ParserFn
globParsers map[string]ParserFn
pathParsers map[string]ParserFn
selectedFiles []source.Location
parsers map[source.Location]ParserFn
upstreamCataloger string
}
// NewGenericCataloger if provided path-to-parser-function and glob-to-parser-function lookups creates a GenericCataloger
func NewGenericCataloger(pathParsers map[string]ParserFn, globParsers map[string]ParserFn) GenericCataloger {
return GenericCataloger{
globParsers: globParsers,
pathParsers: pathParsers,
selectedFiles: make([]file.Reference, 0),
parsers: make(map[file.Reference]ParserFn),
func NewGenericCataloger(pathParsers map[string]ParserFn, globParsers map[string]ParserFn, upstreamCataloger string) *GenericCataloger {
return &GenericCataloger{
globParsers: globParsers,
pathParsers: pathParsers,
selectedFiles: make([]source.Location, 0),
parsers: make(map[source.Location]ParserFn),
upstreamCataloger: upstreamCataloger,
}
}
// Name returns a string that uniquely describes the upstream cataloger that this Generic Cataloger represents.
func (c *GenericCataloger) Name() string {
return c.upstreamCataloger
}
// register pairs a set of file references with a parser function for future cataloging (when the file contents are resolved)
func (a *GenericCataloger) register(files []file.Reference, parser ParserFn) {
a.selectedFiles = append(a.selectedFiles, files...)
func (c *GenericCataloger) register(files []source.Location, parser ParserFn) {
c.selectedFiles = append(c.selectedFiles, files...)
for _, f := range files {
a.parsers[f] = parser
c.parsers[f] = parser
}
}
// clear deletes all registered file-reference-to-parser-function pairings from former SelectFiles() and register() calls
func (a *GenericCataloger) clear() {
a.selectedFiles = make([]file.Reference, 0)
a.parsers = make(map[file.Reference]ParserFn)
func (c *GenericCataloger) clear() {
c.selectedFiles = make([]source.Location, 0)
c.parsers = make(map[source.Location]ParserFn)
}
// Catalog is given an object to resolve file references and content, this function returns any discovered Packages after analyzing the catalog source.
func (c *GenericCataloger) Catalog(resolver source.Resolver) ([]pkg.Package, error) {
fileSelection := c.selectFiles(resolver)
contents, err := resolver.MultipleFileContentsByLocation(fileSelection)
if err != nil {
return nil, err
}
return c.catalog(contents)
}
// SelectFiles takes a set of file trees and resolves and file references of interest for future cataloging
func (a *GenericCataloger) SelectFiles(resolver scope.FileResolver) []file.Reference {
func (c *GenericCataloger) selectFiles(resolver source.FileResolver) []source.Location {
// select by exact path
for path, parser := range a.pathParsers {
files, err := resolver.FilesByPath(file.Path(path))
for path, parser := range c.pathParsers {
files, err := resolver.FilesByPath(path)
if err != nil {
log.Errorf("cataloger failed to select files by path: %w", err)
log.Warnf("cataloger failed to select files by path: %+v", err)
}
if files != nil {
a.register(files, parser)
c.register(files, parser)
}
}
// select by glob pattern
for globPattern, parser := range a.globParsers {
for globPattern, parser := range c.globParsers {
fileMatches, err := resolver.FilesByGlob(globPattern)
if err != nil {
log.Errorf("failed to find files by glob: %s", globPattern)
log.Warnf("failed to find files by glob: %s", globPattern)
}
if fileMatches != nil {
a.register(fileMatches, parser)
c.register(fileMatches, parser)
}
}
return a.selectedFiles
return c.selectedFiles
}
// Catalog takes a set of file contents and uses any configured parser functions to resolve and return discovered packages
func (a *GenericCataloger) Catalog(contents map[file.Reference]string, upstreamMatcher string) ([]pkg.Package, error) {
defer a.clear()
// catalog takes a set of file contents and uses any configured parser functions to resolve and return discovered packages
func (c *GenericCataloger) catalog(contents map[source.Location]io.ReadCloser) ([]pkg.Package, error) {
defer c.clear()
packages := make([]pkg.Package, 0)
for reference, parser := range a.parsers {
content, ok := contents[reference]
for location, parser := range c.parsers {
content, ok := contents[location]
if !ok {
log.Errorf("cataloger '%s' missing file content: %+v", upstreamMatcher, reference)
log.Warnf("cataloger '%s' missing file content: %+v", c.upstreamCataloger, location)
continue
}
entries, err := parser(string(reference.Path), strings.NewReader(content))
entries, err := parser(location.RealPath, content)
if err != nil {
// TODO: should we fail? or only log?
log.Errorf("cataloger '%s' failed to parse entries (reference=%+v): %w", upstreamMatcher, reference, err)
log.Warnf("cataloger '%s' failed to parse entries (location=%+v): %+v", c.upstreamCataloger, location, err)
continue
}
for _, entry := range entries {
entry.FoundBy = upstreamMatcher
entry.Source = []file.Reference{reference}
entry.FoundBy = c.upstreamCataloger
entry.Locations = []source.Location{location}
packages = append(packages, entry)
}
+43 -42
View File
@@ -4,39 +4,56 @@ import (
"fmt"
"io"
"io/ioutil"
"strings"
"testing"
"github.com/anchore/stereoscope/pkg/file"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/source"
)
type testResolver struct {
contents map[file.Reference]string
type testResolverMock struct {
contents map[source.Location]io.ReadCloser
}
func newTestResolver() *testResolver {
return &testResolver{
contents: make(map[file.Reference]string),
func newTestResolver() *testResolverMock {
return &testResolverMock{
contents: make(map[source.Location]io.ReadCloser),
}
}
func (r *testResolver) FilesByPath(paths ...file.Path) ([]file.Reference, error) {
results := make([]file.Reference, len(paths))
func (r testResolverMock) HasPath(path string) bool {
panic("not implemented")
}
func (r *testResolverMock) FileContentsByLocation(_ source.Location) (io.ReadCloser, error) {
return nil, fmt.Errorf("not implemented")
}
func (r *testResolverMock) MultipleFileContentsByLocation([]source.Location) (map[source.Location]io.ReadCloser, error) {
return r.contents, nil
}
func (r *testResolverMock) FilesByPath(paths ...string) ([]source.Location, error) {
results := make([]source.Location, len(paths))
for idx, p := range paths {
results[idx] = file.NewFileReference(p)
r.contents[results[idx]] = fmt.Sprintf("%s file contents!", p)
results[idx] = source.NewLocation(p)
r.contents[results[idx]] = ioutil.NopCloser(strings.NewReader(fmt.Sprintf("%s file contents!", p)))
}
return results, nil
}
func (r *testResolver) FilesByGlob(patterns ...string) ([]file.Reference, error) {
func (r *testResolverMock) FilesByGlob(_ ...string) ([]source.Location, error) {
path := "/a-path.txt"
ref := file.NewFileReference(file.Path(path))
r.contents[ref] = fmt.Sprintf("%s file contents!", path)
return []file.Reference{ref}, nil
location := source.NewLocation(path)
r.contents[location] = ioutil.NopCloser(strings.NewReader(fmt.Sprintf("%s file contents!", path)))
return []source.Location{location}, nil
}
func (r *testResolverMock) RelativeFileByPath(_ source.Location, _ string) *source.Location {
panic(fmt.Errorf("not implemented"))
return nil
}
func parser(_ string, reader io.Reader) ([]pkg.Package, error) {
@@ -60,36 +77,20 @@ func TestGenericCataloger(t *testing.T) {
"/another-path.txt": parser,
"/last/path.txt": parser,
}
resolver := newTestResolver()
cataloger := NewGenericCataloger(pathParsers, globParsers)
selected := cataloger.SelectFiles(resolver)
if len(selected) != 3 {
t.Fatalf("unexpected selection length: %d", len(selected))
}
expectedSelection := internal.NewStringSetFromSlice([]string{"/last/path.txt", "/another-path.txt", "/a-path.txt"})
selectionByPath := make(map[string]file.Reference)
for _, s := range selected {
if !expectedSelection.Contains(string(s.Path)) {
t.Errorf("unexpected selection path: %+v", s.Path)
}
selectionByPath[string(s.Path)] = s
}
upstream := "some-other-cataloger"
expectedPkgs := make(map[file.Reference]pkg.Package)
for path, ref := range selectionByPath {
expectedPkgs[ref] = pkg.Package{
resolver := newTestResolver()
cataloger := NewGenericCataloger(pathParsers, globParsers, upstream)
expectedSelection := []string{"/last/path.txt", "/another-path.txt", "/a-path.txt"}
expectedPkgs := make(map[string]pkg.Package)
for _, path := range expectedSelection {
expectedPkgs[path] = pkg.Package{
FoundBy: upstream,
Source: []file.Reference{ref},
Name: fmt.Sprintf("%s file contents!", path),
}
}
actualPkgs, err := cataloger.Catalog(resolver.contents, upstream)
actualPkgs, err := cataloger.Catalog(resolver)
if err != nil {
t.Fatalf("cataloger catalog action failed: %+v", err)
}
@@ -99,8 +100,8 @@ func TestGenericCataloger(t *testing.T) {
}
for _, p := range actualPkgs {
ref := p.Source[0]
exP, ok := expectedPkgs[ref]
ref := p.Locations[0]
exP, ok := expectedPkgs[ref.RealPath]
if !ok {
t.Errorf("missing expected pkg: ref=%+v", ref)
continue
@@ -111,7 +112,7 @@ func TestGenericCataloger(t *testing.T) {
}
if exP.Name != p.Name {
t.Errorf("bad contents mapping: %+v", p.Source)
t.Errorf("bad contents mapping: %+v", p.Locations)
}
}
}
+118
View File
@@ -0,0 +1,118 @@
package cataloger
import (
"fmt"
"sort"
"strings"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/syft/pkg"
"github.com/facebookincubator/nvdtools/wfn"
)
// this is functionally equivalent to "*" and consistent with no input given (thus easier to test)
const any = ""
func newCPE(product, vendor, version, targetSW string) wfn.Attributes {
cpe := *(wfn.NewAttributesWithAny())
cpe.Part = "a"
cpe.Product = product
cpe.Vendor = vendor
cpe.Version = version
cpe.TargetSW = targetSW
return cpe
}
// generatePackageCPEs Create a list of CPEs, trying to guess the vendor, product tuple and setting TargetSoftware if possible
func generatePackageCPEs(p pkg.Package) []pkg.CPE {
targetSws := candidateTargetSoftwareAttrs(p)
vendors := candidateVendors(p)
products := candidateProducts(p)
keys := internal.NewStringSet()
cpes := make([]pkg.CPE, 0)
for _, product := range products {
for _, vendor := range append([]string{any}, vendors...) {
for _, targetSw := range append([]string{any}, targetSws...) {
// prevent duplicate entries...
key := fmt.Sprintf("%s|%s|%s|%s", product, vendor, p.Version, targetSw)
if keys.Contains(key) {
continue
}
keys.Add(key)
// add a new entry...
c := newCPE(product, vendor, p.Version, targetSw)
cpes = append(cpes, c)
}
}
}
sort.Sort(ByCPESpecificity(cpes))
return cpes
}
func candidateTargetSoftwareAttrs(p pkg.Package) []string {
// TODO: would be great to allow these to be overridden by user data/config
var targetSw []string
switch p.Language {
case pkg.Java:
targetSw = append(targetSw, "java", "maven")
case pkg.JavaScript:
targetSw = append(targetSw, "node.js", "nodejs")
case pkg.Ruby:
targetSw = append(targetSw, "ruby", "rails")
case pkg.Python:
targetSw = append(targetSw, "python")
}
if p.Type == pkg.JenkinsPluginPkg {
targetSw = append(targetSw, "jenkins", "cloudbees_jenkins")
}
return targetSw
}
func candidateVendors(p pkg.Package) []string {
vendors := candidateProducts(p)
switch p.Language {
case pkg.Python:
vendors = append(vendors, fmt.Sprintf("python-%s", p.Name))
case pkg.Java:
if p.MetadataType == pkg.JavaMetadataType {
if metadata, ok := p.Metadata.(pkg.JavaMetadata); ok && metadata.PomProperties != nil {
// derive the vendor from the groupID (e.g. org.sonatype.nexus --> sonatype)
if strings.HasPrefix(metadata.PomProperties.GroupID, "org.") || strings.HasPrefix(metadata.PomProperties.GroupID, "com.") {
fields := strings.Split(metadata.PomProperties.GroupID, ".")
if len(fields) >= 3 {
vendors = append(vendors, fields[1])
}
}
}
}
}
return vendors
}
func candidateProducts(p pkg.Package) []string {
var products = []string{p.Name}
switch p.Language {
case pkg.Java:
if p.MetadataType == pkg.JavaMetadataType {
if metadata, ok := p.Metadata.(pkg.JavaMetadata); ok && metadata.PomProperties != nil {
// derive the product from the groupID (e.g. org.sonatype.nexus --> nexus)
if strings.HasPrefix(metadata.PomProperties.GroupID, "org.") || strings.HasPrefix(metadata.PomProperties.GroupID, "com.") {
fields := strings.Split(metadata.PomProperties.GroupID, ".")
if len(fields) >= 3 {
products = append(products, fields[2])
}
}
}
}
default:
return products
}
return products
}
+31
View File
@@ -0,0 +1,31 @@
package cataloger
import "github.com/facebookincubator/nvdtools/wfn"
type ByCPESpecificity []wfn.Attributes
// Implementing sort.Interface
func (c ByCPESpecificity) Len() int { return len(c) }
func (c ByCPESpecificity) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
func (c ByCPESpecificity) Less(i, j int) bool {
return countSpecifiedFields(c[i]) > countSpecifiedFields(c[j])
}
func countSpecifiedFields(cpe wfn.Attributes) int {
checksForSpecifiedField := []func(cpe wfn.Attributes) bool{
func(cpe wfn.Attributes) bool { return cpe.Part != "" },
func(cpe wfn.Attributes) bool { return cpe.Product != "" },
func(cpe wfn.Attributes) bool { return cpe.Vendor != "" },
func(cpe wfn.Attributes) bool { return cpe.Version != "" },
func(cpe wfn.Attributes) bool { return cpe.TargetSW != "" },
}
count := 0
for _, fieldIsSpecified := range checksForSpecifiedField {
if fieldIsSpecified(cpe) {
count++
}
}
return count
}
+182
View File
@@ -0,0 +1,182 @@
package cataloger
import (
"sort"
"testing"
"github.com/scylladb/go-set/strset"
"github.com/scylladb/go-set"
"github.com/anchore/syft/syft/pkg"
)
func TestGenerate(t *testing.T) {
tests := []struct {
name string
p pkg.Package
expected []string
}{
{
name: "python language",
p: pkg.Package{
Name: "name",
Version: "3.2",
FoundBy: "some-analyzer",
Language: pkg.Python,
Type: pkg.DebPkg,
},
expected: []string{
"cpe:2.3:a:*:name:3.2:*:*:*:*:*:*:*",
"cpe:2.3:a:*:name:3.2:*:*:*:*:python:*:*",
"cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*",
"cpe:2.3:a:name:name:3.2:*:*:*:*:python:*:*",
"cpe:2.3:a:python-name:name:3.2:*:*:*:*:*:*:*",
"cpe:2.3:a:python-name:name:3.2:*:*:*:*:python:*:*",
},
},
{
name: "javascript language",
p: pkg.Package{
Name: "name",
Version: "3.2",
FoundBy: "some-analyzer",
Language: pkg.JavaScript,
Type: pkg.DebPkg,
},
expected: []string{
"cpe:2.3:a:*:name:3.2:*:*:*:*:*:*:*",
"cpe:2.3:a:*:name:3.2:*:*:*:*:node.js:*:*",
"cpe:2.3:a:*:name:3.2:*:*:*:*:nodejs:*:*",
"cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*",
"cpe:2.3:a:name:name:3.2:*:*:*:*:node.js:*:*",
"cpe:2.3:a:name:name:3.2:*:*:*:*:nodejs:*:*",
},
},
{
name: "ruby language",
p: pkg.Package{
Name: "name",
Version: "3.2",
FoundBy: "some-analyzer",
Language: pkg.Ruby,
Type: pkg.DebPkg,
},
expected: []string{
"cpe:2.3:a:*:name:3.2:*:*:*:*:*:*:*",
"cpe:2.3:a:*:name:3.2:*:*:*:*:ruby:*:*",
"cpe:2.3:a:*:name:3.2:*:*:*:*:rails:*:*",
"cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*",
"cpe:2.3:a:name:name:3.2:*:*:*:*:ruby:*:*",
"cpe:2.3:a:name:name:3.2:*:*:*:*:rails:*:*",
},
},
{
name: "java language",
p: pkg.Package{
Name: "name",
Version: "3.2",
FoundBy: "some-analyzer",
Language: pkg.Java,
Type: pkg.DebPkg,
},
expected: []string{
"cpe:2.3:a:*:name:3.2:*:*:*:*:*:*:*",
"cpe:2.3:a:*:name:3.2:*:*:*:*:java:*:*",
"cpe:2.3:a:*:name:3.2:*:*:*:*:maven:*:*",
"cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*",
"cpe:2.3:a:name:name:3.2:*:*:*:*:java:*:*",
"cpe:2.3:a:name:name:3.2:*:*:*:*:maven:*:*",
},
},
{
name: "java language with groupID",
p: pkg.Package{
Name: "name",
Version: "3.2",
FoundBy: "some-analyzer",
Language: pkg.Java,
Type: pkg.JavaPkg,
MetadataType: pkg.JavaMetadataType,
Metadata: pkg.JavaMetadata{
PomProperties: &pkg.PomProperties{
GroupID: "org.sonatype.nexus",
},
},
},
expected: []string{
"cpe:2.3:a:*:name:3.2:*:*:*:*:*:*:*",
"cpe:2.3:a:*:name:3.2:*:*:*:*:java:*:*",
"cpe:2.3:a:*:name:3.2:*:*:*:*:maven:*:*",
"cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*",
"cpe:2.3:a:name:name:3.2:*:*:*:*:java:*:*",
"cpe:2.3:a:name:name:3.2:*:*:*:*:maven:*:*",
"cpe:2.3:a:sonatype:name:3.2:*:*:*:*:*:*:*",
"cpe:2.3:a:sonatype:name:3.2:*:*:*:*:java:*:*",
"cpe:2.3:a:sonatype:name:3.2:*:*:*:*:maven:*:*",
"cpe:2.3:a:*:nexus:3.2:*:*:*:*:*:*:*",
"cpe:2.3:a:*:nexus:3.2:*:*:*:*:java:*:*",
"cpe:2.3:a:*:nexus:3.2:*:*:*:*:maven:*:*",
"cpe:2.3:a:sonatype:nexus:3.2:*:*:*:*:*:*:*",
"cpe:2.3:a:sonatype:nexus:3.2:*:*:*:*:java:*:*",
"cpe:2.3:a:sonatype:nexus:3.2:*:*:*:*:maven:*:*",
"cpe:2.3:a:nexus:name:3.2:*:*:*:*:*:*:*",
"cpe:2.3:a:nexus:name:3.2:*:*:*:*:java:*:*",
"cpe:2.3:a:nexus:name:3.2:*:*:*:*:maven:*:*",
"cpe:2.3:a:name:nexus:3.2:*:*:*:*:*:*:*",
"cpe:2.3:a:name:nexus:3.2:*:*:*:*:java:*:*",
"cpe:2.3:a:name:nexus:3.2:*:*:*:*:maven:*:*",
"cpe:2.3:a:nexus:nexus:3.2:*:*:*:*:*:*:*",
"cpe:2.3:a:nexus:nexus:3.2:*:*:*:*:java:*:*",
"cpe:2.3:a:nexus:nexus:3.2:*:*:*:*:maven:*:*",
},
},
{
name: "jenkins package",
p: pkg.Package{
Name: "name",
Version: "3.2",
FoundBy: "some-analyzer",
Language: pkg.Java,
Type: pkg.JenkinsPluginPkg,
},
expected: []string{
"cpe:2.3:a:*:name:3.2:*:*:*:*:*:*:*",
"cpe:2.3:a:*:name:3.2:*:*:*:*:java:*:*",
"cpe:2.3:a:*:name:3.2:*:*:*:*:maven:*:*",
"cpe:2.3:a:*:name:3.2:*:*:*:*:jenkins:*:*",
"cpe:2.3:a:*:name:3.2:*:*:*:*:cloudbees_jenkins:*:*",
"cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*",
"cpe:2.3:a:name:name:3.2:*:*:*:*:java:*:*",
"cpe:2.3:a:name:name:3.2:*:*:*:*:maven:*:*",
"cpe:2.3:a:name:name:3.2:*:*:*:*:jenkins:*:*",
"cpe:2.3:a:name:name:3.2:*:*:*:*:cloudbees_jenkins:*:*",
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
actual := generatePackageCPEs(test.p)
expectedCpeSet := set.NewStringSet(test.expected...)
actualCpeSet := set.NewStringSet()
for _, a := range actual {
actualCpeSet.Add(a.BindToFmtString())
}
extra := strset.Difference(expectedCpeSet, actualCpeSet).List()
sort.Strings(extra)
for _, d := range extra {
t.Errorf("extra CPE: %+v", d)
}
missing := strset.Difference(actualCpeSet, expectedCpeSet).List()
sort.Strings(missing)
for _, d := range missing {
t.Errorf("missing CPE: %+v", d)
}
})
}
}
+188
View File
@@ -0,0 +1,188 @@
/*
Package dpkg provides a concrete Cataloger implementation for Debian package DB status files.
*/
package deb
import (
"fmt"
"io"
"path"
"path/filepath"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/source"
)
const (
dpkgStatusGlob = "**/var/lib/dpkg/status"
md5sumsExt = ".md5sums"
docsPath = "/usr/share/doc"
)
type Cataloger struct{}
// NewDpkgdbCataloger returns a new Deb package cataloger object.
func NewDpkgdbCataloger() *Cataloger {
return &Cataloger{}
}
// Name returns a string that uniquely describes a cataloger
func (c *Cataloger) Name() string {
return "dpkgdb-cataloger"
}
// Catalog is given an object to resolve file references and content, this function returns any discovered Packages after analyzing dpkg support files.
// nolint:funlen
func (c *Cataloger) Catalog(resolver source.Resolver) ([]pkg.Package, error) {
dbFileMatches, err := resolver.FilesByGlob(dpkgStatusGlob)
if err != nil {
return nil, fmt.Errorf("failed to find dpkg status files's by glob: %w", err)
}
var pkgs []pkg.Package
for _, dbLocation := range dbFileMatches {
dbContents, err := resolver.FileContentsByLocation(dbLocation)
if err != nil {
return nil, err
}
pkgs, err = parseDpkgStatus(dbContents)
if err != nil {
return nil, fmt.Errorf("unable to catalog dpkg package=%+v: %w", dbLocation.RealPath, err)
}
md5ContentsByName, md5RefsByName, err := fetchMd5Contents(resolver, dbLocation, pkgs)
if err != nil {
return nil, fmt.Errorf("unable to find dpkg md5 contents: %w", err)
}
copyrightContentsByName, copyrightLocationByName, err := fetchCopyrightContents(resolver, dbLocation, pkgs)
if err != nil {
return nil, fmt.Errorf("unable to find dpkg copyright contents: %w", err)
}
for i := range pkgs {
p := &pkgs[i]
p.FoundBy = c.Name()
p.Locations = []source.Location{dbLocation}
metadata := p.Metadata.(pkg.DpkgMetadata)
if md5Reader, ok := md5ContentsByName[md5Key(*p)]; ok {
// attach the file list
metadata.Files = parseDpkgMD5Info(md5Reader)
// keep a record of the file where this was discovered
if ref, ok := md5RefsByName[md5Key(*p)]; ok {
p.Locations = append(p.Locations, ref)
}
} else {
// ensure the file list is an empty collection (not nil)
metadata.Files = make([]pkg.DpkgFileRecord, 0)
}
// persist alterations
p.Metadata = metadata
copyrightReader, ok := copyrightContentsByName[p.Name]
if ok {
// attach the licenses
p.Licenses = parseLicensesFromCopyright(copyrightReader)
// keep a record of the file where this was discovered
if ref, ok := copyrightLocationByName[p.Name]; ok {
p.Locations = append(p.Locations, ref)
}
}
}
}
return pkgs, nil
}
func fetchMd5Contents(resolver source.Resolver, dbLocation source.Location, pkgs []pkg.Package) (map[string]io.Reader, map[string]source.Location, error) {
// fetch all MD5 file contents. This approach is more efficient than fetching each MD5 file one at a time
var md5FileMatches []source.Location
var nameByRef = make(map[source.Location]string)
parentPath := filepath.Dir(dbLocation.RealPath)
for _, p := range pkgs {
// look for /var/lib/dpkg/info/NAME:ARCH.md5sums
name := md5Key(p)
md5SumLocation := resolver.RelativeFileByPath(dbLocation, path.Join(parentPath, "info", name+md5sumsExt))
if md5SumLocation == nil {
// the most specific key did not work, fallback to just the name
// look for /var/lib/dpkg/info/NAME.md5sums
md5SumLocation = resolver.RelativeFileByPath(dbLocation, path.Join(parentPath, "info", p.Name+md5sumsExt))
}
// we should have at least one reference
if md5SumLocation != nil {
md5FileMatches = append(md5FileMatches, *md5SumLocation)
nameByRef[*md5SumLocation] = name
}
}
// fetch the md5 contents
md5ContentsByLocation, err := resolver.MultipleFileContentsByLocation(md5FileMatches)
if err != nil {
return nil, nil, err
}
// organize content results and refs by a combination of name and architecture
var contentsByName = make(map[string]io.Reader)
var locationByName = make(map[string]source.Location)
for location, contents := range md5ContentsByLocation {
name := nameByRef[location]
contentsByName[name] = contents
locationByName[name] = location
}
return contentsByName, locationByName, nil
}
func fetchCopyrightContents(resolver source.Resolver, dbLocation source.Location, pkgs []pkg.Package) (map[string]io.Reader, map[string]source.Location, error) {
// fetch all copyright file contents. This approach is more efficient than fetching each copyright file one at a time
var copyrightFileMatches []source.Location
var nameByLocation = make(map[source.Location]string)
for _, p := range pkgs {
// look for /usr/share/docs/NAME/copyright files
name := p.Name
copyrightPath := path.Join(docsPath, name, "copyright")
copyrightLocation := resolver.RelativeFileByPath(dbLocation, copyrightPath)
// we may not have a copyright file for each package, ignore missing files
if copyrightLocation != nil {
copyrightFileMatches = append(copyrightFileMatches, *copyrightLocation)
nameByLocation[*copyrightLocation] = name
}
}
// fetch the copyright contents
copyrightContentsByLocation, err := resolver.MultipleFileContentsByLocation(copyrightFileMatches)
if err != nil {
return nil, nil, err
}
// organize content results and refs by package name
var contentsByName = make(map[string]io.Reader)
var refsByName = make(map[string]source.Location)
for location, contents := range copyrightContentsByLocation {
name := nameByLocation[location]
contentsByName[name] = contents
refsByName[name] = location
}
return contentsByName, refsByName, nil
}
func md5Key(p pkg.Package) string {
metadata := p.Metadata.(pkg.DpkgMetadata)
contentKey := p.Name
if metadata.Architecture != "" && metadata.Architecture != "all" {
contentKey = contentKey + ":" + metadata.Architecture
}
return contentKey
}
+99
View File
@@ -0,0 +1,99 @@
package deb
import (
"testing"
"github.com/anchore/stereoscope/pkg/imagetest"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/source"
"github.com/go-test/deep"
)
func TestDpkgCataloger(t *testing.T) {
tests := []struct {
name string
sources map[string][]string
expected []pkg.Package
}{
{
name: "go-case",
sources: map[string][]string{
"libpam-runtime": {"/var/lib/dpkg/status", "/var/lib/dpkg/info/libpam-runtime.md5sums", "/usr/share/doc/libpam-runtime/copyright"},
},
expected: []pkg.Package{
{
Name: "libpam-runtime",
Version: "1.1.8-3.6",
FoundBy: "dpkgdb-cataloger",
Licenses: []string{"GPL-2", "LGPL-2.1"},
Type: pkg.DebPkg,
MetadataType: pkg.DpkgMetadataType,
Metadata: pkg.DpkgMetadata{
Package: "libpam-runtime",
Source: "pam",
Version: "1.1.8-3.6",
Architecture: "all",
Maintainer: "Steve Langasek <vorlon@debian.org>",
InstalledSize: 1016,
Files: []pkg.DpkgFileRecord{
{Path: "/lib/x86_64-linux-gnu/libz.so.1.2.11", MD5: "55f905631797551d4d936a34c7e73474"},
{Path: "/usr/share/doc/zlib1g/changelog.Debian.gz", MD5: "cede84bda30d2380217f97753c8ccf3a"},
{Path: "/usr/share/doc/zlib1g/changelog.gz", MD5: "f3c9dafa6da7992c47328b4464f6d122"},
{Path: "/usr/share/doc/zlib1g/copyright", MD5: "a4fae96070439a5209a62ae5b8017ab2"},
},
},
},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
img, cleanup := imagetest.GetFixtureImage(t, "docker-archive", "image-dpkg")
defer cleanup()
s, err := source.NewFromImage(img, source.SquashedScope, "")
if err != nil {
t.Fatal(err)
}
c := NewDpkgdbCataloger()
actual, err := c.Catalog(s.Resolver)
if err != nil {
t.Fatalf("failed to catalog: %+v", err)
}
if len(actual) != len(test.expected) {
for _, a := range actual {
t.Logf(" %+v", a)
}
t.Fatalf("unexpected package count: %d!=%d", len(actual), len(test.expected))
}
// test sources...
for idx := range actual {
a := &actual[idx]
// we will test the sources separately
var sourcesList = make([]string, len(a.Locations))
for i, s := range a.Locations {
sourcesList[i] = s.RealPath
}
a.Locations = nil
for _, d := range deep.Equal(sourcesList, test.sources[a.Name]) {
t.Errorf("diff: %+v", d)
}
}
// test remaining fields...
for _, d := range deep.Equal(actual, test.expected) {
t.Errorf("diff: %+v", d)
}
})
}
}
+47
View File
@@ -0,0 +1,47 @@
package deb
import (
"bufio"
"io"
"regexp"
"sort"
"strings"
"github.com/anchore/syft/internal"
)
// For more information see: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/#license-syntax
var licensePattern = regexp.MustCompile(`^License: (?P<license>\S*)`)
func parseLicensesFromCopyright(reader io.Reader) []string {
findings := internal.NewStringSet()
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
line := scanner.Text()
matchesByGroup := internal.MatchCaptureGroups(licensePattern, line)
if len(matchesByGroup) > 0 {
candidate, ok := matchesByGroup["license"]
if !ok {
continue
}
candidate = strings.TrimSpace(candidate)
if strings.Contains(candidate, " or ") || strings.Contains(candidate, " and ") {
// this is a multi-license summary, ignore this as other recurrent license lines should cover this
continue
}
if candidate != "" && strings.ToLower(candidate) != "none" {
findings.Add(candidate)
}
}
}
results := findings.ToSlice()
sort.Strings(results)
return results
}
@@ -0,0 +1,63 @@
package deb
import (
"os"
"testing"
"github.com/go-test/deep"
)
func TestParseLicensesFromCopyright(t *testing.T) {
tests := []struct {
fixture string
expected []string
}{
{
fixture: "test-fixtures/copyright/trilicense",
expected: []string{"GPL-2", "LGPL-2.1", "MPL-1.1"},
},
{
fixture: "test-fixtures/copyright/liblzma5",
expected: []string{"Autoconf", "GPL-2", "GPL-2+", "LGPL-2.1+", "PD", "PD-debian", "config-h", "noderivs", "permissive-fsf", "permissive-nowarranty", "probably-PD"},
},
{
fixture: "test-fixtures/copyright/libaudit-common",
expected: []string{"GPL-2", "LGPL-2.1"},
},
{
fixture: "test-fixtures/copyright/python",
// note: this should not capture #, Permission, This, see ... however it's not clear how to fix this (this is probably good enough)
expected: []string{"#", "Apache", "Apache-2", "Apache-2.0", "Expat", "ISC", "LGPL-2.1+", "PSF-2", "Permission", "Python", "This", "see"},
},
}
for _, test := range tests {
t.Run(test.fixture, func(t *testing.T) {
file, err := os.Open(test.fixture)
if err != nil {
t.Fatal("Unable to read: ", err)
}
defer func() {
err := file.Close()
if err != nil {
t.Fatal("closing file failed:", err)
}
}()
actual := parseLicensesFromCopyright(file)
if len(actual) != len(test.expected) {
for _, a := range actual {
t.Logf(" %+v", a)
}
t.Fatalf("unexpected package count: %d!=%d", len(actual), len(test.expected))
}
diffs := deep.Equal(actual, test.expected)
for _, d := range diffs {
t.Errorf("diff: %+v", d)
}
})
}
}
@@ -0,0 +1,31 @@
package deb
import (
"bufio"
"io"
"strings"
"github.com/anchore/syft/syft/pkg"
)
func parseDpkgMD5Info(reader io.Reader) []pkg.DpkgFileRecord {
// we must preallocate to ensure the resulting struct does not have null
var findings = make([]pkg.DpkgFileRecord, 0)
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
line := scanner.Text()
fields := strings.SplitN(line, " ", 2)
if len(fields) == 2 {
path := strings.TrimSpace(fields[1])
if !strings.HasPrefix(path, "/") {
path = "/" + path
}
findings = append(findings, pkg.DpkgFileRecord{
Path: path,
MD5: strings.TrimSpace(fields[0]),
})
}
}
return findings
}
@@ -0,0 +1,57 @@
package deb
import (
"os"
"testing"
"github.com/go-test/deep"
"github.com/anchore/syft/syft/pkg"
)
func TestMD5SumInfoParsing(t *testing.T) {
tests := []struct {
fixture string
expected []pkg.DpkgFileRecord
}{
{
fixture: "test-fixtures/info/zlib1g.md5sums",
expected: []pkg.DpkgFileRecord{
{Path: "/lib/x86_64-linux-gnu/libz.so.1.2.11", MD5: "55f905631797551d4d936a34c7e73474"},
{Path: "/usr/share/doc/zlib1g/changelog.Debian.gz", MD5: "cede84bda30d2380217f97753c8ccf3a"},
{Path: "/usr/share/doc/zlib1g/changelog.gz", MD5: "f3c9dafa6da7992c47328b4464f6d122"},
{Path: "/usr/share/doc/zlib1g/copyright", MD5: "a4fae96070439a5209a62ae5b8017ab2"},
},
},
}
for _, test := range tests {
t.Run(test.fixture, func(t *testing.T) {
file, err := os.Open(test.fixture)
if err != nil {
t.Fatal("Unable to read: ", err)
}
defer func() {
err := file.Close()
if err != nil {
t.Fatal("closing file failed:", err)
}
}()
actual := parseDpkgMD5Info(file)
if len(actual) != len(test.expected) {
for _, a := range actual {
t.Logf(" %+v", a)
}
t.Fatalf("unexpected package count: %d!=%d", len(actual), len(test.expected))
}
diffs := deep.Equal(actual, test.expected)
for _, d := range diffs {
t.Errorf("diff: %+v", d)
}
})
}
}
+157
View File
@@ -0,0 +1,157 @@
package deb
import (
"bufio"
"errors"
"fmt"
"io"
"regexp"
"strconv"
"strings"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/syft/pkg"
"github.com/mitchellh/mapstructure"
)
var errEndOfPackages = fmt.Errorf("no more packages to read")
// parseDpkgStatus is a parser function for Debian DB status contents, returning all Debian packages listed.
func parseDpkgStatus(reader io.Reader) ([]pkg.Package, error) {
buffedReader := bufio.NewReader(reader)
var packages = make([]pkg.Package, 0)
continueProcessing := true
for continueProcessing {
entry, err := parseDpkgStatusEntry(buffedReader)
if err != nil {
if errors.Is(err, errEndOfPackages) {
continueProcessing = false
} else {
return nil, err
}
}
if entry.Package != "" {
packages = append(packages, pkg.Package{
Name: entry.Package,
Version: entry.Version,
Type: pkg.DebPkg,
MetadataType: pkg.DpkgMetadataType,
Metadata: entry,
})
}
}
return packages, nil
}
// parseDpkgStatusEntry returns an individual Dpkg entry, or returns errEndOfPackages if there are no more packages to parse from the reader.
// nolint:funlen
func parseDpkgStatusEntry(reader *bufio.Reader) (entry pkg.DpkgMetadata, err error) {
dpkgFields := make(map[string]interface{})
var retErr error
var key string
for {
line, err := reader.ReadString('\n')
if err != nil {
if err == io.EOF {
retErr = errEndOfPackages
break
}
return pkg.DpkgMetadata{}, err
}
line = strings.TrimRight(line, "\n")
// empty line indicates end of entry
if len(line) == 0 {
// if the entry has not started, keep parsing lines
if len(dpkgFields) == 0 {
continue
}
break
}
switch {
case strings.HasPrefix(line, " "):
// a field-body continuation
if len(key) == 0 {
return pkg.DpkgMetadata{}, fmt.Errorf("no match for continuation: line: '%s'", line)
}
val, ok := dpkgFields[key]
if !ok {
return pkg.DpkgMetadata{}, fmt.Errorf("no previous key exists, expecting: %s", key)
}
// concatenate onto previous value
val = fmt.Sprintf("%s\n %s", val, strings.TrimSpace(line))
dpkgFields[key] = val
default:
// parse a new key
var val interface{}
key, val, err = handleNewKeyValue(line)
if err != nil {
return pkg.DpkgMetadata{}, err
}
if _, ok := dpkgFields[key]; ok {
return pkg.DpkgMetadata{}, fmt.Errorf("duplicate key discovered: %s", key)
}
dpkgFields[key] = val
}
}
err = mapstructure.Decode(dpkgFields, &entry)
if err != nil {
return pkg.DpkgMetadata{}, err
}
name, version := extractSourceVersion(entry.Source)
if version != "" {
entry.SourceVersion = version
entry.Source = name
}
return entry, retErr
}
// match examples:
// "a-thing (1.2.3)" name="a-thing" version="1.2.3"
// "a-thing" name="a-thing" version=""
// "" name="" version=""
var sourceRegexp = regexp.MustCompile(`(?P<name>\S+)( \((?P<version>.*)\))?`)
// If the source entry string is of the form "<name> (<version>)" then parse and return the components, if
// of the "<name>" form, then return name and nil
func extractSourceVersion(source string) (string, string) {
// special handling for the Source field since it has formatted data
match := internal.MatchCaptureGroups(sourceRegexp, source)
return match["name"], match["version"]
}
// handleNewKeyValue parse a new key-value pair from the given unprocessed line
func handleNewKeyValue(line string) (string, interface{}, error) {
if i := strings.Index(line, ":"); i > 0 {
var key = strings.TrimSpace(line[0:i])
// mapstruct cant handle "-"
key = strings.ReplaceAll(key, "-", "")
val := strings.TrimSpace(line[i+1:])
// further processing of values based on the key that was discovered
switch key {
case "InstalledSize":
numVal, err := strconv.Atoi(val)
if err != nil {
return "", nil, fmt.Errorf("bad installed-size value=%q: %w", val, err)
}
return key, numVal, nil
default:
return key, val, nil
}
}
return "", nil, fmt.Errorf("cannot parse field from line: '%s'", line)
}
@@ -1,4 +1,4 @@
package dpkg
package deb
import (
"bufio"
@@ -24,17 +24,19 @@ func TestSinglePackage(t *testing.T) {
{
name: "Test Single Package",
expected: pkg.DpkgMetadata{
Package: "apt",
Source: "apt-dev",
Version: "1.8.2",
Architecture: "amd64",
Package: "apt",
Source: "apt-dev",
Version: "1.8.2",
Architecture: "amd64",
InstalledSize: 4064,
Maintainer: "APT Development Team <deity@lists.debian.org>",
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
file, err := os.Open("test-fixtures/single")
file, err := os.Open("test-fixtures/status/single")
if err != nil {
t.Fatal("Unable to read test_fixtures/single: ", err)
}
@@ -66,15 +68,19 @@ func TestMultiplePackages(t *testing.T) {
name: "Test Multiple Package",
expected: []pkg.DpkgMetadata{
{
Package: "tzdata",
Version: "2020a-0+deb10u1",
Source: "tzdata-dev",
Architecture: "all",
Package: "tzdata",
Version: "2020a-0+deb10u1",
Source: "tzdata-dev",
Architecture: "all",
InstalledSize: 3036,
Maintainer: "GNU Libc Maintainers <debian-glibc@lists.debian.org>",
},
{
Package: "util-linux",
Version: "2.33.1-0.1",
Architecture: "amd64",
Package: "util-linux",
Version: "2.33.1-0.1",
Architecture: "amd64",
InstalledSize: 4327,
Maintainer: "LaMont Jones <lamont@debian.org>",
},
},
},
@@ -82,7 +88,7 @@ func TestMultiplePackages(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
file, err := os.Open("test-fixtures/multiple")
file, err := os.Open("test-fixtures/status/multiple")
if err != nil {
t.Fatal("Unable to read: ", err)
}
@@ -93,7 +99,7 @@ func TestMultiplePackages(t *testing.T) {
}
}()
pkgs, err := parseDpkgStatus(file.Name(), file)
pkgs, err := parseDpkgStatus(file)
if err != nil {
t.Fatal("Unable to read file contents: ", err)
}
@@ -109,3 +115,43 @@ func TestMultiplePackages(t *testing.T) {
})
}
}
func TestSourceVersionExtract(t *testing.T) {
tests := []struct {
name string
input string
expected []string
}{
{
name: "name and version",
input: "test (1.2.3)",
expected: []string{"test", "1.2.3"},
},
{
name: "only name",
input: "test",
expected: []string{"test", ""},
},
{
name: "empty",
input: "",
expected: []string{"", ""},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
name, version := extractSourceVersion(test.input)
if name != test.expected[0] {
t.Errorf("mismatch name for %q : %q!=%q", test.input, name, test.expected[0])
}
if version != test.expected[1] {
t.Errorf("mismatch version for %q : %q!=%q", test.input, version, test.expected[1])
}
})
}
}
@@ -0,0 +1,43 @@
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: auditd
Source: https://people.redhat.com/sgrubb/audit/
Files: *
Copyright: 2012-2016 Steve Grubb <sgrubb@redhat.com>
2006-2012 Rik Faith
License: GPL-2
Files: src/libev/*
Copyright: 2007-2009 Marc Alexamder Lehmann
License: GPL-2
Files: lib/*
Copyright: 2005-2008 Steve Grubb <sgrubb@redhat.com>
License: LGPL-2.1
The audit daemon's library libaudit.* is released under LGPL
so that it may be linked with 3rd party software.
.
On Debian systems, refer to /usr/share/common-licenses/LGPL-2.1
for the complete text of the GNU Lesser General Public License.
Files: debian/*
Copyright: 2007-2011 Philipp Matthias Hahn <pmhahn@debian.org>
2012-2016 Laurent Bigonville <bigon@debian.org>
License: GPL-2
License: GPL-2
This package is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2,
as published by the Free Software Foundation.
.
This package is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
.
You should have received a copy of the GNU General Public License
along with this package; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
.
On Debian systems, the complete text of the GNU General
Public License can be found in `/usr/share/common-licenses/GPL-1'.
@@ -0,0 +1,383 @@
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: XZ Utils
Upstream-Contact:
Lasse Collin <lasse.collin@tukaani.org>
http://tukaani.org/xz/lists.html
Source:
http://tukaani.org/xz
http://git.tukaani.org/xz.git
Comment:
XZ Utils is developed and maintained upstream by Lasse Collin. Major
portions are based on code by other authors; see AUTHORS for details.
Most of the source has been put into the public domain, but some files
have not (details below).
.
This file describes the source package. The binary packages contain
some files derived from other works: for example, images in the API
documentation come from Doxygen.
License:
Different licenses apply to different files in this package. Here
is a rough summary of which licenses apply to which parts of this
package (but check the individual files to be sure!):
.
- liblzma is in the public domain.
.
- xz, xzdec, and lzmadec command line tools are in the public
domain unless GNU getopt_long had to be compiled and linked
in from the lib directory. The getopt_long code is under
GNU LGPLv2.1+.
.
- The scripts to grep, diff, and view compressed files have been
adapted from gzip. These scripts and their documentation are
under GNU GPLv2+.
.
- All the documentation in the doc directory and most of the
XZ Utils specific documentation files in other directories
are in the public domain.
.
- Translated messages are in the public domain.
.
- The build system contains public domain files, and files that
are under GNU GPLv2+ or GNU GPLv3+. None of these files end up
in the binaries being built.
.
- Test files and test code in the tests directory, and debugging
utilities in the debug directory are in the public domain.
.
- The extra directory may contain public domain files, and files
that are under various free software licenses.
.
You can do whatever you want with the files that have been put into
the public domain. If you find public domain legally problematic,
take the previous sentence as a license grant. If you still find
the lack of copyright legally problematic, you have too many
lawyers.
.
As usual, this software is provided "as is", without any warranty.
.
If you copy significant amounts of public domain code from XZ Utils
into your project, acknowledging this somewhere in your software is
polite (especially if it is proprietary, non-free software), but
naturally it is not legally required. Here is an example of a good
notice to put into "about box" or into documentation:
.
This software includes code from XZ Utils <http://tukaani.org/xz/>.
.
The following license texts are included in the following files:
- COPYING.LGPLv2.1: GNU Lesser General Public License version 2.1
- COPYING.GPLv2: GNU General Public License version 2
- COPYING.GPLv3: GNU General Public License version 3
.
Note that the toolchain (compiler, linker etc.) may add some code
pieces that are copyrighted. Thus, it is possible that e.g. liblzma
binary wouldn't actually be in the public domain in its entirety
even though it contains no copyrighted code from the XZ Utils source
package.
.
If you have questions, don't hesitate to ask the author(s) for more
information.
Files: *
Copyright: 2006-2012, Lasse Collin
1999-2008, Igor Pavlov
2006, Ville Koskinen
1998, Steve Reid
2000, Wei Dai
2003, Kevin Springle
2009, Jonathan Nieder
2010, Anders F Björklund
License: PD
This file has been put in the public domain.
You can do whatever you want with this file.
Comment:
From: Lasse Collin <lasse.collin@tukaani.org>
To: Jonathan Nieder <jrnieder@gmail.com>
Subject: Re: XZ utils for Debian
Date: Sun, 19 Jul 2009 13:28:23 +0300
Message-Id: <200907191328.23816.lasse.collin@tukaani.org>
.
[...]
.
> AUTHORS, ChangeLog, COPYING, README, THANKS, TODO,
> dos/README, windows/README
.
COPYING says that most docs are in the public domain. Maybe that's not
clear enough, but on the other hand it looks a bit stupid to put
copyright information in tiny and relatively small docs like README.
.
I don't dare to say that _all_ XZ Utils specific docs are in the public
domain unless otherwise mentioned in the file. I'm including PDF files
generated by groff + ps2pdf, and some day I might include Doxygen-
generated HTML docs too. Those don't include any copyright notices, but
it seems likely that groff + ps2pdf or at least Doxygen put some
copyrighted content into the generated files.
Files: INSTALL NEWS PACKAGERS
windows/README-Windows.txt
windows/INSTALL-Windows.txt
Copyright: 2009-2010, Lasse Collin
License: probably-PD
See the note on AUTHORS, README, and so on above.
Files: src/scripts/* lib/* extra/scanlzma/scanlzma.c
Copyright: © 1993, Jean-loup Gailly
© 1989-1994, 1996-1999, 2001-2007, Free Software Foundation, Inc.
© 2006 Timo Lindfors
2005, Charles Levert
2005, 2009, Lasse Collin
2009, Andrew Dudman
Other-Authors: Paul Eggert, Ulrich Drepper
License: GPL-2+
Files: src/scripts/Makefile.am src/scripts/xzless.1
Copyright: 2009, Andrew Dudman
2009, Lasse Collin
License: PD
This file has been put in the public domain.
You can do whatever you want with this file.
Files: doc/examples/xz_pipe_comp.c doc/examples/xz_pipe_decomp.c
Copyright: 2010, Daniel Mealha Cabrita
License: PD
Not copyrighted -- provided to the public domain.
Files: lib/getopt.c lib/getopt1.c lib/getopt.in.h
Copyright: © 1987-2007 Free Software Foundation, Inc.
Other-Authors: Ulrich Drepper
License: LGPL-2.1+
Files: m4/getopt.m4 m4/posix-shell.m4
Copyright: © 2002-2006, 2008 Free Software Foundation, Inc.
© 2007-2008 Free Software Foundation, Inc.
Other-Authors: Bruno Haible, Paul Eggert
License: permissive-fsf
Files: m4/acx_pthread.m4
Copyright: © 2008, Steven G. Johnson <stevenj@alum.mit.edu>
License: Autoconf
Files: Doxyfile.in
Copyright: © 1997-2007 by Dimitri van Heesch
Origin: Doxygen 1.4.7
License: GPL-2
Files: src/liblzma/check/crc32_table_?e.h
src/liblzma/check/crc64_table_?e.h
src/liblzma/lzma/fastpos_table.c
src/liblzma/rangecoder/price_table.c
Copyright: none, automatically generated data
Generated-With:
src/liblzma/check/crc32_tablegen.c
src/liblzma/check/crc64_tablegen.c
src/liblzma/lzma/fastpos_tablegen.c
src/liblzma/rangecoder/price_tablegen.c
License: none
No copyright to license.
Files: .gitignore m4/.gitignore po/.gitignore po/LINGUAS po/POTFILES.in
Copyright: none; these are just short lists.
License: none
No copyright to license.
Files: tests/compress_prepared_bcj_*
Copyright: 2008-2009, Lasse Collin
Source-Code: tests/bcj_test.c
License: PD
This file has been put into the public domain.
You can do whatever you want with this file.
Comment:
changelog.gz (commit 975d8fd) explains:
.
Recreated the BCJ test files for x86 and SPARC. The old files
were linked with crt*.o, which are copyrighted, and thus the
old test files were not in the public domain as a whole. They
are freely distributable though, but it is better to be careful
and avoid including any copyrighted pieces in the test files.
The new files are just compiled and assembled object files,
and thus don't contain any copyrighted code.
Files: po/cs.po po/de.po po/fr.po
Copyright: 2010, Marek Černocký
2010, Andre Noll
2011, Adrien Nader
License: PD
This file is put in the public domain.
Files: po/it.po po/pl.po
Copyright: 2009, 2010, Gruppo traduzione italiano di Ubuntu-it
2010, Lorenzo De Liso
2009, 2010, 2011, Milo Casagrande
2011, Jakub Bogusz
License: PD
This file is in the public domain
Files: INSTALL.generic
Copyright: © 1994, 1995, 1996, 1999, 2000, 2001, 2002, 2004, 2005,
2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
License: permissive-nowarranty
Files: dos/config.h
Copyright: © 1992, 1993, 1994, 1999, 2000, 2001, 2002, 2005
Free Software Foundation, Inc.
2007-2010, Lasse Collin
Other-Authors: Roland McGrath, Akim Demaille, Paul Eggert,
David Mackenzie, Bruno Haible, and many others.
Origin: configure.ac from XZ Utils,
visibility.m4 serial 1 (gettext-0.15),
Autoconf 2.52g
License: config-h
configure.ac:
.
# Author: Lasse Collin
#
# This file has been put into the public domain.
# You can do whatever you want with this file.
.
visibility.m4:
.
dnl Copyright (C) 2005 Free Software Foundation, Inc.
dnl This file is free software; the Free Software Foundation
dnl gives unlimited permission to copy and/or distribute it,
dnl with or without modifications, as long as this notice is preserved.
.
dnl From Bruno Haible.
.
comments from Autoconf 2.52g:
.
# Copyright 1992, 1993, 1994, 1999, 2000, 2001, 2002
# Free Software Foundation, Inc.
.
[...]
.
# As a special exception, the Free Software Foundation gives unlimited
# permission to copy, distribute and modify the configure scripts that
# are the output of Autoconf. You need not follow the terms of the GNU
# General Public License when using or distributing such scripts, even
# though portions of the text of Autoconf appear in them. The GNU
# General Public License (GPL) does govern all other use of the material
# that constitutes the Autoconf program.
.
On Debian systems, the complete text of the GNU General Public
License version 2 can be found in ‘/usr/share/common-licenses/GPL-2’.
dos/config.h was generated with autoheader, which tells Autoconf to
output a script to generate a config.h file and then runs it.
Files: po/Makevars
Origin: gettext-runtime/po/Makevars (gettext-0.12)
Copyright: © 2003 Free Software Foundation, Inc.
Authors: Bruno Haible
License: LGPL-2.1+
The gettext-runtime package is under the LGPL, see files intl/COPYING.LIB-2.0
and intl/COPYING.LIB-2.1.
.
On Debian systems, the complete text of intl/COPYING.LIB-2.0 from
gettext-runtime 0.12 can be found in ‘/usr/share/common-licenses/LGPL-2’
and the text of intl/COPYING.LIB-2.1 can be found in
‘/usr/share/common-licenses/LGPL-2.1’.
.
po/Makevars consists mostly of helpful comments and does not contain a
copyright and license notice.
Files: COPYING.GPLv2 COPYING.GPLv3 COPYING.LGPLv2.1
Copyright: © 1989, 1991, 1999, 2007 Free Software Foundation, Inc.
License: noderivs
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Files: debian/*
Copyright: 2009-2012, Jonathan Nieder
License: PD-debian
The Debian packaging files are in the public domain.
You may freely use, modify, distribute, and relicense them.
License: LGPL-2.1+
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1, or (at your option)
any later version.
.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
.
On Debian systems, the complete text of the GNU Lesser General Public
License version 2.1 can be found in ‘/usr/share/common-licenses/LGPL-2.1’.
License: GPL-2
Permission to use, copy, modify, and distribute this software and its
documentation under the terms of the GNU General Public License is
hereby granted. No representations are made about the suitability of
this software for any purpose. It is provided "as is" without express
or implied warranty. See the GNU General Public License for more
details.
.
Documents produced by doxygen are derivative works derived from the
input used in their production; they are not affected by this license.
.
On Debian systems, the complete text of the version of the GNU General
Public License distributed with Doxygen can be found in
‘/usr/share/common-licenses/GPL-2’.
License: GPL-2+
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
.
On Debian systems, the complete text of the GNU General Public License
version 2 can be found in ‘/usr/share/common-licenses/GPL-2’.
License: Autoconf
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
Public License for more details.
.
You should have received a copy of the GNU General Public License along
with this program. If not, see <http://www.gnu.org/licenses/>.
.
As a special exception, the respective Autoconf Macro's copyright owner
gives unlimited permission to copy, distribute and modify the configure
scripts that are the output of Autoconf when processing the Macro. You
need not follow the terms of the GNU General Public License when using
or distributing such scripts, even though portions of the text of the
Macro appear in them. The GNU General Public License (GPL) does govern
all other use of the material that constitutes the Autoconf Macro.
.
This special exception to the GPL applies to versions of the Autoconf
Macro released by the Autoconf Archive. When you make and distribute a
modified version of the Autoconf Macro, you may extend this special
exception to the GPL to apply to your modified version as well.
.
On Debian systems, the complete text of the GNU General Public
License version 3 can be found in ‘/usr/share/common-licenses/GPL-3’.
License: permissive-fsf
This file is free software; the Free Software Foundation
gives unlimited permission to copy and/or distribute it,
with or without modifications, as long as this notice is preserved.
License: permissive-nowarranty
Copying and distribution of this file, with or without modification,
are permitted in any medium without royalty provided the copyright
notice and this notice are preserved. This file is offered as-is,
without warranty of any kind.
@@ -0,0 +1,958 @@
This package was put together by Klee Dienes <klee@debian.org> from
sources from ftp.python.org:/pub/python, based on the Debianization by
the previous maintainers Bernd S. Brentrup <bsb@uni-muenster.de> and
Bruce Perens. Current maintainer is Matthias Klose <doko@debian.org>.
It was downloaded from http://python.org/
Copyright:
Upstream Author: Guido van Rossum <guido@cwi.nl> and others.
License:
The following text includes the Python license and licenses and
acknowledgements for incorporated software. The licenses can be read
in the HTML and texinfo versions of the documentation as well, after
installing the pythonx.y-doc package. Licenses for files not licensed
under the Python Licenses are found at the end of this file.
Python License
==============
A. HISTORY OF THE SOFTWARE
==========================
Python was created in the early 1990s by Guido van Rossum at Stichting
Mathematisch Centrum (CWI, see http://www.cwi.nl) in the Netherlands
as a successor of a language called ABC. Guido remains Python's
principal author, although it includes many contributions from others.
In 1995, Guido continued his work on Python at the Corporation for
National Research Initiatives (CNRI, see http://www.cnri.reston.va.us)
in Reston, Virginia where he released several versions of the
software.
In May 2000, Guido and the Python core development team moved to
BeOpen.com to form the BeOpen PythonLabs team. In October of the same
year, the PythonLabs team moved to Digital Creations (now Zope
Corporation, see http://www.zope.com). In 2001, the Python Software
Foundation (PSF, see http://www.python.org/psf/) was formed, a
non-profit organization created specifically to own Python-related
Intellectual Property. Zope Corporation is a sponsoring member of
the PSF.
All Python releases are Open Source (see http://www.opensource.org for
the Open Source Definition). Historically, most, but not all, Python
releases have also been GPL-compatible; the table below summarizes
the various releases.
Release Derived Year Owner GPL-
from compatible? (1)
0.9.0 thru 1.2 1991-1995 CWI yes
1.3 thru 1.5.2 1.2 1995-1999 CNRI yes
1.6 1.5.2 2000 CNRI no
2.0 1.6 2000 BeOpen.com no
1.6.1 1.6 2001 CNRI yes (2)
2.1 2.0+1.6.1 2001 PSF no
2.0.1 2.0+1.6.1 2001 PSF yes
2.1.1 2.1+2.0.1 2001 PSF yes
2.2 2.1.1 2001 PSF yes
2.1.2 2.1.1 2002 PSF yes
2.1.3 2.1.2 2002 PSF yes
2.2.1 2.2 2002 PSF yes
2.2.2 2.2.1 2002 PSF yes
2.2.3 2.2.2 2003 PSF yes
2.3 2.2.2 2002-2003 PSF yes
2.3.1 2.3 2002-2003 PSF yes
2.3.2 2.3.1 2002-2003 PSF yes
2.3.3 2.3.2 2002-2003 PSF yes
2.3.4 2.3.3 2004 PSF yes
2.3.5 2.3.4 2005 PSF yes
2.4 2.3 2004 PSF yes
2.4.1 2.4 2005 PSF yes
2.4.2 2.4.1 2005 PSF yes
2.4.3 2.4.2 2006 PSF yes
2.5 2.4 2006 PSF yes
2.5.1 2.5 2007 PSF yes
2.5.2 2.5.1 2008 PSF yes
2.5.3 2.5.2 2008 PSF yes
2.6 2.5 2008 PSF yes
2.6.1 2.6 2008 PSF yes
2.6.2 2.6.1 2009 PSF yes
2.6.3 2.6.2 2009 PSF yes
2.6.4 2.6.3 2009 PSF yes
2.6.5 2.6.4 2010 PSF yes
2.7 2.6 2010 PSF yes
Footnotes:
(1) GPL-compatible doesn't mean that we're distributing Python under
the GPL. All Python licenses, unlike the GPL, let you distribute
a modified version without making your changes open source. The
GPL-compatible licenses make it possible to combine Python with
other software that is released under the GPL; the others don't.
(2) According to Richard Stallman, 1.6.1 is not GPL-compatible,
because its license has a choice of law clause. According to
CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1
is "not incompatible" with the GPL.
Thanks to the many outside volunteers who have worked under Guido's
direction to make these releases possible.
B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON
===============================================================
PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
--------------------------------------------
1. This LICENSE AGREEMENT is between the Python Software Foundation
("PSF"), and the Individual or Organization ("Licensee") accessing and
otherwise using this software ("Python") in source or binary form and
its associated documentation.
2. Subject to the terms and conditions of this License Agreement, PSF
hereby grants Licensee a nonexclusive, royalty-free, world-wide
license to reproduce, analyze, test, perform and/or display publicly,
prepare derivative works, distribute, and otherwise use Python
alone or in any derivative version, provided, however, that PSF's
License Agreement and PSF's notice of copyright, i.e., "Copyright (c)
2001, 2002, 2003, 2004, 2005, 2006, 2007 Python Software Foundation;
All Rights Reserved" are retained in Python alone or in any derivative
version prepared by Licensee.
3. In the event Licensee prepares a derivative work that is based on
or incorporates Python or any part thereof, and wants to make
the derivative work available to others as provided herein, then
Licensee hereby agrees to include in any such work a brief summary of
the changes made to Python.
4. PSF is making Python available to Licensee on an "AS IS"
basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
INFRINGE ANY THIRD PARTY RIGHTS.
5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
6. This License Agreement will automatically terminate upon a material
breach of its terms and conditions.
7. Nothing in this License Agreement shall be deemed to create any
relationship of agency, partnership, or joint venture between PSF and
Licensee. This License Agreement does not grant permission to use PSF
trademarks or trade name in a trademark sense to endorse or promote
products or services of Licensee, or any third party.
8. By copying, installing or otherwise using Python, Licensee
agrees to be bound by the terms and conditions of this License
Agreement.
BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0
-------------------------------------------
BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1
1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an
office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the
Individual or Organization ("Licensee") accessing and otherwise using
this software in source or binary form and its associated
documentation ("the Software").
2. Subject to the terms and conditions of this BeOpen Python License
Agreement, BeOpen hereby grants Licensee a non-exclusive,
royalty-free, world-wide license to reproduce, analyze, test, perform
and/or display publicly, prepare derivative works, distribute, and
otherwise use the Software alone or in any derivative version,
provided, however, that the BeOpen Python License is retained in the
Software, alone or in any derivative version prepared by Licensee.
3. BeOpen is making the Software available to Licensee on an "AS IS"
basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT
INFRINGE ANY THIRD PARTY RIGHTS.
4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE
SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS
AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY
DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
5. This License Agreement will automatically terminate upon a material
breach of its terms and conditions.
6. This License Agreement shall be governed by and interpreted in all
respects by the law of the State of California, excluding conflict of
law provisions. Nothing in this License Agreement shall be deemed to
create any relationship of agency, partnership, or joint venture
between BeOpen and Licensee. This License Agreement does not grant
permission to use BeOpen trademarks or trade names in a trademark
sense to endorse or promote products or services of Licensee, or any
third party. As an exception, the "BeOpen Python" logos available at
http://www.pythonlabs.com/logos.html may be used according to the
permissions granted on that web page.
7. By copying, installing or otherwise using the software, Licensee
agrees to be bound by the terms and conditions of this License
Agreement.
CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1
---------------------------------------
1. This LICENSE AGREEMENT is between the Corporation for National
Research Initiatives, having an office at 1895 Preston White Drive,
Reston, VA 20191 ("CNRI"), and the Individual or Organization
("Licensee") accessing and otherwise using Python 1.6.1 software in
source or binary form and its associated documentation.
2. Subject to the terms and conditions of this License Agreement, CNRI
hereby grants Licensee a nonexclusive, royalty-free, world-wide
license to reproduce, analyze, test, perform and/or display publicly,
prepare derivative works, distribute, and otherwise use Python 1.6.1
alone or in any derivative version, provided, however, that CNRI's
License Agreement and CNRI's notice of copyright, i.e., "Copyright (c)
1995-2001 Corporation for National Research Initiatives; All Rights
Reserved" are retained in Python 1.6.1 alone or in any derivative
version prepared by Licensee. Alternately, in lieu of CNRI's License
Agreement, Licensee may substitute the following text (omitting the
quotes): "Python 1.6.1 is made available subject to the terms and
conditions in CNRI's License Agreement. This Agreement together with
Python 1.6.1 may be located on the Internet using the following
unique, persistent identifier (known as a handle): 1895.22/1013. This
Agreement may also be obtained from a proxy server on the Internet
using the following URL: http://hdl.handle.net/1895.22/1013".
3. In the event Licensee prepares a derivative work that is based on
or incorporates Python 1.6.1 or any part thereof, and wants to make
the derivative work available to others as provided herein, then
Licensee hereby agrees to include in any such work a brief summary of
the changes made to Python 1.6.1.
4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS"
basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT
INFRINGE ANY THIRD PARTY RIGHTS.
5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1,
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
6. This License Agreement will automatically terminate upon a material
breach of its terms and conditions.
7. This License Agreement shall be governed by the federal
intellectual property law of the United States, including without
limitation the federal copyright law, and, to the extent such
U.S. federal law does not apply, by the law of the Commonwealth of
Virginia, excluding Virginia's conflict of law provisions.
Notwithstanding the foregoing, with regard to derivative works based
on Python 1.6.1 that incorporate non-separable material that was
previously distributed under the GNU General Public License (GPL), the
law of the Commonwealth of Virginia shall govern this License
Agreement only as to issues arising under or with respect to
Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this
License Agreement shall be deemed to create any relationship of
agency, partnership, or joint venture between CNRI and Licensee. This
License Agreement does not grant permission to use CNRI trademarks or
trade name in a trademark sense to endorse or promote products or
services of Licensee, or any third party.
8. By clicking on the "ACCEPT" button where indicated, or by copying,
installing or otherwise using Python 1.6.1, Licensee agrees to be
bound by the terms and conditions of this License Agreement.
ACCEPT
CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2
--------------------------------------------------
Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam,
The Netherlands. All rights reserved.
Permission to use, copy, modify, and distribute this software and its
documentation for any purpose and without fee is hereby granted,
provided that the above copyright notice appear in all copies and that
both that copyright notice and this permission notice appear in
supporting documentation, and that the name of Stichting Mathematisch
Centrum or CWI not be used in advertising or publicity pertaining to
distribution of the software without specific, written prior
permission.
STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO
THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE
FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
Licenses and Acknowledgements for Incorporated Software
=======================================================
Mersenne Twister
----------------
The `_random' module includes code based on a download from
`http://www.math.keio.ac.jp/~matumoto/MT2002/emt19937ar.html'. The
following are the verbatim comments from the original code:
A C-program for MT19937, with initialization improved 2002/1/26.
Coded by Takuji Nishimura and Makoto Matsumoto.
Before using, initialize the state by using init_genrand(seed)
or init_by_array(init_key, key_length).
Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura,
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. The names of its contributors may not be used to endorse or promote
products derived from this software without specific prior written
permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Any feedback is very welcome.
http://www.math.keio.ac.jp/matumoto/emt.html
email: matumoto@math.keio.ac.jp
Sockets
-------
The `socket' module uses the functions, `getaddrinfo', and
`getnameinfo', which are coded in separate source files from the WIDE
Project, `http://www.wide.ad.jp/about/index.html'.
Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the project nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
GAI_ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
FOR GAI_ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON GAI_ANY THEORY OF LIABILITY, WHETHER
IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN GAI_ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
OF THE POSSIBILITY OF SUCH DAMAGE.
Floating point exception control
--------------------------------
The source for the `fpectl' module includes the following notice:
---------------------------------------------------------------------
/ Copyright (c) 1996. \
| The Regents of the University of California. |
| All rights reserved. |
| |
| Permission to use, copy, modify, and distribute this software for |
| any purpose without fee is hereby granted, provided that this en- |
| tire notice is included in all copies of any software which is or |
| includes a copy or modification of this software and in all |
| copies of the supporting documentation for such software. |
| |
| This work was produced at the University of California, Lawrence |
| Livermore National Laboratory under contract no. W-7405-ENG-48 |
| between the U.S. Department of Energy and The Regents of the |
| University of California for the operation of UC LLNL. |
| |
| DISCLAIMER |
| |
| This software was prepared as an account of work sponsored by an |
| agency of the United States Government. Neither the United States |
| Government nor the University of California nor any of their em- |
| ployees, makes any warranty, express or implied, or assumes any |
| liability or responsibility for the accuracy, completeness, or |
| usefulness of any information, apparatus, product, or process |
| disclosed, or represents that its use would not infringe |
| privately-owned rights. Reference herein to any specific commer- |
| cial products, process, or service by trade name, trademark, |
| manufacturer, or otherwise, does not necessarily constitute or |
| imply its endorsement, recommendation, or favoring by the United |
| States Government or the University of California. The views and |
| opinions of authors expressed herein do not necessarily state or |
| reflect those of the United States Government or the University |
| of California, and shall not be used for advertising or product |
\ endorsement purposes. /
---------------------------------------------------------------------
Cookie management
-----------------
The `Cookie' module contains the following notice:
Copyright 2000 by Timothy O'Malley <timo@alum.mit.edu>
All Rights Reserved
Permission to use, copy, modify, and distribute this software
and its documentation for any purpose and without fee is hereby
granted, provided that the above copyright notice appear in all
copies and that both that copyright notice and this permission
notice appear in supporting documentation, and that the name of
Timothy O'Malley not be used in advertising or publicity
pertaining to distribution of the software without specific, written
prior permission.
Timothy O'Malley DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS, IN NO EVENT SHALL Timothy O'Malley BE LIABLE FOR
ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
Execution tracing
-----------------
The `trace' module contains the following notice:
portions copyright 2001, Autonomous Zones Industries, Inc., all rights...
err... reserved and offered to the public under the terms of the
Python 2.2 license.
Author: Zooko O'Whielacronx
http://zooko.com/
mailto:zooko@zooko.com
Copyright 2000, Mojam Media, Inc., all rights reserved.
Author: Skip Montanaro
Copyright 1999, Bioreason, Inc., all rights reserved.
Author: Andrew Dalke
Copyright 1995-1997, Automatrix, Inc., all rights reserved.
Author: Skip Montanaro
Copyright 1991-1995, Stichting Mathematisch Centrum, all rights reserved.
Permission to use, copy, modify, and distribute this Python software and
its associated documentation for any purpose without fee is hereby
granted, provided that the above copyright notice appears in all copies,
and that both that copyright notice and this permission notice appear in
supporting documentation, and that the name of neither Automatrix,
Bioreason or Mojam Media be used in advertising or publicity pertaining
to distribution of the software without specific, written prior
permission.
UUencode and UUdecode functions
-------------------------------
The `uu' module contains the following notice:
Copyright 1994 by Lance Ellinghouse
Cathedral City, California Republic, United States of America.
All Rights Reserved
Permission to use, copy, modify, and distribute this software and its
documentation for any purpose and without fee is hereby granted,
provided that the above copyright notice appear in all copies and that
both that copyright notice and this permission notice appear in
supporting documentation, and that the name of Lance Ellinghouse
not be used in advertising or publicity pertaining to distribution
of the software without specific, written prior permission.
LANCE ELLINGHOUSE DISCLAIMS ALL WARRANTIES WITH REGARD TO
THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS, IN NO EVENT SHALL LANCE ELLINGHOUSE CENTRUM BE LIABLE
FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
Modified by Jack Jansen, CWI, July 1995:
- Use binascii module to do the actual line-by-line conversion
between ascii and binary. This results in a 1000-fold speedup. The C
version is still 5 times faster, though.
- Arguments more compliant with python standard
XML Remote Procedure Calls
--------------------------
The `xmlrpclib' module contains the following notice:
The XML-RPC client interface is
Copyright (c) 1999-2002 by Secret Labs AB
Copyright (c) 1999-2002 by Fredrik Lundh
By obtaining, using, and/or copying this software and/or its
associated documentation, you agree that you have read, understood,
and will comply with the following terms and conditions:
Permission to use, copy, modify, and distribute this software and
its associated documentation for any purpose and without fee is
hereby granted, provided that the above copyright notice appears in
all copies, and that both that copyright notice and this permission
notice appear in supporting documentation, and that the name of
Secret Labs AB or the author not be used in advertising or publicity
pertaining to distribution of the software without specific, written
prior permission.
SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT-
ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR
BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
OF THIS SOFTWARE.
Licenses for Software linked to
===============================
Note that the choice of GPL compatibility outlined above doesn't extend
to modules linked to particular libraries, since they change the
effective License of the module binary.
GNU Readline
------------
The 'readline' module makes use of GNU Readline.
The GNU Readline Library is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2, or (at
your option) any later version.
On Debian systems, you can find the complete statement in
/usr/share/doc/readline-common/copyright'. A copy of the GNU General
Public License is available in /usr/share/common-licenses/GPL-2'.
OpenSSL
-------
The '_ssl' module makes use of OpenSSL.
The OpenSSL toolkit stays under a dual license, i.e. both the
conditions of the OpenSSL License and the original SSLeay license
apply to the toolkit. Actually both licenses are BSD-style Open
Source licenses. Note that both licenses are incompatible with
the GPL.
On Debian systems, you can find the complete license text in
/usr/share/doc/openssl/copyright'.
Files with other licenses than the Python License
-------------------------------------------------
Files: Lib/profile.py Lib/pstats.py
Copyright: Disney Enterprises, Inc. All Rights Reserved.
License: # Licensed to PSF under a Contributor Agreement
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
either express or implied. See the License for the specific language
overning permissions and limitations under the License.
On Debian systems, the Apache 2.0 license can be found in
/usr/share/common-licenses/Apache-2.0.
Files: Modules/zlib/*
Copyright: (C) 1995-2010 Jean-loup Gailly and Mark Adler
License: This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
Jean-loup Gailly Mark Adler
jloup@gzip.org madler@alumni.caltech.edu
If you use the zlib library in a product, we would appreciate *not* receiving
lengthy legal documents to sign. The sources are provided for free but without
warranty of any kind. The library has been entirely written by Jean-loup
Gailly and Mark Adler; it does not include third-party code.
Files: Modules/_ctypes/libffi/*
Copyright: Copyright (C) 1996-2009 Red Hat, Inc and others.
License: Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
``Software''), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
Documentation:
Permission is granted to copy, distribute and/or modify this document
under the terms of the GNU General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version. A copy of the license is included in the
section entitled ``GNU General Public License''.
Files: Modules/expat/*
Copyright: Copyright (c) 1998, 1999, 2000 Thai Open Source Software Center Ltd
and Clark Cooper
Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006 Expat maintainers
License: Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Files: Misc/python-mode.el
Copyright: Copyright (C) 1992,1993,1994 Tim Peters
License: This software is provided as-is, without express or implied
warranty. Permission to use, copy, modify, distribute or sell this
software, without fee, for any purpose and by any individual or
organization, is hereby granted, provided that the above copyright
notice and this paragraph appear in all copies.
Files: PC/_subprocess.c
Copyright: Copyright (c) 2004 by Fredrik Lundh <fredrik@pythonware.com>
Copyright (c) 2004 by Secret Labs AB, http://www.pythonware.com
Copyright (c) 2004 by Peter Astrand <astrand@lysator.liu.se>
License:
* Permission to use, copy, modify, and distribute this software and
* its associated documentation for any purpose and without fee is
* hereby granted, provided that the above copyright notice appears in
* all copies, and that both that copyright notice and this permission
* notice appear in supporting documentation, and that the name of the
* authors not be used in advertising or publicity pertaining to
* distribution of the software without specific, written prior
* permission.
*
* THE AUTHORS DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
* INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
* CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
* NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
* WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
Files: PC/winsound.c
Copyright: Copyright (c) 1999 Toby Dickenson
License: * Permission to use this software in any way is granted without
* fee, provided that the copyright notice above appears in all
* copies. This software is provided "as is" without any warranty.
*/
/* Modified by Guido van Rossum */
/* Beep added by Mark Hammond */
/* Win9X Beep and platform identification added by Uncle Timmy */
Files: Lib/ensurepip/_bundled/setuptools-*.whl
License: see above, some license as Python.
Files: Lib/ensurepip/_bundled/pip-*.whl
Copyright: Copyright © 2008-2013 The pip developers:
Alex Grönholm
Alex Morega
Alexandre Conrad
Andrey Bulgakov
Antti Kaihola
Armin Ronacher
Aziz Köksal
Ben Rosser
Brian Rosner
Carl Meyer
Chris McDonough
Christian Oudard
Clay McClure
Cody Soyland
Daniel Holth
Dave Abrahams
David (d1b)
Dmitry Gladkov
Donald Stufft
Francesco
Geoffrey Lehée
Georgi Valkov
Hugo Lopes Tavares
Ian Bicking
Igor Sobreira
Ionel Maries Cristian
Jakub Vysoky
James Cleveland
Jannis Leidel
Jay Graves
John-Scott Atlakson
Jon Parise
Jonas Nockert
Josh Bronson
Kamal Bin Mustafa
Kelsey Hightower
Kenneth Belitzky
Kumar McMillan
Luke Macken
Masklinn
Marc Abramowitz
Marcus Smith
Markus Hametner
Matt Maker
Maxime R.
Miguel Araujo
Nick Stenning
Nowell Strite
Oliver Tonnhofer
Olivier Girardot
Patrick Jenkins
Paul Moore
Paul Nasrat
Paul Oswald
Paul van der Linden
Peter Waller
Phil Whelan
Piet Delport
Przemek Wrzos
Qiangning Hong
Rafael Caricio
Rene Dudfield
Roey Berman
Ronny Pfannschmidt
Rory McCann
Simon Cross
Stavros Korokithakis
Thomas Fenzl
Thomas Johansson
Vinay Sajip
Vitaly Babiy
W Trevor King
Wil Tan
Hsiaoming Yang
License: Expat
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
.
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Files: Lib/ensurepip/_bundled/pip-*.whl/pip/_vendor/ipaddress.py
Copyright: 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014 Python Software Foundation; All Rights Reserved
2007 Google Inc.
License: PSF-2
Files: Lib/ensurepip/_bundled/pip-*.whl/pip/_vendor/retrying.py
Copyright: (c) 2013-2014, <ray@blacklocus.com>
(c) 2014, Derek Wilson <dwilson@domaintools.com>
(c) 2014, Alex Kuang <akuang@bizo.com>
License: Apache-2
License: Apache-2
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
.
http://www.apache.org/licenses/LICENSE-2.0
.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
.
On Debian-based systems the full text of the Apache version 2.0 license
can be found in /usr/share/common-licenses/Apache-2.0.
Files: Lib/ensurepip/_bundled/pip-*.whl/pip/_vendor/six.py
Copyright: Copyright (c) 2010-2014 Benjamin Peterson
License: Expat
Files: Lib/ensurepip/_bundled/pip-*.whl/pip/_vendor/cachecontrol
Copyright: 2015 Eric Larson
License: Apache-2.0
Copyright 2015 Eric Larson
.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
.
http://www.apache.org/licenses/LICENSE-2.0
.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied.
.
See the License for the specific language governing permissions and
limitations under the License.
.
On Debian systems, the license is available at
/usr/share/common-licenses/Apache-2.0
Files: Lib/ensurepip/_bundled/pip-*.whl/pip/_vendor/colorama
Files: Lib/ensurepip/_bundled/pip-*.whl/pip/_vendor/distlib
Copyright: Copyright (C) 2012-2013 The Python Software Foundation
Copyright (C) 2012-2013 Vinay Sajip
License: Python
Files: Lib/ensurepip/_bundled/pip-*.whl/pip/_vendor/html5lib
html5lib is Copyright (c) 2006 The Authors
Authors:
James Graham - james@hoppipolla.co.uk
Anne van Kesteren - annevankesteren@gmail.com
Lachlan Hunt - lachlan.hunt@lachy.id.au
Matt McDonald - kanashii@kanashii.ca
Sam Ruby - rubys@intertwingly.net
Ian Hickson (Google) - ian@hixie.ch
Thomas Broyer - t.broyer@gmail.com
License:
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Files: Lib/ensurepip/_bundled/pip-*.whl/pip/_vendor/lockfile
Copyright:
© 2014–2015 OpenStack Foundation <openstack-dev@lists.openstack.org>
© 2007–2012 Skip Montanaro <skip@pobox.com>
© 2008–2014 Ben Finney <ben+python@benfinney.id.au>
License: Expat
Files: Lib/ensurepip/_bundled/pip-*.whl/pip/_vendor/packaging
Copyright: Copyright (C) 2014 Donald Stufft
Copyright (C) 2012-2013 Vinay Sajip
License: Apache-2.0
Files: Lib/ensurepip/_bundled/pip-*.whl/pip/_vendor/progress
Copyright: Copyright (c) 2012 Giorgos Verigakis <verigak@gmail.com>
License: ISC
Files: Lib/ensurepip/_bundled/pip-*.whl/pip/_vendor/requests
Copyright: 2016, Kenneth Reitz
License: Apache
Files: Lib/ensurepip/_bundled/pip-*.whl/pip/_vendor/requests/packages/urllib3/*
Copyright: 2008-2016, Andrey Petrov
License: Expat
Files: Lib/ensurepip/_bundled/pip-*.whl/pip/_vendor/requests/packages/urllib3/packages/ordered_dict.py
Copyright: 2009, Raymond Hettinger
License: Expat
Files: Lib/ensurepip/_bundled/pip-*.whl/pip/_vendor/requests/packages/urllib3/packages/ssl_match_hostname/__init__.py
Copyright: 2011, Python Software Foundation
License: PSF-2
Files: Lib/ensurepip/_bundled/pip-*.whl/pip/_vendor/requests/packages/chardet/*
Copyright: 2006-2008, Mark Pilgrim
2012-2013, Ian Cordasco
License: LGPL-2.1+
@@ -0,0 +1,13 @@
Files: src/js/editline/*
Copyright: 1993, John Doe
1993, Joe Average
License: MPL-1.1 or GPL-2 or LGPL-2.1
License: MPL-1.1
[LICENSE TEXT]
License: GPL-2
[LICENSE TEXT]
License: LGPL-2.1
[LICENSE TEXT]
@@ -0,0 +1,2 @@
FROM scratch
COPY . .
@@ -0,0 +1,43 @@
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: auditd
Source: https://people.redhat.com/sgrubb/audit/
Files: *
Copyright: 2012-2016 Steve Grubb <sgrubb@redhat.com>
2006-2012 Rik Faith
License: GPL-2
Files: src/libev/*
Copyright: 2007-2009 Marc Alexamder Lehmann
License: GPL-2
Files: lib/*
Copyright: 2005-2008 Steve Grubb <sgrubb@redhat.com>
License: LGPL-2.1
The audit daemon's library libaudit.* is released under LGPL
so that it may be linked with 3rd party software.
.
On Debian systems, refer to /usr/share/common-licenses/LGPL-2.1
for the complete text of the GNU Lesser General Public License.
Files: debian/*
Copyright: 2007-2011 Philipp Matthias Hahn <pmhahn@debian.org>
2012-2016 Laurent Bigonville <bigon@debian.org>
License: GPL-2
License: GPL-2
This package is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2,
as published by the Free Software Foundation.
.
This package is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
.
You should have received a copy of the GNU General Public License
along with this package; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
.
On Debian systems, the complete text of the GNU General
Public License can be found in `/usr/share/common-licenses/GPL-1'.
@@ -0,0 +1,4 @@
55f905631797551d4d936a34c7e73474 lib/x86_64-linux-gnu/libz.so.1.2.11
cede84bda30d2380217f97753c8ccf3a usr/share/doc/zlib1g/changelog.Debian.gz
f3c9dafa6da7992c47328b4464f6d122 usr/share/doc/zlib1g/changelog.gz
a4fae96070439a5209a62ae5b8017ab2 usr/share/doc/zlib1g/copyright
@@ -0,0 +1,21 @@
Package: libpam-runtime
Status: install ok installed
Priority: required
Section: admin
Installed-Size: 1016
Maintainer: Steve Langasek <vorlon@debian.org>
Architecture: all
Multi-Arch: foreign
Source: pam
Version: 1.1.8-3.6
Replaces: libpam0g-dev, libpam0g-util
Depends: debconf (>= 0.5) | debconf-2.0, debconf (>= 1.5.19) | cdebconf, libpam-modules (>= 1.0.1-6)
Conflicts: libpam0g-util
Conffiles:
/etc/pam.conf 87fc76f18e98ee7d3848f6b81b3391e5
/etc/pam.d/other 31aa7f2181889ffb00b87df4126d1701
Description: Runtime support for the PAM library
Contains configuration files and directories required for
authentication to work on Debian systems. This package is required
on almost all installations.
Homepage: http://www.linux-pam.org/
@@ -0,0 +1,4 @@
55f905631797551d4d936a34c7e73474 lib/x86_64-linux-gnu/libz.so.1.2.11
cede84bda30d2380217f97753c8ccf3a usr/share/doc/zlib1g/changelog.Debian.gz
f3c9dafa6da7992c47328b4464f6d122 usr/share/doc/zlib1g/changelog.gz
a4fae96070439a5209a62ae5b8017ab2 usr/share/doc/zlib1g/copyright
-42
View File
@@ -1,42 +0,0 @@
/*
Package dpkg provides a concrete Cataloger implementation for Debian package DB status files.
*/
package dpkg
import (
"github.com/anchore/stereoscope/pkg/file"
"github.com/anchore/syft/syft/cataloger/common"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/scope"
)
// Cataloger catalogs pkg.DebPkg Package Types defined in DPKG status files.
type Cataloger struct {
cataloger common.GenericCataloger
}
// New returns a new Deb package cataloger object.
func New() *Cataloger {
globParsers := map[string]common.ParserFn{
"**/var/lib/dpkg/status": parseDpkgStatus,
}
return &Cataloger{
cataloger: common.NewGenericCataloger(nil, globParsers),
}
}
// Name returns a string that uniquely describes this cataloger.
func (a *Cataloger) Name() string {
return "dpkg-cataloger"
}
// SelectFiles returns a set of discovered DPKG status files from the user content source.
func (a *Cataloger) SelectFiles(resolver scope.FileResolver) []file.Reference {
return a.cataloger.SelectFiles(resolver)
}
// Catalog returns the Packages indexed from all DPKG status files discovered.
func (a *Cataloger) Catalog(contents map[file.Reference]string) ([]pkg.Package, error) {
return a.cataloger.Catalog(contents, a.Name())
}
-105
View File
@@ -1,105 +0,0 @@
package dpkg
import (
"bufio"
"fmt"
"io"
"strings"
"github.com/anchore/syft/syft/cataloger/common"
"github.com/anchore/syft/syft/pkg"
"github.com/mitchellh/mapstructure"
)
// integrity check
var _ common.ParserFn = parseDpkgStatus
var errEndOfPackages = fmt.Errorf("no more packages to read")
// parseDpkgStatus is a parser function for Debian DB status contents, returning all Debian packages listed.
func parseDpkgStatus(_ string, reader io.Reader) ([]pkg.Package, error) {
buffedReader := bufio.NewReader(reader)
var packages = make([]pkg.Package, 0)
for {
entry, err := parseDpkgStatusEntry(buffedReader)
if err != nil {
if err == errEndOfPackages {
break
}
return nil, err
}
packages = append(packages, pkg.Package{
Name: entry.Package,
Version: entry.Version,
Type: pkg.DebPkg,
Metadata: entry,
})
}
return packages, nil
}
// parseDpkgStatusEntry returns an individual Dpkg entry, or returns errEndOfPackages if there are no more packages to parse from the reader.
func parseDpkgStatusEntry(reader *bufio.Reader) (entry pkg.DpkgMetadata, err error) {
dpkgFields := make(map[string]string)
var key string
for {
line, err := reader.ReadString('\n')
if err != nil {
if err == io.EOF {
return pkg.DpkgMetadata{}, errEndOfPackages
}
return pkg.DpkgMetadata{}, err
}
line = strings.TrimRight(line, "\n")
// empty line indicates end of entry
if len(line) == 0 {
// if the entry has not started, keep parsing lines
if len(dpkgFields) == 0 {
continue
}
break
}
switch {
case strings.HasPrefix(line, " "):
// a field-body continuation
if len(key) == 0 {
return pkg.DpkgMetadata{}, fmt.Errorf("no match for continuation: line: '%s'", line)
}
val, ok := dpkgFields[key]
if !ok {
return pkg.DpkgMetadata{}, fmt.Errorf("no previous key exists, expecting: %s", key)
}
// concatenate onto previous value
val = fmt.Sprintf("%s\n %s", val, strings.TrimSpace(line))
dpkgFields[key] = val
default:
// parse a new key
if i := strings.Index(line, ":"); i > 0 {
key = strings.TrimSpace(line[0:i])
val := strings.TrimSpace(line[i+1:])
if _, ok := dpkgFields[key]; ok {
return pkg.DpkgMetadata{}, fmt.Errorf("duplicate key discovered: %s", key)
}
dpkgFields[key] = val
} else {
return pkg.DpkgMetadata{}, fmt.Errorf("cannot parse field from line: '%s'", line)
}
}
}
err = mapstructure.Decode(dpkgFields, &entry)
if err != nil {
return pkg.DpkgMetadata{}, err
}
return entry, nil
}
+3 -28
View File
@@ -4,39 +4,14 @@ Package golang provides a concrete Cataloger implementation for go.mod files.
package golang
import (
"github.com/anchore/stereoscope/pkg/file"
"github.com/anchore/syft/syft/cataloger/common"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/scope"
)
// Cataloger catalogs pkg.GoModulePkg Package Types defined in go.mod files.
type Cataloger struct {
cataloger common.GenericCataloger
}
// New returns a new Go module cataloger object.
func New() *Cataloger {
// NewGoModCataloger returns a new Go module cataloger object.
func NewGoModCataloger() *common.GenericCataloger {
globParsers := map[string]common.ParserFn{
"**/go.mod": parseGoMod,
}
return &Cataloger{
cataloger: common.NewGenericCataloger(nil, globParsers),
}
}
// Name returns a string that uniquely describes this cataloger.
func (a *Cataloger) Name() string {
return "go-cataloger"
}
// SelectFiles returns a set of discovered go.mod files from the user content source.
func (a *Cataloger) SelectFiles(resolver scope.FileResolver) []file.Reference {
return a.cataloger.SelectFiles(resolver)
}
// Catalog returns the Packages indexed from all go.mod files discovered.
func (a *Cataloger) Catalog(contents map[file.Reference]string) ([]pkg.Package, error) {
return a.cataloger.Catalog(contents, a.Name())
return common.NewGenericCataloger(nil, globParsers, "go-cataloger")
}
+1 -2
View File
@@ -6,9 +6,8 @@ import (
"io/ioutil"
"sort"
"github.com/rogpeppe/go-internal/modfile"
"github.com/anchore/syft/syft/pkg"
"golang.org/x/mod/modfile"
)
// parseGoMod takes a go.mod and lists all packages discovered.
+85 -41
View File
@@ -10,36 +10,100 @@ import (
"github.com/anchore/syft/syft/pkg"
)
// match examples:
// pkg-extra-field-4.3.2-rc1 --> match(name=pkg-extra-field version=4.3.2-rc1)
// pkg-extra-field-4.3-rc1 --> match(name=pkg-extra-field version=4.3-rc1)
// pkg-extra-field-4.3 --> match(name=pkg-extra-field version=4.3)
var versionPattern = regexp.MustCompile(`(?P<name>.+)-(?P<version>(\d+\.)?(\d+\.)?(\*|\d+)(-[a-zA-Z0-9\-\.]+)*)`)
// nameAndVersionPattern finds the package name and version (as named capture
// groups) in a string. The pattern's strategy is to start at the beginning of
// the string, and for every next dash-delimited group, consider the group to be
// a continuation of the package name, unless the group begins with a number or
// matches any of a specified set of "version-indicating" patterns. When a given
// group meets this criterion, consider the group and the remainder of the
// string to be the package version.
//
// Regex components of note:
//
// (?Ui) ... Sets the "U" and the "i" options for this Regex —— (ungreedy,
// and case-insensitive, respectively). "Ungreedy" is important so that the '*' that trails the package name
// component doesn't consume the rest of the string.
//
// [[:alpha:]][[:word:]]* ... Matches any word, and the word can include "word" characters (
// which includes numbers and underscores), but the first character of the word MUST be a letter.
//
// (?:\.[[:alpha:]][[:word:]]*)* ... This looks redundant, but it's not. It
// extends the previous pattern such that the net effect of both components is
// that words can also include a period and more words (thus, when combined, not
// only is "something" matched, but so is "com.prefix.thing"
//
// (?:\d.*|(?:build\d*.*)|(?:rc?\d+(?:^[[:alpha:]].*)?)) ...
// This match group covers the "version-indicating" patterns mentioned in the above description. Given the pipes (
// '|'), this functions as a series of 'OR'-joined conditions:
//
// \d.* ... "If it starts with a numeric digit, this is a version, no matter what follows."
// build\d*.* ... "If it starts with "build" and then a numeric digit immediately after, this is a version."
// rc?\d+(?:^[[:alpha:]].*)? ... "If it starts with "r" or "rc" and then one or more numeric digits immediately
// after, but no alpha characters right after that (in the same word), this is a version."
//
// Match examples:
// some-package-4.0.1 --> name="some-package", version="4.0.1"
// prefix.thing-4 --> name="prefix.thing", version="4"
// my-http2-server-5 --> name="my-http2-server", version="5"
// jetpack-build235-rc5 --> name="jetpack", version="build2.0-rc5"
// ironman-r4-2009 --> name="ironman", version="r4-2009"
var nameAndVersionPattern = regexp.MustCompile(`(?Ui)^(?P<name>(?:[[:alpha:]][[:word:]]*(?:\.[[:alpha:]][[:word:]]*)*-?)+)(?:-(?P<version>(?:\d.*|(?:build\d*.*)|(?:rc?\d+(?:^[[:alpha:]].*)?))))?$`)
type archiveFilename struct {
raw string
fields []map[string]string
raw string
name string
version string
}
// TODO: Remove this method once we're using Go 1.15+.
//
// Go 1.15 introduces a `SubexpIndex` method for the Regexp type that would let
// this code be made more elegant. Once we've reached 1.15, we should eliminate
// this function in favor of that method.
func subexpIndex(re *regexp.Regexp, name string) int {
for i, subexpName := range re.SubexpNames() {
if subexpName == name {
return i
}
}
return -1
}
func getSubexp(matches []string, subexpName string, re *regexp.Regexp, raw string) string {
if len(matches) < 1 {
log.Warnf("unexpectedly empty matches for archive '%s'", raw)
return ""
}
index := subexpIndex(re, subexpName)
if index < 1 {
log.Warnf("unexpected index of '%s' capture group for Java archive '%s'", subexpName, raw)
return ""
}
// Prevent out-of-range panic
if len(matches) < index+1 {
log.Warnf("no match found for '%s' in '%s'", subexpName, matches[0])
return ""
}
return matches[index]
}
func newJavaArchiveFilename(raw string) archiveFilename {
// trim the file extension and remove any path prefixes
name := strings.TrimSuffix(filepath.Base(raw), filepath.Ext(raw))
cleanedFileName := strings.TrimSuffix(filepath.Base(raw), filepath.Ext(raw))
matches := versionPattern.FindAllStringSubmatch(name, -1)
fields := make([]map[string]string, 0)
for _, match := range matches {
item := make(map[string]string)
for i, name := range versionPattern.SubexpNames() {
if i != 0 && name != "" {
item[name] = match[i]
}
}
fields = append(fields, item)
}
matches := nameAndVersionPattern.FindStringSubmatch(cleanedFileName)
name := getSubexp(matches, "name", nameAndVersionPattern, raw)
version := getSubexp(matches, "version", nameAndVersionPattern, raw)
return archiveFilename{
raw: raw,
fields: fields,
raw: raw,
name: name,
version: version,
}
}
@@ -57,23 +121,3 @@ func (a archiveFilename) pkgType() pkg.Type {
return pkg.UnknownPkg
}
}
func (a archiveFilename) version() string {
if len(a.fields) > 1 {
log.Errorf("discovered multiple name-version pairings from %q: %+v", a.raw, a.fields)
return ""
} else if len(a.fields) < 1 {
return ""
}
return a.fields[0]["version"]
}
func (a archiveFilename) name() string {
// there should be only one name, if there is more or less then something is wrong
if len(a.fields) != 1 {
return ""
}
return a.fields[0]["name"]
}

Some files were not shown because too many files have changed in this diff Show More