Compare commits

...
77 Commits
Author SHA1 Message Date
Christopher Angelo Phillips 3286a4d4cc update docker manifest tagging (#600)
Signed-off-by: Christopher Angelo Phillips <christopher.phillips@anchore.com>
2021-10-28 15:41:39 -04:00
Christopher Angelo Phillips 0ff7013382 update manifest to use Tag over Version
Signed-off-by: Christopher Angelo Phillips <christopher.phillips@anchore.com>
2021-10-28 15:05:34 -04:00
Christopher Angelo Phillips 71708e5bf8 update goreleaser with correct tag information (#598)
Signed-off-by: Christopher Angelo Phillips <christopher.phillips@anchore.com>
2021-10-28 13:37:22 -04:00
Jonas Galvão Xavier dd024298eb stop hidding command from help doc (#596)
Signed-off-by: Jonas Galvão Xavier <jonas.agx@gmail.com>
2021-10-27 13:49:04 -07:00
Christopher Angelo Phillips 1c63943055 Add arm64 image support and Darwin M1 support to .goreleaser.yaml (#591)
* update support arm64

Signed-off-by: Christopher Angelo Phillips <christopher.phillips@anchore.com>

* small update syntax

Signed-off-by: Christopher Angelo Phillips <christopher.phillips@anchore.com>

* restore release command

Signed-off-by: Christopher Angelo Phillips <christopher.phillips@anchore.com>

* add docker manifests

Signed-off-by: Christopher Angelo Phillips <christopher.phillips@anchore.com>
2021-10-26 09:42:35 -04:00
Alex Goodman ff5fa2dc1d pull in space suffix fix (#587)
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2021-10-25 10:14:17 -04:00
Alex Goodman fb588ff500 replace table presenter with format object (#586)
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2021-10-24 08:09:27 -04:00
Alex Goodman d5b425e1b5 force exiting the etui upon worker error (#583)
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2021-10-22 13:44:50 -04:00
Dan Luhring 7b319e32bb Remove webinar announcement (#582)
Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2021-10-22 07:48:36 -04:00
Alex Goodman 89242e6ed3 bump chronicle to v0.2.0-beta (#581)
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2021-10-21 18:34:17 +00:00
Alex Goodman 4bf08e6b6d swap out the changelog generator for chronicle tool (#580)
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2021-10-21 13:37:13 -04:00
Alex Goodman 87ce9fff06 swap go binary parsing warning logging entry (#579)
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2021-10-21 17:03:07 +00:00
Christopher Angelo Phillips 10fa8dc7c9 Add windows support (#548)
* update  build tags, ui support, and stereoscope, and release for windows support

Signed-off-by: Christopher Angelo Phillips <christopher.phillips@anchore.com>
2021-10-21 12:49:36 -04:00
Alex Goodman 560b05c2c9 Introduce new format pattern + port json processing (#550)
* add new format pattern

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

* add syftjson format

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

* add internal formats helper

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

* add SBOM encode/decode to lib API

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

* remove json presenter + update presenter tests to use common utils

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

* remove presenter format enum type + add formats shim in presenter helper

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

* add MustCPE helper for tests

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

* update usage of format enum

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

* add test fixtures for encode/decode tests

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

* fix integration test

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

* migrate format detection to use reader

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

* address review comments

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2021-10-20 21:36:34 +00:00
Alex Goodman 5e315c0f17 Disable ETUI for piped input (#571)
* fixed piped input

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

* allow pipedinput helper to raise an error

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

* factor out verbosity check to function

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2021-10-20 12:40:52 -04:00
Alex Goodman 6f3fa494d7 Show help text when no args are given (#573)
* show help text when no args are given

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

* repurpose the input args validation function

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

* ensure app does not check for update in cli tests

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2021-10-20 09:51:05 -04:00
Alex GoodmanandChristopher Angelo Phillips a82463574b Rollback goreleaser version (#570)
* rollback goreleaser version

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

* update go sum

Signed-off-by: Christopher Angelo Phillips <christopher.phillips@anchore.com>

Co-authored-by: Christopher Angelo Phillips <christopher.phillips@anchore.com>
2021-10-19 12:01:51 -04:00
Alex Goodman cbdb72ea22 remove unnecessary codeql checkout from second parent commit (#567)
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2021-10-18 13:31:19 -04:00
Christopher Angelo Phillips 7a005fec06 update to point at new script for new release (#551)
* update to point at local script release

Signed-off-by: Christopher Angelo Phillips <christopher.phillips@anchore.com>
2021-10-15 13:26:34 -04:00
Dan Luhring c0f55866ec Announce upcoming webinar (#553)
Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2021-10-15 12:13:28 -04:00
Christopher Angelo Phillips 9ea6330ebc update split in py package_cataloger to cap at 2 (#549)
Signed-off-by: Christopher Angelo Phillips <christopher.phillips@anchore.com>
2021-10-14 12:32:06 -04:00
Christopher Angelo Phillips 7d13fbf0d4 540 add additional go package metadata (#546)
* update with distinct golang metadata types

Signed-off-by: Christopher Angelo Phillips <christopher.phillips@anchore.com>
2021-10-13 11:00:28 -04:00
Dan Luhring 10368f6fe8 Add Syft logo (#545)
Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2021-10-11 21:06:27 +00:00
Josh Bressers f2840029d1 Allow Syft to build with podman (#537)
* Don't check the Built-By flag

Signed-off-by: Josh Bressers <josh@bress.net>

* Remove alpine pinning to resolve conflict with main

Signed-off-by: Josh Bressers <josh@bress.net>
2021-10-11 12:39:34 -04:00
Christopher Angelo Phillips b25f5b664b (464) - remove mod and cargo from image cataloger (#539)
* remove mod and cargo from image cataloger

Signed-off-by: Christopher Angelo Phillips <christopher.phillips@anchore.com>

* update test error messages for clear failures

Signed-off-by: Christopher Angelo Phillips <christopher.phillips@anchore.com>
2021-10-07 15:18:20 -04:00
Christopher Angelo PhillipsandAlex Goodman 3462e18af3 478 identify go binaries and extract mod information (#534)
* add query by MIME type to source.FileResolver

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

* import stereoscope lib changes to find mime type

- add bin cataloger
- add bin parser
- add mime type go utils
- import new resolver

Signed-off-by: Christopher Angelo Phillips <christopher.phillips@anchore.com>

* add go std library code to unpack bin

- keep them in their own (original) files
- add note for "this code was copied from"
- comment the lines the required changing

Signed-off-by: Christopher Angelo Phillips <christopher.phillips@anchore.com>
Co-authored-by: Alex Goodman <alex.goodman@anchore.com>
2021-10-07 12:16:38 -04:00
Alex Goodman 05ac3f1eff split application configuration parsing to multiple methods (#532)
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2021-10-06 09:48:53 -04:00
Alex Goodman a000a2926b update linter + fix whitespace (#536)
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2021-10-06 13:46:55 +00:00
Alex Goodman 9189ed68df Add query by MIME type to source.FileResolver (#529)
* add query by MIME type to source.FileResolver

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

* pull in stereoscope MIME type feature

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

* fix tests

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2021-10-05 20:50:53 -04:00
Alex Goodman ba1cd8d753 specify optional file.Digests instances to abbreviate json output (#531)
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2021-10-05 15:35:28 -04:00
Alex Goodman dd2c178085 add --file option to readme (#533)
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2021-10-05 15:34:32 -04:00
Alex Goodman 1b23a94015 Add option to output SBOM report to a file (#530)
* add output to file option

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

* log errors on close of the report destination

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

* remove file option from persistent args

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

* update file option comments and logging

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

* allow for multiple UI fallback options

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

* update UI select signatures + tests

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2021-10-05 14:47:24 -04:00
Christopher Angelo Phillips f47a6a88b1 (#495) Update documentNamespace uniqueness for spdx-json output (#528)
* add unique namespace identifier

Signed-off-by: Christopher Angelo Phillips <christopher.phillips@anchore.com>
2021-10-05 13:10:49 -04:00
Dan Luhring 4b7217f052 Improve documentation (#525)
* Make installation methods more obvious

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

* Create linkable section headers

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

* Add badge for joining Slack

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

* Document requirement for signed commits

Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2021-10-04 09:29:27 -04:00
Christopher Angelo Phillips 5e4b668e5a 497 stable sorted CPE array (JSON and SPDX) (#522)
* add small sorting change to our specificity

Signed-off-by: Christopher Angelo Phillips <christopher.phillips@anchore.com>
2021-10-01 15:31:17 -04:00
Dan Luhring 6480f06173 Update description for Slack link (#523)
Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2021-09-30 21:43:43 +00:00
Christopher Angelo Phillips 75aed5f3ec (#460) Extend license mapping for common SPDX license names (#509)
Fixes #460 
Signed-off-by: Christopher Angelo Phillips <christopher.phillips@anchore.com>
2021-09-30 16:54:36 -04:00
Jamie Magee 270fbb7665 Add mariner to distro types (#520)
Signed-off-by: Jamie Magee <jamie.magee@gmail.com>
2021-09-30 13:20:15 -04:00
Alex GoodmanandChristopher Angelo Phillips 0395c47445 Add vendor + product known good CPE field values (#517)
* add better infrastructure around CPE candidate additions

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

* add test cases for CPE candidate additions

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

* small ergonomic updates

Signed-off-by: Christopher Angelo Phillips <christopher.phillips@anchore.com>

* clean java OSGI directives from group ID

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

* unquote CPE candidate field values (double quotes only)

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

* add remaining CPE vendor & product additions (+ tests)

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

Co-authored-by: Christopher Angelo Phillips <christopher.phillips@anchore.com>
2021-09-27 18:31:59 -04:00
Keith Zantow e5caba043d Add SBOM to releases (#500)
Signed-off-by: Keith Zantow <kzantow@gmail.com>
2021-09-24 20:21:23 -04:00
Dan Luhring d1ac476986 Add announcement for KubeCon meetup (#515)
Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2021-09-24 11:42:06 -04:00
Dan Luhring dd23d49986 Prevent invalid CPE field values (#514)
* Fix CPE set comparison mismatch

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

* Add failing test to assert CPE generation excludes URLs

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

* Add removeByCondition method to fieldCandidateSet

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

* Prevent invalid CPE values for products and vendors

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

* Introduce removeWhere and rename filter to condition

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

* Refactor fieldCandidateSet and condition logic

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

* Move CPE parsing filter to end of CPE generation

Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2021-09-24 09:23:58 -04:00
Alex Goodman 6d4d083acc Filter out CPE product candidates that are asterisks (#513)
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2021-09-22 20:55:03 -04:00
Dan Luhring 316d4341c8 Use Anchore fork of packageurl lib without replace directive (#512)
Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2021-09-22 15:04:09 -04:00
Christopher Angelo Phillips 3e8afc5274 update log file permissions to 0644 (#511)
Signed-off-by: Christopher Angelo Phillips <christopher.phillips@anchore.com>
2021-09-21 10:34:10 -04:00
Christopher Angelo Phillips 93d00dc340 Populate Files and Relationship fields for spdx-json output (#507)
* update spdx22 Document model to include relationships field

Signed-off-by: Christopher Angelo Phillips <christopher.phillips@anchore.com>

* update document and relationship to match current JSON spec
https://github.com/spdx/spdx-spec/blob/development/v2.2.1/schemas/spdx-schema.json
https://github.com/spdx/spdx-spec/pull/528
https://github.com/spdx/spdx-spec/pull/528#issuecomment-904180177

Signed-off-by: Christopher Angelo Phillips <christopher.phillips@anchore.com>

* update File struct based on SPDX schema

Required fields:
[ "SPDXID", "fileName", "copyrightText", "licenseConcluded" ]
Signed-off-by: Christopher Angelo Phillips <christopher.phillips@anchore.com>
2021-09-17 09:06:12 -04:00
Alex Goodman 9fe1da8ee6 update go-rpmdb and restruct gomod references (#506)
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2021-09-14 14:51:19 -04:00
Christopher Angelo Phillips 95df7dd035 Add command examples to release documentation (#503)
* update release document with commands run
Signed-off-by: Christopher Angelo Phillips <christopher.phillips@anchore.com>

* small edits
Signed-off-by: Christopher Angelo Phillips <christopher.phillips@anchore.com>
2021-09-14 09:08:37 -04:00
Alex Goodman ddfc8e20c0 Revert "disable docker releases (workaround) (#493)" (#501)
This reverts commit 06dcd3261d.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2021-09-13 13:06:23 -04:00
Christopher Angelo Phillips 3f305aa6cd (#342) - remove strong distro check (#496)
* add Type conversion to remove strong distro type limit
Signed-off-by: Christopher Angelo Phillips <christopher.phillips@anchore.com>

* update signatures to be correct variable from os-release
Signed-off-by: Christopher Angelo Phillips <christopher.phillips@anchore.com>
2021-09-09 13:05:22 -04:00
houdini91 2f99a35f51 Power user command support for directory scans (#467)
* Power-user directory source support
Signed-off-by: Mikey Strauss <mikey@scribe-security.com>

Signed-off-by: houdini91 <mdstrauss91@gmail.com>

* Remove newline

Signed-off-by: houdini91 <mdstrauss91@gmail.com>

* Shared filetree (#1)

* Shared directory resolver filetree

Signed-off-by: houdini91 <mdstrauss91@gmail.com>

* PR - change error ErrObserve to ErrPath

Signed-off-by: houdini91 <mdstrauss91@gmail.com>

* PR - share directory resolver
* Use pointer to source struct

Signed-off-by: houdini91 <mdstrauss91@gmail.com>

* Fix Lint

Signed-off-by: houdini91 <mdstrauss91@gmail.com>
2021-09-08 09:18:53 -04:00
Alex Goodman 06dcd3261d disable docker releases (workaround) (#493)
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2021-09-03 21:29:34 +00:00
Alex Goodman abbba3fc19 Modify CPE vendor candidate generation approach (#484)
* consider additional vendor candidates for ruby, python, rpm, npm, and java

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

* add java pom.xml processing

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

* allow for downstream transform control in cpe generation processing

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

* migrate CPE generation logic to dedicated package

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

* split java manifest groupID extraction into two tiers

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

* extract groupID from pom parent project during CPE generation

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

* update java groupID processing tests to cover multi-tier approach

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

* fix constructor names for cpe.fieldCandidate

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

* rename helper function to startsWithTopLevelDomain

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

* add nil changes for java manifest sections

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

* update comment to reflect parsing maven files

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

* split out java description parsing

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

* split out pom parent processing

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

* simplify vendorsFromGroupIDs and associated tests

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

* simplify test type for vendorsFromGroupIDs

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

* copy candidate varidations to new instances

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

* rename CPE generation string util functions

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

* add an explanation around fieldCandidate

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

* simplify type for the cpe.fieldCandidateSet

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

* make CPE filter function names more readable

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

* update groupIDsFromJavaManifest to use a guard clause

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

* extract groupID extraction from artifactID fields into a separate function

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

* bump goreleaser version to combat failure

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2021-09-03 14:21:25 -04:00
Samuel Dacanay 0799fd9d46 Add support for older CentOS versions (6 & 5) by checking additional release files for information
Signed-off-by: Samuel Dacanay <sam.dacanay@anchore.com>
2021-09-03 16:06:10 +01:00
Alex Goodman 5de1a0a236 Revert "Add announcement for upcoming OSS meetup (#490)" (#491)
This reverts commit 035ef03b0c.

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2021-09-02 10:59:51 -04:00
Dan Luhring 035ef03b0c Add announcement for upcoming OSS meetup (#490)
Signed-off-by: Dan Luhring <dan.luhring@anchore.com>
2021-08-25 14:35:32 -04:00
Toure Dunnon 9acabb8307 Merge pull request #489 from anchore/SLES_support_berkeleyDB
Updated the distro package to include SLES
2021-08-23 16:14:52 -04:00
Toure Dunnon 0f0fe59bf2 Updated the distro package to include SLES type to expand support, this change will not include the new sqlite3 rpmDB format
Signed-off-by: Toure Dunnon <toure.dunnon@anchore.com>
2021-08-23 15:05:39 -04:00
Keith Zantow ff828fbac2 Skip package-lock.json and yarn.lock in node_modules #431 (#485)
Signed-off-by: Keith Zantow <kzantow@gmail.com>
2021-08-20 13:50:28 -04:00
cba5b5723b Added parser for Pipfile.lock to cataloger (#473)
* Added parser for Pipfile.lock to cataloger

Signed-off-by: Nikita <33390074+Zilborg@users.noreply.github.com>

* make lint-fix

Signed-off-by: Nikita <33390074+Zilborg@users.noreply.github.com>

* Update syft/pkg/cataloger/python/parse_pipfile_lock.go

Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
Signed-off-by: Nikita <33390074+Zilborg@users.noreply.github.com>

* fix _version

Signed-off-by: Nikita <33390074+Zilborg@users.noreply.github.com>

* swap method for trimming "==" prefix from pipfile pkg versions

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

Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
Co-authored-by: Alex Goodman <alex.goodman@anchore.com>
2021-08-19 10:32:10 -04:00
Alex Goodman b0e9b92a25 Merge pull request #483 from anchore/fix-gomod-gosum-validation
Fix go module tidy checks for release pipeline
2021-08-18 11:00:49 -04:00
Alex Goodman d25d3b1fb3 fix go module tidy checks for release pipeline
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2021-08-18 10:13:56 -04:00
Keith Zantow 93b4bf9379 Add option to enable http registry connections (#482)
Signed-off-by: Keith Zantow <kzantow@gmail.com>
2021-08-17 12:52:51 -04:00
Keith Zantow 58f2be95fd chore: update packageurl-go and add encoding tests for #351 (#481)
Signed-off-by: Keith Zantow <kzantow@gmail.com>
2021-08-13 15:39:52 -04:00
Alex Goodman 6f038e9208 Merge pull request #447 from bureado/main
Minimal changes to use ID_LIKE
2021-08-12 14:20:13 -04:00
Alex Goodman 32a54b94be add unit tests for distro identification based on "like" field
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2021-08-12 14:09:22 -04:00
Alex Goodman 98d4749f86 Enhance CPE generation (#472)
* adjust CPE specificity sorting to include field length and bias certain fields

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

* remove * vendor values from CPE generation

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

* re-enable generating CPEs for jenkins and jira plugins

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

* improve CPE generation logic based on java artifactID and groupID

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

* add ruby-lang as target software candidate for gems in CPE generation logic

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

* rename filterCpes to filterCPEs

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

* refactor CPE filters and groupID processing (for linting)

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

* use ruby-lang as vendor candidate not target software

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

* address PR comments for CPE generation

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2021-08-09 19:52:19 +00:00
Alex Goodman 3a5168917e Update SPDX license list from 3.13 to 3.14 (#474)
* update SPDX license list from 3.13 to 3.14

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

* remove license list version from spdx snapshot unit tests

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2021-08-09 19:47:33 +00:00
Alex Goodman 58b21c03c3 Merge pull request #461 from anchore/update-slack
Update slack channels
2021-07-12 08:28:01 -04:00
Alex Goodman 2093d2a1dc update slack channels
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2021-07-12 08:04:04 -04:00
Alex Goodman dd56450f92 Revert "Add callout for OSS meetup (#446)" (#459)
This reverts commit 66e71c39e2.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2021-07-02 14:29:27 +00:00
Alex Goodman 6f2b52971d directory resolver indexer should report one progressable (#457)
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2021-07-02 10:18:41 -04:00
Alex Goodman fee74d57b8 Update documentation to show SPDX support (#452)
* add docs for spdx support

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

* copy updates for format options

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2021-07-01 14:52:57 +00:00
Alex Goodman fee35dd175 redirect cursor hide/show to stderr (#456)
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2021-06-30 13:10:00 -04:00
Alex Goodman ecf4e5546a add help message when no arguments are provided (#455)
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2021-06-30 12:53:12 -04:00
Jose Miguel Parrella c833472b6e If distro name is unknown, try with the ID_LIKE
Signed-off-by: Jose Miguel Parrella <j@bureado.com>
2021-06-25 22:18:40 -07:00
Jose Miguel Parrella c825ae2404 Completing the test for distro version
Signed-off-by: Jose Miguel Parrella <j@bureado.com>
2021-06-25 22:18:40 -07:00
352 changed files with 35709 additions and 2795 deletions
+2 -2
View File
@@ -6,7 +6,7 @@ permit:
- ISC
ignore-packages:
# packageurl-go is released under the MIT license located in the root of the repo at /mit.LICENSE
- github.com/package-url/packageurl-go
- github.com/anchore/packageurl-go
# from: https://github.com/spdx/tools-golang/blob/main/LICENSE.code
# The tools-golang source code is provided and may be used, at your option,
@@ -14,4 +14,4 @@ ignore-packages:
# * Apache License, version 2.0 (Apache-2.0), OR
# * GNU General Public License, version 2.0 or later (GPL-2.0-or-later).
# (we choose Apache-2.0)
- github.com/spdx/tools-golang
- github.com/spdx/tools-golang
+1 -1
View File
@@ -3,4 +3,4 @@ contact_links:
- name: Join the Slack community 💬
# link to our community Slack registration page
url: https://anchore.com/slack
about: 'Be sure to check out the channels #toolbox-help (ask us questions) and #toolbox-dev (collaborate with us on design and development)!'
about: 'Come chat with us! Ask for help, join our software development efforts, or just give us feedback!'
+15 -11
View File
@@ -1,27 +1,31 @@
#!/usr/bin/env bash
set -u
set -eu
TMP_GOMOD=$(mktemp)
TMP_GOSUM=$(mktemp)
ORIGINAL_STATE_DIR=$(mktemp -d "TEMP-original-state-XXXXXXXXX")
TIDY_STATE_DIR=$(mktemp -d "TEMP-tidy-state-XXXXXXXXX")
trap "rm -f ${TMP_GOSUM} ${TMP_GOMOD}" EXIT
trap "cp -v ${ORIGINAL_STATE_DIR}/* ./ && rm -fR ${ORIGINAL_STATE_DIR} ${TIDY_STATE_DIR}" EXIT
cp go.mod "${TMP_GOMOD}"
cp go.sum "${TMP_GOSUM}"
echo "Capturing original state of files..."
cp -v go.mod go.sum "${ORIGINAL_STATE_DIR}"
echo "Capturing state of go.mod and go.sum after running go mod tidy..."
go mod tidy
cp -v go.mod go.sum "${TIDY_STATE_DIR}"
echo ""
DIFF_MOD=$(diff -u "${TMP_GOMOD}" go.mod)
DIFF_SUM=$(diff -u "${TMP_GOSUM}" go.sum)
set +e
cp "${TMP_GOMOD}" go.mod
cp "${TMP_GOSUM}" go.sum
# Detect difference between the git HEAD state and the go mod tidy state
DIFF_MOD=$(diff -u "${ORIGINAL_STATE_DIR}/go.mod" "${TIDY_STATE_DIR}/go.mod")
DIFF_SUM=$(diff -u "${ORIGINAL_STATE_DIR}/go.sum" "${TIDY_STATE_DIR}/go.sum")
if [[ -n "${DIFF_MOD}" || -n "${DIFF_SUM}" ]]; then
echo "go.mod and/or go.sum are not tidy; please run go mod tidy"
echo "go.mod diff:"
echo "${DIFF_MOD}"
echo "go.sum diff:"
echo "${DIFF_SUM}"
echo ""
printf "FAILED! go.mod and/or go.sum are NOT tidy; please run 'go mod tidy'.\n\n"
exit 1
fi
+398
View File
@@ -0,0 +1,398 @@
#!/bin/sh
set -e
# Code generated by godownloader on 2019-12-25T12:47:14Z. DO NOT EDIT.
#
usage() {
this=$1
cat <<EOF
$this: download go binaries for goreleaser/goreleaser
Usage: $this [-b] bindir [-d] [tag]
-b sets bindir or installation directory, Defaults to ./bin
-d turns on debug logging
[tag] is a tag from
https://github.com/goreleaser/goreleaser/releases
If tag is missing, then the latest will be used.
Generated by godownloader
https://github.com/goreleaser/godownloader
EOF
exit 2
}
parse_args() {
#BINDIR is ./bin unless set be ENV
# over-ridden by flag below
BINDIR=${BINDIR:-./bin}
while getopts "b:dh?x" arg; do
case "$arg" in
b) BINDIR="$OPTARG" ;;
d) log_set_priority 10 ;;
h | \?) usage "$0" ;;
x) set -x ;;
esac
done
shift $((OPTIND - 1))
TAG=$1
}
# this function wraps all the destructive operations
# if a curl|bash cuts off the end of the script due to
# network, either nothing will happen or will syntax error
# out preventing half-done work
execute() {
tmpdir=$(mktemp -d)
log_debug "downloading files into ${tmpdir}"
http_download "${tmpdir}/${TARBALL}" "${TARBALL_URL}"
http_download "${tmpdir}/${CHECKSUM}" "${CHECKSUM_URL}"
hash_sha256_verify "${tmpdir}/${TARBALL}" "${tmpdir}/${CHECKSUM}"
srcdir="${tmpdir}"
(cd "${tmpdir}" && untar "${TARBALL}")
test ! -d "${BINDIR}" && install -d "${BINDIR}"
for binexe in $BINARIES; do
if [ "$OS" = "windows" ]; then
binexe="${binexe}.exe"
fi
install "${srcdir}/${binexe}" "${BINDIR}/"
log_info "installed ${BINDIR}/${binexe}"
done
rm -rf "${tmpdir}"
}
get_binaries() {
case "$PLATFORM" in
darwin/386) BINARIES="goreleaser" ;;
darwin/amd64) BINARIES="goreleaser" ;;
darwin/arm64) BINARIES="goreleaser" ;;
darwin/armv6) BINARIES="goreleaser" ;;
linux/386) BINARIES="goreleaser" ;;
linux/amd64) BINARIES="goreleaser" ;;
linux/arm64) BINARIES="goreleaser" ;;
linux/armv6) BINARIES="goreleaser" ;;
windows/386) BINARIES="goreleaser" ;;
windows/amd64) BINARIES="goreleaser" ;;
windows/arm64) BINARIES="goreleaser" ;;
windows/armv6) BINARIES="goreleaser" ;;
*)
log_crit "platform $PLATFORM is not supported. Make sure this script is up-to-date and file request at https://github.com/${PREFIX}/issues/new"
exit 1
;;
esac
}
tag_to_version() {
if [ -z "${TAG}" ]; then
log_info "checking GitHub for latest tag"
else
log_info "checking GitHub for tag '${TAG}'"
fi
REALTAG=$(github_release "$OWNER/$REPO" "${TAG}") && true
if test -z "$REALTAG"; then
log_crit "unable to find '${TAG}' - use 'latest' or see https://github.com/${PREFIX}/releases for details"
exit 1
fi
# if version starts with 'v', remove it
TAG="$REALTAG"
VERSION=${TAG#v}
}
adjust_format() {
# change format (tar.gz or zip) based on OS
case ${OS} in
windows) FORMAT=zip ;;
esac
true
}
adjust_os() {
# adjust archive name based on OS
case ${OS} in
386) OS=i386 ;;
amd64) OS=x86_64 ;;
darwin) OS=Darwin ;;
linux) OS=Linux ;;
windows) OS=Windows ;;
esac
true
}
adjust_arch() {
# adjust archive name based on ARCH
case ${ARCH} in
386) ARCH=i386 ;;
amd64) ARCH=x86_64 ;;
darwin) ARCH=Darwin ;;
linux) ARCH=Linux ;;
windows) ARCH=Windows ;;
esac
true
}
cat /dev/null <<EOF
------------------------------------------------------------------------
https://github.com/client9/shlib - portable posix shell functions
Public domain - http://unlicense.org
https://github.com/client9/shlib/blob/master/LICENSE.md
but credit (and pull requests) appreciated.
------------------------------------------------------------------------
EOF
is_command() {
command -v "$1" >/dev/null
}
echoerr() {
echo "$@" 1>&2
}
log_prefix() {
echo "$0"
}
_logp=6
log_set_priority() {
_logp="$1"
}
log_priority() {
if test -z "$1"; then
echo "$_logp"
return
fi
[ "$1" -le "$_logp" ]
}
log_tag() {
case $1 in
0) echo "emerg" ;;
1) echo "alert" ;;
2) echo "crit" ;;
3) echo "err" ;;
4) echo "warning" ;;
5) echo "notice" ;;
6) echo "info" ;;
7) echo "debug" ;;
*) echo "$1" ;;
esac
}
log_debug() {
log_priority 7 || return 0
echoerr "$(log_prefix)" "$(log_tag 7)" "$@"
}
log_info() {
log_priority 6 || return 0
echoerr "$(log_prefix)" "$(log_tag 6)" "$@"
}
log_err() {
log_priority 3 || return 0
echoerr "$(log_prefix)" "$(log_tag 3)" "$@"
}
log_crit() {
log_priority 2 || return 0
echoerr "$(log_prefix)" "$(log_tag 2)" "$@"
}
uname_os() {
os=$(uname -s | tr '[:upper:]' '[:lower:]')
case "$os" in
cygwin_nt*) os="windows" ;;
mingw*) os="windows" ;;
msys_nt*) os="windows" ;;
esac
echo "$os"
}
uname_arch() {
arch=$(uname -m)
case $arch in
x86_64) arch="amd64" ;;
x86) arch="386" ;;
i686) arch="386" ;;
i386) arch="386" ;;
aarch64) arch="arm64" ;;
armv5*) arch="armv5" ;;
armv6*) arch="armv6" ;;
armv7*) arch="armv7" ;;
esac
echo ${arch}
}
uname_os_check() {
os=$(uname_os)
case "$os" in
darwin) return 0 ;;
dragonfly) return 0 ;;
freebsd) return 0 ;;
linux) return 0 ;;
android) return 0 ;;
nacl) return 0 ;;
netbsd) return 0 ;;
openbsd) return 0 ;;
plan9) return 0 ;;
solaris) return 0 ;;
windows) return 0 ;;
esac
log_crit "uname_os_check '$(uname -s)' got converted to '$os' which is not a GOOS value. Please file bug at https://github.com/client9/shlib"
return 1
}
uname_arch_check() {
arch=$(uname_arch)
case "$arch" in
386) return 0 ;;
amd64) return 0 ;;
arm64) return 0 ;;
armv5) return 0 ;;
armv6) return 0 ;;
armv7) return 0 ;;
ppc64) return 0 ;;
ppc64le) return 0 ;;
mips) return 0 ;;
mipsle) return 0 ;;
mips64) return 0 ;;
mips64le) return 0 ;;
s390x) return 0 ;;
amd64p32) return 0 ;;
esac
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}" ;;
*)
log_err "untar unknown archive format for ${tarball}"
return 1
;;
esac
}
http_download_curl() {
local_file=$1
source_url=$2
header=$3
if [ -z "$header" ]; then
code=$(curl -w '%{http_code}' -sL -o "$local_file" "$source_url")
else
code=$(curl -w '%{http_code}' -sL -H "$header" -o "$local_file" "$source_url")
fi
if [ "$code" != "200" ]; then
log_debug "http_download_curl received HTTP status $code"
return 1
fi
return 0
}
http_download_wget() {
local_file=$1
source_url=$2
header=$3
if [ -z "$header" ]; then
wget -q -O "$local_file" "$source_url"
else
wget -q --header "$header" -O "$local_file" "$source_url"
fi
}
http_download() {
log_debug "http_download $2"
if is_command curl; then
http_download_curl "$@"
return
elif is_command wget; then
http_download_wget "$@"
return
fi
log_crit "http_download unable to find wget or curl"
return 1
}
http_copy() {
tmp=$(mktemp)
http_download "${tmp}" "$1" "$2" || return 1
body=$(cat "$tmp")
rm -f "${tmp}"
echo "$body"
}
github_release() {
owner_repo=$1
version=$2
test -z "$version" && version="latest"
giturl="https://github.com/${owner_repo}/releases/${version}"
json=$(http_copy "$giturl" "Accept:application/json")
test -z "$json" && return 1
version=$(echo "$json" | tr -s '\n' ' ' | sed 's/.*"tag_name":"//' | sed 's/".*//')
test -z "$version" && return 1
echo "$version"
}
hash_sha256() {
TARGET=${1:-/dev/stdin}
if is_command gsha256sum; then
hash=$(gsha256sum "$TARGET") || return 1
echo "$hash" | cut -d ' ' -f 1
elif is_command sha256sum; then
hash=$(sha256sum "$TARGET") || return 1
echo "$hash" | cut -d ' ' -f 1
elif is_command shasum; then
hash=$(shasum -a 256 "$TARGET" 2>/dev/null) || return 1
echo "$hash" | cut -d ' ' -f 1
elif is_command openssl; then
hash=$(openssl -dst openssl dgst -sha256 "$TARGET") || return 1
echo "$hash" | cut -d ' ' -f a
else
log_crit "hash_sha256 unable to find command to compute sha-256 hash"
return 1
fi
}
hash_sha256_verify() {
TARGET=$1
checksums=$2
if [ -z "$checksums" ]; then
log_err "hash_sha256_verify checksum file not specified in arg2"
return 1
fi
BASENAME=${TARGET##*/}
want=$(grep "${BASENAME}" "${checksums}" 2>/dev/null | tr '\t' ' ' | cut -d ' ' -f 1)
if [ -z "$want" ]; then
log_err "hash_sha256_verify unable to find checksum for '${TARGET}' in '${checksums}'"
return 1
fi
got=$(hash_sha256 "$TARGET")
if [ "$want" != "$got" ]; then
log_err "hash_sha256_verify checksum for '$TARGET' did not verify ${want} vs $got"
return 1
fi
}
cat /dev/null <<EOF
------------------------------------------------------------------------
End of functions from https://github.com/client9/shlib
------------------------------------------------------------------------
EOF
PROJECT_NAME="goreleaser"
OWNER=goreleaser
REPO="goreleaser"
BINARY=goreleaser
FORMAT=tar.gz
OS=$(uname_os)
ARCH=$(uname_arch)
PREFIX="$OWNER/$REPO"
# use in logging routines
log_prefix() {
echo "$PREFIX"
}
PLATFORM="${OS}/${ARCH}"
GITHUB_DOWNLOAD=https://github.com/${OWNER}/${REPO}/releases/download
uname_os_check "$OS"
uname_arch_check "$ARCH"
parse_args "$@"
get_binaries
tag_to_version
adjust_format
adjust_os
adjust_arch
log_info "found version: ${VERSION} for ${TAG}/${OS}/${ARCH}"
NAME=${PROJECT_NAME}_${OS}_${ARCH}
TARBALL=${NAME}.${FORMAT}
TARBALL_URL=${GITHUB_DOWNLOAD}/${TAG}/${TARBALL}
CHECKSUM=checksums.txt
CHECKSUM_URL=${GITHUB_DOWNLOAD}/${TAG}/${CHECKSUM}
execute
log_err "this script is deprecated, please do not use it anymore. check https://github.com/goreleaser/godownloader/issues/207"
-9
View File
@@ -31,15 +31,6 @@ jobs:
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
+4 -4
View File
@@ -96,10 +96,6 @@ jobs:
steps:
- uses: docker-practice/actions-setup-docker@v1
# note, it is important to always be auth'd into docker.io to prevent rate limiting issues
- name: Login to Docker Hub
run: echo ${{ secrets.TOOLBOX_DOCKER_PASS }} | docker login docker.io -u ${{ secrets.TOOLBOX_DOCKER_USER }} --password-stdin
- uses: actions/setup-go@v2
with:
go-version: ${{ env.GO_VERSION }}
@@ -158,6 +154,10 @@ jobs:
AC_USERNAME: ${{ secrets.ENG_CI_APPLE_ID }} # Used during macOS notarization.
AC_PASSWORD: ${{ secrets.ENG_CI_APPLE_ID_PASS }} # Used during macOS notarization.
- uses: anchore/sbom-action@v0
with:
artifact-name: sbom.spdx.json
- uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
+7
View File
@@ -232,6 +232,9 @@ jobs:
- uses: actions/checkout@v2
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Restore tool cache
id: tool-cache
uses: actions/cache@v2.1.3
@@ -305,6 +308,10 @@ jobs:
needs: [Build-Snapshot-Artifacts]
runs-on: ubuntu-20.04
steps:
- uses: actions/setup-go@v2
with:
go-version: ${{ env.GO_VERSION }}
- uses: actions/checkout@v2
- name: Restore go cache
+5 -3
View File
@@ -15,6 +15,7 @@ linters:
- dogsled
- dupl
- errcheck
- exportloopref
- funlen
- gocognit
- goconst
@@ -22,7 +23,6 @@ linters:
- gocyclo
- gofmt
- goimports
- golint
- goprintffuncname
- gosec
- gosimple
@@ -31,8 +31,8 @@ linters:
- misspell
- nakedret
- nolintlint
- revive
- rowserrcheck
- scopelint
- staticcheck
- structcheck
- stylecheck
@@ -49,11 +49,13 @@ linters:
# - godot
# - godox
# - goerr113
# - golint # deprecated
# - gomnd # this is too aggressive
# - interfacer # this is a good idea, but is no longer supported and is prone to false positives
# - 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
# - scopelint # deprecated
# - testpackage
# - wsl
# - wsl # this doens't have an auto-fixer yet and is pretty noisy (https://github.com/bombsimon/wsl/issues/90)
+64 -10
View File
@@ -12,9 +12,27 @@ builds:
env:
- CGO_ENABLED=0
goos:
# windows not supported yet (due to jotframe)
# - windows
- linux
goarch:
- amd64
- arm64
# 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}}
- binary: syft
id: syft-win
env:
- CGO_ENABLED=0
goos:
- windows
goarch:
- amd64
# Set the modified timestamp on the output binary to the git timestamp (to ensure a reproducible build)
@@ -27,7 +45,8 @@ builds:
-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
# For more info on this macOS build, see: https://github.com/mitchellh/gon#usage-with-goreleaser
- binary: syft
id: syft-macos
env:
@@ -36,6 +55,7 @@ builds:
- darwin
goarch:
- amd64
- arm64
# Set the modified timestamp on the output binary to the git timestamp (to ensure a reproducible build)
mod_timestamp: '{{ .CommitTimestamp }}'
ldflags: |
@@ -51,10 +71,11 @@ 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)
- format: zip # This is a hack for syft-macos! 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
- syft-win
signs:
- artifacts: checksum
@@ -92,15 +113,48 @@ brews:
description: *description
dockers:
- dockerfile: Dockerfile
image_templates:
- image_templates:
- "anchore/syft:latest"
- "anchore/syft:{{ .Tag }}"
- "anchore/syft:v{{ .Major }}"
- "anchore/syft:v{{ .Major }}.{{ .Minor }}"
- "anchore/syft:{{ .Tag }}-amd64"
- "anchore/syft:v{{ .Major }}-amd64"
- "anchore/syft:v{{ .Major }}.{{ .Minor }}-amd64"
dockerfile: Dockerfile
use: buildx
build_flag_templates:
- "--platform=linux/amd64"
- "--build-arg=BUILD_DATE={{.Date}}"
- "--build-arg=BUILD_VERSION={{.Version}}"
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
use_buildx: true
- image_templates:
- "anchore/syft:{{ .Tag }}-arm64v8"
- "anchore/syft:v{{ .Major }}-arm64v8"
- "anchore/syft:v{{ .Major }}.{{ .Minor }}-arm64v8"
goarch: arm64
dockerfile: Dockerfile
use: buildx
build_flag_templates:
- "--platform=linux/arm64/v8"
- "--build-arg=BUILD_DATE={{.Date}}"
- "--build-arg=BUILD_VERSION={{.Version}}"
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
docker_manifests:
- name_template: anchore/syft:{{ .Tag }}
image_templates:
- anchore/syft:{{ .Tag }}-amd64
- anchore/syft:v{{ .Major }}-amd64
- anchore/syft:v{{ .Major }}.{{ .Minor }}-amd64
- anchore/syft:{{ .Tag }}-arm64v8
- anchore/syft:v{{ .Major }}-arm64v8
- anchore/syft:v{{ .Major }}.{{ .Minor }}-arm64v8
- name_template: anchore/syft:latest
image_templates:
- anchore/syft:{{ .Tag }}-amd64
- anchore/syft:v{{ .Major }}-amd64
- anchore/syft:v{{ .Major }}.{{ .Minor }}-amd64
- anchore/syft:{{ .Tag }}-arm64v8
- anchore/syft:v{{ .Major }}-arm64v8
- anchore/syft:v{{ .Major }}.{{ .Minor }}-arm64v8
+11 -6
View File
@@ -1,12 +1,12 @@
# Contributing to `syft`
# Contributing to Syft
If you are looking to contribute to this project and want to open a Github pull request ("PR"), there are a few guidelines of what we are looking for in patches. Make sure you go through this document and ensure that your code proposal is aligned.
If you are looking to contribute to this project and want to open a GitHub pull request ("PR"), there are a few guidelines of what we are looking for in patches. Make sure you go through this document and ensure that your code proposal is aligned.
## Sign your work
## Sign off your work
The `sign-off` is an added line at the end of the explanation for the commit, certifying that you wrote it or otherwise have the right to submit it as an open-source patch. By submitting a contribution, you agree to be bound by the terms of the DCO Version 1.1 and Apache License Version 2.0.
Signing a commit certifies the below Developer's Certificate of Origin (DCO):
Signing off a commit certifies the below Developer's Certificate of Origin (DCO):
```text
Developer's Certificate of Origin 1.1
@@ -51,13 +51,13 @@ $ git config --global user.name "John Doe"
$ git config --global user.email "john.doe@example.com"
```
Creating a signed commit is then possible with `-s`:
Creating a signed-off commit is then possible with `-s` or `--signoff`:
```text
$ git commit -s -m "this is a commit message"
```
To double check if the commit was signed, look at the log output:
To double-check that the commit was signed-off, look at the log output:
```text
$ git log -1
@@ -82,6 +82,11 @@ Date: Mon Aug 1 11:27:13 2020 -0400
[//]: # (TODO: PR title and description)
## Sign your commits
To ensure the authenticity and integrity of code contributions, **we require that all commits are signed**. Signing commits proves that your commits were truly created by you, as the holder of a private key.
Configuring git to sign your commits is a straightforward process. To get set up, see [GitHub's documentation on signing commits](https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits).
## Test your changes
+10 -23
View File
@@ -101,14 +101,14 @@ $(TEMPDIR):
.PHONY: bootstrap-tools
bootstrap-tools: $(TEMPDIR)
GO111MODULE=off GOBIN=$(shell realpath $(TEMPDIR)) go get -u golang.org/x/perf/cmd/benchstat
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(TEMPDIR)/ v1.26.0
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(TEMPDIR)/ v1.42.1
curl -sSfL https://raw.githubusercontent.com/wagoodman/go-bouncer/master/bouncer.sh | sh -s -- -b $(TEMPDIR)/ v0.2.0
curl -sfL https://install.goreleaser.com/github.com/goreleaser/goreleaser.sh | sh -s -- -b $(TEMPDIR)/ v0.160.0
curl -sSfL https://raw.githubusercontent.com/anchore/chronicle/main/install.sh | sh -s -- -b $(TEMPDIR)/ v0.2.0-beta
.github/scripts/goreleaser-install.sh -b $(TEMPDIR)/ v0.177.0
.PHONY: bootstrap-go
bootstrap-go:
go mod download
go mod tidy # note: it is important that the go.sum is kept in a consistent state at all times (especially during release)
.PHONY: bootstrap
bootstrap: $(RESULTSDIR) bootstrap-go bootstrap-tools ## Download and install all go dependencies (+ prep tooling in the ./tmp dir)
@@ -204,7 +204,9 @@ $(SNAPSHOTDIR): ## Build snapshot release binaries and packages
cat .goreleaser.yaml >> $(TEMPDIR)/goreleaser.yaml
# build release snapshots
# DOCKER_CLI_EXPERIMENTAL needed to support multi architecture builds for goreleaser
BUILD_GIT_TREE_STATE=$(GITTREESTATE) \
DOCKER_CLI_EXPERIMENTAL=enabled \
$(TEMPDIR)/goreleaser release --skip-publish --skip-sign --rm-dist --snapshot --config $(TEMPDIR)/goreleaser.yaml
# note: we cannot clean the snapshot directory since the pipeline builds the snapshot separately
@@ -250,33 +252,16 @@ cli: $(SNAPSHOTDIR) ## Run CLI tests
SYFT_BINARY_LOCATION='$(SNAPSHOT_CMD)' \
go test -count=1 -v ./test/cli
.PHONY: changlog-release
.PHONY: changelog-release
changelog-release:
@echo "Last tag: $(SECOND_TO_LAST_TAG)"
@docker run --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' \
--no-pr-wo-labels \
--no-issues-wo-labels \
--since-tag $(SECOND_TO_LAST_TAG)
$(TEMPDIR)/chronicle --since-tag $(SECOND_TO_LAST_TAG) --until-tag $(LAST_TAG) -vv > CHANGELOG.md
@printf '\n$(BOLD)$(CYAN)Release $(VERSION) Changelog$(RESET)\n\n'
@cat CHANGELOG.md
.PHONY: changelog-unreleased
changelog-unreleased: ## show the current changelog that will be produced on the next release (note: requires GITHUB_TOKEN set)
@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)
$(TEMPDIR)/chronicle --since-tag $(LAST_TAG) -vv > CHANGELOG.md
@printf '\n$(BOLD)$(CYAN)Unreleased Changes (closed PRs and issues will not be in the final changelog)$(RESET)\n'
@@ -302,9 +287,11 @@ release: clean-dist changelog-release ## Build and publish final binaries and pa
cat .goreleaser.yaml >> $(TEMPDIR)/goreleaser.yaml
# release (note the version transformation from v0.7.0 --> 0.7.0)
# DOCKER_CLI_EXPERIMENTAL needed to support multi architecture builds for goreleaser
bash -c "\
BUILD_GIT_TREE_STATE=$(GITTREESTATE) \
VERSION=$(VERSION:v%=%) \
DOCKER_CLI_EXPERIMENTAL=enabled \
$(TEMPDIR)/goreleaser \
--rm-dist \
--config $(TEMPDIR)/goreleaser.yaml \
+44 -39
View File
@@ -1,33 +1,19 @@
# syft
<p align="center">
<img src="https://user-images.githubusercontent.com/5199289/136844524-1527b09f-c5cb-4aa9-be54-5aa92a6086c1.png" width="271">
</p>
[![Validations](https://github.com/anchore/syft/actions/workflows/validations.yaml/badge.svg)](https://github.com/anchore/syft/actions/workflows/validations.yaml)
[![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)
[![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/anchore/syft.svg)](https://github.com/anchore/syft)
[![License: Apache-2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/anchore/syft/blob/main/LICENSE)
[![Slack Invite](https://img.shields.io/badge/Slack-Join-blue?logo=slack)](https://anchore.com/slack)
A CLI tool and go library for generating a Software Bill of Materials (SBOM) from container images and filesystems. Exceptional for vulnerability detection when used with a scanner tool like [Grype](https://github.com/anchore/grype).
## Happening soon: [OSS Virtual Meetup](https://get.anchore.com/anchore-oss-meetup-jun-30-2021/)
[**Register here!**](https://get.anchore.com/anchore-oss-meetup-jun-30-2021/)
**When:** June 30, 11am-noon PT
**What:** 3 fast-paced talks, with time for Q&A
- **Using Syft to Streamline Compliance** (Chet Burgess — Principal Engineer, Cisco)
- **Fast Container Scanning in CI: Three Ways to Use Grype in Your Builds** (Dan Luhring — Manager of Open Source Engineering, Anchore)
- **Building More Optimal Container Images with Dive** (Alex Goodman — Creator of [Dive](https://github.com/wagoodman/dive), Sr Software Engineer for Open Source, Anchore)
A CLI tool and Go library for generating a Software Bill of Materials (SBOM) from container images and filesystems. Exceptional for vulnerability detection when used with a scanner tool like [Grype](https://github.com/anchore/grype).
![syft-demo](https://user-images.githubusercontent.com/590471/90277200-2a253000-de33-11ea-893f-32c219eea11a.gif)
**Features**
## Features
- 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)
@@ -37,6 +23,27 @@ A CLI tool and go library for generating a Software Bill of Materials (SBOM) fro
If you encounter an issue, please [let us know using the issue tracker](https://github.com/anchore/syft/issues).
## Installation
### Recommended
```bash
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
```
...or, you can specify a release version and destination directory for the installation:
```
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b <DESTINATION_DIR> <RELEASE_VERSION>
```
### Homebrew
```bash
brew tap anchore/syft
brew install syft
```
**Note**: Currently, Syft is built only for macOS and Linux.
## Getting started
To generate an SBOM for a Docker or OCI image:
@@ -56,7 +63,10 @@ To include software from all image layers in the SBOM, regardless of its presenc
syft packages <image> --scope all-layers
```
### Supported sources
Syft can generate a SBOM from a variety of sources:
```
# catalog a container image archive (from the result of `docker image save ...`, `podman save ...`, or `skopeo copy` commands)
syft packages path/to/image.tar
@@ -66,6 +76,7 @@ syft packages path/to/dir
```
Sources can be explicitly provided with a scheme:
```
docker:yourrepo/yourimage:tag use images from the Docker daemon
docker-archive:path/to/yourimage.tar use a tarball from disk for archives created from "docker save"
@@ -75,7 +86,10 @@ dir:path/to/yourproject read directly from a path on disk (any di
registry:yourrepo/yourimage:tag pull image directly from a registry (no container runtime required)
```
### Output formats
The output format for Syft is configurable as well:
```
syft packages <image> -o <format>
```
@@ -83,26 +97,11 @@ syft packages <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.
- `cyclonedx`: A XML report conforming to the [CycloneDX 1.2 specification](https://cyclonedx.org/specification/overview/).
- `spdx`: A tag-value formatted report conforming to the [SPDX 2.2 specification](https://spdx.github.io/spdx-spec/).
- `spdx-json`: A JSON report conforming to the [SPDX 2.2 JSON Schema](https://github.com/spdx/spdx-spec/blob/v2.2/schemas/spdx-schema.json).
- `table`: A columnar summary (default).
## Installation
**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 -- -b <SOME_BIN_PATH> <RELEASE_VERSION>
```
**Homebrew (macOS)**
```bash
brew tap anchore/syft
brew install syft
```
## Configuration
Configuration search paths:
@@ -123,6 +122,9 @@ output: "table"
# same as -q ; SYFT_QUIET env var
quiet: false
# same as --file; write output report to a file (default is to write to stdout)
file: ""
# enable/disable checking for application updates on startup
# same as SYFT_CHECK_FOR_APP_UPDATE env var
check-for-app-update: true
@@ -219,6 +221,9 @@ registry:
# skip TLS verification when communicating with the registry
# SYFT_REGISTRY_INSECURE_SKIP_TLS_VERIFY env var
insecure-skip-tls-verify: false
# use http instead of https when connecting to the registry
# SYFT_REGISTRY_INSECURE_USE_HTTP env var
insecure-use-http: false
# credentials for specific registries
auth:
+5 -2
View File
@@ -87,9 +87,12 @@ This release process itself should be as automated as possible, and have only a
1. Tag the main branch with a full semantic-version, prefixed with a `v`. If there is a
milestone with a partial version, the full version should be used for the git tag (e.g.
with a Milestone of `v0.1` the tag should be `v0.1.0`).
with a Milestone of `v0.1` the tag should be `v0.1.0`). You can determine the changes going
into a release by running `make changelog-unreleased`. Use this change list to determine the
release increment. After determining the release increment (major, minor, patch), create the tag.
Given the above example the command to create the tag would be `git tag v0.1.0`.
1. Push the tag.
1. Push the tag. Given the above example the command to push the tag would be `git push origin v0.1.0`.
1. A release admin must approve the release on the GitHub Actions release pipeline run page.
Once approved, the release pipeline will generate all assets and draft a GitHub Release.
+2 -3
View File
@@ -14,9 +14,8 @@ import (
// completionCmd represents the completion command
var completionCmd = &cobra.Command{
Hidden: true,
Use: "completion [bash|zsh|fish]",
Short: "Generate a shell completion for Syft (listing local docker images)",
Use: "completion [bash|zsh|fish]",
Short: "Generate a shell completion for Syft (listing local docker images)",
Long: `To load completions (docker image list):
Bash:
+19 -11
View File
@@ -2,6 +2,7 @@ package cmd
import (
"errors"
"fmt"
"os"
"github.com/anchore/syft/internal/log"
@@ -14,11 +15,13 @@ import (
// signal interrupts. Is responsible for handling each event relative to a given UI an to coordinate eventing until
// an eventual graceful exit.
// nolint:gocognit,funlen
func eventLoop(workerErrs <-chan error, signals <-chan os.Signal, subscription *partybus.Subscription, ux ui.UI, cleanupFn func()) error {
func eventLoop(workerErrs <-chan error, signals <-chan os.Signal, subscription *partybus.Subscription, cleanupFn func(), uxs ...ui.UI) error {
defer cleanupFn()
events := subscription.Events()
var err error
if ux, err = setupUI(subscription.Unsubscribe, ux); err != nil {
var ux ui.UI
if ux, err = setupUI(subscription.Unsubscribe, uxs...); err != nil {
return err
}
@@ -41,6 +44,9 @@ func eventLoop(workerErrs <-chan error, signals <-chan os.Signal, subscription *
if err := subscription.Unsubscribe(); err != nil {
retErr = multierror.Append(retErr, err)
}
// the worker has exited, we may have been mid-handling events for the UI which should now be
// ignored, in which case forcing a teardown of the UI irregardless of the state is required.
forceTeardown = true
}
case e, isOpen := <-events:
if !isOpen {
@@ -50,7 +56,6 @@ func eventLoop(workerErrs <-chan error, signals <-chan os.Signal, subscription *
if err := ux.Handle(e); err != nil {
if errors.Is(err, partybus.ErrUnsubscribe) {
log.Warnf("unable to unsubscribe from the event bus")
events = nil
} else {
retErr = multierror.Append(retErr, err)
@@ -78,15 +83,18 @@ func eventLoop(workerErrs <-chan error, signals <-chan os.Signal, subscription *
return retErr
}
func setupUI(unsubscribe func() error, ux ui.UI) (ui.UI, error) {
if err := ux.Setup(unsubscribe); err != nil {
// replace the existing UI with a (simpler) logger UI
ux = ui.NewLoggerUI()
// setupUI takes one or more UIs that responds to events and takes a event bus unsubscribe function for use
// during teardown. With the given UIs, the first UI which the ui.Setup() function does not return an error
// will be utilized in execution. Providing a set of UIs allows for the caller to provide graceful fallbacks
// when there are environmental problem (e.g. unable to setup a TUI with the current TTY).
func setupUI(unsubscribe func() error, uis ...ui.UI) (ui.UI, error) {
for _, ux := range uis {
if err := ux.Setup(unsubscribe); err != nil {
// something is very wrong, bail.
return ux, err
log.Warnf("unable to setup given UI, falling back to alternative UI: %+v", err)
continue
}
log.Errorf("unable to setup given UI, falling back to logger: %+v", err)
return ux, nil
}
return ux, nil
return nil, fmt.Errorf("unable to setup any UI")
}
+6 -6
View File
@@ -96,8 +96,8 @@ func Test_eventLoop_gracefulExit(t *testing.T) {
worker(),
signaler(),
subscription,
ux,
cleanupFn,
ux,
),
)
@@ -159,8 +159,8 @@ func Test_eventLoop_workerError(t *testing.T) {
worker(),
signaler(),
subscription,
ux,
cleanupFn,
ux,
),
workerErr,
"should have seen a worker error, but did not",
@@ -230,8 +230,8 @@ func Test_eventLoop_unsubscribeError(t *testing.T) {
worker(),
signaler(),
subscription,
ux,
cleanupFn,
ux,
),
)
@@ -300,8 +300,8 @@ func Test_eventLoop_handlerError(t *testing.T) {
worker(),
signaler(),
subscription,
ux,
cleanupFn,
ux,
),
finalEvent.Error,
"should have seen a event error, but did not",
@@ -355,8 +355,8 @@ func Test_eventLoop_signalsStopExecution(t *testing.T) {
worker(),
signaler(),
subscription,
ux,
cleanupFn,
ux,
),
)
@@ -425,8 +425,8 @@ func Test_eventLoop_uiTeardownError(t *testing.T) {
worker(),
signaler(),
subscription,
ux,
cleanupFn,
ux,
),
teardownError,
"should have seen a UI teardown error, but did not",
+62 -24
View File
@@ -6,6 +6,8 @@ import (
"io/ioutil"
"os"
"github.com/anchore/syft/syft/format"
"github.com/anchore/stereoscope"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/anchore"
@@ -28,12 +30,14 @@ import (
const (
packagesExample = ` {{.appName}} {{.command}} alpine:latest a summary of discovered packages
{{.appName}} {{.command}} alpine:latest -o json show all possible cataloging details
{{.appName}} {{.command}} alpine:latest -o cyclonedx show a CycloneDX SBOM
{{.appName}} {{.command}} alpine:latest -o cyclonedx show a CycloneDX formatted SBOM
{{.appName}} {{.command}} alpine:latest -o spdx show a SPDX 2.2 tag-value formatted SBOM
{{.appName}} {{.command}} alpine:latest -o spdx-json show a SPDX 2.2 JSON formatted SBOM
{{.appName}} {{.command}} alpine:latest -vv show verbose debug information
Supports the following image sources:
{{.appName}} {{.command}} yourrepo/yourimage:tag defaults to using images from a Docker daemon. If Docker is not present, the image is pulled directly from the registry.
{{.appName}} {{.command}} path/to/a/file/or/dir a Docker tar, OCI tar, OCI directory, or generic filesystem directory
{{.appName}} {{.command}} path/to/a/file/or/dir a Docker tar, OCI tar, OCI directory, or generic filesystem directory
You can also explicitly specify the scheme to use:
{{.appName}} {{.command}} docker:yourrepo/yourimage:tag explicitly use the Docker daemon
@@ -46,8 +50,7 @@ const (
)
var (
packagesPresenterOpt packages.PresenterOption
packagesArgs = cobra.MinimumNArgs(1)
packagesPresenterOpt format.Option
packagesCmd = &cobra.Command{
Use: "packages [SOURCE]",
Short: "Generate a package SBOM",
@@ -56,22 +59,13 @@ var (
"appName": internal.ApplicationName,
"command": "packages",
}),
Args: packagesArgs,
Args: validateInputArgs,
SilenceUsage: true,
SilenceErrors: true,
PreRunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
err := cmd.Help()
if err != nil {
return err
}
// silently exit
return fmt.Errorf("")
}
// set the presenter
presenterOption := packages.ParsePresenterOption(appConfig.Output)
if presenterOption == packages.UnknownPresenterOption {
presenterOption := format.ParseOption(appConfig.Output)
if presenterOption == format.UnknownFormatOption {
return fmt.Errorf("bad --output value '%s'", appConfig.Output)
}
packagesPresenterOpt = presenterOption
@@ -101,18 +95,23 @@ func init() {
}
func setPackageFlags(flags *pflag.FlagSet) {
///////// Formatting & Input options //////////////////////////////////////////////
// Formatting & Input options //////////////////////////////////////////////
flags.StringP(
"scope", "s", source.SquashedScope.String(),
fmt.Sprintf("selection of layers to catalog, options=%v", source.AllScopes))
flags.StringP(
"output", "o", string(packages.TablePresenterOption),
fmt.Sprintf("report output formatter, options=%v", packages.AllPresenters),
"output", "o", string(format.TableOption),
fmt.Sprintf("report output formatter, options=%v", format.AllPresenters),
)
///////// Upload options //////////////////////////////////////////////////////////
flags.StringP(
"file", "", "",
"file to write the report output to (default is STDOUT)",
)
// Upload options //////////////////////////////////////////////////////////
flags.StringP(
"host", "H", "",
"the hostname or URL of the Anchore Enterprise instance to upload to",
@@ -145,7 +144,7 @@ func setPackageFlags(flags *pflag.FlagSet) {
}
func bindPackagesConfigOptions(flags *pflag.FlagSet) error {
///////// Formatting & Input options //////////////////////////////////////////////
// Formatting & Input options //////////////////////////////////////////////
if err := viper.BindPFlag("package.cataloger.scope", flags.Lookup("scope")); err != nil {
return err
@@ -155,7 +154,11 @@ func bindPackagesConfigOptions(flags *pflag.FlagSet) error {
return err
}
///////// Upload options //////////////////////////////////////////////////////////
if err := viper.BindPFlag("file", flags.Lookup("file")); err != nil {
return err
}
// Upload options //////////////////////////////////////////////////////////
if err := viper.BindPFlag("anchore.host", flags.Lookup("host")); err != nil {
return err
@@ -184,18 +187,53 @@ func bindPackagesConfigOptions(flags *pflag.FlagSet) error {
return nil
}
func validateInputArgs(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
// in the case that no arguments are given we want to show the help text and return with a non-0 return code.
if err := cmd.Help(); err != nil {
return fmt.Errorf("unable to display help: %w", err)
}
return fmt.Errorf("an image/directory argument is required")
}
return cobra.MaximumNArgs(1)(cmd, args)
}
func packagesExec(_ *cobra.Command, args []string) error {
// could be an image or a directory, with or without a scheme
userInput := args[0]
reporter, closer, err := reportWriter()
defer func() {
if err := closer(); err != nil {
log.Warnf("unable to write to report destination: %+v", err)
}
}()
if err != nil {
return err
}
return eventLoop(
packagesExecWorker(userInput),
setupSignals(),
eventSubscription,
ui.Select(appConfig.CliOptions.Verbosity > 0, appConfig.Quiet),
stereoscope.Cleanup,
ui.Select(isVerbose(), appConfig.Quiet, reporter)...,
)
}
func isVerbose() (result bool) {
isPipedInput, err := internal.IsPipedInput()
if err != nil {
// since we can't tell if there was piped input we assume that there could be to disable the ETUI
log.Warnf("unable to determine if there is piped input: %+v", err)
return true
}
// verbosity should consider if there is piped input (in which case we should not show the ETUI)
return appConfig.CliOptions.Verbosity > 0 || isPipedInput
}
func packagesExecWorker(userInput string) <-chan error {
errs := make(chan error)
go func() {
@@ -236,7 +274,7 @@ func packagesExecWorker(userInput string) <-chan error {
return errs
}
func runPackageSbomUpload(src source.Source, s source.Metadata, catalog *pkg.Catalog, d *distro.Distro, scope source.Scope) error {
func runPackageSbomUpload(src *source.Source, s source.Metadata, catalog *pkg.Catalog, d *distro.Distro, scope source.Scope) error {
log.Infof("uploading results to %s", appConfig.Anchore.Host)
if src.Metadata.Scheme != source.ImageScheme {
+15 -7
View File
@@ -7,6 +7,7 @@ import (
"github.com/anchore/stereoscope"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/bus"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/presenter/poweruser"
"github.com/anchore/syft/internal/ui"
"github.com/anchore/syft/syft/event"
@@ -34,7 +35,7 @@ var powerUserCmd = &cobra.Command{
"appName": internal.ApplicationName,
"command": "power-user",
}),
Args: cobra.ExactArgs(1),
Args: validateInputArgs,
Hidden: true,
SilenceUsage: true,
SilenceErrors: true,
@@ -65,12 +66,24 @@ func init() {
func powerUserExec(_ *cobra.Command, args []string) error {
// could be an image or a directory, with or without a scheme
userInput := args[0]
reporter, closer, err := reportWriter()
defer func() {
if err := closer(); err != nil {
log.Warnf("unable to write to report destination: %+v", err)
}
}()
if err != nil {
return err
}
return eventLoop(
powerUserExecWorker(userInput),
setupSignals(),
eventSubscription,
ui.Select(appConfig.CliOptions.Verbosity > 0, appConfig.Quiet),
stereoscope.Cleanup,
ui.Select(isVerbose(), appConfig.Quiet, reporter)...,
)
}
@@ -94,11 +107,6 @@ func powerUserExecWorker(userInput string) <-chan error {
}
defer cleanup()
if src.Metadata.Scheme != source.ImageScheme {
errs <- fmt.Errorf("the power-user subcommand only allows for 'image' schemes, given %q", src.Metadata.Scheme)
return
}
analysisResults := poweruser.JSONDocumentConfig{
SourceMetadata: src.Metadata,
ApplicationConfig: *appConfig,
+7 -7
View File
@@ -10,7 +10,7 @@ import (
"github.com/anchore/syft/syft/source"
)
type powerUserTask func(*poweruser.JSONDocumentConfig, source.Source) error
type powerUserTask func(*poweruser.JSONDocumentConfig, *source.Source) error
func powerUserTasks() ([]powerUserTask, error) {
var tasks []powerUserTask
@@ -42,7 +42,7 @@ func catalogPackagesTask() (powerUserTask, error) {
return nil, nil
}
task := func(results *poweruser.JSONDocumentConfig, src source.Source) error {
task := func(results *poweruser.JSONDocumentConfig, src *source.Source) error {
packageCatalog, theDistro, err := syft.CatalogPackages(src, appConfig.Package.Cataloger.ScopeOpt)
if err != nil {
return err
@@ -64,7 +64,7 @@ func catalogFileMetadataTask() (powerUserTask, error) {
metadataCataloger := file.NewMetadataCataloger()
task := func(results *poweruser.JSONDocumentConfig, src source.Source) error {
task := func(results *poweruser.JSONDocumentConfig, src *source.Source) error {
resolver, err := src.FileResolver(appConfig.FileMetadata.Cataloger.ScopeOpt)
if err != nil {
return err
@@ -110,7 +110,7 @@ func catalogFileDigestsTask() (powerUserTask, error) {
return nil, err
}
task := func(results *poweruser.JSONDocumentConfig, src source.Source) error {
task := func(results *poweruser.JSONDocumentConfig, src *source.Source) error {
resolver, err := src.FileResolver(appConfig.FileMetadata.Cataloger.ScopeOpt)
if err != nil {
return err
@@ -142,7 +142,7 @@ func catalogSecretsTask() (powerUserTask, error) {
return nil, err
}
task := func(results *poweruser.JSONDocumentConfig, src source.Source) error {
task := func(results *poweruser.JSONDocumentConfig, src *source.Source) error {
resolver, err := src.FileResolver(appConfig.Secrets.Cataloger.ScopeOpt)
if err != nil {
return err
@@ -170,7 +170,7 @@ func catalogFileClassificationsTask() (powerUserTask, error) {
return nil, err
}
task := func(results *poweruser.JSONDocumentConfig, src source.Source) error {
task := func(results *poweruser.JSONDocumentConfig, src *source.Source) error {
resolver, err := src.FileResolver(appConfig.FileClassification.Cataloger.ScopeOpt)
if err != nil {
return err
@@ -197,7 +197,7 @@ func catalogContentsTask() (powerUserTask, error) {
return nil, err
}
task := func(results *poweruser.JSONDocumentConfig, src source.Source) error {
task := func(results *poweruser.JSONDocumentConfig, src *source.Source) error {
resolver, err := src.FileResolver(appConfig.FileContents.Cataloger.ScopeOpt)
if err != nil {
return err
+33
View File
@@ -0,0 +1,33 @@
package cmd
import (
"fmt"
"io"
"os"
"strings"
"github.com/anchore/syft/internal/log"
)
func reportWriter() (io.Writer, func() error, error) {
nop := func() error { return nil }
path := strings.TrimSpace(appConfig.File)
switch len(path) {
case 0:
return os.Stdout, nop, nil
default:
reportFile, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
return nil, nop, fmt.Errorf("unable to create report file: %w", err)
}
return reportFile, func() error {
log.Infof("report written to file=%q", path)
return reportFile.Close()
}, nil
}
}
+1
View File
@@ -33,6 +33,7 @@ func init() {
flag, "q", false,
"suppress all logging output",
)
if err := viper.BindPFlag(flag, rootCmd.PersistentFlags().Lookup(flag)); err != nil {
fmt.Printf("unable to bind flag '%s': %+v", flag, err)
os.Exit(1)
+11 -9
View File
@@ -7,42 +7,44 @@ require (
github.com/adrg/xdg v0.2.1
github.com/alecthomas/jsonschema v0.0.0-20210301060011-54c507b6f074
github.com/anchore/client-go v0.0.0-20210222170800-9c70f9b80bcf
github.com/anchore/go-rpmdb v0.0.0-20210602151223-1f0f707a2894
github.com/anchore/go-rpmdb v0.0.0-20210914181456-a9c52348da63
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-20210524175238-3b7662f3a66f
github.com/anchore/packageurl-go v0.0.0-20210922164639-b3fa992ebd29
github.com/anchore/stereoscope v0.0.0-20211024152658-003132a67c10
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/facebookincubator/nvdtools v0.1.4
github.com/go-test/deep v1.0.7
github.com/google/uuid v1.1.1
github.com/google/go-cmp v0.4.1
github.com/google/uuid v1.2.0
github.com/gookit/color v1.2.7
github.com/hashicorp/go-multierror v1.1.0
github.com/hashicorp/go-version v1.2.0
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213
github.com/mitchellh/go-homedir v1.1.0
github.com/mitchellh/mapstructure v1.3.1
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/pelletier/go-toml v1.8.1
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/sirupsen/logrus v1.8.1
github.com/spdx/tools-golang v0.1.0
github.com/spf13/afero v1.2.2
github.com/spf13/cobra v1.0.1-0.20200909172742-8a63648dd905
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.7.0
github.com/stretchr/testify v1.7.0
github.com/vifraa/gopom v0.1.0
github.com/wagoodman/go-partybus v0.0.0-20210627031916-db1f5573bbc5
github.com/wagoodman/go-progress v0.0.0-20200731105512-1020f39e6240
github.com/wagoodman/jotframe v0.0.0-20200730190914-3517092dd163
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/mod v0.3.0
gopkg.in/yaml.v2 v2.3.0
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1
gopkg.in/yaml.v2 v2.4.0
)
+34 -24
View File
@@ -73,7 +73,6 @@ github.com/Azure/go-autorest/autorest/validation v0.1.0/go.mod h1:Ha3z/SqBeaalWQ
github.com/Azure/go-autorest/autorest/validation v0.2.0/go.mod h1:3EEqHnBxQGHXRYq3HT1WyXAvT7LLY3tl70hw6tQIbjI=
github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Djarvur/go-err113 v0.0.0-20200410182137-af658d038157/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs=
@@ -109,19 +108,20 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/anchore/client-go v0.0.0-20210222170800-9c70f9b80bcf h1:DYssiUV1pBmKqzKsm4mqXx8artqC0Q8HgZsVI3lMsAg=
github.com/anchore/client-go v0.0.0-20210222170800-9c70f9b80bcf/go.mod h1:FaODhIA06mxO1E6R32JE0TL1JWZZkmjRIAd4ULvHUKk=
github.com/anchore/go-rpmdb v0.0.0-20210602151223-1f0f707a2894 h1:VvCq7fFNU8dwWkCTGUykm4p64nVaDCYkKrj87x350Sk=
github.com/anchore/go-rpmdb v0.0.0-20210602151223-1f0f707a2894/go.mod h1:8jNYOxCJC5kyD/Ct4MbzsDN2hOhRoCAzQcb/7KdYYGw=
github.com/anchore/go-rpmdb v0.0.0-20210914181456-a9c52348da63 h1:C9W/LAydEz/qdUhx1MdjO9l8NEcFKYknkxDVyo9LAoM=
github.com/anchore/go-rpmdb v0.0.0-20210914181456-a9c52348da63/go.mod h1:6qH8c6U/3CBVvDDDBZnPSTbTINq3cIdADUYTaVf75EM=
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-20210524175238-3b7662f3a66f h1:bFadyOLOkzME3BrZFZ5m8cf/b2hsn3aMSS9s+SKubRk=
github.com/anchore/stereoscope v0.0.0-20210524175238-3b7662f3a66f/go.mod h1:vhh1M99rfWx5ejMvz1lkQiFZUrC5wu32V12R4JXH+ZI=
github.com/anchore/packageurl-go v0.0.0-20210922164639-b3fa992ebd29 h1:K9LfnxwhqvihqU0+MF325FNy7fsKV9EGaUxdfR4gnWk=
github.com/anchore/packageurl-go v0.0.0-20210922164639-b3fa992ebd29/go.mod h1:Oc1UkGaJwY6ND6vtAqPSlYrptKRJngHwkwB6W7l1uP0=
github.com/anchore/stereoscope v0.0.0-20211024152658-003132a67c10 h1:BmK/CgNlu+X9foWK2ZAIehxzYws760AZSGVNamQZpiw=
github.com/anchore/stereoscope v0.0.0-20211024152658-003132a67c10/go.mod h1:Rqltg0KqVKoDy4kCuJMLPFDfUg7pEmSqUEA9pOO1L7c=
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=
github.com/apex/logs v0.0.4/go.mod h1:XzxuLZ5myVHDy9SAmYpamKKRNApGj54PfYLcFrXqDwo=
github.com/aphistic/golf v0.0.0-20180712155816-02c07f170c5a/go.mod h1:3NqKYiepwy8kCu4PNA+aP7WUV72eXWJeP9/r3/K9aLE=
@@ -147,6 +147,8 @@ github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAw
github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
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/bmatcuk/doublestar/v4 v4.0.2 h1:X0krlUVAVmtr2cRoTqR8aDMrDqnB36ht8wpWTiQ3jsA=
github.com/bmatcuk/doublestar/v4 v4.0.2/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
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=
@@ -241,6 +243,8 @@ github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHqu
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gabriel-vasile/mimetype v1.3.0 h1:4YOHITFLyYwF+iqG0ybSLGArRItynpfwdlWRmJnd75E=
github.com/gabriel-vasile/mimetype v1.3.0/go.mod h1:fA8fi6KUiG7MgQQ+mEWotXoEOvmxRtOJlERCzSmRvr8=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-critic/go-critic v0.4.1/go.mod h1:7/14rZGnZbY6E38VEGk2kVhoq6itzc1E68facVDK23g=
@@ -266,8 +270,8 @@ github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8
github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-restruct/restruct v0.0.0-20191227155143-5734170a48a1 h1:LoN2wx/aN8JPGebG+2DaUyk4M+xRcqJXfuIbs8AWHdE=
github.com/go-restruct/restruct v0.0.0-20191227155143-5734170a48a1/go.mod h1:KqrpKpn4M8OLznErihXTGLlsXFGeLxHUrLRRI/1YjGk=
github.com/go-restruct/restruct v1.2.0-alpha h1:2Lp474S/9660+SJjpVxoKuWX09JsXHSrdV7Nv3/gkvc=
github.com/go-restruct/restruct v1.2.0-alpha/go.mod h1:KqrpKpn4M8OLznErihXTGLlsXFGeLxHUrLRRI/1YjGk=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
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=
@@ -371,8 +375,9 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4
github.com/google/rpmpack v0.0.0-20191226140753-aa36bfddb3a0/go.mod h1:RaTPr0KUf2K7fnZYLNDrr8rxAamWs3iNywJLtQ2AzBg=
github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/wire v0.3.0/go.mod h1:i1DMg/Lu8Sz5yYl25iOdmc5CT5qusaa+zmRWs16741s=
github.com/google/wire v0.4.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU=
github.com/googleapis/gax-go v2.0.2+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
@@ -466,7 +471,6 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 h1:qGQQKEcAR99REcMpsXCp3lJ03zYT1PkRd3kQGPn9GVg=
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
@@ -477,7 +481,6 @@ github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e/go.mod h1:Pj4uuM52
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@@ -590,13 +593,12 @@ github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5X
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 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=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.8.0 h1:Keo9qb7iRJs2voHvunFtuuYFsbWeOBh8/P9v/kVMFtw=
github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs=
github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM=
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d/go.mod h1:3OzsM7FXDQlpCiw2j81fOmAwQLnZnLGXVKUzeKQXIAw=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -660,8 +662,9 @@ 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.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v1.0.0 h1:UVQPSSmc3qtTi+zPPkCXvZX9VvW/xT/NsRvKfwY81a8=
github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
@@ -713,6 +716,7 @@ github.com/stretchr/testify v1.3.1-0.20190311161405-34c6fa2dc709/go.mod h1:M5WIy
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
@@ -723,7 +727,6 @@ github.com/tetafro/godot v0.3.7/go.mod h1:/7NLHhv08H1+8DNj0MElpAACw1ajsCuf3TKNQx
github.com/tetafro/godot v0.4.2/go.mod h1:/7NLHhv08H1+8DNj0MElpAACw1ajsCuf3TKNQxA5S+0=
github.com/timakin/bodyclose v0.0.0-20190930140734-f7f2e9bca95e/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk=
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=
@@ -746,6 +749,8 @@ github.com/valyala/fasthttp v1.2.0/go.mod h1:4vX61m6KN+xDduDNwXrhIAVZaZaZiQ1luJk
github.com/valyala/quicktemplate v1.2.0/go.mod h1:EH+4AkTd43SvgIbQHYu59/cJyxDoOVRUAfrukLPuGJ4=
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
github.com/vdemeester/k8s-pkg-credentialprovider v1.17.4/go.mod h1:inCTmtUdr5KJbreVojo06krnTgaeAz/Z7lynpPk/Q2c=
github.com/vifraa/gopom v0.1.0 h1:v897eVxf6lflkEXzPmKbo4YhX2oS/LGjz7cqjWnSmCU=
github.com/vifraa/gopom v0.1.0/go.mod h1:oPa1dcrGrtlO37WPDBm5SqHAT+wTgF8An1Q71Z6Vv4o=
github.com/vmware/govmomi v0.20.3/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU=
github.com/wagoodman/go-partybus v0.0.0-20200526224238-eb215533f07d/go.mod h1:JPirS5jde/CF5qIjcK4WX+eQmKXdPc6vcZkJ/P0hfPw=
github.com/wagoodman/go-partybus v0.0.0-20210627031916-db1f5573bbc5 h1:phTLPgMRDYTizrBSKsNSOa2zthoC2KsJsaY/8sg3rD8=
@@ -798,9 +803,8 @@ golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 h1:cg5LA/zNPRzIXIWSCxQW10Rvpy94aQh3LT/ShoCpkHw=
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -872,8 +876,9 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/
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/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-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d h1:LO7XpTYMwTqxjLcGWPijK3vRXg1aWdlNOVOHRq45d7c=
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
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=
@@ -938,14 +943,18 @@ golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
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=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -1132,8 +1141,9 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
+3 -3
View File
@@ -8,7 +8,7 @@ import (
"fmt"
"net/http"
"github.com/anchore/syft/internal/presenter/packages"
"github.com/anchore/syft/internal/formats/syftjson"
"github.com/wagoodman/go-progress"
@@ -26,8 +26,8 @@ type packageSBOMImportAPI interface {
func packageSbomModel(s source.Metadata, catalog *pkg.Catalog, d *distro.Distro, scope source.Scope) (*external.ImagePackageManifest, error) {
var buf bytes.Buffer
pres := packages.NewJSONPresenter(catalog, s, d, scope)
err := pres.Present(&buf)
err := syftjson.Format().Presenter(catalog, &s, d, scope).Present(&buf)
if err != nil {
return nil, fmt.Errorf("unable to serialize results: %w", err)
}
+8 -11
View File
@@ -9,18 +9,15 @@ import (
"strings"
"testing"
"github.com/anchore/syft/internal/presenter/packages"
"github.com/wagoodman/go-progress"
"github.com/anchore/syft/syft/distro"
"github.com/docker/docker/pkg/ioutils"
"github.com/anchore/client-go/pkg/external"
"github.com/anchore/syft/internal/formats/syftjson"
syftjsonModel "github.com/anchore/syft/internal/formats/syftjson/model"
"github.com/anchore/syft/syft/distro"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/source"
"github.com/docker/docker/pkg/ioutils"
"github.com/go-test/deep"
"github.com/wagoodman/go-progress"
)
func must(c pkg.CPE, e error) pkg.CPE {
@@ -88,19 +85,19 @@ func TestPackageSbomToModel(t *testing.T) {
}
var buf bytes.Buffer
pres := packages.NewJSONPresenter(c, m, &d, source.AllLayersScope)
pres := syftjson.Format().Presenter(c, &m, &d, source.AllLayersScope)
if err := pres.Present(&buf); err != nil {
t.Fatalf("unable to get expected json: %+v", err)
}
// unmarshal expected result
var expectedDoc packages.JSONDocument
var expectedDoc syftjsonModel.Document
if err := json.Unmarshal(buf.Bytes(), &expectedDoc); err != nil {
t.Fatalf("unable to parse json doc: %+v", err)
}
// unmarshal actual result
var actualDoc packages.JSONDocument
var actualDoc syftjsonModel.Document
if err := json.Unmarshal(modelJSON, &actualDoc); err != nil {
t.Fatalf("unable to parse json doc: %+v", err)
}
+51 -32
View File
@@ -29,6 +29,7 @@ type parser interface {
type Application struct {
ConfigPath string `yaml:",omitempty" json:"configPath"` // the location where the application config was read from (either from -c or discovered while loading)
Output string `yaml:"output" json:"output" mapstructure:"output"` // -o, the Presenter hint string to use for report formatting
File string `yaml:"file" json:"file" mapstructure:"file"` // --file, the file to write report output to
Quiet bool `yaml:"quiet" json:"quiet" mapstructure:"quiet"` // -q, indicates to not show any status output to stderr (ETUI or logging UI)
CheckForAppUpdate bool `yaml:"check-for-app-update" json:"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" json:"anchore" mapstructure:"anchore"` // options for interacting with Anchore Engine/Enterprise
@@ -88,42 +89,18 @@ func (cfg Application) loadDefaultValues(v *viper.Viper) {
}
}
// build inflates simple config values into syft native objects (or other complex objects) after the config is fully read in.
func (cfg *Application) parseConfigValues() error {
if cfg.Quiet {
// TODO: this is bad: quiet option trumps all other logging options
// we should be able to quiet the console logging and leave file logging alone...
// ... this will be an enhancement for later
cfg.Log.LevelOpt = logrus.PanicLevel
} else {
if cfg.Log.Level != "" {
if cfg.CliOptions.Verbosity > 0 {
return fmt.Errorf("cannot explicitly set log level (cfg file or env var) and use -v flag together")
}
lvl, err := logrus.ParseLevel(strings.ToLower(cfg.Log.Level))
if err != nil {
return fmt.Errorf("bad log level configured (%q): %w", cfg.Log.Level, err)
}
// set the log level explicitly
cfg.Log.LevelOpt = lvl
} else {
// set the log level implicitly
switch v := cfg.CliOptions.Verbosity; {
case v == 1:
cfg.Log.LevelOpt = logrus.InfoLevel
case v >= 2:
cfg.Log.LevelOpt = logrus.DebugLevel
default:
cfg.Log.LevelOpt = logrus.WarnLevel
}
// parse application config options
for _, optionFn := range []func() error{
cfg.parseUploadOptions,
cfg.parseLogLevelOption,
} {
if err := optionFn(); err != nil {
return err
}
}
if cfg.Anchore.Host == "" && cfg.Anchore.Dockerfile != "" {
return fmt.Errorf("cannot provide dockerfile option without enabling upload")
}
// parse nested config options
// for each field in the configuration struct, see if the field implements the parser interface
// note: the app config is a pointer, so we need to grab the elements explicitly (to traverse the address)
value := reflect.ValueOf(cfg).Elem()
@@ -136,6 +113,48 @@ func (cfg *Application) parseConfigValues() error {
}
}
}
return nil
}
func (cfg *Application) parseUploadOptions() error {
if cfg.Anchore.Host == "" && cfg.Anchore.Dockerfile != "" {
return fmt.Errorf("cannot provide dockerfile option without enabling upload")
}
return nil
}
func (cfg *Application) parseLogLevelOption() error {
switch {
case cfg.Quiet:
// TODO: this is bad: quiet option trumps all other logging options (such as to a file on disk)
// we should be able to quiet the console logging and leave file logging alone...
// ... this will be an enhancement for later
cfg.Log.LevelOpt = logrus.PanicLevel
case cfg.Log.Level != "":
if cfg.CliOptions.Verbosity > 0 {
return fmt.Errorf("cannot explicitly set log level (cfg file or env var) and use -v flag together")
}
lvl, err := logrus.ParseLevel(strings.ToLower(cfg.Log.Level))
if err != nil {
return fmt.Errorf("bad log level configured (%q): %w", cfg.Log.Level, err)
}
cfg.Log.LevelOpt = lvl
if cfg.Log.LevelOpt >= logrus.InfoLevel {
cfg.CliOptions.Verbosity = 1
}
default:
switch v := cfg.CliOptions.Verbosity; {
case v == 1:
cfg.Log.LevelOpt = logrus.InfoLevel
case v >= 2:
cfg.Log.LevelOpt = logrus.DebugLevel
default:
cfg.Log.LevelOpt = logrus.ErrorLevel
}
}
return nil
}
+3
View File
@@ -20,11 +20,13 @@ type RegistryCredentials struct {
type registry struct {
InsecureSkipTLSVerify bool `yaml:"insecure-skip-tls-verify" json:"insecure-skip-tls-verify" mapstructure:"insecure-skip-tls-verify"`
InsecureUseHTTP bool `yaml:"insecure-use-http" json:"insecure-use-http" mapstructure:"insecure-use-http"`
Auth []RegistryCredentials `yaml:"auth" json:"auth" mapstructure:"auth"`
}
func (cfg registry) loadDefaultValues(v *viper.Viper) {
v.SetDefault("registry.insecure-skip-tls-verify", false)
v.SetDefault("registry.insecure-use-http", false)
v.SetDefault("registry.auth", []RegistryCredentials{})
}
@@ -67,6 +69,7 @@ func (cfg *registry) ToOptions() *image.RegistryOptions {
}
return &image.RegistryOptions{
InsecureSkipTLSVerify: cfg.InsecureSkipTLSVerify,
InsecureUseHTTP: cfg.InsecureUseHTTP,
Credentials: auth,
}
}
+55
View File
@@ -2,6 +2,7 @@ package config
import (
"fmt"
"github.com/anchore/stereoscope/pkg/image"
"testing"
"github.com/stretchr/testify/assert"
@@ -54,3 +55,57 @@ func TestHasNonEmptyCredentials(t *testing.T) {
})
}
}
func Test_registry_ToOptions(t *testing.T) {
tests := []struct {
name string
input registry
expected image.RegistryOptions
}{
{
name: "no registry options",
input: registry{},
expected: image.RegistryOptions{
Credentials: []image.RegistryCredentials{},
},
},
{
name: "set InsecureSkipTLSVerify",
input: registry{
InsecureSkipTLSVerify: true,
},
expected: image.RegistryOptions{
InsecureSkipTLSVerify: true,
Credentials: []image.RegistryCredentials{},
},
},
{
name: "set InsecureUseHTTP",
input: registry{
InsecureUseHTTP: true,
},
expected: image.RegistryOptions{
InsecureUseHTTP: true,
Credentials: []image.RegistryCredentials{},
},
},
{
name: "set all bool options",
input: registry{
InsecureSkipTLSVerify: true,
InsecureUseHTTP: true,
},
expected: image.RegistryOptions{
InsecureSkipTLSVerify: true,
InsecureUseHTTP: true,
Credentials: []image.RegistryCredentials{},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
assert.Equal(t, &test.expected, test.input.ToOptions())
})
}
}
+24
View File
@@ -1,7 +1,9 @@
package internal
import (
"fmt"
"io"
"os"
"github.com/anchore/syft/internal/log"
)
@@ -12,3 +14,25 @@ func CloseAndLogError(closer io.Closer, location string) {
log.Warnf("unable to close file for location=%q: %+v", location, err)
}
}
type ErrPath struct {
Path string
Err error
}
func (e ErrPath) Error() string {
return fmt.Sprintf("unable to observe contents of %+v: %v", e.Path, e.Err)
}
func IsErrPath(err error) bool {
_, ok := err.(ErrPath)
return ok
}
func IsErrPathPermission(err error) bool {
pathErr, ok := err.(ErrPath)
if ok {
return os.IsPermission(pathErr.Err)
}
return ok
}
@@ -1,15 +1,16 @@
package packages
package testutils
import (
"bytes"
"testing"
"github.com/anchore/syft/syft/presenter"
"github.com/anchore/go-testutils"
"github.com/anchore/stereoscope/pkg/filetree"
"github.com/anchore/stereoscope/pkg/imagetest"
"github.com/anchore/syft/syft/distro"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/presenter"
"github.com/anchore/syft/syft/source"
"github.com/sergi/go-diff/diffmatchpatch"
"github.com/stretchr/testify/assert"
@@ -17,7 +18,7 @@ import (
type redactor func(s []byte) []byte
func assertPresenterAgainstGoldenImageSnapshot(t *testing.T, pres presenter.Presenter, testImage string, updateSnapshot bool, redactors ...redactor) {
func AssertPresenterAgainstGoldenImageSnapshot(t *testing.T, pres presenter.Presenter, testImage string, updateSnapshot bool, redactors ...redactor) {
var buffer bytes.Buffer
// grab the latest image contents and persist
@@ -50,7 +51,7 @@ func assertPresenterAgainstGoldenImageSnapshot(t *testing.T, pres presenter.Pres
}
}
func assertPresenterAgainstGoldenSnapshot(t *testing.T, pres presenter.Presenter, updateSnapshot bool, redactors ...redactor) {
func AssertPresenterAgainstGoldenSnapshot(t *testing.T, pres presenter.Presenter, updateSnapshot bool, redactors ...redactor) {
var buffer bytes.Buffer
err := pres.Present(&buffer)
@@ -77,7 +78,7 @@ func assertPresenterAgainstGoldenSnapshot(t *testing.T, pres presenter.Presenter
}
}
func presenterImageInput(t testing.TB, testImage string) (*pkg.Catalog, source.Metadata, *distro.Distro) {
func ImageInput(t testing.TB, testImage string) (*pkg.Catalog, source.Metadata, *distro.Distro) {
t.Helper()
catalog := pkg.NewCatalog()
img := imagetest.GetGoldenFixtureImage(t, testImage)
@@ -104,7 +105,7 @@ func presenterImageInput(t testing.TB, testImage string) (*pkg.Catalog, source.M
},
PURL: "a-purl-1",
CPEs: []pkg.CPE{
must(pkg.NewCPE("cpe:2.3:*:some:package:1:*:*:*:*:*:*:*")),
pkg.MustCPE("cpe:2.3:*:some:package:1:*:*:*:*:*:*:*"),
},
})
catalog.Add(pkg.Package{
@@ -123,7 +124,7 @@ func presenterImageInput(t testing.TB, testImage string) (*pkg.Catalog, source.M
},
PURL: "a-purl-2",
CPEs: []pkg.CPE{
must(pkg.NewCPE("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*")),
pkg.MustCPE("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
},
})
@@ -139,7 +140,7 @@ func presenterImageInput(t testing.TB, testImage string) (*pkg.Catalog, source.M
return catalog, src.Metadata, &dist
}
func presenterDirectoryInput(t testing.TB) (*pkg.Catalog, source.Metadata, *distro.Distro) {
func DirectoryInput(t testing.TB) (*pkg.Catalog, source.Metadata, *distro.Distro) {
catalog := pkg.NewCatalog()
// populate catalog with test data
@@ -158,10 +159,15 @@ func presenterDirectoryInput(t testing.TB) (*pkg.Catalog, source.Metadata, *dist
Metadata: pkg.PythonPackageMetadata{
Name: "package-1",
Version: "1.0.1",
Files: []pkg.PythonFileRecord{
{
Path: "/some/path/pkg1/dependencies/foo",
},
},
},
PURL: "a-purl-2",
CPEs: []pkg.CPE{
must(pkg.NewCPE("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*")),
pkg.MustCPE("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
},
})
catalog.Add(pkg.Package{
@@ -180,7 +186,7 @@ func presenterDirectoryInput(t testing.TB) (*pkg.Catalog, source.Metadata, *dist
},
PURL: "a-purl-2",
CPEs: []pkg.CPE{
must(pkg.NewCPE("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*")),
pkg.MustCPE("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
},
})
+37
View File
@@ -0,0 +1,37 @@
package formats
import (
"bytes"
"github.com/anchore/syft/internal/formats/table"
"github.com/anchore/syft/internal/formats/syftjson"
"github.com/anchore/syft/syft/format"
)
// TODO: eventually this is the source of truth for all formatters
func All() []format.Format {
return []format.Format{
syftjson.Format(),
table.Format(),
}
}
func Identify(by []byte) (*format.Format, error) {
for _, f := range All() {
if err := f.Validate(bytes.NewReader(by)); err != nil {
continue
}
return &f, nil
}
return nil, nil
}
func ByOption(option format.Option) *format.Format {
for _, f := range All() {
if f.Option == option {
return &f
}
}
return nil
}
+34
View File
@@ -0,0 +1,34 @@
package formats
import (
"io"
"os"
"testing"
"github.com/anchore/syft/syft/format"
"github.com/stretchr/testify/assert"
)
func TestIdentify(t *testing.T) {
tests := []struct {
fixture string
expected format.Option
}{
{
fixture: "test-fixtures/alpine-syft.json",
expected: format.JSONOption,
},
}
for _, test := range tests {
t.Run(test.fixture, func(t *testing.T) {
f, err := os.Open(test.fixture)
assert.NoError(t, err)
by, err := io.ReadAll(f)
assert.NoError(t, err)
frmt, err := Identify(by)
assert.NoError(t, err)
assert.NotNil(t, frmt)
assert.Equal(t, test.expected, frmt.Option)
})
}
}
+24
View File
@@ -0,0 +1,24 @@
package syftjson
import (
"encoding/json"
"fmt"
"io"
"github.com/anchore/syft/internal/formats/syftjson/model"
"github.com/anchore/syft/syft/distro"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/source"
)
func decoder(reader io.Reader) (*pkg.Catalog, *source.Metadata, *distro.Distro, source.Scope, error) {
dec := json.NewDecoder(reader)
var doc model.Document
err := dec.Decode(&doc)
if err != nil {
return nil, nil, nil, source.UnknownScope, fmt.Errorf("unable to decode syft-json: %w", err)
}
return toSyftModel(doc)
}
+52
View File
@@ -0,0 +1,52 @@
package syftjson
import (
"bytes"
"strings"
"testing"
"github.com/anchore/syft/syft/source"
"github.com/anchore/syft/internal/formats/common/testutils"
"github.com/go-test/deep"
"github.com/stretchr/testify/assert"
)
func TestEncodeDecodeCycle(t *testing.T) {
testImage := "image-simple"
originalCatalog, originalMetadata, _ := testutils.ImageInput(t, testImage)
var buf bytes.Buffer
assert.NoError(t, encoder(&buf, originalCatalog, &originalMetadata, nil, source.SquashedScope))
actualCatalog, actualMetadata, _, _, err := decoder(bytes.NewReader(buf.Bytes()))
assert.NoError(t, err)
for _, d := range deep.Equal(originalMetadata, *actualMetadata) {
t.Errorf("metadata difference: %+v", d)
}
actualPackages := actualCatalog.Sorted()
for idx, p := range originalCatalog.Sorted() {
if !assert.Equal(t, p.Name, actualPackages[idx].Name) {
t.Errorf("different package at idx=%d: %s vs %s", idx, p.Name, actualPackages[idx].Name)
continue
}
// ids will never be equal
p.ID = ""
actualPackages[idx].ID = ""
for _, d := range deep.Equal(*p, *actualPackages[idx]) {
if strings.Contains(d, ".VirtualPath: ") {
// location.Virtual path is not exposed in the json output
continue
}
if strings.HasSuffix(d, "<nil slice> != []") {
// semantically the same
continue
}
t.Errorf("package difference (%s): %+v", p.Name, d)
}
}
}
+23
View File
@@ -0,0 +1,23 @@
package syftjson
import (
"encoding/json"
"io"
"github.com/anchore/syft/syft/distro"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/source"
)
func encoder(output io.Writer, catalog *pkg.Catalog, srcMetadata *source.Metadata, d *distro.Distro, scope source.Scope) error {
// TODO: application config not available yet
doc := ToFormatModel(catalog, srcMetadata, d, scope, nil)
enc := json.NewEncoder(output)
// prevent > and < from being escaped in the payload
enc.SetEscapeHTML(false)
enc.SetIndent("", " ")
return enc.Encode(&doc)
}
+31
View File
@@ -0,0 +1,31 @@
package syftjson
import (
"flag"
"testing"
"github.com/anchore/syft/syft/source"
"github.com/anchore/syft/internal/formats/common/testutils"
"github.com/anchore/syft/syft/format"
)
var updateJson = flag.Bool("update-json", false, "update the *.golden files for json presenters")
func TestDirectoryPresenter(t *testing.T) {
catalog, metadata, distro := testutils.DirectoryInput(t)
testutils.AssertPresenterAgainstGoldenSnapshot(t,
format.NewPresenter(encoder, catalog, &metadata, distro, source.SquashedScope),
*updateJson,
)
}
func TestImagePresenter(t *testing.T) {
testImage := "image-simple"
catalog, metadata, distro := testutils.ImageInput(t, testImage)
testutils.AssertPresenterAgainstGoldenImageSnapshot(t,
format.NewPresenter(encoder, catalog, &metadata, distro, source.SquashedScope),
testImage,
*updateJson,
)
}
+12
View File
@@ -0,0 +1,12 @@
package syftjson
import "github.com/anchore/syft/syft/format"
func Format() format.Format {
return format.NewFormat(
format.JSONOption,
encoder,
decoder,
validator,
)
}
@@ -0,0 +1,8 @@
package model
// Distro provides information about a detected Linux Distro.
type Distro struct {
Name string `json:"name"` // Name of the Linux distribution
Version string `json:"version"` // Version of the Linux distribution (major or major.minor version)
IDLike string `json:"idLike"` // the ID_LIKE field found within the /etc/os-release file
}
@@ -0,0 +1,23 @@
package model
// Document represents the syft cataloging findings as a JSON document
type Document struct {
Artifacts []Package `json:"artifacts"` // Artifacts is the list of packages discovered and placed into the catalog
ArtifactRelationships []Relationship `json:"artifactRelationships"`
Source Source `json:"source"` // Source represents the original object that was cataloged
Distro Distro `json:"distro"` // Distro represents the Linux distribution that was detected from the source
Descriptor Descriptor `json:"descriptor"` // Descriptor is a block containing self-describing information about syft
Schema Schema `json:"schema"` // Schema is a block reserved for defining the version for the shape of this JSON document and where to find the schema document to validate the shape
}
// Descriptor describes what created the document as well as surrounding metadata
type Descriptor struct {
Name string `json:"name"`
Version string `json:"version"`
Configuration interface{} `json:"configuration,omitempty"`
}
type Schema struct {
Version string `json:"version"`
URL string `json:"url"`
}
+123
View File
@@ -0,0 +1,123 @@
package model
import (
"encoding/json"
"fmt"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/source"
)
// Package represents a pkg.Package object specialized for JSON marshaling and unmarshaling.
type Package struct {
PackageBasicData
PackageCustomData
}
// PackageBasicData contains non-ambiguous values (type-wise) from pkg.Package.
type PackageBasicData struct {
ID string `json:"id"`
Name string `json:"name"`
Version string `json:"version"`
Type pkg.Type `json:"type"`
FoundBy string `json:"foundBy"`
Locations []source.Location `json:"locations"`
Licenses []string `json:"licenses"`
Language pkg.Language `json:"language"`
CPEs []string `json:"cpes"`
PURL string `json:"purl"`
}
// PackageCustomData contains ambiguous values (type-wise) from pkg.Package.
type PackageCustomData struct {
MetadataType pkg.MetadataType `json:"metadataType"`
Metadata interface{} `json:"metadata"`
}
// packageMetadataUnpacker is all values needed from Package to disambiguate ambiguous fields during json unmarshaling.
type packageMetadataUnpacker struct {
MetadataType pkg.MetadataType `json:"metadataType"`
Metadata json.RawMessage `json:"metadata"`
}
func (p *packageMetadataUnpacker) String() string {
return fmt.Sprintf("metadataType: %s, metadata: %s", p.MetadataType, string(p.Metadata))
}
// UnmarshalJSON is a custom unmarshaller for handling basic values and values with ambiguous types.
// nolint:funlen
func (p *Package) UnmarshalJSON(b []byte) error {
var basic PackageBasicData
if err := json.Unmarshal(b, &basic); err != nil {
return err
}
p.PackageBasicData = basic
var unpacker packageMetadataUnpacker
if err := json.Unmarshal(b, &unpacker); err != nil {
log.Warnf("failed to unmarshall into packageMetadataUnpacker: %v", err)
return err
}
p.MetadataType = unpacker.MetadataType
switch p.MetadataType {
case pkg.ApkMetadataType:
var payload pkg.ApkMetadata
if err := json.Unmarshal(unpacker.Metadata, &payload); err != nil {
return err
}
p.Metadata = payload
case pkg.RpmdbMetadataType:
var payload pkg.RpmdbMetadata
if err := json.Unmarshal(unpacker.Metadata, &payload); err != nil {
return err
}
p.Metadata = payload
case pkg.DpkgMetadataType:
var payload pkg.DpkgMetadata
if err := json.Unmarshal(unpacker.Metadata, &payload); err != nil {
return err
}
p.Metadata = payload
case pkg.JavaMetadataType:
var payload pkg.JavaMetadata
if err := json.Unmarshal(unpacker.Metadata, &payload); err != nil {
return err
}
p.Metadata = payload
case pkg.RustCargoPackageMetadataType:
var payload pkg.CargoPackageMetadata
if err := json.Unmarshal(unpacker.Metadata, &payload); err != nil {
return err
}
p.Metadata = payload
case pkg.GemMetadataType:
var payload pkg.GemMetadata
if err := json.Unmarshal(unpacker.Metadata, &payload); err != nil {
return err
}
p.Metadata = payload
case pkg.KbPackageMetadataType:
var payload pkg.KbPackageMetadata
if err := json.Unmarshal(unpacker.Metadata, &payload); err != nil {
return err
}
p.Metadata = payload
case pkg.PythonPackageMetadataType:
var payload pkg.PythonPackageMetadata
if err := json.Unmarshal(unpacker.Metadata, &payload); err != nil {
return err
}
p.Metadata = payload
case pkg.NpmPackageJSONMetadataType:
var payload pkg.NpmPackageJSONMetadata
if err := json.Unmarshal(unpacker.Metadata, &payload); err != nil {
return err
}
p.Metadata = payload
}
return nil
}
@@ -0,0 +1,8 @@
package model
type Relationship struct {
Parent string `json:"parent"`
Child string `json:"child"`
Type string `json:"type"`
Metadata interface{} `json:"metadata"`
}
+57
View File
@@ -0,0 +1,57 @@
package model
import (
"encoding/json"
"fmt"
"strconv"
"github.com/anchore/syft/syft/source"
)
// Source object represents the thing that was cataloged
type Source struct {
Type string `json:"type"`
Target interface{} `json:"target"`
}
// sourceUnpacker is used to unmarshal Source objects
type sourceUnpacker struct {
Type string `json:"type"`
Target json.RawMessage `json:"target"`
}
type ImageSource struct {
source.ImageMetadata
Scope source.Scope `json:"scope"`
}
// UnmarshalJSON populates a source object from JSON bytes.
func (s *Source) UnmarshalJSON(b []byte) error {
var unpacker sourceUnpacker
if err := json.Unmarshal(b, &unpacker); err != nil {
return err
}
s.Type = unpacker.Type
switch s.Type {
case "directory":
if target, err := strconv.Unquote(string(unpacker.Target)); err == nil {
s.Target = target
} else {
s.Target = string(unpacker.Target[:])
}
case "image":
var payload ImageSource
if err := json.Unmarshal(unpacker.Target, &payload); err != nil {
return err
}
s.Target = payload
default:
return fmt.Errorf("unsupported package metadata type: %+v", s.Type)
}
return nil
}
@@ -0,0 +1,4 @@
# Note: changes to this file will result in updating several test values. Consider making a new image fixture instead of editing this one.
FROM scratch
ADD file-1.txt /somefile-1.txt
ADD file-2.txt /somefile-2.txt
@@ -0,0 +1 @@
this file has contents
@@ -0,0 +1 @@
file-2 contents!
@@ -0,0 +1,86 @@
{
"artifacts": [
{
"id": "package-1-id",
"name": "package-1",
"version": "1.0.1",
"type": "python",
"foundBy": "the-cataloger-1",
"locations": [
{
"path": "/some/path/pkg1"
}
],
"licenses": [
"MIT"
],
"language": "python",
"cpes": [
"cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"
],
"purl": "a-purl-2",
"metadataType": "PythonPackageMetadata",
"metadata": {
"name": "package-1",
"version": "1.0.1",
"license": "",
"author": "",
"authorEmail": "",
"platform": "",
"files": [
{
"path": "/some/path/pkg1/dependencies/foo"
}
],
"sitePackagesRootPath": ""
}
},
{
"id": "package-2-id",
"name": "package-2",
"version": "2.0.1",
"type": "deb",
"foundBy": "the-cataloger-2",
"locations": [
{
"path": "/some/path/pkg1"
}
],
"licenses": [],
"language": "",
"cpes": [
"cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"
],
"purl": "a-purl-2",
"metadataType": "DpkgMetadata",
"metadata": {
"package": "package-2",
"source": "",
"version": "2.0.1",
"sourceVersion": "",
"architecture": "",
"maintainer": "",
"installedSize": 0,
"files": null
}
}
],
"artifactRelationships": [],
"source": {
"type": "directory",
"target": "/some/path"
},
"distro": {
"name": "debian",
"version": "1.2.3",
"idLike": "like!"
},
"descriptor": {
"name": "syft",
"version": "[not provided]"
},
"schema": {
"version": "1.1.0",
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-1.1.0.json"
}
}
@@ -0,0 +1,108 @@
{
"artifacts": [
{
"id": "package-1-id",
"name": "package-1",
"version": "1.0.1",
"type": "python",
"foundBy": "the-cataloger-1",
"locations": [
{
"path": "/somefile-1.txt",
"layerID": "sha256:fb6beecb75b39f4bb813dbf177e501edd5ddb3e69bb45cedeb78c676ee1b7a59"
}
],
"licenses": [
"MIT"
],
"language": "python",
"cpes": [
"cpe:2.3:*:some:package:1:*:*:*:*:*:*:*"
],
"purl": "a-purl-1",
"metadataType": "PythonPackageMetadata",
"metadata": {
"name": "package-1",
"version": "1.0.1",
"license": "",
"author": "",
"authorEmail": "",
"platform": "",
"sitePackagesRootPath": ""
}
},
{
"id": "package-2-id",
"name": "package-2",
"version": "2.0.1",
"type": "deb",
"foundBy": "the-cataloger-2",
"locations": [
{
"path": "/somefile-2.txt",
"layerID": "sha256:319b588ce64253a87b533c8ed01cf0025e0eac98e7b516e12532957e1244fdec"
}
],
"licenses": [],
"language": "",
"cpes": [
"cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"
],
"purl": "a-purl-2",
"metadataType": "DpkgMetadata",
"metadata": {
"package": "package-2",
"source": "",
"version": "2.0.1",
"sourceVersion": "",
"architecture": "",
"maintainer": "",
"installedSize": 0,
"files": null
}
}
],
"artifactRelationships": [],
"source": {
"type": "image",
"target": {
"userInput": "user-image-input",
"imageID": "sha256:2480160b55bec40c44d3b145c7b2c1c47160db8575c3dcae086d76b9370ae7ca",
"manifestDigest": "sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368",
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"tags": [
"stereoscope-fixture-image-simple:85066c51088bdd274f7a89e99e00490f666c49e72ffc955707cd6e18f0e22c5b"
],
"imageSize": 38,
"layers": [
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"digest": "sha256:fb6beecb75b39f4bb813dbf177e501edd5ddb3e69bb45cedeb78c676ee1b7a59",
"size": 22
},
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"digest": "sha256:319b588ce64253a87b533c8ed01cf0025e0eac98e7b516e12532957e1244fdec",
"size": 16
}
],
"manifest": "eyJzY2hlbWFWZXJzaW9uIjoyLCJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmRpc3RyaWJ1dGlvbi5tYW5pZmVzdC52Mitqc29uIiwiY29uZmlnIjp7Im1lZGlhVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5kb2NrZXIuY29udGFpbmVyLmltYWdlLnYxK2pzb24iLCJzaXplIjo2NjcsImRpZ2VzdCI6InNoYTI1NjoyNDgwMTYwYjU1YmVjNDBjNDRkM2IxNDVjN2IyYzFjNDcxNjBkYjg1NzVjM2RjYWUwODZkNzZiOTM3MGFlN2NhIn0sImxheWVycyI6W3sibWVkaWFUeXBlIjoiYXBwbGljYXRpb24vdm5kLmRvY2tlci5pbWFnZS5yb290ZnMuZGlmZi50YXIuZ3ppcCIsInNpemUiOjIwNDgsImRpZ2VzdCI6InNoYTI1NjpmYjZiZWVjYjc1YjM5ZjRiYjgxM2RiZjE3N2U1MDFlZGQ1ZGRiM2U2OWJiNDVjZWRlYjc4YzY3NmVlMWI3YTU5In0seyJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmltYWdlLnJvb3Rmcy5kaWZmLnRhci5nemlwIiwic2l6ZSI6MjA0OCwiZGlnZXN0Ijoic2hhMjU2OjMxOWI1ODhjZTY0MjUzYTg3YjUzM2M4ZWQwMWNmMDAyNWUwZWFjOThlN2I1MTZlMTI1MzI5NTdlMTI0NGZkZWMifV19",
"config": "eyJhcmNoaXRlY3R1cmUiOiJhbWQ2NCIsImNvbmZpZyI6eyJFbnYiOlsiUEFUSD0vdXNyL2xvY2FsL3NiaW46L3Vzci9sb2NhbC9iaW46L3Vzci9zYmluOi91c3IvYmluOi9zYmluOi9iaW4iXSwiV29ya2luZ0RpciI6Ii8iLCJPbkJ1aWxkIjpudWxsfSwiY3JlYXRlZCI6IjIwMjEtMTAtMDRUMTE6NDA6MDAuNjM4Mzk0NVoiLCJoaXN0b3J5IjpbeyJjcmVhdGVkIjoiMjAyMS0xMC0wNFQxMTo0MDowMC41OTA3MzE2WiIsImNyZWF0ZWRfYnkiOiJBREQgZmlsZS0xLnR4dCAvc29tZWZpbGUtMS50eHQgIyBidWlsZGtpdCIsImNvbW1lbnQiOiJidWlsZGtpdC5kb2NrZXJmaWxlLnYwIn0seyJjcmVhdGVkIjoiMjAyMS0xMC0wNFQxMTo0MDowMC42MzgzOTQ1WiIsImNyZWF0ZWRfYnkiOiJBREQgZmlsZS0yLnR4dCAvc29tZWZpbGUtMi50eHQgIyBidWlsZGtpdCIsImNvbW1lbnQiOiJidWlsZGtpdC5kb2NrZXJmaWxlLnYwIn1dLCJvcyI6ImxpbnV4Iiwicm9vdGZzIjp7InR5cGUiOiJsYXllcnMiLCJkaWZmX2lkcyI6WyJzaGEyNTY6ZmI2YmVlY2I3NWIzOWY0YmI4MTNkYmYxNzdlNTAxZWRkNWRkYjNlNjliYjQ1Y2VkZWI3OGM2NzZlZTFiN2E1OSIsInNoYTI1NjozMTliNTg4Y2U2NDI1M2E4N2I1MzNjOGVkMDFjZjAwMjVlMGVhYzk4ZTdiNTE2ZTEyNTMyOTU3ZTEyNDRmZGVjIl19fQ==",
"repoDigests": [],
"scope": "Squashed"
}
},
"distro": {
"name": "debian",
"version": "1.2.3",
"idLike": "like!"
},
"descriptor": {
"name": "syft",
"version": "[not provided]"
},
"schema": {
"version": "1.1.0",
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-1.1.0.json"
}
}
@@ -0,0 +1,133 @@
package syftjson
import (
"fmt"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/formats/syftjson/model"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/version"
"github.com/anchore/syft/syft/distro"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/source"
)
// TODO: this is export4ed for the use of the power-user command (temp)
func ToFormatModel(catalog *pkg.Catalog, srcMetadata *source.Metadata, d *distro.Distro, scope source.Scope, applicationConfig interface{}) model.Document {
src, err := toSourceModel(srcMetadata, scope)
if err != nil {
log.Warnf("unable to create syft-json source object: %+v", err)
}
return model.Document{
Artifacts: toPackageModels(catalog),
ArtifactRelationships: toRelationshipModel(pkg.NewRelationships(catalog)),
Source: src,
Distro: toDistroModel(d),
Descriptor: model.Descriptor{
Name: internal.ApplicationName,
Version: version.FromBuild().Version,
Configuration: applicationConfig,
},
Schema: model.Schema{
Version: internal.JSONSchemaVersion,
URL: fmt.Sprintf("https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-%s.json", internal.JSONSchemaVersion),
},
}
}
func toPackageModels(catalog *pkg.Catalog) []model.Package {
artifacts := make([]model.Package, 0)
if catalog == nil {
return artifacts
}
for _, p := range catalog.Sorted() {
artifacts = append(artifacts, toPackageModel(p))
}
return artifacts
}
// toPackageModel crates a new Package from the given pkg.Package.
func toPackageModel(p *pkg.Package) model.Package {
var cpes = make([]string, len(p.CPEs))
for i, c := range p.CPEs {
cpes[i] = c.BindToFmtString()
}
// ensure collections are never nil for presentation reasons
var locations = make([]source.Location, 0)
if p.Locations != nil {
locations = p.Locations
}
var licenses = make([]string, 0)
if p.Licenses != nil {
licenses = p.Licenses
}
return model.Package{
PackageBasicData: model.PackageBasicData{
ID: string(p.ID),
Name: p.Name,
Version: p.Version,
Type: p.Type,
FoundBy: p.FoundBy,
Locations: locations,
Licenses: licenses,
Language: p.Language,
CPEs: cpes,
PURL: p.PURL,
},
PackageCustomData: model.PackageCustomData{
MetadataType: p.MetadataType,
Metadata: p.Metadata,
},
}
}
func toRelationshipModel(relationships []pkg.Relationship) []model.Relationship {
result := make([]model.Relationship, len(relationships))
for i, r := range relationships {
result[i] = model.Relationship{
Parent: string(r.Parent),
Child: string(r.Child),
Type: string(r.Type),
Metadata: r.Metadata,
}
}
return result
}
// toSourceModel creates a new source object to be represented into JSON.
func toSourceModel(src *source.Metadata, scope source.Scope) (model.Source, error) {
switch src.Scheme {
case source.ImageScheme:
return model.Source{
Type: "image",
Target: model.ImageSource{
ImageMetadata: src.ImageMetadata,
Scope: scope,
},
}, nil
case source.DirectoryScheme:
return model.Source{
Type: "directory",
Target: src.Path,
}, nil
default:
return model.Source{}, fmt.Errorf("unsupported source: %q", src.Scheme)
}
}
// toDistroModel creates a struct with the Linux distribution to be represented in JSON.
func toDistroModel(d *distro.Distro) model.Distro {
if d == nil {
return model.Distro{}
}
return model.Distro{
Name: d.Name(),
Version: d.FullVersion(),
IDLike: d.IDLike,
}
}
@@ -0,0 +1,73 @@
package syftjson
import (
"github.com/anchore/syft/internal/formats/syftjson/model"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/distro"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/source"
)
func toSyftModel(doc model.Document) (*pkg.Catalog, *source.Metadata, *distro.Distro, source.Scope, error) {
dist, err := distro.NewDistro(distro.Type(doc.Distro.Name), doc.Distro.Version, doc.Distro.IDLike)
if err != nil {
return nil, nil, nil, source.UnknownScope, err
}
srcMetadata, scope := toSyftSourceData(doc.Source)
return toSyftCatalog(doc.Artifacts), srcMetadata, &dist, scope, nil
}
func toSyftSourceData(s model.Source) (*source.Metadata, source.Scope) {
switch s.Type {
case "directory":
return &source.Metadata{
Scheme: source.DirectoryScheme,
Path: s.Target.(string),
}, source.UnknownScope
case "image":
parsedSource := s.Target.(model.ImageSource)
return &source.Metadata{
Scheme: source.ImageScheme,
ImageMetadata: parsedSource.ImageMetadata,
}, parsedSource.Scope
}
return nil, source.UnknownScope
}
func toSyftCatalog(pkgs []model.Package) *pkg.Catalog {
catalog := pkg.NewCatalog()
for _, p := range pkgs {
catalog.Add(toSyftPackage(p))
}
return catalog
}
func toSyftPackage(p model.Package) pkg.Package {
var cpes []pkg.CPE
for _, c := range p.CPEs {
value, err := pkg.NewCPE(c)
if err != nil {
log.Warnf("excluding invalid CPE %q: %v", c, err)
continue
}
cpes = append(cpes, value)
}
return pkg.Package{
ID: pkg.ID(p.ID),
Name: p.Name,
Version: p.Version,
FoundBy: p.FoundBy,
Locations: p.Locations,
Licenses: p.Licenses,
Language: p.Language,
Type: p.Type,
CPEs: cpes,
PURL: p.PURL,
MetadataType: p.MetadataType,
Metadata: p.Metadata,
}
}
+31
View File
@@ -0,0 +1,31 @@
package syftjson
import (
"encoding/json"
"fmt"
"io"
"strings"
"github.com/anchore/syft/internal/formats/syftjson/model"
)
func validator(reader io.Reader) error {
type Document struct {
Schema model.Schema `json:"schema"`
}
dec := json.NewDecoder(reader)
var doc Document
err := dec.Decode(&doc)
if err != nil {
return fmt.Errorf("unable to decode: %w", err)
}
// note: we accept all schema versions
// TODO: add per-schema version parsing
if strings.Contains(doc.Schema.URL, "anchore/syft") {
return nil
}
return fmt.Errorf("could not extract syft schema")
}
@@ -1,4 +1,4 @@
package packages
package table
import (
"fmt"
@@ -8,24 +8,16 @@ import (
"github.com/olekukonko/tablewriter"
"github.com/anchore/syft/syft/distro"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/source"
)
type TablePresenter struct {
catalog *pkg.Catalog
}
func NewTablePresenter(catalog *pkg.Catalog) *TablePresenter {
return &TablePresenter{
catalog: catalog,
}
}
func (pres *TablePresenter) Present(output io.Writer) error {
rows := make([][]string, 0)
func encoder(output io.Writer, catalog *pkg.Catalog, _ *source.Metadata, _ *distro.Distro, _ source.Scope) error {
var rows [][]string
columns := []string{"Name", "Version", "Type"}
for _, p := range pres.catalog.Sorted() {
for _, p := range catalog.Sorted() {
row := []string{
p.Name,
p.Version,
@@ -35,8 +27,8 @@ func (pres *TablePresenter) Present(output io.Writer) error {
}
if len(rows) == 0 {
fmt.Fprintln(output, "No packages discovered")
return nil
_, err := fmt.Fprintln(output, "No packages discovered")
return err
}
// sort by name, version, then type
@@ -1,21 +1,22 @@
package packages
package table
import (
"flag"
"testing"
"github.com/anchore/syft/internal/formats/common/testutils"
"github.com/anchore/syft/syft/format"
"github.com/anchore/syft/syft/source"
"github.com/go-test/deep"
)
var updateTablePresenterGoldenFiles = flag.Bool("update-table", false, "update the *.golden files for table presenters")
var updateTableGoldenFiles = flag.Bool("update-table", false, "update the *.golden files for table format")
func TestTablePresenter(t *testing.T) {
testImage := "image-simple"
catalog, _, _ := presenterImageInput(t, testImage)
assertPresenterAgainstGoldenImageSnapshot(t,
NewTablePresenter(catalog),
testImage,
*updateTablePresenterGoldenFiles,
catalog, metadata, distro := testutils.DirectoryInput(t)
testutils.AssertPresenterAgainstGoldenSnapshot(t,
format.NewPresenter(encoder, catalog, &metadata, distro, source.SquashedScope),
*updateTableGoldenFiles,
)
}
+12
View File
@@ -0,0 +1,12 @@
package table
import "github.com/anchore/syft/syft/format"
func Format() format.Format {
return format.NewFormat(
format.TableOption,
encoder,
nil,
nil,
)
}
File diff suppressed because it is too large Load Diff
+16
View File
@@ -0,0 +1,16 @@
package internal
import (
"fmt"
"os"
)
// IsPipedInput returns true if there is no input device, which means the user **may** be providing input via a pipe.
func IsPipedInput() (bool, error) {
fi, err := os.Stdin.Stat()
if err != nil {
return false, fmt.Errorf("unable to determine if there is piped input: %w", err)
}
return fi.Mode()&os.ModeCharDevice == 0, nil
}
+5 -2
View File
@@ -3,6 +3,7 @@ package logger
import (
"fmt"
"io"
"io/fs"
"io/ioutil"
"os"
@@ -10,6 +11,8 @@ import (
prefixed "github.com/x-cray/logrus-prefixed-formatter"
)
const defaultLogFilePermissions fs.FileMode = 0644
// LogrusConfig contains all configurable values for the Logrus logger
type LogrusConfig struct {
EnableConsole bool
@@ -38,7 +41,7 @@ func NewLogrusLogger(cfg LogrusConfig) *LogrusLogger {
var output io.Writer
switch {
case cfg.EnableConsole && cfg.EnableFile:
logFile, err := os.OpenFile(cfg.FileLocation, os.O_WRONLY|os.O_CREATE, 0755)
logFile, err := os.OpenFile(cfg.FileLocation, os.O_WRONLY|os.O_CREATE, defaultLogFilePermissions)
if err != nil {
panic(fmt.Errorf("unable to setup log file: %w", err))
}
@@ -46,7 +49,7 @@ func NewLogrusLogger(cfg LogrusConfig) *LogrusLogger {
case cfg.EnableConsole:
output = os.Stderr
case cfg.EnableFile:
logFile, err := os.OpenFile(cfg.FileLocation, os.O_WRONLY|os.O_CREATE, 0755)
logFile, err := os.OpenFile(cfg.FileLocation, os.O_WRONLY|os.O_CREATE, defaultLogFilePermissions)
if err != nil {
panic(fmt.Errorf("unable to setup log file: %w", err))
}
@@ -4,13 +4,15 @@ import (
"flag"
"regexp"
"testing"
"github.com/anchore/syft/internal/formats/common/testutils"
)
var updateCycloneDx = flag.Bool("update-cyclonedx", false, "update the *.golden files for cyclone-dx presenters")
func TestCycloneDxDirectoryPresenter(t *testing.T) {
catalog, metadata, _ := presenterDirectoryInput(t)
assertPresenterAgainstGoldenSnapshot(t,
catalog, metadata, _ := testutils.DirectoryInput(t)
testutils.AssertPresenterAgainstGoldenSnapshot(t,
NewCycloneDxPresenter(catalog, metadata),
*updateCycloneDx,
cycloneDxRedactor,
@@ -19,8 +21,8 @@ func TestCycloneDxDirectoryPresenter(t *testing.T) {
func TestCycloneDxImagePresenter(t *testing.T) {
testImage := "image-simple"
catalog, metadata, _ := presenterImageInput(t, testImage)
assertPresenterAgainstGoldenImageSnapshot(t,
catalog, metadata, _ := testutils.ImageInput(t, testImage)
testutils.AssertPresenterAgainstGoldenImageSnapshot(t,
NewCycloneDxPresenter(catalog, metadata),
testImage,
*updateCycloneDx,
@@ -1,23 +0,0 @@
package packages
import "github.com/anchore/syft/syft/distro"
// JSONDistribution provides information about a detected Linux JSONDistribution.
type JSONDistribution struct {
Name string `json:"name"` // Name of the Linux distribution
Version string `json:"version"` // Version of the Linux distribution (major or major.minor version)
IDLike string `json:"idLike"` // the ID_LIKE field found within the /etc/os-release file
}
// NewJSONDistribution creates a struct with the Linux distribution to be represented in JSON.
func NewJSONDistribution(d *distro.Distro) JSONDistribution {
if d == nil {
return JSONDistribution{}
}
return JSONDistribution{
Name: d.Name(),
Version: d.FullVersion(),
IDLike: d.IDLike,
}
}
@@ -1,62 +0,0 @@
package packages
import (
"fmt"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/version"
"github.com/anchore/syft/syft/distro"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/source"
)
// JSONDocument represents the syft cataloging findings as a JSON document
type JSONDocument struct {
Artifacts []JSONPackage `json:"artifacts"` // Artifacts is the list of packages discovered and placed into the catalog
ArtifactRelationships []JSONRelationship `json:"artifactRelationships"`
Source JSONSource `json:"source"` // Source represents the original object that was cataloged
Distro JSONDistribution `json:"distro"` // Distro represents the Linux distribution that was detected from the source
Descriptor JSONDescriptor `json:"descriptor"` // Descriptor is a block containing self-describing information about syft
Schema JSONSchema `json:"schema"` // Schema is a block reserved for defining the version for the shape of this JSON document and where to find the schema document to validate the shape
}
// NewJSONDocument creates and populates a new JSON document struct from the given cataloging results.
func NewJSONDocument(catalog *pkg.Catalog, srcMetadata source.Metadata, d *distro.Distro, scope source.Scope, configuration interface{}) (JSONDocument, error) {
src, err := NewJSONSource(srcMetadata, scope)
if err != nil {
return JSONDocument{}, err
}
artifacts, err := NewJSONPackages(catalog)
if err != nil {
return JSONDocument{}, err
}
return JSONDocument{
Artifacts: artifacts,
ArtifactRelationships: newJSONRelationships(pkg.NewRelationships(catalog)),
Source: src,
Distro: NewJSONDistribution(d),
Descriptor: JSONDescriptor{
Name: internal.ApplicationName,
Version: version.FromBuild().Version,
Configuration: configuration,
},
Schema: JSONSchema{
Version: internal.JSONSchemaVersion,
URL: fmt.Sprintf("https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-%s.json", internal.JSONSchemaVersion),
},
}, nil
}
// JSONDescriptor describes what created the document as well as surrounding metadata
type JSONDescriptor struct {
Name string `json:"name"`
Version string `json:"version"`
Configuration interface{} `json:"configuration,omitempty"`
}
type JSONSchema struct {
Version string `json:"version"`
URL string `json:"url"`
}
@@ -1,71 +0,0 @@
package packages
import (
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/source"
)
// JSONPackage represents a pkg.Package object specialized for JSON marshaling and unmarshaling.
type JSONPackage struct {
ID string `json:"id"`
Name string `json:"name"`
Version string `json:"version"`
Type string `json:"type"`
FoundBy string `json:"foundBy"`
Locations []source.Location `json:"locations"`
Licenses []string `json:"licenses"`
Language string `json:"language"`
CPEs []string `json:"cpes"`
PURL string `json:"purl"`
MetadataType string `json:"metadataType"`
Metadata interface{} `json:"metadata"`
}
func NewJSONPackages(catalog *pkg.Catalog) ([]JSONPackage, error) {
artifacts := make([]JSONPackage, 0)
if catalog == nil {
return artifacts, nil
}
for _, p := range catalog.Sorted() {
art, err := NewJSONPackage(p)
if err != nil {
return nil, err
}
artifacts = append(artifacts, art)
}
return artifacts, nil
}
// NewJSONPackage crates a new JSONPackage from the given pkg.Package.
func NewJSONPackage(p *pkg.Package) (JSONPackage, error) {
var cpes = make([]string, len(p.CPEs))
for i, c := range p.CPEs {
cpes[i] = c.BindToFmtString()
}
// ensure collections are never nil for presentation reasons
var locations = make([]source.Location, 0)
if p.Locations != nil {
locations = p.Locations
}
var licenses = make([]string, 0)
if p.Licenses != nil {
licenses = p.Licenses
}
return JSONPackage{
ID: string(p.ID),
Name: p.Name,
Version: p.Version,
Type: string(p.Type),
FoundBy: p.FoundBy,
Locations: locations,
Licenses: licenses,
Language: string(p.Language),
CPEs: cpes,
PURL: p.PURL,
MetadataType: string(p.MetadataType),
Metadata: p.Metadata,
}, nil
}
@@ -1,43 +0,0 @@
package packages
import (
"encoding/json"
"io"
"github.com/anchore/syft/syft/distro"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/source"
)
// JSONPresenter is a JSON presentation object for the syft results
type JSONPresenter struct {
catalog *pkg.Catalog
srcMetadata source.Metadata
distro *distro.Distro
scope source.Scope
}
// NewJSONPresenter creates a new JSON presenter object for the given cataloging results.
func NewJSONPresenter(catalog *pkg.Catalog, s source.Metadata, d *distro.Distro, scope source.Scope) *JSONPresenter {
return &JSONPresenter{
catalog: catalog,
srcMetadata: s,
distro: d,
scope: scope,
}
}
// Present the catalog results to the given writer.
func (pres *JSONPresenter) Present(output io.Writer) error {
// we do not pass in configuration for backwards compatibility
doc, err := NewJSONDocument(pres.catalog, pres.srcMetadata, pres.distro, pres.scope, nil)
if err != nil {
return err
}
enc := json.NewEncoder(output)
// prevent > and < from being escaped in the payload
enc.SetEscapeHTML(false)
enc.SetIndent("", " ")
return enc.Encode(&doc)
}
@@ -1,37 +0,0 @@
package packages
import (
"flag"
"testing"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/source"
)
var updateJSONGoldenFiles = flag.Bool("update-json", false, "update the *.golden files for json presenters")
func must(c pkg.CPE, e error) pkg.CPE {
if e != nil {
panic(e)
}
return c
}
func TestJSONDirectoryPresenter(t *testing.T) {
catalog, metadata, dist := presenterDirectoryInput(t)
assertPresenterAgainstGoldenSnapshot(t,
NewJSONPresenter(catalog, metadata, dist, source.SquashedScope),
*updateJSONGoldenFiles,
)
}
func TestJSONImagePresenter(t *testing.T) {
testImage := "image-simple"
catalog, metadata, dist := presenterImageInput(t, testImage)
assertPresenterAgainstGoldenImageSnapshot(t,
NewJSONPresenter(catalog, metadata, dist, source.SquashedScope),
testImage,
*updateJSONGoldenFiles,
)
}
@@ -1,23 +0,0 @@
package packages
import "github.com/anchore/syft/syft/pkg"
type JSONRelationship struct {
Parent string `json:"parent"`
Child string `json:"child"`
Type string `json:"type"`
Metadata interface{} `json:"metadata"`
}
func newJSONRelationships(relationships []pkg.Relationship) []JSONRelationship {
result := make([]JSONRelationship, len(relationships))
for i, r := range relationships {
result[i] = JSONRelationship{
Parent: string(r.Parent),
Child: string(r.Child),
Type: string(r.Type),
Metadata: r.Metadata,
}
}
return result
}
@@ -1,39 +0,0 @@
package packages
import (
"fmt"
"github.com/anchore/syft/syft/source"
)
// JSONSource object represents the thing that was cataloged
type JSONSource struct {
Type string `json:"type"`
Target interface{} `json:"target"`
}
type JSONImageSource struct {
source.ImageMetadata
Scope source.Scope `json:"scope"`
}
// NewJSONSource creates a new source object to be represented into JSON.
func NewJSONSource(src source.Metadata, scope source.Scope) (JSONSource, error) {
switch src.Scheme {
case source.ImageScheme:
return JSONSource{
Type: "image",
Target: JSONImageSource{
Scope: scope,
ImageMetadata: src.ImageMetadata,
},
}, nil
case source.DirectoryScheme:
return JSONSource{
Type: "directory",
Target: src.Path,
}, nil
default:
return JSONSource{}, fmt.Errorf("unsupported source: %q", src.Scheme)
}
}
@@ -10,7 +10,7 @@ type CreationInfo struct {
Created time.Time `json:"created"`
// Identify who (or what, in the case of a tool) created the SPDX file. If the SPDX file was created by an
// individual, indicate the person's name. If the SPDX file was created on behalf of a company or organization,
//indicate the entity name. If the SPDX file was created using a software tool, indicate the name and version
// indicate the entity name. If the SPDX file was created using a software tool, indicate the name and version
// for that tool. If multiple participants or tools were involved, use multiple instances of this field. Person
// name or organization name may be designated as “anonymous” if appropriate.
Creators []string `json:"creators"`
@@ -40,4 +40,6 @@ type Document struct {
Files []File `json:"files,omitempty"`
// Snippets referenced in the SPDX document
Snippets []Snippet `json:"snippets,omitempty"`
// Relationships referenced in the SPDX document
Relationships []Relationship `json:"relationships,omitempty"`
}
@@ -20,22 +20,22 @@ type File struct {
Item
// (At least one is required.) The checksum property provides a mechanism that can be used to verify that the
// contents of a File or Package have not changed.
Checksums []Checksum `json:"checksums"`
Checksums []Checksum `json:"checksums,omitempty"`
// This field provides a place for the SPDX file creator to record file contributors. Contributors could include
// names of copyright holders and/or authors who may not be copyright holders yet contributed to the file content.
FileContributors []string `json:"fileContributors"`
FileContributors []string `json:"fileContributors,omitempty"`
// Each element is a SPDX ID for a File.
FileDependencies []string `json:"fileDependencies"`
FileDependencies []string `json:"fileDependencies,omitempty"`
// The name of the file relative to the root of the package.
FileName string `json:"fileName"`
// The type of the file
FileTypes []string `json:"fileTypes"`
FileTypes []string `json:"fileTypes,omitempty"`
// This field provides a place for the SPDX file creator to record potential legal notices found in the file.
// This may or may not include copyright statements.
NoticeText string `json:"noticeText,omitempty"`
// Indicates the project in which the SpdxElement originated. Tools must preserve doap:homepage and doap:name
// properties and the URI (if one is known) of doap:Project resources that are values of this property. All other
// properties of doap:Projects are not directly supported by SPDX and may be dropped when translating to or
// from some SPDX formats.
ArtifactOf []string `json:"artifactOf"`
// from some SPDX formats(deprecated).
ArtifactOf []string `json:"artifactOf,omitempty"`
}
@@ -1,11 +1,13 @@
package spdx22
type Relationship struct {
// SPDX ID for SpdxElement. A related SpdxElement.
RelatedSpdxElement string `json:"relatedSpdxElement"`
// Id to which the SPDX element is related
SpdxElementID string `json:"spdxElementId"`
// Describes the type of relationship between two SPDX elements.
RelationshipType RelationshipType `json:"relationshipType"`
Comment string `json:"comment,omitempty"`
// SPDX ID for SpdxElement. A related SpdxElement.
RelatedSpdxElement string `json:"relatedSpdxElement"`
Comment string `json:"comment,omitempty"`
}
// source: https://spdx.github.io/spdx-spec/7-relationships-between-SPDX-elements/
@@ -1,7 +1,9 @@
package packages
import (
"crypto/sha256"
"fmt"
"path/filepath"
"strings"
"github.com/anchore/syft/internal/presenter/packages/model/spdx22"
@@ -29,6 +31,43 @@ func getSPDXExternalRefs(p *pkg.Package) (externalRefs []spdx22.ExternalRef) {
return externalRefs
}
func getSPDXFiles(packageSpdxID string, p *pkg.Package) (files []spdx22.File, fileIDs []string, relationships []spdx22.Relationship) {
files = make([]spdx22.File, 0)
fileIDs = make([]string, 0)
relationships = make([]spdx22.Relationship, 0)
pkgFileOwner, ok := p.Metadata.(pkg.FileOwner)
if !ok {
return files, fileIDs, relationships
}
for _, ownedFilePath := range pkgFileOwner.OwnedFiles() {
baseFileName := filepath.Base(ownedFilePath)
pathHash := sha256.Sum256([]byte(ownedFilePath))
fileSpdxID := spdx22.ElementID(fmt.Sprintf("File-%s-%x", p.Name, pathHash)).String()
fileIDs = append(fileIDs, fileSpdxID)
files = append(files, spdx22.File{
FileName: ownedFilePath,
Item: spdx22.Item{
Element: spdx22.Element{
SPDXID: fileSpdxID,
Name: baseFileName,
},
},
})
relationships = append(relationships, spdx22.Relationship{
SpdxElementID: packageSpdxID,
RelationshipType: spdx22.ContainsRelationship,
RelatedSpdxElement: fileSpdxID,
})
}
return files, fileIDs, relationships
}
func getSPDXLicense(p *pkg.Package) string {
// source: https://spdx.github.io/spdx-spec/3-package-information/#313-concluded-license
// The options to populate this field are limited to:
@@ -12,7 +12,7 @@ import (
)
func Test_getSPDXExternalRefs(t *testing.T) {
testCPE := must(pkg.NewCPE("cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*"))
testCPE := pkg.MustCPE("cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*")
tests := []struct {
name string
input pkg.Package
@@ -95,6 +95,15 @@ func Test_getSPDXLicense(t *testing.T) {
},
expected: "GPL-3.0",
},
{
name: "debian to spdx conversion",
input: pkg.Package{
Licenses: []string{
"GPL-2",
},
},
expected: "GPL-2.0",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
@@ -4,6 +4,8 @@ import (
"encoding/json"
"fmt"
"io"
"path"
"strings"
"time"
"github.com/anchore/syft/internal"
@@ -12,8 +14,11 @@ import (
"github.com/anchore/syft/internal/version"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/source"
"github.com/google/uuid"
)
const anchoreNamespace = "https://anchore.com/syft"
// SPDXJsonPresenter is a SPDX presentation object for the syft results (see https://github.com/spdx/spdx-spec)
type SPDXJsonPresenter struct {
catalog *pkg.Catalog
@@ -39,15 +44,29 @@ func (pres *SPDXJsonPresenter) Present(output io.Writer) error {
return enc.Encode(&doc)
}
// newSPDXJsonDocument creates and populates a new JSON document struct that follows the SPDX 2.2 spec from the given cataloging results.
func newSPDXJsonDocument(catalog *pkg.Catalog, srcMetadata source.Metadata) spdx22.Document {
var name string
uniqueID := uuid.Must(uuid.NewRandom())
var name, input, identifier string
switch srcMetadata.Scheme {
case source.ImageScheme:
name = srcMetadata.ImageMetadata.UserInput
name = cleanSPDXName(srcMetadata.ImageMetadata.UserInput)
input = "image"
case source.DirectoryScheme:
name = srcMetadata.Path
name = cleanSPDXName(srcMetadata.Path)
input = "dir"
}
if name != "." {
identifier = path.Join(input, fmt.Sprintf("%s-%s", name, uniqueID.String()))
} else {
identifier = path.Join(input, uniqueID.String())
}
namespace := path.Join(anchoreNamespace, identifier)
packages, files, relationships := newSPDXJsonElements(catalog)
return spdx22.Document{
Element: spdx22.Element{
SPDXID: spdx22.ElementID("DOCUMENT").String(),
@@ -64,23 +83,35 @@ func newSPDXJsonDocument(catalog *pkg.Catalog, srcMetadata source.Metadata) spdx
LicenseListVersion: spdxlicense.Version,
},
DataLicense: "CC0-1.0",
DocumentNamespace: fmt.Sprintf("https://anchore.com/syft/image/%s", srcMetadata.ImageMetadata.UserInput),
Packages: newSPDXJsonPackages(catalog),
DocumentNamespace: namespace,
Packages: packages,
Files: files,
Relationships: relationships,
}
}
func newSPDXJsonPackages(catalog *pkg.Catalog) []spdx22.Package {
results := make([]spdx22.Package, 0)
func newSPDXJsonElements(catalog *pkg.Catalog) ([]spdx22.Package, []spdx22.File, []spdx22.Relationship) {
packages := make([]spdx22.Package, 0)
relationships := make([]spdx22.Relationship, 0)
files := make([]spdx22.File, 0)
for _, p := range catalog.Sorted() {
license := getSPDXLicense(p)
packageSpdxID := spdx22.ElementID(fmt.Sprintf("Package-%+v-%s-%s", p.Type, p.Name, p.Version)).String()
packageFiles, fileIDs, packageFileRelationships := getSPDXFiles(packageSpdxID, p)
files = append(files, packageFiles...)
relationships = append(relationships, packageFileRelationships...)
// note: the license concluded and declared should be the same since we are collecting license information
// from the project data itself (the installed package files).
results = append(results, spdx22.Package{
packages = append(packages, spdx22.Package{
Description: getSPDXDescription(p),
DownloadLocation: getSPDXDownloadLocation(p),
ExternalRefs: getSPDXExternalRefs(p),
FilesAnalyzed: false,
HasFiles: fileIDs,
Homepage: getSPDXHomepage(p),
LicenseDeclared: license, // The Declared License is what the authors of a project believe govern the package
Originator: getSPDXOriginator(p),
@@ -89,11 +120,23 @@ func newSPDXJsonPackages(catalog *pkg.Catalog) []spdx22.Package {
Item: spdx22.Item{
LicenseConcluded: license, // The Concluded License field is the license the SPDX file creator believes governs the package
Element: spdx22.Element{
SPDXID: spdx22.ElementID(fmt.Sprintf("Package-%+v-%s-%s", p.Type, p.Name, p.Version)).String(),
SPDXID: packageSpdxID,
Name: p.Name,
},
},
})
}
return results
return packages, files, relationships
}
func cleanSPDXName(name string) string {
// remove # according to specification
name = strings.ReplaceAll(name, "#", "-")
// remove : for url construction
name = strings.ReplaceAll(name, ":", "-")
// clean relative pathing
return path.Clean(name)
}
@@ -4,13 +4,15 @@ import (
"flag"
"regexp"
"testing"
"github.com/anchore/syft/internal/formats/common/testutils"
)
var updateSpdxJson = flag.Bool("update-spdx-json", false, "update the *.golden files for spdx-json presenters")
func TestSPDXJSONDirectoryPresenter(t *testing.T) {
catalog, metadata, _ := presenterDirectoryInput(t)
assertPresenterAgainstGoldenSnapshot(t,
catalog, metadata, _ := testutils.DirectoryInput(t)
testutils.AssertPresenterAgainstGoldenSnapshot(t,
NewSPDXJSONPresenter(catalog, metadata),
*updateSpdxJson,
spdxJsonRedactor,
@@ -19,8 +21,8 @@ func TestSPDXJSONDirectoryPresenter(t *testing.T) {
func TestSPDXJSONImagePresenter(t *testing.T) {
testImage := "image-simple"
catalog, metadata, _ := presenterImageInput(t, testImage)
assertPresenterAgainstGoldenImageSnapshot(t,
catalog, metadata, _ := testutils.ImageInput(t, testImage)
testutils.AssertPresenterAgainstGoldenImageSnapshot(t,
NewSPDXJSONPresenter(catalog, metadata),
testImage,
*updateSpdxJson,
@@ -29,5 +31,12 @@ func TestSPDXJSONImagePresenter(t *testing.T) {
}
func spdxJsonRedactor(s []byte) []byte {
return regexp.MustCompile(`"created": .*`).ReplaceAll(s, []byte("redacted"))
// each SBOM reports the time it was generated, which is not useful during snapshot testing
s = regexp.MustCompile(`"created": .*`).ReplaceAll(s, []byte("redacted"))
// each SBOM reports a unique documentNamespace when generated, this is not useful for snapshot testing
s = regexp.MustCompile(`"documentNamespace": .*`).ReplaceAll(s, []byte("redacted"))
// the license list will be updated periodically, the value here should not be directly tested in snapshot tests
return regexp.MustCompile(`"licenseListVersion": .*`).ReplaceAll(s, []byte("redacted"))
}
@@ -4,13 +4,15 @@ import (
"flag"
"regexp"
"testing"
"github.com/anchore/syft/internal/formats/common/testutils"
)
var updateSpdxTagValue = flag.Bool("update-spdx-tv", false, "update the *.golden files for spdx-tv presenters")
func TestSPDXTagValueDirectoryPresenter(t *testing.T) {
catalog, metadata, _ := presenterDirectoryInput(t)
assertPresenterAgainstGoldenSnapshot(t,
catalog, metadata, _ := testutils.DirectoryInput(t)
testutils.AssertPresenterAgainstGoldenSnapshot(t,
NewSPDXTagValuePresenter(catalog, metadata),
*updateSpdxTagValue,
spdxTagValueRedactor,
@@ -19,8 +21,8 @@ func TestSPDXTagValueDirectoryPresenter(t *testing.T) {
func TestSPDXTagValueImagePresenter(t *testing.T) {
testImage := "image-simple"
catalog, metadata, _ := presenterImageInput(t, testImage)
assertPresenterAgainstGoldenImageSnapshot(t,
catalog, metadata, _ := testutils.ImageInput(t, testImage)
testutils.AssertPresenterAgainstGoldenImageSnapshot(t,
NewSPDXTagValuePresenter(catalog, metadata),
testImage,
*updateSpdxTagValue,
@@ -29,5 +31,8 @@ func TestSPDXTagValueImagePresenter(t *testing.T) {
}
func spdxTagValueRedactor(s []byte) []byte {
return regexp.MustCompile(`Created: .*`).ReplaceAll(s, []byte("redacted"))
// each SBOM reports the time it was generated, which is not useful during snapshot testing
s = regexp.MustCompile(`Created: .*`).ReplaceAll(s, []byte("redacted"))
// the license list will be updated periodically, the value here should not be directly tested in snapshot tests
return regexp.MustCompile(`LicenseListVersion: .*`).ReplaceAll(s, []byte("redacted"))
}
@@ -27,6 +27,11 @@
"author": "",
"authorEmail": "",
"platform": "",
"files": [
{
"path": "/some/path/pkg1/depedencies/foo"
}
],
"sitePackagesRootPath": ""
}
},
@@ -9,7 +9,7 @@
"locations": [
{
"path": "/somefile-1.txt",
"layerID": "sha256:fb6beecb75b39f4bb813dbf177e501edd5ddb3e69bb45cedeb78c676ee1b7a59"
"layerID": "sha256:ffb5e9eaa453a002110719d12c294960117ca2903953d1faa40f01dc3f77045c"
}
],
"licenses": [
@@ -40,7 +40,7 @@
"locations": [
{
"path": "/somefile-2.txt",
"layerID": "sha256:319b588ce64253a87b533c8ed01cf0025e0eac98e7b516e12532957e1244fdec"
"layerID": "sha256:8463854829fc53d47b9dcdf7ee79fe7eb4ca7933c910f67f8521412f7a2f5c21"
}
],
"licenses": [],
@@ -67,7 +67,7 @@
"type": "image",
"target": {
"userInput": "user-image-input",
"imageID": "sha256:5900c94a5bc1e083aa24ad1a223bf6eb9910dc8a6b01cb979ec306cb91709ea1",
"imageID": "sha256:112851310e48e604f7379e2a3acddab50e91ce926edacb598a532e60ff6b776a",
"manifestDigest": "sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368",
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"tags": [
@@ -77,17 +77,17 @@
"layers": [
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"digest": "sha256:fb6beecb75b39f4bb813dbf177e501edd5ddb3e69bb45cedeb78c676ee1b7a59",
"digest": "sha256:ffb5e9eaa453a002110719d12c294960117ca2903953d1faa40f01dc3f77045c",
"size": 22
},
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"digest": "sha256:319b588ce64253a87b533c8ed01cf0025e0eac98e7b516e12532957e1244fdec",
"digest": "sha256:8463854829fc53d47b9dcdf7ee79fe7eb4ca7933c910f67f8521412f7a2f5c21",
"size": 16
}
],
"manifest": "eyJzY2hlbWFWZXJzaW9uIjoyLCJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmRpc3RyaWJ1dGlvbi5tYW5pZmVzdC52Mitqc29uIiwiY29uZmlnIjp7Im1lZGlhVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5kb2NrZXIuY29udGFpbmVyLmltYWdlLnYxK2pzb24iLCJzaXplIjo2NjUsImRpZ2VzdCI6InNoYTI1Njo1OTAwYzk0YTViYzFlMDgzYWEyNGFkMWEyMjNiZjZlYjk5MTBkYzhhNmIwMWNiOTc5ZWMzMDZjYjkxNzA5ZWExIn0sImxheWVycyI6W3sibWVkaWFUeXBlIjoiYXBwbGljYXRpb24vdm5kLmRvY2tlci5pbWFnZS5yb290ZnMuZGlmZi50YXIuZ3ppcCIsInNpemUiOjIwNDgsImRpZ2VzdCI6InNoYTI1NjpmYjZiZWVjYjc1YjM5ZjRiYjgxM2RiZjE3N2U1MDFlZGQ1ZGRiM2U2OWJiNDVjZWRlYjc4YzY3NmVlMWI3YTU5In0seyJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmltYWdlLnJvb3Rmcy5kaWZmLnRhci5nemlwIiwic2l6ZSI6MjA0OCwiZGlnZXN0Ijoic2hhMjU2OjMxOWI1ODhjZTY0MjUzYTg3YjUzM2M4ZWQwMWNmMDAyNWUwZWFjOThlN2I1MTZlMTI1MzI5NTdlMTI0NGZkZWMifV19",
"config": "eyJhcmNoaXRlY3R1cmUiOiJhbWQ2NCIsImNvbmZpZyI6eyJFbnYiOlsiUEFUSD0vdXNyL2xvY2FsL3NiaW46L3Vzci9sb2NhbC9iaW46L3Vzci9zYmluOi91c3IvYmluOi9zYmluOi9iaW4iXSwiV29ya2luZ0RpciI6Ii8iLCJPbkJ1aWxkIjpudWxsfSwiY3JlYXRlZCI6IjIwMjEtMDQtMDZUMTk6MTM6NTIuNTI0Mzc4WiIsImhpc3RvcnkiOlt7ImNyZWF0ZWQiOiIyMDIxLTA0LTA2VDE5OjEzOjUyLjQ1ODIwNjFaIiwiY3JlYXRlZF9ieSI6IkFERCBmaWxlLTEudHh0IC9zb21lZmlsZS0xLnR4dCAjIGJ1aWxka2l0IiwiY29tbWVudCI6ImJ1aWxka2l0LmRvY2tlcmZpbGUudjAifSx7ImNyZWF0ZWQiOiIyMDIxLTA0LTA2VDE5OjEzOjUyLjUyNDM3OFoiLCJjcmVhdGVkX2J5IjoiQUREIGZpbGUtMi50eHQgL3NvbWVmaWxlLTIudHh0ICMgYnVpbGRraXQiLCJjb21tZW50IjoiYnVpbGRraXQuZG9ja2VyZmlsZS52MCJ9XSwib3MiOiJsaW51eCIsInJvb3RmcyI6eyJ0eXBlIjoibGF5ZXJzIiwiZGlmZl9pZHMiOlsic2hhMjU2OmZiNmJlZWNiNzViMzlmNGJiODEzZGJmMTc3ZTUwMWVkZDVkZGIzZTY5YmI0NWNlZGViNzhjNjc2ZWUxYjdhNTkiLCJzaGEyNTY6MzE5YjU4OGNlNjQyNTNhODdiNTMzYzhlZDAxY2YwMDI1ZTBlYWM5OGU3YjUxNmUxMjUzMjk1N2UxMjQ0ZmRlYyJdfX0=",
"manifest": "eyJzY2hlbWFWZXJzaW9uIjoyLCJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmRpc3RyaWJ1dGlvbi5tYW5pZmVzdC52Mitqc29uIiwiY29uZmlnIjp7Im1lZGlhVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5kb2NrZXIuY29udGFpbmVyLmltYWdlLnYxK2pzb24iLCJzaXplIjo2NzMsImRpZ2VzdCI6InNoYTI1NjoxMTI4NTEzMTBlNDhlNjA0ZjczNzllMmEzYWNkZGFiNTBlOTFjZTkyNmVkYWNiNTk4YTUzMmU2MGZmNmI3NzZhIn0sImxheWVycyI6W3sibWVkaWFUeXBlIjoiYXBwbGljYXRpb24vdm5kLmRvY2tlci5pbWFnZS5yb290ZnMuZGlmZi50YXIuZ3ppcCIsInNpemUiOjIwNDgsImRpZ2VzdCI6InNoYTI1NjpmZmI1ZTllYWE0NTNhMDAyMTEwNzE5ZDEyYzI5NDk2MDExN2NhMjkwMzk1M2QxZmFhNDBmMDFkYzNmNzcwNDVjIn0seyJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmltYWdlLnJvb3Rmcy5kaWZmLnRhci5nemlwIiwic2l6ZSI6MjA0OCwiZGlnZXN0Ijoic2hhMjU2Ojg0NjM4NTQ4MjlmYzUzZDQ3YjlkY2RmN2VlNzlmZTdlYjRjYTc5MzNjOTEwZjY3Zjg1MjE0MTJmN2EyZjVjMjEifV19",
"config": "eyJhcmNoaXRlY3R1cmUiOiJhbWQ2NCIsImNvbmZpZyI6eyJFbnYiOlsiUEFUSD0vdXNyL2xvY2FsL3NiaW46L3Vzci9sb2NhbC9iaW46L3Vzci9zYmluOi91c3IvYmluOi9zYmluOi9iaW4iXSwiV29ya2luZ0RpciI6Ii8iLCJPbkJ1aWxkIjpudWxsfSwiY3JlYXRlZCI6IjIwMjEtMDktMDhUMTc6MjE6NTguODk2NTI5MTkyWiIsImhpc3RvcnkiOlt7ImNyZWF0ZWQiOiIyMDIxLTA5LTA4VDE3OjIxOjU4Ljg3OTY5MDgyNFoiLCJjcmVhdGVkX2J5IjoiQUREIGZpbGUtMS50eHQgL3NvbWVmaWxlLTEudHh0ICMgYnVpbGRraXQiLCJjb21tZW50IjoiYnVpbGRraXQuZG9ja2VyZmlsZS52MCJ9LHsiY3JlYXRlZCI6IjIwMjEtMDktMDhUMTc6MjE6NTguODk2NTI5MTkyWiIsImNyZWF0ZWRfYnkiOiJBREQgZmlsZS0yLnR4dCAvc29tZWZpbGUtMi50eHQgIyBidWlsZGtpdCIsImNvbW1lbnQiOiJidWlsZGtpdC5kb2NrZXJmaWxlLnYwIn1dLCJvcyI6ImxpbnV4Iiwicm9vdGZzIjp7InR5cGUiOiJsYXllcnMiLCJkaWZmX2lkcyI6WyJzaGEyNTY6ZmZiNWU5ZWFhNDUzYTAwMjExMDcxOWQxMmMyOTQ5NjAxMTdjYTI5MDM5NTNkMWZhYTQwZjAxZGMzZjc3MDQ1YyIsInNoYTI1Njo4NDYzODU0ODI5ZmM1M2Q0N2I5ZGNkZjdlZTc5ZmU3ZWI0Y2E3OTMzYzkxMGY2N2Y4NTIxNDEyZjdhMmY1YzIxIl19fQ==",
"repoDigests": [],
"scope": "Squashed"
}
@@ -3,15 +3,15 @@
"name": "/some/path",
"spdxVersion": "SPDX-2.2",
"creationInfo": {
"created": "2021-06-23T17:48:32.734847Z",
"created": "2021-10-12T18:40:22.948394Z",
"creators": [
"Organization: Anchore, Inc",
"Tool: syft-[not provided]"
],
"licenseListVersion": "3.13"
"licenseListVersion": "3.14"
},
"dataLicense": "CC0-1.0",
"documentNamespace": "https://anchore.com/syft/image/",
"documentNamespace": "https:/anchore.com/syft/dir/some/path-98ae71fb-f276-4c5c-acf7-25770bf7bca2",
"packages": [
{
"SPDXID": "SPDXRef-Package-python-package-1-1.0.1",
@@ -31,6 +31,9 @@
}
],
"filesAnalyzed": false,
"hasFiles": [
"SPDXRef-File-package-1-efae7fecc76ca25da40f79d7ef5b8933510434914835832c7976f3e866aa756a"
],
"licenseDeclared": "MIT",
"sourceInfo": "acquired package info from installed python package manifest file: /some/path/pkg1",
"versionInfo": "1.0.1"
@@ -57,5 +60,20 @@
"sourceInfo": "acquired package info from DPKG DB: /some/path/pkg1",
"versionInfo": "2.0.1"
}
],
"files": [
{
"SPDXID": "SPDXRef-File-package-1-efae7fecc76ca25da40f79d7ef5b8933510434914835832c7976f3e866aa756a",
"name": "foo",
"licenseConcluded": "",
"fileName": "/some/path/pkg1/dependencies/foo"
}
],
"relationships": [
{
"spdxElementId": "SPDXRef-Package-python-package-1-1.0.1",
"relationshipType": "CONTAINS",
"relatedSpdxElement": "SPDXRef-File-package-1-efae7fecc76ca25da40f79d7ef5b8933510434914835832c7976f3e866aa756a"
}
]
}
@@ -3,15 +3,15 @@
"name": "user-image-input",
"spdxVersion": "SPDX-2.2",
"creationInfo": {
"created": "2021-06-23T17:48:32.7379Z",
"created": "2021-10-12T18:40:22.953633Z",
"creators": [
"Organization: Anchore, Inc",
"Tool: syft-[not provided]"
],
"licenseListVersion": "3.13"
"licenseListVersion": "3.14"
},
"dataLicense": "CC0-1.0",
"documentNamespace": "https://anchore.com/syft/image/user-image-input",
"documentNamespace": "https:/anchore.com/syft/image/user-image-input-149edbad-3c01-4ee0-b3a0-75232312bf51",
"packages": [
{
"SPDXID": "SPDXRef-Package-python-package-1-1.0.1",
@@ -32,7 +32,7 @@ func (pres *TextPresenter) Present(output io.Writer) error {
switch pres.srcMetadata.Scheme {
case source.DirectoryScheme:
fmt.Fprintln(w, fmt.Sprintf("[Path: %s]", pres.srcMetadata.Path))
fmt.Fprintf(w, "[Path: %s]\n", pres.srcMetadata.Path)
case source.ImageScheme:
fmt.Fprintln(w, "[Image]")
@@ -51,7 +51,7 @@ func (pres *TextPresenter) Present(output io.Writer) error {
// populate artifacts...
rows := 0
for _, p := range pres.catalog.Sorted() {
fmt.Fprintln(w, fmt.Sprintf("[%s]", p.Name))
fmt.Fprintf(w, "[%s]\n", p.Name)
fmt.Fprintln(w, " Version:\t", p.Version)
fmt.Fprintln(w, " Type:\t", string(p.Type))
fmt.Fprintln(w, " Found by:\t", p.FoundBy)
@@ -3,13 +3,15 @@ package packages
import (
"flag"
"testing"
"github.com/anchore/syft/internal/formats/common/testutils"
)
var updateTextPresenterGoldenFiles = flag.Bool("update-text", false, "update the *.golden files for text presenters")
func TestTextDirectoryPresenter(t *testing.T) {
catalog, metadata, _ := presenterDirectoryInput(t)
assertPresenterAgainstGoldenSnapshot(t,
catalog, metadata, _ := testutils.DirectoryInput(t)
testutils.AssertPresenterAgainstGoldenSnapshot(t,
NewTextPresenter(catalog, metadata),
*updateTextPresenterGoldenFiles,
)
@@ -17,8 +19,8 @@ func TestTextDirectoryPresenter(t *testing.T) {
func TestTextImagePresenter(t *testing.T) {
testImage := "image-simple"
catalog, metadata, _ := presenterImageInput(t, testImage)
assertPresenterAgainstGoldenImageSnapshot(t,
catalog, metadata, _ := testutils.ImageInput(t, testImage)
testutils.AssertPresenterAgainstGoldenImageSnapshot(t,
NewTextPresenter(catalog, metadata),
testImage,
*updateTextPresenterGoldenFiles,
@@ -1,7 +1,8 @@
package poweruser
import (
"github.com/anchore/syft/internal/presenter/packages"
"github.com/anchore/syft/internal/formats/syftjson"
"github.com/anchore/syft/internal/formats/syftjson/model"
)
type JSONDocument struct {
@@ -13,16 +14,11 @@ type JSONDocument struct {
FileContents []JSONFileContents `json:"fileContents,omitempty"` // note: must have omitempty
FileMetadata []JSONFileMetadata `json:"fileMetadata,omitempty"` // note: must have omitempty
Secrets []JSONSecrets `json:"secrets,omitempty"` // note: must have omitempty
packages.JSONDocument
model.Document
}
// NewJSONDocument creates and populates a new JSON document struct from the given cataloging results.
func NewJSONDocument(config JSONDocumentConfig) (JSONDocument, error) {
pkgsDoc, err := packages.NewJSONDocument(config.PackageCatalog, config.SourceMetadata, config.Distro, config.ApplicationConfig.Package.Cataloger.ScopeOpt, config.ApplicationConfig)
if err != nil {
return JSONDocument{}, err
}
fileMetadata, err := NewJSONFileMetadata(config.FileMetadata, config.FileDigests)
if err != nil {
return JSONDocument{}, err
@@ -33,6 +29,6 @@ func NewJSONDocument(config JSONDocumentConfig) (JSONDocument, error) {
FileContents: NewJSONFileContents(config.FileContents),
FileMetadata: fileMetadata,
Secrets: NewJSONSecrets(config.Secrets),
JSONDocument: pkgsDoc,
Document: syftjson.ToFormatModel(config.PackageCatalog, &config.SourceMetadata, config.Distro, config.ApplicationConfig.Package.Cataloger.ScopeOpt, config.ApplicationConfig),
}, nil
}
@@ -22,6 +22,7 @@ type JSONFileMetadataEntry struct {
UserID int `json:"userID"`
GroupID int `json:"groupID"`
Digests []file.Digest `json:"digests,omitempty"`
MIMEType string `json:"mimeType"`
}
func NewJSONFileMetadata(data map[source.Location]source.FileMetadata, digests map[source.Location][]file.Digest) ([]JSONFileMetadata, error) {
@@ -46,6 +47,7 @@ func NewJSONFileMetadata(data map[source.Location]source.FileMetadata, digests m
UserID: metadata.UserID,
GroupID: metadata.GroupID,
Digests: digestResults,
MIMEType: metadata.MIMEType,
},
})
}
@@ -16,7 +16,8 @@
"mode": 775,
"type": "directory",
"userID": 0,
"groupID": 0
"groupID": 0,
"mimeType": ""
}
},
{
@@ -33,7 +34,8 @@
"algorithm": "sha256",
"value": "366a3f5653e34673b875891b021647440d0127c2ef041e3b1a22da2a7d4f3703"
}
]
],
"mimeType": ""
}
},
{
@@ -45,7 +47,8 @@
"type": "symbolicLink",
"linkDestination": "/c",
"userID": 0,
"groupID": 0
"groupID": 0,
"mimeType": ""
}
},
{
@@ -62,7 +65,8 @@
"algorithm": "sha256",
"value": "1b3722da2a7d90d033b87581a2a3f12021647445653e34666ef041e3b4f3707c"
}
]
],
"mimeType": ""
}
}
],
@@ -168,6 +172,7 @@
"configuration": {
"configPath": "",
"output": "",
"file": "",
"quiet": false,
"check-for-app-update": false,
"anchore": {
@@ -227,6 +232,7 @@
},
"registry": {
"insecure-skip-tls-verify": false,
"insecure-use-http": false,
"auth": null
}
}
@@ -0,0 +1,190 @@
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"regexp"
"sort"
"strings"
"text/template"
"time"
"github.com/scylladb/go-set/strset"
)
// This program generates license_list.go.
const (
source = "license_list.go"
url = "https://spdx.org/licenses/licenses.json"
)
var tmp = template.Must(template.New("").Parse(`// Code generated by go generate; DO NOT EDIT.
// This file was generated by robots at {{ .Timestamp }}
// using data from {{ .URL }}
package spdxlicense
const Version = {{ printf "%q" .Version }}
var licenseIDs = map[string]string{
{{- range $k, $v := .LicenseIDs }}
{{ printf "%q" $k }}: {{ printf "%q" $v }},
{{- end }}
}
`))
var versionMatch = regexp.MustCompile(`-([0-9]+)\.?([0-9]+)?\.?([0-9]+)?\.?`)
type LicenseList struct {
Version string `json:"licenseListVersion"`
Licenses []struct {
ID string `json:"licenseId"`
Name string `json:"name"`
Text string `json:"licenseText"`
Deprecated bool `json:"isDeprecatedLicenseId"`
OSIApproved bool `json:"isOsiApproved"`
SeeAlso []string `json:"seeAlso"`
} `json:"licenses"`
}
func main() {
if err := run(); err != nil {
fmt.Println(err.Error())
os.Exit(1)
}
}
func run() error {
resp, err := http.Get(url)
if err != nil {
return fmt.Errorf("unable to get licenses list: %+v", err)
}
var result LicenseList
if err = json.NewDecoder(resp.Body).Decode(&result); err != nil {
return fmt.Errorf("unable to decode license list: %+v", err)
}
defer func() {
if err := resp.Body.Close(); err != nil {
log.Fatalf("unable to close body: %+v", err)
}
}()
f, err := os.Create(source)
if err != nil {
return fmt.Errorf("unable to create %q: %+v", source, err)
}
defer func() {
if err := f.Close(); err != nil {
log.Fatalf("unable to close %q: %+v", source, err)
}
}()
licenseIDs := processSPDXLicense(result)
err = tmp.Execute(f, struct {
Timestamp time.Time
URL string
Version string
LicenseIDs map[string]string
}{
Timestamp: time.Now(),
URL: url,
Version: result.Version,
LicenseIDs: licenseIDs,
})
if err != nil {
return fmt.Errorf("unable to generate template: %+v", err)
}
return nil
}
// Parsing the provided SPDX license list necessitates a two pass approach.
// The first pass is only related to what SPDX considers the truth. These K:V pairs will never be overwritten.
// The second pass attempts to generate known short/long version listings for each key.
// For info on some short name conventions see this document:
// https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/#license-short-name.
// The short long listing generation attempts to build all license permutations for a given key.
// The new keys are then also associated with their relative SPDX value. If a key has already been entered
// we know to ignore it since it came from the first pass which is considered the SPDX source of truth.
// We also sort the licenses for the second pass so that cases like `GPL-1` associate to `GPL-1.0` and not `GPL-1.1`.
func processSPDXLicense(result LicenseList) map[string]string {
// first pass build map
var licenseIDs = make(map[string]string)
for _, l := range result.Licenses {
cleanID := strings.ToLower(l.ID)
if _, exists := licenseIDs[cleanID]; exists {
log.Fatalf("duplicate license ID found: %q", cleanID)
}
licenseIDs[cleanID] = l.ID
}
sort.Slice(result.Licenses, func(i, j int) bool {
return result.Licenses[i].ID < result.Licenses[j].ID
})
// second pass build exceptions
// do not overwrite if already exists
for _, l := range result.Licenses {
var multipleID []string
cleanID := strings.ToLower(l.ID)
multipleID = append(multipleID, buildLicensePermutations(cleanID)...)
for _, id := range multipleID {
if _, exists := licenseIDs[id]; !exists {
licenseIDs[id] = l.ID
}
}
}
return licenseIDs
}
func buildLicensePermutations(license string) (perms []string) {
lv := findLicenseVersion(license)
vp := versionPermutations(lv)
version := strings.Join(lv, ".")
for _, p := range vp {
perms = append(perms, strings.Replace(license, version, p, 1))
}
return perms
}
func findLicenseVersion(license string) (version []string) {
versionList := versionMatch.FindAllStringSubmatch(license, -1)
if len(versionList) == 0 {
return version
}
for i, v := range versionList[0] {
if v != "" && i != 0 {
version = append(version, v)
}
}
return version
}
func versionPermutations(version []string) []string {
ver := append([]string(nil), version...)
perms := strset.New()
for i := 1; i <= 3; i++ {
if len(ver) < i+1 {
ver = append(ver, "0")
}
perm := strings.Join(ver[:i], ".")
badCount := strings.Count(perm, "0") + strings.Count(perm, ".")
if badCount != len(perm) {
perms.Add(perm)
}
}
return perms.List()
}
@@ -0,0 +1,146 @@
package main
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func TestLicensePermutations(t *testing.T) {
var tests = []struct {
shortName string
permutations []string
}{
{
"GPL-1-only",
[]string{
"GPL-1-only",
"GPL-1.0-only",
"GPL-1.0.0-only",
},
},
{
"GPL-2",
[]string{
"GPL-2",
"GPL-2.0",
"GPL-2.0.0",
},
},
{
"GPL-2.0+",
[]string{
"GPL-2+",
"GPL-2.0+",
"GPL-2.0.0+",
},
},
{
"GPL-3.0.0-or-later",
[]string{
"GPL-3-or-later",
"GPL-3.0-or-later",
"GPL-3.0.0-or-later",
},
},
{
"oldap-2.0",
[]string{
"oldap-2",
"oldap-2.0",
"oldap-2.0.0",
},
},
}
for _, test := range tests {
t.Run(test.shortName, func(t *testing.T) {
perms := buildLicensePermutations(test.shortName)
assert.ElementsMatch(t, test.permutations, perms)
})
}
}
func TestVersionPermutations(t *testing.T) {
var tests = []struct {
version []string
permutations []string
}{
{
[]string{"1", "0"},
[]string{"1", "1.0", "1.0.0"},
},
{
[]string{"2"},
[]string{"2", "2.0", "2.0.0"},
},
{
[]string{"2", "0"},
[]string{"2", "2.0", "2.0.0"},
},
{
[]string{"3", "0", "0"},
[]string{"3", "3.0", "3.0.0"},
},
{
[]string{"0", "3"},
[]string{"0.3", "0.3.0"},
},
{
[]string{"0", "0", "3"},
[]string{"0.0.3"},
},
}
for _, test := range tests {
t.Run(strings.Join(test.version, "."), func(t *testing.T) {
got := versionPermutations(test.version)
assert.ElementsMatch(t, test.permutations, got)
})
}
}
func TestFindLicenseVersion(t *testing.T) {
var tests = []struct {
license string
version []string
}{
{
"GPL-1.0-only",
[]string{"1", "0"},
},
{
"GPL-2.0",
[]string{"2", "0"},
},
{
"GPL-2.0.0",
[]string{"2", "0", "0"},
},
{
"GPL-2",
[]string{"2"},
},
{
"bzip2-1",
[]string{"1"},
},
{
"php-3.01",
[]string{"3", "01"},
},
{
"oldap-2.0",
[]string{"2", "0"},
},
}
for _, test := range tests {
t.Run(test.license, func(t *testing.T) {
got := findLicenseVersion(test.license)
assert.Equal(t, test.version, got)
})
}
}
@@ -1,92 +0,0 @@
// +build ignore
package main
import (
"encoding/json"
"log"
"net/http"
"os"
"strings"
"text/template"
"time"
)
// This program generates license_list.go.
const (
source = "license_list.go"
url = "https://spdx.org/licenses/licenses.json"
)
var tmp = template.Must(template.New("").Parse(`// Code generated by go generate; DO NOT EDIT.
// This file was generated by robots at {{ .Timestamp }}
// using data from {{ .URL }}
package spdxlicense
const Version = {{ printf "%q" .Version }}
var licenseIDs = map[string]string{
{{- range $k, $v := .LicenseIDs }}
{{ printf "%q" $k }}: {{ printf "%q" $v }},
{{- end }}
}
`))
type LicenseList struct {
Version string `json:"licenseListVersion"`
Licenses []struct {
ID string `json:"licenseId"`
Name string `json:"name"`
Text string `json:"licenseText"`
Deprecated bool `json:"isDeprecatedLicenseId"`
OSIApproved bool `json:"isOsiApproved"`
SeeAlso []string `json:"seeAlso"`
} `json:"licenses"`
}
func main() {
resp, err := http.Get(url)
if err != nil {
log.Fatalf("unable to get licenses list: %+v", err)
}
var result LicenseList
if err = json.NewDecoder(resp.Body).Decode(&result); err != nil {
log.Fatalf("unable to decode license list: %+v", err)
}
f, err := os.Create(source)
if err != nil {
log.Fatalf("unable to create %q: %+v", source, err)
}
defer func() {
if err := f.Close(); err != nil {
log.Fatalf("unable to close %q: %+v", source, err)
}
}()
var licenseIDs = make(map[string]string)
for _, l := range result.Licenses {
cleanID := strings.ToLower(l.ID)
if _, exists := licenseIDs[cleanID]; exists {
log.Fatalf("duplicate license ID found: %q", cleanID)
}
licenseIDs[cleanID] = l.ID
}
err = tmp.Execute(f, struct {
Timestamp time.Time
URL string
Version string
LicenseIDs map[string]string
}{
Timestamp: time.Now(),
URL: url,
Version: result.Version,
LicenseIDs: licenseIDs,
})
if err != nil {
log.Fatalf("unable to generate template: %+v", err)
}
}
+9 -1
View File
@@ -4,7 +4,15 @@ import (
"strings"
)
//go:generate go run generate_license_list.go
// https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/#license-short-name
// License generated in license_list.go uses a regular expression to help resolve cases where
// x.0.0 and x are supplied as version numbers. For SPDX compatibility, versions with trailing
// dot-zeroes are considered to be equivalent to versions without (e.g., “2.0.0” is considered equal to “2.0” and “2”).
// EX: gpl-2+ ---> GPL-2.0+
// EX: gpl-2.0.0-only ---> GPL-2.0-only
// See the debian link for more details on the spdx license differences
//go:generate go run generate/generate_license_list.go
func ID(id string) (string, bool) {
value, exists := licenseIDs[strings.ToLower(id)]
File diff suppressed because it is too large Load Diff
@@ -10,5 +10,6 @@ func TestLicenceListIDs(t *testing.T) {
// do a sanity check on the generated data
assert.Equal(t, "0BSD", licenseIDs["0bsd"])
assert.Equal(t, "ZPL-2.1", licenseIDs["zpl-2.1"])
assert.Equal(t, "GPL-2.0", licenseIDs["gpl-2"])
assert.NotEmpty(t, Version)
}
+60
View File
@@ -0,0 +1,60 @@
package spdxlicense
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestIDParse(t *testing.T) {
var tests = []struct {
shortName string
spdx string
}{
{
"GPL-1-only",
"GPL-1.0-only",
},
{
"GPL-2",
"GPL-2.0",
},
{
"GPL-2+",
"GPL-2.0+",
},
{
"GPL-3.0.0-or-later",
"GPL-3.0-or-later",
},
{
"GPL-3-with-autoconf-exception",
"GPL-3.0-with-autoconf-exception",
},
{
"CC-by-nc-3-de",
"CC-BY-NC-3.0-DE",
},
// the below few cases are NOT expected, however, seem unavoidable given the current approach
{
"w3c-20150513.0.0",
"W3C-20150513",
},
{
"spencer-86.0.0",
"Spencer-86",
},
{
"unicode-dfs-2015.0.0",
"Unicode-DFS-2015",
},
}
for _, test := range tests {
t.Run(test.shortName, func(t *testing.T) {
got, exists := ID(test.shortName)
assert.True(t, exists)
assert.Equal(t, test.spdx, got)
})
}
}
+24
View File
@@ -0,0 +1,24 @@
package ui
import (
"fmt"
"io"
syftEventParsers "github.com/anchore/syft/syft/event/parsers"
"github.com/wagoodman/go-partybus"
)
// handleCatalogerPresenterReady is a UI function for processing the CatalogerFinished bus event, displaying the catalog
// via the given presenter to stdout.
func handleCatalogerPresenterReady(event partybus.Event, reportOutput io.Writer) error {
// show the report to stdout
pres, err := syftEventParsers.ParsePresenterReady(event)
if err != nil {
return fmt.Errorf("bad CatalogerFinished event: %w", err)
}
if err := pres.Present(reportOutput); err != nil {
return fmt.Errorf("unable to show package catalog report: %w", err)
}
return nil
}
+31 -15
View File
@@ -1,9 +1,13 @@
//go:build linux || darwin
// +build linux darwin
package ui
import (
"bytes"
"context"
"fmt"
"io"
"os"
"sync"
@@ -11,7 +15,6 @@ import (
"github.com/anchore/syft/internal/logger"
syftEvent "github.com/anchore/syft/syft/event"
"github.com/anchore/syft/ui"
"github.com/k0kubun/go-ansi"
"github.com/wagoodman/go-partybus"
"github.com/wagoodman/jotframe/pkg/frame"
)
@@ -33,23 +36,28 @@ import (
// or in the shared ui package as a function on the main handler object. All handler functions should be completed
// processing an event before the ETUI exits (coordinated with a sync.WaitGroup)
type ephemeralTerminalUI struct {
unsubscribe func() error
handler *ui.Handler
waitGroup *sync.WaitGroup
frame *frame.Frame
logBuffer *bytes.Buffer
unsubscribe func() error
handler *ui.Handler
waitGroup *sync.WaitGroup
frame *frame.Frame
logBuffer *bytes.Buffer
uiOutput *os.File
reportOutput io.Writer
}
func NewEphemeralTerminalUI() UI {
// NewEphemeralTerminalUI writes all events to a TUI and writes the final report to the given writer.
func NewEphemeralTerminalUI(reportWriter io.Writer) UI {
return &ephemeralTerminalUI{
handler: ui.NewHandler(),
waitGroup: &sync.WaitGroup{},
handler: ui.NewHandler(),
waitGroup: &sync.WaitGroup{},
uiOutput: os.Stderr,
reportOutput: reportWriter,
}
}
func (h *ephemeralTerminalUI) Setup(unsubscribe func() error) error {
h.unsubscribe = unsubscribe
ansi.CursorHide()
hideCursor(h.uiOutput)
// prep the logger to not clobber the screen from now on (logrus only)
h.logBuffer = bytes.NewBufferString("")
@@ -79,7 +87,7 @@ func (h *ephemeralTerminalUI) Handle(event partybus.Event) error {
// are about to write bytes to stdout, so we should reset the terminal state first
h.closeScreen(false)
if err := handleCatalogerPresenterReady(event); err != nil {
if err := handleCatalogerPresenterReady(event, h.reportOutput); err != nil {
log.Errorf("unable to show %s event: %+v", event.Type, err)
}
@@ -93,7 +101,7 @@ func (h *ephemeralTerminalUI) openScreen() error {
config := frame.Config{
PositionPolicy: frame.PolicyFloatForward,
// only report output to stderr, reserve report output for stdout
Output: os.Stderr,
Output: h.uiOutput,
}
fr, err := frame.New(config)
@@ -126,14 +134,22 @@ func (h *ephemeralTerminalUI) flushLog() {
logWrapper, ok := log.Log.(*logger.LogrusLogger)
if ok {
fmt.Fprint(logWrapper.Output, h.logBuffer.String())
logWrapper.Logger.SetOutput(os.Stderr)
logWrapper.Logger.SetOutput(h.uiOutput)
} else {
fmt.Fprint(os.Stderr, h.logBuffer.String())
fmt.Fprint(h.uiOutput, h.logBuffer.String())
}
}
func (h *ephemeralTerminalUI) Teardown(force bool) error {
h.closeScreen(force)
ansi.CursorShow()
showCursor(h.uiOutput)
return nil
}
func hideCursor(output io.Writer) {
fmt.Fprint(output, "\x1b[?25l")
}
func showCursor(output io.Writer) {
fmt.Fprint(output, "\x1b[?25h")
}
@@ -1,10 +1,12 @@
//go:build linux || darwin
// +build linux darwin
package ui
import (
"context"
"fmt"
"io"
"os"
"sync"
"github.com/anchore/syft/internal"
@@ -15,21 +17,6 @@ import (
"github.com/wagoodman/go-partybus"
)
// handleCatalogerPresenterReady is a UI function for processing the CatalogerFinished bus event, displaying the catalog
// via the given presenter to stdout.
func handleCatalogerPresenterReady(event partybus.Event) error {
// show the report to stdout
pres, err := syftEventParsers.ParsePresenterReady(event)
if err != nil {
return fmt.Errorf("bad CatalogerFinished event: %w", err)
}
if err := pres.Present(os.Stdout); err != nil {
return fmt.Errorf("unable to show package catalog report: %w", err)
}
return nil
}
// handleAppUpdateAvailable is a UI handler function to display a new application version to the top of the screen.
func handleAppUpdateAvailable(_ context.Context, fr *frame.Frame, event partybus.Event, _ *sync.WaitGroup) error {
newVersion, err := syftEventParsers.ParseAppUpdateAvailable(event)
+10 -4
View File
@@ -1,17 +1,23 @@
package ui
import (
"io"
"github.com/anchore/syft/internal/log"
syftEvent "github.com/anchore/syft/syft/event"
"github.com/wagoodman/go-partybus"
)
type loggerUI struct {
unsubscribe func() error
unsubscribe func() error
reportOutput io.Writer
}
func NewLoggerUI() UI {
return &loggerUI{}
// NewLoggerUI writes all events to the common application logger and writes the final report to the given writer.
func NewLoggerUI(reportWriter io.Writer) UI {
return &loggerUI{
reportOutput: reportWriter,
}
}
func (l *loggerUI) Setup(unsubscribe func() error) error {
@@ -25,7 +31,7 @@ func (l loggerUI) Handle(event partybus.Event) error {
return nil
}
if err := handleCatalogerPresenterReady(event); err != nil {
if err := handleCatalogerPresenterReady(event, l.reportOutput); err != nil {
log.Warnf("unable to show catalog image finished event: %+v", err)
}
+15 -10
View File
@@ -1,29 +1,34 @@
//go:build linux || darwin
// +build linux darwin
package ui
import (
"io"
"os"
"runtime"
"golang.org/x/crypto/ssh/terminal"
"golang.org/x/term"
)
// TODO: build tags to exclude options from windows
// Select is responsible for determining the specific UI function given select user option, the current platform
// config values, and environment status (such as a TTY being present).
func Select(verbose, quiet bool) UI {
var ui UI
isStdoutATty := terminal.IsTerminal(int(os.Stdout.Fd()))
isStderrATty := terminal.IsTerminal(int(os.Stderr.Fd()))
// config values, and environment status (such as a TTY being present). The first UI in the returned slice of UIs
// is intended to be used and the UIs that follow are meant to be attempted only in a fallback posture when there
// are environmental problems (e.g. cannot write to the terminal). A writer is provided to capture the output of
// the final SBOM report.
func Select(verbose, quiet bool, reportWriter io.Writer) (uis []UI) {
isStdoutATty := term.IsTerminal(int(os.Stdout.Fd()))
isStderrATty := term.IsTerminal(int(os.Stderr.Fd()))
notATerminal := !isStderrATty && !isStdoutATty
switch {
case runtime.GOOS == "windows" || verbose || quiet || notATerminal || !isStderrATty:
ui = NewLoggerUI()
uis = append(uis, NewLoggerUI(reportWriter))
default:
ui = NewEphemeralTerminalUI()
uis = append(uis, NewEphemeralTerminalUI(reportWriter), NewLoggerUI(reportWriter))
}
return ui
return uis
}
+17
View File
@@ -0,0 +1,17 @@
//go:build windows
// +build windows
package ui
import (
"io"
)
// Select is responsible for determining the specific UI function given select user option, the current platform
// config values, and environment status (such as a TTY being present). The first UI in the returned slice of UIs
// is intended to be used and the UIs that follow are meant to be attempted only in a fallback posture when there
// are environmental problems (e.g. cannot write to the terminal). A writer is provided to capture the output of
// the final SBOM report.
func Select(verbose, quiet bool, reportWriter io.Writer) (uis []UI) {
return append(uis, NewLoggerUI(reportWriter))
}
+1 -5
View File
@@ -45,11 +45,7 @@ func build() *jsonschema.Schema {
reflector := &jsonschema.Reflector{
AllowAdditionalProperties: true,
TypeNamer: func(r reflect.Type) string {
name := r.Name()
if strings.HasPrefix(name, "JSON") {
name = strings.TrimPrefix(name, "JSON")
}
return name
return strings.TrimPrefix(r.Name(), "JSON")
},
}
documentSchema := reflector.ReflectFromType(reflect.TypeOf(&poweruser.JSONDocument{}))
+3 -1
View File
@@ -19,10 +19,12 @@ func NewDistro(t Type, ver, like string) (Distro, error) {
if ver == "" {
return Distro{Type: t}, nil
}
verObj, err := hashiVer.NewVersion(ver)
if err != nil {
return Distro{}, fmt.Errorf("could not create distro version: %w", err)
}
return Distro{
Type: t,
Version: verObj,
@@ -39,7 +41,7 @@ func (d Distro) Name() string {
// MajorVersion returns the major version value from the pseudo-semantically versioned distro version value.
func (d Distro) MajorVersion() string {
if d.Version == nil {
return fmt.Sprint("(version unknown)")
return "(version unknown)"
}
return fmt.Sprintf("%d", d.Version.Segments()[0])
}
+72 -10
View File
@@ -35,6 +35,16 @@ var identityFiles = []parseEntry{
path: "/bin/busybox",
fn: parseBusyBox,
},
{
// check for centos:6
path: "/etc/system-release-cpe",
fn: parseSystemReleaseCPE,
},
{
// last ditch effort for determining older centos version distro information
path: "/etc/redhat-release",
fn: parseRedhatRelease,
},
}
// Identify parses distro-specific files to determine distro metadata like version and release.
@@ -87,23 +97,30 @@ identifyLoop:
return distro
}
func assemble(name, version, like string) *Distro {
distroType, ok := IDMapping[name]
func assemble(id, version, like string) *Distro {
distroType, ok := IDMapping[id]
// Both distro and version must be present
if len(name) == 0 {
if len(id) == 0 && len(version) == 0 {
return nil
}
if ok {
distro, err := NewDistro(distroType, version, like)
if err != nil {
return nil
}
return &distro
// If it's an unknown distro, try mapping the ID_LIKE
if !ok && len(like) != 0 {
distroType, ok = IDMapping[like]
}
return nil
// If we still can't match allow name to be used in constructor
if !ok {
distroType = Type(id)
}
distro, err := NewDistro(distroType, version, like)
if err != nil {
return nil
}
return &distro
}
func parseOsRelease(contents string) *Distro {
@@ -140,3 +157,48 @@ func parseBusyBox(contents string) *Distro {
}
return nil
}
// TODO: we should update parseSystemReleaseCPE to use the CPE struct, pkg.CPE, which requires a refactor to avoid a circular import:
// TODO: pkg depends on distro to support pURLs. To avoid the circular import, either try to make pkg to not depend on distro (medium lift-ish)
// TODO: or migrate the cpe code out of the pkg package (small lift).
// example CPE: cpe:/o:centos:linux:6:GA
var systemReleaseCpeMatcher = regexp.MustCompile(`cpe:\/o:(.*?):.*?:(.*?):.*?$`)
// parseSystemReleaseCPE parses the older centos (6) file to determine distro metadata
func parseSystemReleaseCPE(contents string) *Distro {
matches := systemReleaseCpeMatcher.FindAllStringSubmatch(contents, -1)
for _, match := range matches {
if len(match) < 3 {
log.Warnf("system release cpe does not match expected format")
return nil
}
// note: in SubMatches (capture groups), the 0th index is the full match string
// see https://pkg.go.dev/regexp#pkg-overview for more info
distro := assemble(match[1], match[2], "")
if distro != nil {
return distro
}
}
return nil
}
// example: "CentOS release 6.10 (Final)"
var redhatReleaseMatcher = regexp.MustCompile(`(.*?)\srelease\s(\d\.\d+)`)
// parseRedhatRelease is a fallback parsing method for determining distro information in older redhat versions
func parseRedhatRelease(contents string) *Distro {
matches := redhatReleaseMatcher.FindAllStringSubmatch(contents, -1)
for _, match := range matches {
if len(match) < 3 {
log.Warnf("failed to parse redhat-release file, unexpected format")
return nil
}
// note: in SubMatches (capture groups), the 0th index is the full match string
// see https://pkg.go.dev/regexp#pkg-overview for more info
distro := assemble(strings.ToLower(match[1]), match[2], "")
if distro != nil {
return distro
}
}
return nil
}

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