Compare commits

...
227 Commits
Author SHA1 Message Date
Đỗ Trọng HảiandAlex Goodman b7fa75d7f8 chore: switch to stdlib's slices pkg (#2148)
* chore: switch to stdlib's slices pkg

Signed-off-by: hainenber <dotronghai96@gmail.com>

* fix linting

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: hainenber <dotronghai96@gmail.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
2023-09-20 18:38:37 +00:00
Keith Zantow 7d0d3e1977 fix: prevent errors from clobbering terminal (#2161)
Signed-off-by: Keith Zantow <kzantow@gmail.com>
2023-09-20 14:35:41 -04:00
Alex Goodman 58f8c852df Require ordering of relationships when comparing parser output (#2160)
* require ordering of relationships when comparing parser output

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* [wip] fix cataloger test

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* change method of relationship sort to simple string dump

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2023-09-20 17:39:18 +00:00
William Murphy b8f52d570e chore: stop unit test switch on host arch (#2156)
Now that the test fixture pins to a particular digest, there's no need
for platform specific architecture switches in this test.

Signed-off-by: Will Murphy <will.murphy@anchore.com>
2023-09-20 11:45:13 -04:00
dependabot[bot] ba00f3328d chore(deps): bump github.com/github/go-spdx/v2 from 2.1.2 to 2.2.0 (#2158)
Bumps [github.com/github/go-spdx/v2](https://github.com/github/go-spdx) from 2.1.2 to 2.2.0.
- [Release notes](https://github.com/github/go-spdx/releases)
- [Commits](https://github.com/github/go-spdx/compare/v2.1.2...v2.2.0)

---
updated-dependencies:
- dependency-name: github.com/github/go-spdx/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-20 10:12:33 -04:00
dependabot[bot] 962ff1ec49 chore(deps): bump tibdex/github-app-token from 2.0.0 to 2.1.0 (#2157)
Bumps [tibdex/github-app-token](https://github.com/tibdex/github-app-token) from 2.0.0 to 2.1.0.
- [Release notes](https://github.com/tibdex/github-app-token/releases)
- [Commits](https://github.com/tibdex/github-app-token/compare/0914d50df753bbc42180d982a6550f195390069f...3beb63f4bd073e61482598c45c71c1019b59b73a)

---
updated-dependencies:
- dependency-name: tibdex/github-app-token
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-20 10:12:13 -04:00
Alex Goodman 40899adb87 use annotated tags, update chronicle, fix cache keys (#2154)
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2023-09-20 10:11:44 -04:00
Christopher Angelo Phillips 650f71cbe0 chore: update to latest stereoscope (#2151)
* chore: update to latest stereoscope

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

* chore: go mod tidy

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

---------

Signed-off-by: Christopher Phillips <christopher.phillips@anchore.com>
2023-09-19 15:22:10 -04:00
dependabot[bot] 30885ed92e chore(deps): bump github/codeql-action from 2.21.7 to 2.21.8 (#2150)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 2.21.7 to 2.21.8.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/04daf014b50eaf774287bf3f0f1869d4b4c4b913...6a28655e3dcb49cb0840ea372fd6d17733edd8a4)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-19 14:37:54 -04:00
anchore-actions-token-generator[bot]andkzantow 51243aa65f chore(deps): update stereoscope to 41288870305034fade27388afa7326c44eb8ff17 (#2149)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: kzantow <kzantow@users.noreply.github.com>
2023-09-19 09:07:15 -04:00
23e3de75e3 Add containerd support (#1793)
* [wip] add containerd UI handlers

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

* Add containerd support

- Add UI handlers (done by @wagoodman)
- Add containerd types and wrappers (done by @wagoodman)
- Add flag for specifying containerd address

Closes #201

Signed-off-by: Shane Dell <shanedell100@gmail.com>

* Fix lint

Signed-off-by: Shane Dell <shanedell100@gmail.com>

* add containerd ui handler

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* add containerd scheme to readme

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* add test for scheme detection

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
Signed-off-by: Shane Dell <shanedell100@gmail.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Co-authored-by: Alex Goodman <alex.goodman@anchore.com>
Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
2023-09-18 11:33:43 -04:00
Christopher Angelo Phillips 594ba5f295 chore: pin workflow checkout for cpe update-cpe-dictionary-index (#2141)
Signed-off-by: Christopher Phillips <christopher.phillips@anchore.com>
2023-09-15 16:00:15 -04:00
Alex Goodman 5d48882a78 Add GitHub actions and shared workflow usage catalogers (#2140)
* add github actions usage cataloger

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* update integration and cli tests with github actions sample

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* add support for shared workflows

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* split github actions usage cataloger

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* add source explanation for github action types

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* a github purl does not always mean the package is a github action

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* keep github action catalogers as dir only catalogers

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2023-09-15 18:51:21 +00:00
Stefan Profanter ec4d595920 feat: add dependency information to conan lockfile parser (#2131)
Signed-off-by: Stefan Profanter <stefan.profanter@agile-robots.com>
2023-09-15 14:31:08 -04:00
Christopher Angelo Phillips 094b41b301 chore: pin and update all workflow dependencies; add permission scopes (#2138)
---------

Signed-off-by: Christopher Phillips <christopher.phillips@anchore.com>
2023-09-15 14:18:42 -04:00
William Murphy 2eb2d55551 chore: pin all cli test FROM lines to linux/amd64 (#2137)
Many of these images have a slightly different sets of packages when the
arm64 variant is pulled, so that leaving this digest unpinned causes the
tests to fail on arm64 hosts. Pin the FROM lines to force stable
platform values regardless of host architecture.

Signed-off-by: Will Murphy <will.murphy@anchore.com>
2023-09-15 12:49:02 -04:00
Keith Zantow a46d12270f fix: encode and decode FileLicenses and FileContents in Syft JSON (#2083)
Signed-off-by: Keith Zantow <kzantow@gmail.com>
2023-09-13 16:14:20 -04:00
Christopher Angelo Phillips 3e16c6813f feat: add cyclonedx schema version selection (#2123)
---------

Signed-off-by: Christopher Phillips <christopher.phillips@anchore.com>
2023-09-13 14:50:22 -04:00
Ahmet Taha 5035d9ca1a fix: allow cyclonedx json input with no components (#2127)
Signed-off-by: Ahmet Taha Özdemir <me@ahoz.de>
2023-09-13 13:14:14 -04:00
dependabot[bot] c21b16d924 chore(deps): bump docker/login-action from 2 to 3 (#2119)
Bumps [docker/login-action](https://github.com/docker/login-action) from 2 to 3.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-13 10:34:19 -04:00
dependabot[bot] 4a2fc226dd chore(deps): bump github.com/go-git/go-git/v5 from 5.8.1 to 5.9.0 (#2125)
Bumps [github.com/go-git/go-git/v5](https://github.com/go-git/go-git) from 5.8.1 to 5.9.0.
- [Release notes](https://github.com/go-git/go-git/releases)
- [Commits](https://github.com/go-git/go-git/compare/v5.8.1...v5.9.0)

---
updated-dependencies:
- dependency-name: github.com/go-git/go-git/v5
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-13 10:33:47 -04:00
Alex Goodman 7de5643227 fix source-version typo in flag description (#2126)
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2023-09-13 10:05:24 -04:00
William Murphy 9de4129638 chore: enforce race detector (#2122)
Previously, there were some data races in syft. Right now, none are
detected, so check for data races on the overall command, and on unit
tests. (Checking for races on integration tests triples the time needed
for those tests, from ~1 minute to ~3 minutes on my workstation, so that
was not done at this time.)

Signed-off-by: Will Murphy <will.murphy@anchore.com>
2023-09-12 13:09:18 -04:00
anchore-actions-token-generator[bot]andkzantow 3a45653cfa chore(deps): update stereoscope to 2fc2d6c2503b6e2212e04c64ceffd57c3395ae70 (#2117)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: kzantow <kzantow@users.noreply.github.com>
2023-09-12 11:49:20 -04:00
GGMU b82c0ffc34 fix(help): power-user help text to indicate it supports file-system (#2113)
Signed-off-by: tomersein <tomersein@gmail.com>
2023-09-11 16:12:04 +00:00
dependabot[bot] b2be411f77 chore(deps): bump tibdex/github-app-token from 1 to 2 (#2116)
Bumps [tibdex/github-app-token](https://github.com/tibdex/github-app-token) from 1 to 2.
- [Release notes](https://github.com/tibdex/github-app-token/releases)
- [Commits](https://github.com/tibdex/github-app-token/compare/v1...v2)

---
updated-dependencies:
- dependency-name: tibdex/github-app-token
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-11 09:56:22 -04:00
anchore-actions-token-generator[bot]andwagoodman ec22f4b773 chore(deps): update CPE dictionary index (#2114)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: wagoodman <wagoodman@users.noreply.github.com>
2023-09-11 09:42:59 -04:00
anchore-actions-token-generator[bot]andkzantow e3c525b4b8 chore(deps): update stereoscope to 057dda3667e7f2b5e6ec6716747badd5f403c6de (#2109)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: kzantow <kzantow@users.noreply.github.com>
2023-09-08 14:10:00 -04:00
Christopher Angelo Phillips 3842d28e90 fix: update codeql-analysis for go 1.21 (#2108)
* fix: update codeql-analysis for go 1.21

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

* nit: remove comment

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

---------

Signed-off-by: Christopher Phillips <christopher.phillips@anchore.com>
2023-09-07 15:54:42 -04:00
dlorencandChristopher Phillips 9f22ab6137 Bump the golang.org/x/exp dependency and fix a build breakage. (#2088)
* Bump the golang.org/x/exp dependency and fix a build breakage.

---------

Signed-off-by: Dan Lorenc <dlorenc@chainguard.dev>
Signed-off-by: Christopher Phillips <christopher.phillips@anchore.com>
Co-authored-by: Christopher Phillips <christopher.phillips@anchore.com>
2023-09-07 14:55:52 -04:00
dependabot[bot] 1315cfd787 chore(deps): bump actions/checkout from 3 to 4 (#2094)
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-07 09:57:51 -04:00
dependabot[bot] 212aa9b6cf chore(deps): bump github.com/gkampitakis/go-snaps from 0.4.7 to 0.4.10 (#2106)
Bumps [github.com/gkampitakis/go-snaps](https://github.com/gkampitakis/go-snaps) from 0.4.7 to 0.4.10.
- [Release notes](https://github.com/gkampitakis/go-snaps/releases)
- [Commits](https://github.com/gkampitakis/go-snaps/compare/v0.4.7...v0.4.10)

---
updated-dependencies:
- dependency-name: github.com/gkampitakis/go-snaps
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-07 09:56:41 -04:00
anchore-actions-token-generator[bot]andspiffcs 46e4ac1474 chore(deps): update bootstrap tools to latest versions (#2086)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: spiffcs <spiffcs@users.noreply.github.com>
2023-09-07 09:30:44 -04:00
anchore-actions-token-generator[bot]andwagoodman 6800a5f64b chore(deps): update CPE dictionary index (#2089)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: wagoodman <wagoodman@users.noreply.github.com>
2023-09-07 09:30:18 -04:00
dependabot[bot] 9caf51596e chore(deps): bump github.com/saferwall/pe from 1.4.4 to 1.4.5 (#2096)
Bumps [github.com/saferwall/pe](https://github.com/saferwall/pe) from 1.4.4 to 1.4.5.
- [Release notes](https://github.com/saferwall/pe/releases)
- [Changelog](https://github.com/saferwall/pe/blob/main/CHANGELOG.md)
- [Commits](https://github.com/saferwall/pe/compare/v1.4.4...v1.4.5)

---
updated-dependencies:
- dependency-name: github.com/saferwall/pe
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-07 09:29:06 -04:00
dependabot[bot] 7645d5759d chore(deps): bump github.com/docker/docker (#2098)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 24.0.5+incompatible to 24.0.6+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v24.0.5...v24.0.6)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-07 09:27:21 -04:00
dependabot[bot] ce32f8bb74 chore(deps): bump golang.org/x/net from 0.14.0 to 0.15.0 (#2099)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.14.0 to 0.15.0.
- [Commits](https://github.com/golang/net/compare/v0.14.0...v0.15.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-07 09:26:56 -04:00
Đỗ Trọng Hải f8ab7c4695 feat(cmd/update): add UA header with current ver when check for update (#2100)
Signed-off-by: hainenber <dotronghai96@gmail.com>
2023-09-06 11:43:01 -04:00
Đỗ Trọng Hải 305ee87052 fix(cdx): validate external refs before encoding (#2091)
Signed-off-by: hainenber <dotronghai96@gmail.com>
2023-09-05 14:39:51 +00:00
Alex Goodman 49e7f399f9 expose cobra command in cli package (#2097)
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2023-09-05 14:33:38 +00:00
William Murphy 007b034ee3 fix: correct group IDs for commons-codec, okhttp, okio, and add integration tests for Java PURL generation (#2075)
Add overall integration test for java PURL detection.

Signed-off-by: Will Murphy <will.murphy@anchore.com>
2023-08-31 16:57:55 -04:00
Alex Goodman b454160549 tidy gomod and gitignore (#2082)
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2023-08-31 14:50:32 +00:00
Alex Goodman 36d794febe fix quiet flag (#2081)
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2023-08-31 10:40:11 -04:00
William Murphy 51d38f8e59 fix: in some cases, try to use pom info to guess name and version to top level jar (#2080)
Otherwise, small renames like 'hudson-war-2.2.1.war' to 'hudson.war', would cause
syft to incorrectly catolog the archive.

Signed-off-by: Will Murphy <will.murphy@anchore.com>
2023-08-31 10:19:55 -04:00
William Murphy cfebae27f5 fix: don't panic on universal go binaries (#2078)
If crypto settings or arch cannot be determined, still attempt to catalog packages from
the build info, rather than panicking.

Signed-off-by: Will Murphy <will.murphy@anchore.com>
2023-08-30 08:37:50 -04:00
Keith ZantowandAlex Goodman 2b7a9d0be3 chore: update CLI to CLIO (#2001)
Signed-off-by: Keith Zantow <kzantow@gmail.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
2023-08-29 15:52:26 -04:00
b03e9c6868 Add registry certificate verification support (#1734)
* add registry certificate verification support

* replace stereoscope version

* modify go.mod

* pull in stereoscope update

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* rename registry cert options, add docs, and add test

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* update to account for changes in anchore/stereoscope#195

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* fix cli tests

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Co-authored-by: lishituo <24578666@qq.com>
Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
2023-08-29 11:45:20 -04:00
witchcraze cedfa05e93 fix: CPE generation for django (#2068)
Signed-off-by: witchcraze <witchcraze@gmail.com>
2023-08-28 08:28:01 -04:00
Keith Zantow dd09e0362e chore: update quill to the latest version (#2065)
Signed-off-by: Keith Zantow <kzantow@gmail.com>
2023-08-25 20:45:04 +00:00
Keith Zantow 4ae94c37eb fix: duplicate entries in cyclonedx dependency list (#2063)
Signed-off-by: Keith Zantow <kzantow@gmail.com>
2023-08-25 12:19:01 -04:00
William Murphy d08e2be768 Fix panic in pom parsing (#2064)
A recent update to gopom changed many fields to be omitted when empty,
resulting in a number of nil pointers inside the nested structure of the
pom that previously didn't exist. Defend against these in the search for
the property value.

Signed-off-by: Will Murphy <will.murphy@anchore.com>
2023-08-25 12:04:57 -04:00
William Murphy faa902209e Fix: don't validate pom declared group (#2054)
Signed-off-by: Will Murphy <will.murphy@anchore.com>
2023-08-24 13:28:40 -04:00
William Murphy 9a2a988e7f chore: trace log pom property reflect usage (#2059)
This reflect code occasionally throws an obscure panic, but not enough
information is logged before the panic to know why it panicked. Log
enough to tell what property and package are being analyzed when the
panic occurs.

Signed-off-by: Will Murphy <will.murphy@anchore.com>
2023-08-24 11:28:44 -04:00
Chris SelzoandJoseph Palermo 5ceef48949 fix: do not double-prefix symlink paths that already contain volume names (#2051)
Signed-off-by: Joseph Palermo <jpalermo@vmware.com>
Signed-off-by: Chris Selzo <cselzo@vmware.com>
Co-authored-by: Joseph Palermo <jpalermo@vmware.com>
2023-08-24 10:45:22 -04:00
witchcraze 1848aa22cf feat: add bash classifier (#2055)
Signed-off-by: witchcraze <witchcraze@gmail.com>
2023-08-24 10:13:59 -04:00
Sirish Bathina 62f689824c Detect golang boring crypto and fipsonly modules (#2021)
* Extending build info to include crypto settings

Signed-off-by: Sirish Bathina <sirish@kasten.io>

* Use kasten fork for goversion module

Signed-off-by: Sirish Bathina <sirish@kasten.io>

* go mod tidy

Signed-off-by: Sirish Bathina <sirish@kasten.io>

* change key to GoCryptoSettings and lint fix

Signed-off-by: Sirish Bathina <sirish@kasten.io>

* Addressing feedback

Signed-off-by: Sirish Bathina <sirish@kasten.io>

---------

Signed-off-by: Sirish Bathina <sirish@kasten.io>
2023-08-24 09:49:59 -04:00
Stefan ProfanterandAlex Goodman 07ac640ac5 fix: properly parse conan ref and include user and channel (#2034)
* fix: properly parse conan ref and include user and channel

Signed-off-by: Stefan Profanter <stefan.profanter@agile-robots.com>

* unexport the conanRef type

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Stefan Profanter <stefan.profanter@agile-robots.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
2023-08-23 17:51:07 +00:00
dependabot[bot] a2b389523d chore(deps): bump github.com/charmbracelet/lipgloss from 0.7.1 to 0.8.0 (#2053)
Bumps [github.com/charmbracelet/lipgloss](https://github.com/charmbracelet/lipgloss) from 0.7.1 to 0.8.0.
- [Release notes](https://github.com/charmbracelet/lipgloss/releases)
- [Commits](https://github.com/charmbracelet/lipgloss/compare/v0.7.1...v0.8.0)

---
updated-dependencies:
- dependency-name: github.com/charmbracelet/lipgloss
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-23 13:41:17 -04:00
Alex Goodman 17d4203bbb Enable reading non-utf-8 encodings for java pom.xml files (#2047)
* fix reading non utf8 encodings

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* in cases where we cant tell the encoding use the UTF8 replacement char

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* decompose the xml decoding func to get a valid utf8 reader first and test unknown encoding

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2023-08-23 10:06:34 -04:00
Christopher Angelo PhillipsandKeith Zantow ee121cff21 feat: 1944 - update purl generation to use a consistent groupID (#2033)
Separate the logic for CPE and PURL generation. 

PURL generation needs a single answer for groupID based on a priority of discovering the field. 
CPE generation still uses multiple potential groupID to populate the candidate cpe.

Improve GroupID detection. 

Currently syft does not use any hierarchy for GroupID detection and treats all sources as equal. 
It treats fields from the manifest file with priority. This change adds a hierarchy to the fields and returns a single answer based on that hierarchy.
---------
Signed-off-by: Christopher Phillips <christopher.phillips@anchore.com>
Signed-off-by: Keith Zantow <kzantow@gmail.com>
Co-authored-by: Keith Zantow <kzantow@gmail.com>
2023-08-22 10:47:07 -04:00
dependabot[bot] cf37b17869 chore(deps): bump github.com/google/uuid from 1.3.0 to 1.3.1 (#2049)
Bumps [github.com/google/uuid](https://github.com/google/uuid) from 1.3.0 to 1.3.1.
- [Release notes](https://github.com/google/uuid/releases)
- [Changelog](https://github.com/google/uuid/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/uuid/compare/v1.3.0...v1.3.1)

---
updated-dependencies:
- dependency-name: github.com/google/uuid
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-22 10:42:19 -04:00
anchore-actions-token-generator[bot]andspiffcs ee656fe088 chore(deps): update bootstrap tools to latest versions (#2048)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: spiffcs <spiffcs@users.noreply.github.com>
2023-08-22 08:48:42 -04:00
dependabot[bot] f58425a305 chore(deps): bump github.com/jinzhu/copier from 0.3.5 to 0.4.0 (#2045)
Bumps [github.com/jinzhu/copier](https://github.com/jinzhu/copier) from 0.3.5 to 0.4.0.
- [Commits](https://github.com/jinzhu/copier/compare/v0.3.5...v0.4.0)

---
updated-dependencies:
- dependency-name: github.com/jinzhu/copier
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-21 10:37:11 -04:00
anchore-actions-token-generator[bot]andwagoodman 01c7709e0d chore(deps): update CPE dictionary index (#2043)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: wagoodman <wagoodman@users.noreply.github.com>
2023-08-21 09:33:41 -04:00
Alex Goodman cb0214ec1d fill out new version notice (#2042)
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2023-08-18 16:03:11 -04:00
Keith Zantow 4762ba0943 feat: use java package names to determine known groupids (#2032)
Signed-off-by: Keith Zantow <kzantow@gmail.com>
2023-08-17 12:55:25 -04:00
Keith Zantow d1635971a1 fix: inconsistent removal of binaries by overlap (#2036)
Signed-off-by: Keith Zantow <kzantow@gmail.com>
2023-08-17 15:27:31 +00:00
9467bd66c2 fix: CycloneDX relationships not output or decoded properly (#1974)
Signed-off-by: Mark Galpin <mark@tidelift.com>
Signed-off-by: Keith Zantow <kzantow@gmail.com>
Co-authored-by: Mark Galpin <mark@tidelift.com>
Co-authored-by: Keith Zantow <kzantow@gmail.com>
2023-08-17 11:02:12 -04:00
Keith Zantow 59107324ce chore: restore cataloger.DefaultConfig (#2028)
Signed-off-by: Keith Zantow <kzantow@gmail.com>
2023-08-14 20:28:07 +00:00
Keith Zantow b3d7ba569b fix: read direct package files when decoding SPDX tag-value (#2014)
* fix: read direct package files when decoding SPDX tag-value

---------

Signed-off-by: Keith Zantow <kzantow@gmail.com>
2023-08-14 11:37:24 -04:00
anchore-actions-token-generator[bot]andspiffcs c7fe58683d chore(deps): update bootstrap tools to latest versions (#2022)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: spiffcs <spiffcs@users.noreply.github.com>
2023-08-14 11:36:15 -04:00
anchore-actions-token-generator[bot]andwagoodman 28b06dae25 chore(deps): update CPE dictionary index (#2025)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: wagoodman <wagoodman@users.noreply.github.com>
2023-08-14 11:35:57 -04:00
anchore-actions-token-generator[bot]andspiffcs a90cff1cd2 chore(deps): update bootstrap tools to latest versions (#2012)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: spiffcs <spiffcs@users.noreply.github.com>
2023-08-10 13:20:09 -04:00
dependabot[bot]andChristopher Phillips 82eafeaf4a chore(deps): bump github.com/vifraa/gopom from 0.2.2 to 1.0.0 (#2008)
* chore(deps): bump github.com/vifraa/gopom from 0.2.2 to 1.0.0
* refactor: update consumer code to use new optional values

Bumps [github.com/vifraa/gopom](https://github.com/vifraa/gopom) from 0.2.2 to 1.0.0.
- [Release notes](https://github.com/vifraa/gopom/releases)
- [Commits](https://github.com/vifraa/gopom/compare/v0.2.2...v1.0.0)

---
updated-dependencies:
- dependency-name: github.com/vifraa/gopom
  dependency-type: direct:production
  update-type: version-update:semver-major
...
---------

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Christopher Phillips <christopher.phillips@anchore.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Christopher Phillips <christopher.phillips@anchore.com>
2023-08-09 17:22:51 -04:00
Christopher Angelo Phillips 541c8d339b 1948-filter-pkg-by-type (#2011)
---------

Signed-off-by: Christopher Phillips <christopher.phillips@anchore.com>
2023-08-09 16:05:52 -04:00
dependabot[bot] 6bf6f85584 chore(deps): bump github.com/dave/jennifer from 1.6.1 to 1.7.0 (#2009)
Bumps [github.com/dave/jennifer](https://github.com/dave/jennifer) from 1.6.1 to 1.7.0.
- [Commits](https://github.com/dave/jennifer/compare/v1.6.1...v1.7.0)

---
updated-dependencies:
- dependency-name: github.com/dave/jennifer
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-09 14:46:11 -04:00
Keith Zantow c7272fd6a5 fix: SPDX license values and download location (#2007)
Signed-off-by: Keith Zantow <kzantow@gmail.com>
2023-08-08 15:55:50 -04:00
Christopher Angelo Phillips 466da7cbda 931: binary cataloger exclusion defaults for ownership by overlap (#1948)
Fixes #931

PR #1948 introduces a new implicit exclusion for binary packages that overlap by file ownership and have certain characteristics:

1) the relationship between packages is OwnershipByFileOverlap
2) the parent package is an "os" package - see changelog for included catalogers
3) the child is a synthetic package generated by the binary cataloger - see changelog for included catalogers
4) the package names are identical

---------

Signed-off-by: Christopher Phillips <christopher.phillips@anchore.com>
2023-08-08 13:00:52 -04:00
dependabot[bot] 2fc65094b7 chore(deps): bump golang.org/x/net from 0.13.0 to 0.14.0 (#2004)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.13.0 to 0.14.0.
- [Commits](https://github.com/golang/net/compare/v0.13.0...v0.14.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-07 10:34:00 -04:00
dependabot[bot] d7ff77072a chore(deps): bump modernc.org/sqlite from 1.24.0 to 1.25.0 (#1998)
Bumps [modernc.org/sqlite](https://gitlab.com/cznic/sqlite) from 1.24.0 to 1.25.0.
- [Commits](https://gitlab.com/cznic/sqlite/compare/v1.24.0...v1.25.0)

---
updated-dependencies:
- dependency-name: modernc.org/sqlite
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-04 14:24:31 -04:00
Christopher Angelo Phillips 78660022bf test: add coverage for new rpmdb paths (#1999)
Signed-off-by: Christopher Phillips <christopher.phillips@anchore.com>
2023-08-04 13:04:36 -04:00
Keith Zantow aaf767f8d3 chore: improve spdx purl decoding (#1996)
Signed-off-by: Keith Zantow <kzantow@gmail.com>
2023-08-04 11:43:21 -04:00
Keith Zantow 79014ed8c8 fix: gradle lockfile parser groupId handling (#1995)
Signed-off-by: Keith Zantow <kzantow@gmail.com>
2023-08-04 11:42:26 -04:00
Christopher Angelo Phillips e774006052 fix: update glob to use newer usr/lib/sysimage path (#1997)
See this link for details on the path migration for the rpmdb
https://fedoraproject.org/wiki/Changes/RelocateRPMToUsr

Signed-off-by: Christopher Phillips <christopher.phillips@anchore.com>
2023-08-03 16:23:50 -07:00
Nicholas R. Smith 1d6d5f7f5f fix: opkg search glob (#1994)
Signed-off-by: Nicholas R. Smith <nicholas_smith@selinc.com>
2023-08-03 19:33:11 +00:00
Sem Provoost 433a7b8a42 feat: nginx binary classifier (#1988)
Signed-off-by: SemProvoost <27961543+SemProvoost@users.noreply.github.com>
2023-08-03 13:09:31 -04:00
Nicholas R. SmithandNicholas R. Smith e55277f26d Expand deb cataloger to include opkg (#1985)
* Add opkg info directory and status file to deb cataloger

opkg uses the same or nearly the same metadata and structure as Debian:
**/lib/opkg/status lists status information for all packages
**/lib/opkg/info/opkg.conffiles is a list of configuration files
**/lib/opkg/info/*.list contains files and directories installed by the package
**/lib/opkg/info/*.preinst are scripts to run before installation
**/lib/opkg/info/*.postinst are scripts to run after installation
**/lib/opkg/info/*.postrm are scripts to run after package removal
**/lib/opkg/info/*.control provides package metadata

Signed-off-by: Nicholas R. Smith <nicholas_smith@selinc.com>

---------

Signed-off-by: Nicholas R. Smith <nicholas_smith@selinc.com>
Co-authored-by: Nicholas R. Smith <nicholas_smith@selinc.com>
2023-08-03 12:33:14 -04:00
anchore-actions-token-generator[bot]andspiffcs c2b4231cc3 chore(deps): update bootstrap tools to latest versions (#1991)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: spiffcs <spiffcs@users.noreply.github.com>
2023-08-03 10:53:29 -04:00
dependabot[bot] c150b4e358 chore(deps): bump github.com/google/go-containerregistry (#1993)
Bumps [github.com/google/go-containerregistry](https://github.com/google/go-containerregistry) from 0.15.2 to 0.16.1.
- [Release notes](https://github.com/google/go-containerregistry/releases)
- [Changelog](https://github.com/google/go-containerregistry/blob/main/.goreleaser.yml)
- [Commits](https://github.com/google/go-containerregistry/compare/v0.15.2...v0.16.1)

---
updated-dependencies:
- dependency-name: github.com/google/go-containerregistry
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-03 10:53:09 -04:00
Keith Zantow 3f0475efb7 chore: update bubbly to fix hanging (#1990)
Signed-off-by: Keith Zantow <kzantow@gmail.com>
2023-08-02 10:28:35 -04:00
dependabot[bot] 2e376d067f chore(deps): bump golang.org/x/net from 0.12.0 to 0.13.0 (#1989) 2023-08-02 14:16:49 +00:00
Christopher Angelo Phillips 8e893dfc20 feat: use originator logic to fill supplier (#1980)
* feat: use Originator to fill supplier for NTIA minimum
---------
Signed-off-by: Christopher Phillips <christopher.phillips@anchore.com>
2023-08-01 17:19:49 -04:00
Alex Goodman 756d0f29af add metadata types to all cpe test fixtures (#1982)
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2023-07-31 16:35:09 -04:00
Keith Zantow e2f7befbfb fix: default image source name to user input (#1979)
* fix: default image source name to user input

Signed-off-by: Keith Zantow <kzantow@gmail.com>

* chore: add test

Signed-off-by: Keith Zantow <kzantow@gmail.com>

---------

Signed-off-by: Keith Zantow <kzantow@gmail.com>
2023-07-31 17:29:18 +00:00
anchore-actions-token-generator[bot]andkzantow f14742b3f3 chore(deps): update stereoscope to d1f3d766295ed3c8362ac1be68070e2a1dba4d03 (#1975)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: kzantow <kzantow@users.noreply.github.com>
2023-07-31 12:02:33 -04:00
Christopher Angelo Phillips 3aae316456 chore: update to latest commit in tools-golang (#1969)
* chore: update to latest commit in tools-golang

---------

Signed-off-by: Christopher Phillips <christopher.phillips@anchore.com>
2023-07-27 15:29:22 -04:00
Alex Goodmanandmanifestori 063e9da65d Guess unpinned versions in python requirements.txt (#1966)
* feat: python requirements.txt parsing inclusive

Signed-off-by: manifestori <ori@manifestcyber.com>

* refactor: parseVersion

Signed-off-by: manifestori <ori@manifestcyber.com>

* add python config for optional requirements version constraint resolution

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

* fix tests

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* allow for python requirements metadata to be optional

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* restore cyclonedx dependency

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: manifestori <ori@manifestcyber.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
Co-authored-by: manifestori <ori@manifestcyber.com>
2023-07-27 14:26:59 -04:00
dependabot[bot] bf1102c3f1 chore(deps): bump github.com/vifraa/gopom from 0.2.1 to 0.2.2 (#1965)
Bumps [github.com/vifraa/gopom](https://github.com/vifraa/gopom) from 0.2.1 to 0.2.2.
- [Release notes](https://github.com/vifraa/gopom/releases)
- [Commits](https://github.com/vifraa/gopom/compare/v0.2.1...v0.2.2)

---
updated-dependencies:
- dependency-name: github.com/vifraa/gopom
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-27 13:28:42 -04:00
Alex Goodman bbd2d42dbb Fix panic condition on docker pull failure (#1968)
* [wip] add image pull error handlers

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* fix panic and ui hang on docker pull failure

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* linter fix

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2023-07-27 11:32:02 -04:00
Alex Goodman d84120f499 bump JSON schema to account for simplified python env markers (#1967)
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2023-07-27 14:13:17 +00:00
Keith Zantow 9480f10ccd feat: support top-level SPDX package and graph (#1934)
Signed-off-by: Keith Zantow <kzantow@gmail.com>
2023-07-26 13:54:32 -04:00
dependabot[bot] 1e4d26f526 chore(deps): bump github.com/go-git/go-git/v5 from 5.8.0 to 5.8.1 (#1959)
Bumps [github.com/go-git/go-git/v5](https://github.com/go-git/go-git) from 5.8.0 to 5.8.1.
- [Release notes](https://github.com/go-git/go-git/releases)
- [Commits](https://github.com/go-git/go-git/compare/v5.8.0...v5.8.1)

---
updated-dependencies:
- dependency-name: github.com/go-git/go-git/v5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-26 13:34:03 +00:00
Tristan Farkas e1c1832f84 Add cataloger for Swift Package Manager. (#1919)
Signed-off-by: Tristan Farkas <Tristan.Farkas@axis.com>
2023-07-25 14:35:21 -04:00
anchore-actions-token-generator[bot]andkzantow 9a73380f29 chore(deps): update stereoscope to d515761c6ca2743a67d7d08053db69235ae76d1d (#1953)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: kzantow <kzantow@users.noreply.github.com>
2023-07-25 10:49:21 -04:00
dependabot[bot] 2e718cf865 chore(deps): bump github.com/docker/docker (#1955)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 24.0.2+incompatible to 24.0.5+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v24.0.2...v24.0.5)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-25 10:37:16 -04:00
dependabot[bot] 4000a84624 chore(deps): bump github.com/go-git/go-git/v5 from 5.7.0 to 5.8.0 (#1951)
Bumps [github.com/go-git/go-git/v5](https://github.com/go-git/go-git) from 5.7.0 to 5.8.0.
- [Release notes](https://github.com/go-git/go-git/releases)
- [Commits](https://github.com/go-git/go-git/compare/v5.7.0...v5.8.0)

---
updated-dependencies:
- dependency-name: github.com/go-git/go-git/v5
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-24 11:28:54 -04:00
Dan LuhringandAlex Goodman 99d172f0d1 Introduce indexed embedded CPE dictionary (#1897)
* Introduce indexed embedded CPE dictionary

Signed-off-by: Dan Luhring <dluhring@chainguard.dev>

* Don't generate cpe-index on make snapshot

Signed-off-by: Dan Luhring <dluhring@chainguard.dev>

* Add unit tests for individual addEntry funcs

Signed-off-by: Dan Luhring <dluhring@chainguard.dev>

* migrate CPE index build to go generate and add periodic workflow

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* add test to ensure generated cpe index is wired up to function that uses it

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Dan Luhring <dluhring@chainguard.dev>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
2023-07-21 13:54:19 +00:00
dependabot[bot] 3f5c601620 chore(deps): bump github.com/gookit/color from 1.5.3 to 1.5.4 (#1949)
Bumps [github.com/gookit/color](https://github.com/gookit/color) from 1.5.3 to 1.5.4.
- [Release notes](https://github.com/gookit/color/releases)
- [Commits](https://github.com/gookit/color/compare/v1.5.3...v1.5.4)

---
updated-dependencies:
- dependency-name: github.com/gookit/color
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-21 08:50:47 -04:00
Dan LuhringandAlex Goodman 8478e0bef7 Add support for parsing .NET assemblies (#1943)
* Add support for parsing .NET assemblies

Signed-off-by: Dan Luhring <dluhring@chainguard.dev>

Former-commit-id: 69c33fe4d77357d843c11590f3b07825bc6249ac

* Add dll and exe files

Signed-off-by: Dan Luhring <dluhring@chainguard.dev>

Former-commit-id: b9d204efa6d2ef385b5fbb7a59a3474ecabea641

* Add PE cataloger to directory catalogers

Signed-off-by: Dan Luhring <dluhring@chainguard.dev>

Former-commit-id: 9711c00d9da92e2887e0c1f92edd740ea5345849

* Don't set language to dotnet for PEs

Signed-off-by: Dan Luhring <dluhring@chainguard.dev>

Former-commit-id: 368313fddac9160d8a06a01ebe8c5ac7990232f5

* Fix spelling of cataloger in constructor

Signed-off-by: Dan Luhring <dluhring@chainguard.dev>

Former-commit-id: e42fd77b2f8b6d42e076a84f6cce386861260941

* Adjust which cases in PE parsing return errors

Signed-off-by: Dan Luhring <dluhring@chainguard.dev>

Former-commit-id: 95b25f8fc3a7d4e18fe30e489b09851f316795ff

* remove build binary from branch

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

Former-commit-id: fa54c0d0aef0998d5520e9f44cae51f5f9cd38a2

* Fix failing CLI tests

Signed-off-by: Dan Luhring <dluhring@chainguard.dev>

---------

Signed-off-by: Dan Luhring <dluhring@chainguard.dev>
Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
2023-07-19 15:34:07 -04:00
Christopher Angelo Phillips 0327fdc88a docs: capture artifactory dev settings from 1895 (#1947)
* docs: capture artifactory dev settings from 1895

---------

Signed-off-by: Christopher Phillips <christopher.phillips@anchore.com>
2023-07-19 16:54:18 +00:00
Alex Goodman 88b3d1e9bb remove build binary and add explicit git ignore
Former-commit-id: 6455f2d8a5f1910b4a8b681ddef79919886d638d
2023-07-18 14:06:34 -04:00
Christopher Angelo Phillips 204b790012 docs: update docs with new docker specific instructions (#1941)
* docs: update docs with new docker specific instructions
---------

Signed-off-by: Christopher Phillips <christopher.phillips@anchore.com>
Former-commit-id: c67c76e84df84e3e24aa307637d884ca8b7e3eea
2023-07-17 18:19:21 +00:00
Alex Goodman 35699f6fdc remove jotframe UI (#1932)
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2023-07-13 13:21:52 -04:00
Christopher Angelo Phillips 2e7fd031d4 fix: remove indirect dependency of circl v1.1.0 (#1940)
Signed-off-by: Christopher Phillips <christopher.phillips@anchore.com>
2023-07-13 12:30:37 -04:00
Christopher Angelo Phillips 32296f5943 chore: move wait before iteration to guarantee read before tea (#1931)
* chore: move wait before iteration to guarantee read before tea

---------

Signed-off-by: Christopher Phillips <christopher.phillips@anchore.com>
2023-07-12 13:59:31 -04:00
Alex Goodman 4fc17edd14 implement ui handle waiter (#1930)
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2023-07-12 13:14:54 -04:00
Christopher Angelo Phillips 38efe4ec5f fix: background reader apart from global handler for testing (#1929)
Signed-off-by: Christopher Phillips <christopher.phillips@anchore.com>
2023-07-12 12:37:19 -04:00
dependabot[bot] 05a61897f2 chore(deps): bump modernc.org/sqlite from 1.23.1 to 1.24.0 (#1928)
Bumps [modernc.org/sqlite](https://gitlab.com/cznic/sqlite) from 1.23.1 to 1.24.0.
- [Commits](https://gitlab.com/cznic/sqlite/compare/v1.23.1...v1.24.0)

---
updated-dependencies:
- dependency-name: modernc.org/sqlite
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-11 14:01:48 -04:00
James NeateandChristopher Phillips 5a7c200911 fix: allow valid cyclonedx input with no components (#1873)
fix: allow valid cyclonedx input with no components
---------

Signed-off-by: James Neate <jamesmneate@gmail.com>
Signed-off-by: Christopher Phillips <christopher.phillips@anchore.com>
Co-authored-by: Christopher Phillips <christopher.phillips@anchore.com>
2023-07-11 13:56:36 -04:00
Christopher Angelo Phillips 72616db81f fix: "or-later" suffix updated to consider deprecated "+" operator (#1907)
* fix: or-later suffix has been updated to consider deprecated +

If a given license has the suffix "or-later" it previously could have
been considered or represented with a "+". Example "GFDL-1.0-or-later"
could have been represented as "GFDL-1.0+". This PR allows the license
list generation to consider "or-later" as == to "+" when generating
permutations for upgrading deprecated licenses.

---------

Signed-off-by: Christopher Phillips <christopher.phillips@anchore.com>
2023-07-11 16:21:29 +00:00
Avi DeitcherandKeith Zantow 4ab9f393fc feat: CLI flag for directory base (#1867)
Signed-off-by: Avi Deitcher <avi@deitcher.net>
Signed-off-by: Keith Zantow <kzantow@gmail.com>
Co-authored-by: Keith Zantow <kzantow@gmail.com>
2023-07-10 13:36:41 -04:00
Dan LuhringandChristopher Phillips 9744f4c009 Fix CPE gen for k8s python client (#1921)
Signed-off-by: Dan Luhring <dluhring@chainguard.dev>
Co-authored-by: Christopher Phillips <christopher.phillips@anchore.com>
2023-07-10 15:54:19 +00:00
Christopher Angelo Phillips d21fa84335 chore: update iterations to protect against race (#1927)
* chore: update iterations to protect against race
---------

Signed-off-by: Christopher Phillips <christopher.phillips@anchore.com>
2023-07-10 11:44:54 -04:00
anchore-actions-token-generator[bot]andspiffcs d5d95da3b6 chore(deps): update bootstrap tools to latest versions (#1922)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: spiffcs <spiffcs@users.noreply.github.com>
2023-07-10 11:03:09 -04:00
Dan Luhring c0c089ffd5 fix: Don't use the actual redis or grpc CPEs for gems (#1926)
Signed-off-by: Dan Luhring <dluhring@chainguard.dev>
2023-07-10 10:24:42 -04:00
Lorenzo OrsattiandChristopher Phillips 376c42893b fix(install): return with right error code (#1915)
This resolves #1566.

Signed-off-by: Lorenzo Orsatti <49567430+lorsatti@users.noreply.github.com>
Co-authored-by: Christopher Phillips <cphillips918@gmail.com>
2023-07-06 16:56:07 -04:00
Dan Luhring 81d8019207 Remove erroneous Java CPEs from generation (#1918)
Signed-off-by: Dan Luhring <dluhring@chainguard.dev>
2023-07-06 16:12:55 -04:00
dependabot[bot] 8ce88e11fd chore(deps): bump golang.org/x/net from 0.11.0 to 0.12.0 (#1916)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.11.0 to 0.12.0.
- [Commits](https://github.com/golang/net/compare/v0.11.0...v0.12.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-06 16:02:44 -04:00
Alex Goodman f8b832e6c3 Switch UI to bubbletea (#1888)
* add bubbletea UI

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

* swap pipeline to go 1.20.x and add attest guard for cosign binary

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* update note in developing.md about the required golang version

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* fix merge conflict for windows path handling

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* temp test for attest handler

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* add addtional test iterations for background reader

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2023-07-06 09:00:46 -04:00
DD (Devdatta) DeshpandeandKeith Zantow a00a3df10c fix: use filepath.EvalSymlinks if os.Readlink fails to evaluate the link (#1884)
Signed-off-by: DD (Devdatta) Deshpande <dd@codewits.in>
Co-authored-by: Keith Zantow <kzantow@gmail.com>
2023-07-05 14:49:22 -04:00
Alex Goodman cfbb9f703b add file source digest support (#1914)
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2023-07-05 13:47:13 -04:00
anchore-actions-token-generator[bot]andspiffcs 6280146c81 chore(deps): update bootstrap tools to latest versions (#1908)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: spiffcs <spiffcs@users.noreply.github.com>
2023-07-05 11:06:22 -04:00
dependabot[bot] e8f7108e6e chore(deps): bump golang.org/x/mod from 0.11.0 to 0.12.0 (#1912)
Bumps [golang.org/x/mod](https://github.com/golang/mod) from 0.11.0 to 0.12.0.
- [Commits](https://github.com/golang/mod/compare/v0.11.0...v0.12.0)

---
updated-dependencies:
- dependency-name: golang.org/x/mod
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-05 11:06:05 -04:00
dependabot[bot] 023ca1be32 chore(deps): bump golang.org/x/term from 0.9.0 to 0.10.0 (#1913)
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.9.0 to 0.10.0.
- [Commits](https://github.com/golang/term/compare/v0.9.0...v0.10.0)

---
updated-dependencies:
- dependency-name: golang.org/x/term
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-05 11:05:46 -04:00
Marco Damiani 2e3c7fa158 doc(readme): add installation section with scoop (#1909)
Signed-off-by: drazen04 <hangtime23@hotmail.it>
2023-07-03 13:50:01 -04:00
Alex Goodman 4da3be864f Refactor source API (#1846)
* refactor source API and syft json source block

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

* update source detection and format test utils

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

* generate list of all source metadata types

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* extract base and root normalization into helper functions

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* preserve syftjson model package name import ref

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* alias should not be a pointer

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2023-06-30 14:19:16 +00:00
anchore-actions-token-generator[bot]andspiffcs 608dbded06 chore(deps): update bootstrap tools to latest versions (#1905)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: spiffcs <spiffcs@users.noreply.github.com>
2023-06-29 14:10:30 -04:00
anchore-actions-token-generator[bot]andkzantow 791d1f9552 chore(deps): update stereoscope to cd49355d934e9e09339e0b690398afe7bd9f63f1 (#1903)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: kzantow <kzantow@users.noreply.github.com>
2023-06-28 12:05:12 -04:00
anchore-actions-token-generator[bot]andspiffcs e5e97b5c4e chore(deps): update bootstrap tools to latest versions (#1902)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: spiffcs <spiffcs@users.noreply.github.com>
2023-06-28 12:04:39 -04:00
Weston Steimel 8219f8d55b fix: discover deb file relationships in distroless images (#1901)
Signed-off-by: Weston Steimel <weston.steimel@anchore.com>
2023-06-28 13:28:20 +01:00
Alex Goodman 026be3c0f1 add oss community board auto-add workflow (#1898)
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2023-06-27 15:53:59 -04:00
anchore-actions-token-generator[bot]andkzantow 0d4f19043e chore(deps): update stereoscope to 8c7173ebcf69187d480d4d8b0c6cafaa7aef7024 (#1890)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: kzantow <kzantow@users.noreply.github.com>
2023-06-26 13:58:44 -04:00
anchore-actions-token-generator[bot]andspiffcs 38b47e484c chore(deps): update bootstrap tools to latest versions (#1894)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: spiffcs <spiffcs@users.noreply.github.com>
2023-06-26 13:58:17 -04:00
Stephane Rufer 7943c73d3f fix: add support for Dart SDK package dependencies (#1891)
Signed-off-by: Stephane Rufer <1128559+rufman@users.noreply.github.com>
2023-06-23 12:40:46 -04:00
Alex Goodman 25ce245c03 Simplify the SBOM writer interface (#1892)
* remove sbom.writer bytes call and consolidate helpers to options pkg

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

* dont close stdout

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

* remove close operation from multiwriter

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

---------

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2023-06-23 11:21:22 -04:00
Dan Luhring 7de7a7990a fix: improve version detection in Java archive name parsing (#1889)
Signed-off-by: Dan Luhring <dluhring@chainguard.dev>
2023-06-22 18:42:10 +00:00
Keith Zantow f79cb9587f fix: only output valid cyclonedx license choices (#1879)
* fix: only output valid cyclonedx license choices

Signed-off-by: Keith Zantow <kzantow@gmail.com>

* chore: update tests

Signed-off-by: Keith Zantow <kzantow@gmail.com>

* chore: return nil for emtpty cdx license list

Signed-off-by: Keith Zantow <kzantow@gmail.com>

---------

Signed-off-by: Keith Zantow <kzantow@gmail.com>
2023-06-22 12:05:38 -04:00
Tim Gerla c27d5b11d4 docs: clarify reasoning of default catalogers for images or directories (#1887)
Add some explanation around why there are different default sets of catalogers for image scans versus directory scans. Hopefully clarify questions related to #1776.

Signed-off-by: Timothy Gerla <tim@gerla.net>
2023-06-20 19:47:50 +00:00
William Murphy 5d54e6e847 Configure chronicle to pre-1.0 mode (#1886)
Track a chronicle config file that causes chronicle to bump minor
version instead of major version in response to the "breaking-change"
label for pre-1.0 releases.

Signed-off-by: Will Murphy <will.murphy@anchore.com>
2023-06-20 16:08:35 +00:00
Keith Zantow 631d50d038 chore: update SPDX license list to 3.21 (#1885) 2023-06-20 15:47:02 +00:00
anchore-actions-token-generator[bot] 269006bf04 chore(deps): update bootstrap tools to latest versions (#1880) 2023-06-20 10:22:18 -04:00
William Murphy e2ed89f700 Pad artifact IDs (#1882)
Otherwise the hash can sometimes be short if it results in a low uint64.

Signed-off-by: Will Murphy <will.murphy@anchore.com>
2023-06-16 13:26:18 -04:00
dependabot[bot] badb957888 chore(deps): bump golang.org/x/mod from 0.10.0 to 0.11.0 (#1878)
Bumps [golang.org/x/mod](https://github.com/golang/mod) from 0.10.0 to 0.11.0.
- [Commits](https://github.com/golang/mod/compare/v0.10.0...v0.11.0)

---
updated-dependencies:
- dependency-name: golang.org/x/mod
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-15 14:10:11 -04:00
dependabot[bot] a1bba36d51 chore(deps): bump modernc.org/sqlite from 1.23.0 to 1.23.1 (#1874)
Bumps [modernc.org/sqlite](https://gitlab.com/cznic/sqlite) from 1.23.0 to 1.23.1.
- [Commits](https://gitlab.com/cznic/sqlite/compare/v1.23.0...v1.23.1)

---
updated-dependencies:
- dependency-name: modernc.org/sqlite
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-14 11:45:39 -04:00
anchore-actions-token-generator[bot]andkzantow c019cd51da chore(deps): update stereoscope to 5b5049bf4d3a99df9a2b1c31d5d52ddff7b5cec2 (#1871)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: kzantow <kzantow@users.noreply.github.com>
2023-06-14 11:29:39 -04:00
dependabot[bot] 5406d8a366 chore(deps): bump golang.org/x/net from 0.10.0 to 0.11.0 (#1876)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.10.0 to 0.11.0.
- [Commits](https://github.com/golang/net/compare/v0.10.0...v0.11.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-14 10:30:19 -04:00
James Neate 098c255a2d fix: pom properties not setting artifact id (#1870)
Signed-off-by: James Neate <jamesmneate@gmail.com>
2023-06-12 09:59:14 -04:00
dependabot[bot] 2c5d64ac9e chore(deps): bump github.com/spdx/tools-golang from 0.5.1 to 0.5.2 (#1868)
Bumps [github.com/spdx/tools-golang](https://github.com/spdx/tools-golang) from 0.5.1 to 0.5.2.
- [Release notes](https://github.com/spdx/tools-golang/releases)
- [Changelog](https://github.com/spdx/tools-golang/blob/main/RELEASE-NOTES.md)
- [Commits](https://github.com/spdx/tools-golang/compare/v0.5.1...v0.5.2)

---
updated-dependencies:
- dependency-name: github.com/spdx/tools-golang
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-08 17:01:19 -04:00
Avi Deitcher 1764e1c3f6 fix: handle invalid symlinks (#1861)
Signed-off-by: Avi Deitcher <avi@deitcher.net>
2023-06-05 15:04:14 -04:00
dependabot[bot]andChristopher Phillips c560ffd811 chore(deps): bump github.com/spdx/tools-golang from 0.5.0 to 0.5.1 (#1850)
* chore(deps): bump github.com/spdx/tools-golang from 0.5.0 to 0.5.1

Bumps [github.com/spdx/tools-golang](https://github.com/spdx/tools-golang) from 0.5.0 to 0.5.1.
- [Release notes](https://github.com/spdx/tools-golang/releases)
- [Changelog](https://github.com/spdx/tools-golang/blob/main/RELEASE-NOTES.md)
- [Commits](https://github.com/spdx/tools-golang/compare/v0.5.0...v0.5.1)

---
updated-dependencies:
- dependency-name: github.com/spdx/tools-golang
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore: update fixtures for spdx with new library changes

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

---------

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Christopher Phillips <christopher.phillips@anchore.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Christopher Phillips <christopher.phillips@anchore.com>
2023-06-05 15:01:06 -04:00
anchore-actions-token-generator[bot]andChristopher Phillips 7d1b292ad0 chore(deps): update bootstrap tools to latest versions (#1857)
* chore(deps): update bootstrap tools to latest versions

Signed-off-by: Christopher Phillips <christopher.phillips@anchore.com>
Co-authored-by: Christopher Phillips <christopher.phillips@anchore.com>
2023-06-05 18:56:04 +00:00
Christopher Angelo Phillipsandguoguangwu f07581f504 Pr 1825 (#1865)
chore: code cleanup

Signed-off-by: guoguangwu <guoguangwu@magic-shield.com>

---------

Signed-off-by: guoguangwu <guoguangwu@magic-shield.com>
Signed-off-by: Christopher Phillips <christopher.phillips@anchore.com>
Co-authored-by: guoguangwu <guoguangwu@magic-shield.com>
2023-06-05 17:01:00 +00:00
dependabot[bot] d676e5e781 chore(deps): bump github.com/sirupsen/logrus from 1.9.2 to 1.9.3 (#1862)
Bumps [github.com/sirupsen/logrus](https://github.com/sirupsen/logrus) from 1.9.2 to 1.9.3.
- [Release notes](https://github.com/sirupsen/logrus/releases)
- [Changelog](https://github.com/sirupsen/logrus/blob/master/CHANGELOG.md)
- [Commits](https://github.com/sirupsen/logrus/compare/v1.9.2...v1.9.3)

---
updated-dependencies:
- dependency-name: github.com/sirupsen/logrus
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-05 10:48:18 -04:00
dependabot[bot] 903d29b6f7 chore(deps): bump modernc.org/sqlite from 1.22.1 to 1.23.0 (#1863)
Bumps [modernc.org/sqlite](https://gitlab.com/cznic/sqlite) from 1.22.1 to 1.23.0.
- [Commits](https://gitlab.com/cznic/sqlite/compare/v1.22.1...v1.23.0)

---
updated-dependencies:
- dependency-name: modernc.org/sqlite
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-05 10:47:59 -04:00
Keith Zantow 79a955b1a9 feat: source-version flag (#1859) 2023-06-05 10:36:34 -04:00
dependabot[bot] 1bd9de9047 chore(deps): bump github.com/spf13/viper from 1.15.0 to 1.16.0 (#1851)
Bumps [github.com/spf13/viper](https://github.com/spf13/viper) from 1.15.0 to 1.16.0.
- [Release notes](https://github.com/spf13/viper/releases)
- [Commits](https://github.com/spf13/viper/compare/v1.15.0...v1.16.0)

---
updated-dependencies:
- dependency-name: github.com/spf13/viper
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-01 08:35:14 -04:00
Avi Deitcher 68f8df9594 accept main.version ldflags even without vcs (#1855)
Signed-off-by: Avi Deitcher <avi@deitcher.net>
2023-06-01 08:34:46 -04:00
James Neate c69cdd9f4a feat: add scope to pom properties (#1779)
* feat: add scope to pom properties

Signed-off-by: James Neate <jamesmneate@gmail.com>

* fix: fixed conflict with schema bump

Signed-off-by: James Neate <jamesmneate@gmail.com>

---------

Signed-off-by: James Neate <jamesmneate@gmail.com>
2023-06-01 12:22:29 +00:00
dependabot[bot] 5842fc2a64 chore(deps): bump github.com/stretchr/testify from 1.8.3 to 1.8.4 (#1852)
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.8.3 to 1.8.4.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.8.3...v1.8.4)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-30 13:48:54 -04:00
dependabot[bot] f0307fdd62 chore(deps): bump github.com/docker/docker (#1849)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 24.0.1+incompatible to 24.0.2+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v24.0.1...v24.0.2)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-26 16:08:20 -04:00
Alex Goodman 74013d7da7 Add test to ensure package metadata is represented in the JSON schema (#1841)
* [wip] try to reflect metadata types... probably wont work

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

* refactor to add unit test to ensure there is coverage in the schema

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

* [wip] generate metadata container

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

* add generation of metadata container struct for JSON schema generation

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

* fix linting

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

* update linter script to account for code generation

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

---------

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2023-05-25 13:26:56 -04:00
Alex Goodman 6afbffce28 Fix directory resolver to consider CWD and root path input correctly (#1840)
* [wip] put in initial fix

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

* capture expected behavior of dir resolver in tests

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

* update tests + comments to reflect current dir resolver behavior

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

* add additional test cases

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

* fix linting

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

* fix additional tests

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

* fix bad merge conflict resolution

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

---------

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2023-05-25 13:41:18 +00:00
Alex Goodman 07e76907f6 Migrate location-related structs to the file package (#1751)
* migrate location structs to file package

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

* replace source.Location refs with file package call

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

* fix linting

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

* remove hardlink test for file based catalogers

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

* remove hardlink test for all-regular-files testing

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

* migrate file resolver implementations to separate package

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

* fix linting

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

* [wip] migrate resolvers to internal

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

* migrate resolvers to syft/internal

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

---------

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
Signed-off-by: <>
2023-05-24 17:06:38 -04:00
dependabot[bot] 4bf17a94b9 chore(deps): bump github.com/go-git/go-git/v5 from 5.6.1 to 5.7.0 (#1843)
Bumps [github.com/go-git/go-git/v5](https://github.com/go-git/go-git) from 5.6.1 to 5.7.0.
- [Release notes](https://github.com/go-git/go-git/releases)
- [Commits](https://github.com/go-git/go-git/compare/v5.6.1...v5.7.0)

---
updated-dependencies:
- dependency-name: github.com/go-git/go-git/v5
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-24 11:40:11 -04:00
Christopher Angelo Phillips 4ac8fdf6df fix: add panic recovery for license parse (#1839)
* fix: add panic recovery for license parse
---------
Signed-off-by: Christopher Phillips <christopher.phillips@anchore.com>
2023-05-23 16:58:49 +00:00
Idan Frimark 087a6356b9 chore: return both failures when failed to retrieve an image with a scheme (#1801)
Signed-off-by: Idan Frimark <idanf@cisco.com>
2023-05-23 10:32:12 -04:00
Alex GoodmanandWeston Steimel 26c201f7f7 Extract go module versions from ldflags for binaries built by go (#1832)
* wip

Signed-off-by: Weston Steimel <weston.steimel@anchore.com>

* with golang bin ldflags refactor

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

* add test for golang binary cataloger for ldflag extraction

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

* remove binary classfiers that overlap with new go ldflags detection

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

---------

Signed-off-by: Weston Steimel <weston.steimel@anchore.com>
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
Co-authored-by: Weston Steimel <weston.steimel@anchore.com>
2023-05-23 10:27:48 -04:00
Keith Zantow a3c5550217 fix: duplicate packages, support pnpm lockfile v6 (#1778) 2023-05-23 10:24:25 -04:00
anchore-actions-token-generator[bot]andkzantow 798af57853 chore(deps): update stereoscope to e14bc4437b2eac481c5b6f101890b22df4f33596 (#1834)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: kzantow <kzantow@users.noreply.github.com>
2023-05-23 10:18:39 -04:00
dependabot[bot] f50302b2ba chore(deps): bump github.com/stretchr/testify from 1.8.2 to 1.8.3 (#1829)
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.8.2 to 1.8.3.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.8.2...v1.8.3)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-22 14:01:17 -04:00
dependabot[bot] b09cf6c6b5 chore(deps): bump github.com/docker/docker (#1833)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 24.0.0+incompatible to 24.0.1+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v24.0.0...v24.0.1)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-22 13:07:24 -04:00
Alex Goodman 334a775cb9 Keep original FileInfo persisted on file.Metadata structs (#1794)
* pull in fileinfo changes from stereoscope #172

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

* fix CLI test assumption about the docker daemon

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

---------

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
Signed-off-by: <>
2023-05-19 14:21:10 +00:00
dependabot[bot] f1b6f38ea8 chore(deps): bump github.com/sirupsen/logrus from 1.9.1 to 1.9.2 (#1827)
Bumps [github.com/sirupsen/logrus](https://github.com/sirupsen/logrus) from 1.9.1 to 1.9.2.
- [Release notes](https://github.com/sirupsen/logrus/releases)
- [Changelog](https://github.com/sirupsen/logrus/blob/master/CHANGELOG.md)
- [Commits](https://github.com/sirupsen/logrus/compare/v1.9.1...v1.9.2)

---
updated-dependencies:
- dependency-name: github.com/sirupsen/logrus
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-19 09:01:05 -04:00
dependabot[bot] f6f8332b7f chore(deps): bump github.com/google/go-containerregistry (#1823)
Bumps [github.com/google/go-containerregistry](https://github.com/google/go-containerregistry) from 0.15.1 to 0.15.2.
- [Release notes](https://github.com/google/go-containerregistry/releases)
- [Changelog](https://github.com/google/go-containerregistry/blob/main/.goreleaser.yml)
- [Commits](https://github.com/google/go-containerregistry/compare/v0.15.1...v0.15.2)

---
updated-dependencies:
- dependency-name: github.com/google/go-containerregistry
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-17 14:34:27 -04:00
dependabot[bot] 74351567ab chore(deps): bump github.com/sirupsen/logrus from 1.9.0 to 1.9.1 (#1822)
Bumps [github.com/sirupsen/logrus](https://github.com/sirupsen/logrus) from 1.9.0 to 1.9.1.
- [Release notes](https://github.com/sirupsen/logrus/releases)
- [Changelog](https://github.com/sirupsen/logrus/blob/master/CHANGELOG.md)
- [Commits](https://github.com/sirupsen/logrus/compare/v1.9.0...v1.9.1)

---
updated-dependencies:
- dependency-name: github.com/sirupsen/logrus
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-17 14:33:48 -04:00
dependabot[bot] 51d4c9b4ab chore(deps): bump github.com/docker/docker (#1824)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 23.0.6+incompatible to 24.0.0+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v23.0.6...v24.0.0)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-17 14:33:30 -04:00
Christopher Angelo Phillips 4601ca3735 fix: update field plurality of 8.0.0 schema before release (#1820)
to keep things consistent across the schema we want Locations and URLs to be plural fields now that they are fields on the License struct
---------

Signed-off-by: Christopher Phillips <christopher.phillips@anchore.com>
2023-05-16 13:05:48 -04:00
Christopher Angelo Phillips 1a2a49840b fix: update cataloger to check for expressions before split (#1819)
Signed-off-by: Christopher Phillips <christopher.phillips@anchore.com>
2023-05-16 16:04:28 +00:00
Christopher Angelo Phillips 42fa9e4965 feat: update syft license concept to complex struct (#1743)
this PR makes the following changes to update the underlying license model to have more expressive capabilities
it also provides some guarantee's surrounding the license values themselves

- Licenses are updated from string -> pkg.LicenseSet which contain pkg.License with the following fields:
- original `Value` read by syft
- If it's possible to construct licenses will always have a valid SPDX expression for downstream consumption
- the above is run against a generated list of SPDX license ID to try and find the correct ID
- SPDX concluded vs declared is added to the new struct
- URL source for license is added to the new struct
- Location source is added to the new struct to show where the expression was pulled from
2023-05-15 16:23:39 -04:00
Shane Alvarez 8046f09562 fix: cyclonedx depends-on relationship inverted (#1816)
Signed-off-by: Shane Alvarez <shane.alv@gmail.com>
2023-05-15 09:59:26 -04:00
mikey straussandEitan Goldenstein b4ed599481 fix: retain sbom cataloger relationships (#1509)
Signed-off-by: Eitan Goldenstein <eitan@scribesecurity.com>
Co-authored-by: Eitan Goldenstein <eitan@scribesecurity.com>
2023-05-15 09:57:21 -04:00
William Murphy e925d9d4a5 feat: warn if parsing newer SBOM (#1810)
If syft is asked to parse an SBOM that was written by a newer version of
syft, emit a warning, since the current version of syft doesn't know about 
fields that may be added in the future.

Signed-off-by: Will Murphy <will.murphy@anchore.com>
2023-05-11 08:55:27 -04:00
William Murphy da3624644a feat: Add R cataloger (#1790)
Add a cataloger that detects installed R packages by looking for DESCRIPTION
files. The base R package is now picked up in coverageImage tests in
test/cli/packages_cmd_test.go, so increment expected package counts for the
tests that use that image.

Signed-off-by: Will Murphy <will.murphy@anchore.com>
2023-05-10 12:30:11 -04:00
Bob Callaway 0580328ad9 update cosign to v2 release (different go module) (#1805)
Signed-off-by: Bob Callaway <bcallaway@google.com>
2023-05-10 11:12:37 -04:00
William Murphy 291da8cd12 fix: Reduce log spam on unknown relationship type (#1797)
Rather than log a warning for every instance of an unknown relationship type,
or similar error, log a count of how many times each of these errors is
raised.

---------

Signed-off-by: Will Murphy <will.murphy@anchore.com>
2023-05-10 09:51:12 -04:00
anchore-actions-token-generator[bot] 8a3cbf2fdd chore(deps): update bootstrap tools to latest versions (#1807) 2023-05-10 08:25:36 -04:00
dependabot[bot] ef08d0fa39 chore(deps): bump golang.org/x/net from 0.9.0 to 0.10.0 (#1802)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.9.0 to 0.10.0.
- [Commits](https://github.com/golang/net/compare/v0.9.0...v0.10.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-09 11:59:39 -04:00
dependabot[bot] 75d625b697 chore(deps): bump github.com/docker/docker (#1795)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 23.0.5+incompatible to 23.0.6+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v23.0.5...v23.0.6)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-08 12:45:50 -04:00
dependabot[bot] 88ba8b78fc chore(deps): bump github.com/google/go-containerregistry (#1796)
Bumps [github.com/google/go-containerregistry](https://github.com/google/go-containerregistry) from 0.14.0 to 0.15.1.
- [Release notes](https://github.com/google/go-containerregistry/releases)
- [Changelog](https://github.com/google/go-containerregistry/blob/main/.goreleaser.yml)
- [Commits](https://github.com/google/go-containerregistry/compare/v0.14.0...v0.15.1)

---
updated-dependencies:
- dependency-name: github.com/google/go-containerregistry
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-08 12:45:30 -04:00
anchore-actions-token-generator[bot] 3f19aa589c chore(deps): update bootstrap tools to latest versions (#1792) 2023-05-07 13:23:41 -04:00
William Murphy 630c18e0d3 Print package list when extra packages found (#1791)
The tests in test/cli/packages_cmd_test.go are hard to debug when different
packages are found in different environments. For example, CI runs and M1 macs
have been observed to have different package counts. Therefore, if the test is
about to fail, log a sorted list of the packages that were found, so that it is
easy to compare failures of these tests.

Signed-off-by: Will Murphy <will.murphy@anchore.com>
2023-05-05 15:57:13 -04:00
anchore-actions-token-generator[bot] 1860bab24b chore(deps): update bootstrap tools to latest versions (#1786) 2023-05-05 14:57:02 -04:00
dependabot[bot] e31839a370 chore(deps): bump golang.org/x/term from 0.7.0 to 0.8.0 (#1787) 2023-05-05 18:56:40 +00:00
Josh Bressers 0f1aed4477 Update the CPE generation for spring-security-core (#1789)
* Update the CPE generation for spring-security-core
* Add vendor test for spring-security

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

---------

Signed-off-by: Josh Bressers <josh@bress.net>
2023-05-05 15:41:41 +00:00
Keith Zantow ddb338d834 chore: do not HTML escape PackageURLs (#1782)
Signed-off-by: Keith Zantow <kzantow@gmail.com>
2023-05-05 10:08:04 -04:00
Keith Zantow 354c72bbf4 chore: do not include kernel module cataloger by default (#1784) 2023-05-05 09:54:24 -04:00
Jeff Squyres d63a1f5f80 chore(docs): Update lists of catalogers (#1780)
Signed-off-by: Jeff Squyres <jeff@squyres.com>
2023-05-04 15:36:22 -04:00
Keith Zantow 645206735e chore: add more detail on SPDX file IDs (#1769) 2023-05-02 16:52:18 -04:00
Filip PytlounandAlex Goodman 95a04cadea Search /usr/share for rpmdb to fix scan on ostree-managed images (#1756)
Fixes: https://github.com/anchore/syft/issues/1755

Signed-off-by: Filip Pytloun <filip@pytloun.cz>
Co-authored-by: Alex Goodman <alex.goodman@anchore.com>
2023-05-02 16:43:52 -04:00
dependabot[bot] dd458a2b33 chore(deps): bump github.com/docker/docker (#1767)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 23.0.4+incompatible to 23.0.5+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v23.0.4...v23.0.5)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-02 16:43:16 -04:00
Alex Goodman 5f3d4d285b rename sbom.PackageCatalog to sbom.Packages (#1773)
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2023-05-01 10:19:58 -04:00
dependabot[bot] 10c3cc27e8 chore(deps): bump modernc.org/sqlite from 1.22.0 to 1.22.1 (#1768)
Bumps [modernc.org/sqlite](https://gitlab.com/cznic/sqlite) from 1.22.0 to 1.22.1.
- [Release notes](https://gitlab.com/cznic/sqlite/tags)
- [Commits](https://gitlab.com/cznic/sqlite/compare/v1.22.0...v1.22.1)

---
updated-dependencies:
- dependency-name: modernc.org/sqlite
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-27 11:58:59 -04:00
Shane Dell a07bfe7dfa Create python requirements metadata (#1759)
- Create new metadata struct and type for python requirements.
- Update parsing of python requirements to use python requirements metadata.
- Remove extras and url from line. Add them to metadata instead.
- Add unit test to test that extras are removed from package name.
- Update test to look at requirements metadata.
- Will need updated in future to support more than just == for the version constraint.
- Update JSON schema data

Closes anchore/grype#1246
Closes anchore/grype#1251

Signed-off-by: Shane Dell <shanedell100@gmail.com>
2023-04-27 09:04:30 -04:00
Keith Zantow 451cb9d5ca chore: update test redactor ordering (#1765)
Signed-off-by: Keith Zantow <kzantow@gmail.com>
2023-04-26 20:42:43 +00:00
Alex Goodman fd02bef0a3 rename pkg.Catalog to pkg.Collection (#1764)
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2023-04-26 13:56:33 -04:00
dependabot[bot] 02bd52728e chore(deps): bump modernc.org/sqlite from 1.21.2 to 1.22.0 (#1758)
Bumps [modernc.org/sqlite](https://gitlab.com/cznic/sqlite) from 1.21.2 to 1.22.0.
- [Release notes](https://gitlab.com/cznic/sqlite/tags)
- [Commits](https://gitlab.com/cznic/sqlite/compare/v1.21.2...v1.22.0)

---
updated-dependencies:
- dependency-name: modernc.org/sqlite
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-26 10:37:49 -04:00
Christopher Angelo PhillipsandAlex Goodman c038f13d44 chore: go-rpmdb update (#1757)
Signed-off-by: Christopher Phillips <christopher.phillips@anchore.com>
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
Co-authored-by: Alex Goodman <alex.goodman@anchore.com>
2023-04-24 10:34:13 -04:00
dependabot[bot] 8102ad4edc chore(deps): bump github.com/CycloneDX/cyclonedx-go from 0.7.1-0.20221222100750-41a1ac565cce to 0.7.1 (#1706) 2023-04-24 10:20:12 -04:00
Shane Dell 13485ca5e7 fix: Improve pnpm support (#1752)
Signed-off-by: Shane Dell <shanedell100@gmail.com>
2023-04-21 17:58:23 +00:00
Alex Lehman b2b332e8b2 feat: Add template func hasField (#1754)
Signed-off-by: Lehman, Alex <alex.lehman@gtri.gatech.edu>
2023-04-21 09:34:06 -04:00
Christopher Angelo Phillips a42bac6fcc fix: only cache java packages and not source content (#1750)
* fix: only cache java packages and not source content

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

* fix: add gradle to matched files for ci checksum

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

---------

Signed-off-by: Christopher Phillips <christopher.phillips@anchore.com>
2023-04-19 16:07:34 -04:00
Shane Dell 98a6c6efbe Add sections of interest for Gemfile.lock cataloger (#1749)
- Updated tests to reflect the new sections being added to show they function properly.

Closes #1660

Signed-off-by: Shane Dell <shanedell100@gmail.com>
2023-04-19 12:18:17 -04:00
Christopher Angelo Phillips 55a90a2ee0 fix: update cache.fingerprint file to java-builds dir (#1748)
Signed-off-by: Christopher Phillips <christopher.phillips@anchore.com>
2023-04-19 12:17:07 -04:00
Shane Dell 6e835fd8fc Add ALPM Metadata to CYCLONEDX and SPDX output formats (#1747)
Signed-off-by: Shane Dell <shanedell100@gmail.com>
2023-04-18 11:53:02 -04:00
Weston Steimel ee80349ea0 chore: bump stereoscope to latest version (#1741)
Resolves reporting of GHSA-hw7c-3rfg-p46j

Signed-off-by: Weston Steimel <weston.steimel@anchore.com>
2023-04-18 15:44:03 +00:00
anchore-actions-token-generator[bot]andspiffcs 52b54bbad9 chore(deps): update bootstrap tools to latest versions (#1744)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: spiffcs <spiffcs@users.noreply.github.com>
2023-04-18 10:25:02 -04:00
dependabot[bot] 66d9c5637b chore(deps): bump github.com/docker/docker (#1746)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 23.0.3+incompatible to 23.0.4+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v23.0.3...v23.0.4)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-18 10:22:41 -04:00
806 changed files with 77199 additions and 15196 deletions
+1
View File
@@ -0,0 +1 @@
enforce-v0: true # don't make breaking-change label bump major version before 1.0.
+10 -9
View File
@@ -1,10 +1,11 @@
name: "Bootstrap"
description: "Bootstrap all tools and dependencies"
inputs:
go-version:
description: "Go version to install"
required: true
default: "1.19.x"
default: "1.21.x"
use-go-cache:
description: "Restore go cache"
required: true
@@ -24,13 +25,13 @@ inputs:
runs:
using: "composite"
steps:
- uses: actions/setup-go@v3
- uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe #v4.1.0
with:
go-version: ${{ inputs.go-version }}
- name: Restore tool cache
id: tool-cache
uses: actions/cache@v3
uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 #v3.3.2
with:
path: ${{ github.workspace }}/.tmp
key: ${{ inputs.cache-key-prefix }}-${{ runner.os }}-tool-${{ hashFiles('Makefile') }}
@@ -40,13 +41,13 @@ runs:
- name: Restore go module cache
id: go-mod-cache
if: inputs.use-go-cache == 'true'
uses: actions/cache@v3
uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 #v3.3.2
with:
path: |
~/go/pkg/mod
key: ${{ inputs.cache-key-prefix }}-${{ runner.os }}-go-${{ inputs.go-version }}-${{ hashFiles('**/go.sum') }}
key: ${{ inputs.cache-key-prefix }}-${{ runner.os }}-go-mod-${{ inputs.go-version }}-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ inputs.cache-key-prefix }}-${{ runner.os }}-go-${{ inputs.go-version }}-
${{ inputs.cache-key-prefix }}-${{ runner.os }}-go-mod-${{ inputs.go-version }}-
- name: (cache-miss) Bootstrap project tools
shell: bash
@@ -56,13 +57,13 @@ runs:
- name: Restore go build cache
id: go-cache
if: inputs.use-go-cache == 'true'
uses: actions/cache@v3
uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 #v3.3.2
with:
path: |
~/.cache/go-build
key: ${{ inputs.cache-key-prefix }}-${{ inputs.build-cache-key-prefix }}-${{ runner.os }}-go-${{ inputs.go-version }}-${{ hashFiles('**/go.sum') }}
key: ${{ inputs.cache-key-prefix }}-${{ inputs.build-cache-key-prefix }}-${{ runner.os }}-go-build-${{ inputs.go-version }}-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ inputs.cache-key-prefix }}-${{ inputs.build-cache-key-prefix }}-${{ runner.os }}-go-${{ inputs.go-version }}-
${{ inputs.cache-key-prefix }}-${{ inputs.build-cache-key-prefix }}-${{ runner.os }}-go-build-${{ inputs.go-version }}-
- name: (cache-miss) Bootstrap go dependencies
shell: bash
+7 -5
View File
@@ -1,10 +1,12 @@
version: 2
updates:
- package-ecosystem: gomod
directory: "/"
schedule:
interval: "daily"
open-pull-requests-limit: 10
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: daily
- package-ecosystem: "gomod"
directory: "/"
schedule:
interval: daily
interval: "daily"
open-pull-requests-limit: 10
+5 -15
View File
@@ -1,27 +1,17 @@
#!/usr/bin/env bash
set -u
if ! git diff-index --quiet HEAD --; then
git diff-index HEAD --
git --no-pager diff
echo "there are uncommitted changes, please commit them before running this check"
if [ "$(git status --porcelain | wc -l)" -ne "0" ]; then
echo " 🔴 there are uncommitted changes, please commit them before running this check"
exit 1
fi
success=true
if ! make generate-json-schema; then
echo "Generating json schema failed"
success=false
exit 1
fi
if ! git diff-index --quiet HEAD --; then
git diff-index HEAD --
git --no-pager diff
echo "JSON schema drift detected!"
success=false
fi
if ! $success; then
if [ "$(git status --porcelain | wc -l)" -ne "0" ]; then
echo " 🔴 there are uncommitted changes, please commit them before running this check"
exit 1
fi
+8 -4
View File
@@ -4,6 +4,9 @@ on:
workflow_dispatch:
pull_request:
permissions:
contents: read
jobs:
Benchmark-Test:
@@ -13,13 +16,14 @@ jobs:
# we also want to run on push such that merges to main are recorded to the cache. For this reason we don't filter
# the job by event.
steps:
- uses: actions/checkout@v3
- name: Checkout code
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
- name: Bootstrap environment
uses: ./.github/actions/bootstrap
- name: Restore base benchmark result
uses: actions/cache@v3
uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 #v3.3.2
with:
path: test/results/benchmark-main.txt
# use base sha for PR or new commit hash for main push in benchmark result key
@@ -35,13 +39,13 @@ jobs:
OUTPUT="${OUTPUT//$'\r'/'%0D'}" # URL encode all '\r' characters
echo "result=$OUTPUT" >> $GITHUB_OUTPUT
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
with:
name: benchmark-test-results
path: test/results/**/*
- name: Update PR benchmark results comment
uses: marocchino/sticky-pull-request-comment@v2
uses: marocchino/sticky-pull-request-comment@efaaab3fd41a9c3de579aba759d2552635e590fd #v2.8.0
continue-on-error: true
with:
header: benchmark
+15 -4
View File
@@ -14,11 +14,17 @@ on:
schedule:
- cron: '0 0 * * 3'
permissions:
contents: read
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
security-events: write
strategy:
fail-fast: false
matrix:
@@ -30,11 +36,16 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac #v4.0.0
- name: Install Go
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe #v4.1.0
with:
go-version-file: go.mod
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
uses: github/codeql-action/init@6a28655e3dcb49cb0840ea372fd6d17733edd8a4 #v2.21.8
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -45,7 +56,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
uses: github/codeql-action/autobuild@6a28655e3dcb49cb0840ea372fd6d17733edd8a4 #v2.21.8
# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@@ -59,4 +70,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
uses: github/codeql-action/analyze@6a28655e3dcb49cb0840ea372fd6d17733edd8a4 #v2.21.8
@@ -0,0 +1,18 @@
name: Add to OSS board
permissions:
contents: read
on:
issues:
types:
- opened
- reopened
- transferred
- labeled
jobs:
run:
uses: "anchore/workflows/.github/workflows/oss-project-board-add.yaml@main"
secrets:
token: ${{ secrets.OSS_PROJECT_GH_TOKEN }}
+20 -17
View File
@@ -1,4 +1,8 @@
name: "Release"
permissions:
contents: read
on:
workflow_dispatch:
inputs:
@@ -6,15 +10,12 @@ on:
description: tag the latest commit on main with the given version (prefixed with v)
required: true
env:
GO_VERSION: "1.19.x"
jobs:
quality-gate:
environment: release
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac #v4.0.0
- name: Check if tag already exists
# note: this will fail if the tag already exists
@@ -23,7 +24,7 @@ jobs:
git tag ${{ github.event.inputs.version }}
- name: Check static analysis results
uses: fountainhead/action-wait-for-check@v1.1.0
uses: fountainhead/action-wait-for-check@297be350cf8393728ea4d4b39435c7d7ae167c93 # v1.1.0
id: static-analysis
with:
token: ${{ secrets.GITHUB_TOKEN }}
@@ -32,7 +33,7 @@ jobs:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Check unit test results
uses: fountainhead/action-wait-for-check@v1.1.0
uses: fountainhead/action-wait-for-check@297be350cf8393728ea4d4b39435c7d7ae167c93 # v1.1.0
id: unit
with:
token: ${{ secrets.GITHUB_TOKEN }}
@@ -41,7 +42,7 @@ jobs:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Check integration test results
uses: fountainhead/action-wait-for-check@v1.1.0
uses: fountainhead/action-wait-for-check@297be350cf8393728ea4d4b39435c7d7ae167c93 # v1.1.0
id: integration
with:
token: ${{ secrets.GITHUB_TOKEN }}
@@ -50,7 +51,7 @@ jobs:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Check acceptance test results (linux)
uses: fountainhead/action-wait-for-check@v1.1.0
uses: fountainhead/action-wait-for-check@297be350cf8393728ea4d4b39435c7d7ae167c93 # v1.1.0
id: acceptance-linux
with:
token: ${{ secrets.GITHUB_TOKEN }}
@@ -59,7 +60,7 @@ jobs:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Check acceptance test results (mac)
uses: fountainhead/action-wait-for-check@v1.1.0
uses: fountainhead/action-wait-for-check@297be350cf8393728ea4d4b39435c7d7ae167c93 # v1.1.0
id: acceptance-mac
with:
token: ${{ secrets.GITHUB_TOKEN }}
@@ -68,7 +69,7 @@ jobs:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Check cli test results (linux)
uses: fountainhead/action-wait-for-check@v1.1.0
uses: fountainhead/action-wait-for-check@297be350cf8393728ea4d4b39435c7d7ae167c93 # v1.1.0
id: cli-linux
with:
token: ${{ secrets.GITHUB_TOKEN }}
@@ -94,7 +95,7 @@ jobs:
contents: write
packages: write
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac #v4.0.0
with:
fetch-depth: 0
@@ -105,13 +106,13 @@ jobs:
build-cache-key-prefix: "snapshot"
- name: Login to Docker Hub
uses: docker/login-action@v2
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d #v3.0.0
with:
username: ${{ secrets.TOOLBOX_DOCKER_USER }}
password: ${{ secrets.TOOLBOX_DOCKER_PASS }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v2
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d #v3.0.0
with:
registry: ghcr.io
username: ${{ github.actor }}
@@ -119,7 +120,9 @@ jobs:
- name: Tag release
run: |
git tag ${{ github.event.inputs.version }}
git config --global user.name "anchoreci"
git config --global user.email "anchoreci@users.noreply.github.com"
git tag -a ${{ github.event.inputs.version }} -m "Release ${{ github.event.inputs.version }}"
git push origin --tags
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -141,12 +144,12 @@ jobs:
AWS_ACCESS_KEY_ID: ${{ secrets.TOOLBOX_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.TOOLBOX_AWS_SECRET_ACCESS_KEY }}
- uses: anchore/sbom-action@v0
- uses: anchore/sbom-action@78fc58e266e87a38d4194b2137a3d4e9bcaf7ca1 #v0.14.3
continue-on-error: true
with:
artifact-name: sbom.spdx.json
- uses: 8398a7/action-slack@v3
- uses: 8398a7/action-slack@49aa08e51e8782f49c601d79ec72d07fd39a4612 #v3.15.1
continue-on-error: true
with:
status: ${{ job.status }}
@@ -156,7 +159,7 @@ jobs:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_TOOLBOX_WEBHOOK_URL }}
if: ${{ success() }}
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 #v3.1.3
with:
name: artifacts
path: dist/**/*
+13 -6
View File
@@ -6,17 +6,20 @@ on:
workflow_dispatch:
env:
GO_VERSION: "1.19.x"
GO_VERSION: "1.21.x"
GO_STABLE_VERSION: true
permissions:
contents: read
jobs:
update-bootstrap-tools:
runs-on: ubuntu-latest
if: github.repository == 'anchore/syft' # only run for main repo
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac #v4.0.0
- uses: actions/setup-go@v4
- uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe #v4.1.0
with:
go-version: ${{ env.GO_VERSION }}
stable: ${{ env.GO_STABLE_VERSION }}
@@ -28,7 +31,8 @@ jobs:
GORELEASER_LATEST_VERSION=$(go list -m -json github.com/goreleaser/goreleaser@latest 2>/dev/null | jq -r '.Version')
GOSIMPORTS_LATEST_VERSION=$(go list -m -json github.com/rinchsan/gosimports@latest 2>/dev/null | jq -r '.Version')
YAJSV_LATEST_VERSION=$(go list -m -json github.com/neilpa/yajsv@latest 2>/dev/null | jq -r '.Version')
COSIGN_LATEST_VERSION=$(go list -m -json github.com/sigstore/cosign@latest 2>/dev/null | jq -r '.Version')
COSIGN_LATEST_VERSION=$(go list -m -json github.com/sigstore/cosign/v2@latest 2>/dev/null | jq -r '.Version')
QUILL_LATEST_VERSION=$(go list -m -json github.com/anchore/quill@latest 2>/dev/null | jq -r '.Version')
GLOW_LATEST_VERSION=$(go list -m -json github.com/charmbracelet/glow@latest 2>/dev/null | jq -r '.Version')
# update version variables in the Makefile
@@ -39,6 +43,7 @@ jobs:
sed -r -i -e 's/^(GOSIMPORTS_VERSION := ).*/\1'${GOSIMPORTS_LATEST_VERSION}'/' Makefile
sed -r -i -e 's/^(YAJSV_VERSION := ).*/\1'${YAJSV_LATEST_VERSION}'/' Makefile
sed -r -i -e 's/^(COSIGN_VERSION := ).*/\1'${COSIGN_LATEST_VERSION}'/' Makefile
sed -r -i -e 's/^(QUILL_VERSION := ).*/\1'${QUILL_LATEST_VERSION}'/' Makefile
sed -r -i -e 's/^(GLOW_VERSION := ).*/\1'${GLOW_LATEST_VERSION}'/' Makefile
# export the versions for use with create-pull-request
@@ -49,16 +54,17 @@ jobs:
echo "GOSIMPORTS=$GOSIMPORTS_LATEST_VERSION" >> $GITHUB_OUTPUT
echo "YAJSV=$YAJSV_LATEST_VERSION" >> $GITHUB_OUTPUT
echo "COSIGN=$COSIGN_LATEST_VERSION" >> $GITHUB_OUTPUT
echo "QUILL=$QUILL_LATEST_VERSION" >> $GITHUB_OUTPUT
echo "GLOW=GLOW_LATEST_VERSION" >> $GITHUB_OUTPUT
id: latest-versions
- uses: tibdex/github-app-token@v1
- uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a #v2.1.0
id: generate-token
with:
app_id: ${{ secrets.TOKEN_APP_ID }}
private_key: ${{ secrets.TOKEN_APP_PRIVATE_KEY }}
- uses: peter-evans/create-pull-request@v5
- uses: peter-evans/create-pull-request@153407881ec5c347639a548ade7d8ad1d6740e38 #v5.0.2
with:
signoff: true
delete-branch: true
@@ -74,6 +80,7 @@ jobs:
- [gosimports ${{ steps.latest-versions.outputs.GOSIMPORTS }}](https://github.com/rinchsan/gosimports/releases/tag/${{ steps.latest-versions.outputs.GOSIMPORTS }})
- [yajsv ${{ steps.latest-versions.outputs.YAJSV }}](https://github.com/neilpa/yajsv/releases/tag/${{ steps.latest-versions.outputs.YAJSV }})
- [cosign ${{ steps.latest-versions.outputs.COSIGN }}](https://github.com/sigstore/cosign/releases/tag/${{ steps.latest-versions.outputs.COSIGN }})
- [quill ${{ steps.latest-versions.outputs.QUILL }}](https://github.com/anchore/quill/releases/tag/${{ steps.latest-versions.outputs.QUILL }})
- [glow ${{ steps.latest-versions.outputs.GLOW }}](https://github.com/charmbracelet/glow/releases/tag/${{ steps.latest-versions.outputs.GLOW }})
This is an auto-generated pull request to update all of the bootstrap tools to the latest versions.
token: ${{ steps.generate-token.outputs.token }}
@@ -0,0 +1,46 @@
name: PR to update CPE dictionary index
on:
schedule:
- cron: "0 1 * * 1" # every monday at 1 AM
workflow_dispatch:
permissions:
contents: read
env:
GO_VERSION: "1.21.x"
GO_STABLE_VERSION: true
jobs:
upgrade-cpe-dictionary-index:
runs-on: ubuntu-latest
if: github.repository == 'anchore/syft' # only run for main repo
steps:
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac #v4.0.0
- uses: actions/setup-go@3df4ab11eba7bda6032a0b82a6bb43b11571feac #v4.0.0
with:
go-version: ${{ env.GO_VERSION }}
stable: ${{ env.GO_STABLE_VERSION }}
- run: |
make generate-cpe-dictionary-index
- uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a #v2.1.0
id: generate-token
with:
app_id: ${{ secrets.TOKEN_APP_ID }}
private_key: ${{ secrets.TOKEN_APP_PRIVATE_KEY }}
- uses: peter-evans/create-pull-request@153407881ec5c347639a548ade7d8ad1d6740e38 #v5.0.2
with:
signoff: true
delete-branch: true
branch: auto/latest-cpe-dictionary-index
labels: dependencies
commit-message: "chore(deps): update CPE dictionary index"
title: "chore(deps): update CPE dictionary index"
body: |
Update CPE dictionary index based on the latest available CPE dictionary
token: ${{ steps.generate-token.outputs.token }}
@@ -6,17 +6,20 @@ on:
workflow_dispatch:
env:
GO_VERSION: "1.19.x"
GO_VERSION: "1.21.x"
GO_STABLE_VERSION: true
permissions:
contents: read
jobs:
upgrade-stereoscope:
runs-on: ubuntu-latest
if: github.repository == 'anchore/syft' # only run for main repo
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac #v4.0.0
- uses: actions/setup-go@v4
- uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe #v4.1.0
with:
go-version: ${{ env.GO_VERSION }}
stable: ${{ env.GO_STABLE_VERSION }}
@@ -32,13 +35,13 @@ jobs:
echo "LATEST_VERSION=$LATEST_VERSION" >> $GITHUB_OUTPUT
id: latest-version
- uses: tibdex/github-app-token@v1
- uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a #v2.1.0
id: generate-token
with:
app_id: ${{ secrets.TOKEN_APP_ID }}
private_key: ${{ secrets.TOKEN_APP_PRIVATE_KEY }}
- uses: peter-evans/create-pull-request@v5
- uses: peter-evans/create-pull-request@153407881ec5c347639a548ade7d8ad1d6740e38 #v5.0.2
with:
signoff: true
delete-branch: true
+24 -22
View File
@@ -7,14 +7,16 @@ on:
branches:
- main
jobs:
permissions:
contents: read
jobs:
Static-Analysis:
# Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline
name: "Static analysis"
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac #v4.0.0
- name: Bootstrap environment
uses: ./.github/actions/bootstrap
@@ -28,37 +30,37 @@ jobs:
name: "Unit tests"
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac #v4.0.0
- name: Bootstrap environment
uses: ./.github/actions/bootstrap
- name: Restore Java test-fixture cache
uses: actions/cache@v3
uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 #v3.3.2
with:
path: syft/pkg/cataloger/java/test-fixtures/java-builds/packages
key: ${{ runner.os }}-unit-java-cache-${{ hashFiles( 'syft/pkg/cataloger/java/test-fixtures/java-builds/packages.fingerprint' ) }}
key: ${{ runner.os }}-unit-java-cache-${{ hashFiles( 'syft/pkg/cataloger/java/test-fixtures/java-builds/cache.fingerprint' ) }}
- name: Restore RPM test-fixture cache
uses: actions/cache@v3
uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 #v3.3.2
with:
path: syft/pkg/cataloger/rpm/test-fixtures/rpms
key: ${{ runner.os }}-unit-rpm-cache-${{ hashFiles( 'syft/pkg/cataloger/rpm/test-fixtures/rpms.fingerprint' ) }}
- name: Restore go binary test-fixture cache
uses: actions/cache@v3
uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 #v3.3.2
with:
path: syft/pkg/cataloger/golang/test-fixtures/archs/binaries
key: ${{ runner.os }}-unit-go-binaries-cache-${{ hashFiles( 'syft/pkg/cataloger/golang/test-fixtures/archs/binaries.fingerprint' ) }}
- name: Restore binary cataloger test-fixture cache
uses: actions/cache@v3
uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 #v3.3.2
with:
path: syft/pkg/cataloger/binary/test-fixtures/classifiers/dynamic
key: ${{ runner.os }}-unit-binary-cataloger-cache-${{ hashFiles( 'syft/pkg/cataloger/binary/test-fixtures/cache.fingerprint' ) }}
- name: Restore Kernel test-fixture cache
uses: actions/cache@v3
uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 #v3.3.2
with:
path: syft/pkg/cataloger/kernel/test-fixtures/cache
key: ${{ runner.os }}-unit-kernel-cache-${{ hashFiles( 'syft/pkg/cataloger/kernel/test-fixtures/cache.fingerprint' ) }}
@@ -72,7 +74,7 @@ jobs:
name: "Integration tests"
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac #v4.0.0
- name: Bootstrap environment
uses: ./.github/actions/bootstrap
@@ -81,7 +83,7 @@ jobs:
run: make validate-cyclonedx-schema
- name: Restore integration test cache
uses: actions/cache@v3
uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 #v3.3.2
with:
path: ${{ github.workspace }}/test/integration/test-fixtures/cache
key: ${{ runner.os }}-integration-test-cache-${{ hashFiles('test/integration/test-fixtures/cache.fingerprint') }}
@@ -94,7 +96,7 @@ jobs:
name: "Build snapshot artifacts"
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac #v4.0.0
- name: Bootstrap environment
uses: ./.github/actions/bootstrap
@@ -113,7 +115,7 @@ jobs:
# why not use actions/upload-artifact? It is very slow (3 minutes to upload ~600MB of data, vs 10 seconds with this approach).
# see https://github.com/actions/upload-artifact/issues/199 for more info
- name: Upload snapshot artifacts
uses: actions/cache/save@v3
uses: actions/cache/save@704facf57e6136b1bc63b828d79edcd491f0ee84 #v3.3.2
with:
path: snapshot
key: snapshot-build-${{ github.run_id }}
@@ -125,10 +127,10 @@ jobs:
needs: [Build-Snapshot-Artifacts]
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac #v4.0.0
- name: Download snapshot build
uses: actions/cache/restore@v3
uses: actions/cache/restore@704facf57e6136b1bc63b828d79edcd491f0ee84 #v3.3.2
with:
path: snapshot
key: snapshot-build-${{ github.run_id }}
@@ -138,7 +140,7 @@ jobs:
- name: Restore install.sh test image cache
id: install-test-image-cache
uses: actions/cache@v3
uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 #v3.3.2
with:
path: ${{ github.workspace }}/test/install/cache
key: ${{ runner.os }}-install-test-image-cache-${{ hashFiles('test/install/cache.fingerprint') }}
@@ -161,17 +163,17 @@ jobs:
needs: [Build-Snapshot-Artifacts]
runs-on: macos-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac #v4.0.0
- name: Download snapshot build
uses: actions/cache/restore@v3
uses: actions/cache/restore@704facf57e6136b1bc63b828d79edcd491f0ee84 #v3.3.2
with:
path: snapshot
key: snapshot-build-${{ github.run_id }}
- name: Restore docker image cache for compare testing
id: mac-compare-testing-cache
uses: actions/cache@v3
uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 #v3.3.2
with:
path: image.tar
key: ${{ runner.os }}-${{ hashFiles('test/compare/mac.sh') }}
@@ -189,19 +191,19 @@ jobs:
needs: [Build-Snapshot-Artifacts]
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac #v4.0.0
- name: Bootstrap environment
uses: ./.github/actions/bootstrap
- name: Restore CLI test-fixture cache
uses: actions/cache@v3
uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 #v3.3.2
with:
path: ${{ github.workspace }}/test/cli/test-fixtures/cache
key: ${{ runner.os }}-cli-test-cache-${{ hashFiles('test/cli/test-fixtures/cache.fingerprint') }}
- name: Download snapshot build
uses: actions/cache/restore@v3
uses: actions/cache/restore@704facf57e6136b1bc63b828d79edcd491f0ee84 #v3.3.2
with:
path: snapshot
key: snapshot-build-${{ github.run_id }}
+32 -10
View File
@@ -1,12 +1,39 @@
CHANGELOG.md
VERSION
/test/results
# local development tailoring
go.work
go.work.sum
.tool-versions
# app configuration
/.syft.yaml
# tool and bin directories
.tmp/
bin/
/bin
/.bin
/build
/dist
/snapshot
.server/
# changelog generation
CHANGELOG.md
VERSION
# IDE configuration
.vscode/
.idea/
.server/
.history/
# test related
*.fingerprint
/test/results
coverage.txt
*.log
test/integration/test-fixtures/**/go.sum
# probable archives
.images
*.tar
*.jar
*.war
@@ -14,12 +41,7 @@ VERSION
*.jpi
*.hpi
*.zip
.idea/
*.log
.images
.tmp/
coverage.txt
bin/
*.iml
# Binaries for programs and plugins
*.exe
+1 -1
View File
@@ -12,7 +12,6 @@ linters:
enable:
- asciicheck
- bodyclose
- depguard
- dogsled
- dupl
- errcheck
@@ -57,6 +56,7 @@ run:
# do not enable...
# - deadcode # The owner seems to have abandoned the linter. Replaced by "unused".
# - depguard # We don't have a configuration for this yet
# - goprintffuncname # does not catch all cases and there are exceptions
# - nakedret # does not catch all cases and should not fail a build
# - gochecknoglobals
+4 -4
View File
@@ -24,10 +24,10 @@ builds:
-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.gitDescription={{.Summary}}
-X main.version={{.Version}}
-X main.gitCommit={{.Commit}}
-X main.buildDate={{.Date}}
-X main.gitDescription={{.Summary}}
- id: darwin-build
dir: ./cmd/syft
+4
View File
@@ -5,3 +5,7 @@ The following Syft components were contributed by external authors/organizations
## GraalVM Native Image
A cataloger contributed by Oracle Corporation that extracts packages given within GraalVM Native Image SBOMs.
## Swift Package Manager
A cataloger contributed by Axis Communications that catalogs packages resolved by Swift Package Manager.
+42 -3
View File
@@ -7,6 +7,25 @@ In order to test and develop in this repo you will need the following dependenci
- docker
- make
### Docker settings for getting started
Make sure you've updated your docker settings so the default docker socket path is available.
Go to:
docker -> settings -> advanced
Make sure:
```
Allow the default Docker socket to be used
```
is checked.
Also double check that the docker context being used is the default context. If it is not, run:
`docker context use default`
After cloning the following step can help you get setup:
1. run `make bootstrap` to download go mod dependencies, create the `/.tmp` dir, and download helper utilities.
2. run `make` to view the selection of developer commands in the Makefile
@@ -19,6 +38,26 @@ The main make tasks for common static analysis and testing are `lint`, `format`,
See `make help` for all the current make tasks.
### Internal Artifactory Settings
**Not always applicable**
Some companies have Artifactory setup internally as a solution for sourcing secure dependencies.
If you're seeing an issue where the unit tests won't run because of the below error then this section might be relevant for your use case.
```
[ERROR] [ERROR] Some problems were encountered while processing the POMs
```
If you're dealing with an issue where the unit tests will not pull/build certain java fixtures check some of these settings:
- a `settings.xml` file should be available to help you communicate with your internal artifactory deployment
- this can be moved to `syft/pkg/cataloger/java/test-fixtures/java-builds/example-jenkins-plugin/` to help build the unit test-fixtures
- you'll also want to modify the `build-example-jenkins-plugin.sh` to use `settings.xml`
For more information on this setup and troubleshooting see [issue 1895](https://github.com/anchore/syft/issues/1895#issuecomment-1610085319)
## Architecture
Syft is used to generate a Software Bill of Materials (SBOM) from different kinds of input.
@@ -167,12 +206,12 @@ always feel free to file an issue or reach out to us [on slack](https://anchore.
#### Searching for files
All catalogers are provided an instance of the [`source.FileResolver`](https://github.com/anchore/syft/blob/v0.70.0/syft/source/file_resolver.go#L8) to interface with the image and search for files. The implementations for these
All catalogers are provided an instance of the [`file.Resolver`](https://github.com/anchore/syft/blob/v0.70.0/syft/source/file_resolver.go#L8) to interface with the image and search for files. The implementations for these
abstractions leverage [`stereoscope`](https://github.com/anchore/stereoscope) in order to perform searching. Here is a
rough outline how that works:
1. a stereoscope `file.Index` is searched based on the input given (a path, glob, or MIME type). The index is relatively fast to search, but requires results to be filtered down to the files that exist in the specific layer(s) of interest. This is done automatically by the `filetree.Searcher` abstraction. This abstraction will fallback to searching directly against the raw `filetree.FileTree` if the index does not contain the file(s) of interest. Note: the `filetree.Searcher` is used by the `source.FileResolver` abstraction.
2. Once the set of files are returned from the `filetree.Searcher` the results are filtered down further to return the most unique file results. For example, you may have requested for files by a glob that returns multiple results. These results are filtered down to deduplicate by real files, so if a result contains two references to the same file, say one accessed via symlink and one accessed via the real path, then the real path reference is returned and the symlink reference is filtered out. If both were accessed by symlink then the first (by lexical order) is returned. This is done automatically by the `source.FileResolver` abstraction.
1. a stereoscope `file.Index` is searched based on the input given (a path, glob, or MIME type). The index is relatively fast to search, but requires results to be filtered down to the files that exist in the specific layer(s) of interest. This is done automatically by the `filetree.Searcher` abstraction. This abstraction will fallback to searching directly against the raw `filetree.FileTree` if the index does not contain the file(s) of interest. Note: the `filetree.Searcher` is used by the `file.Resolver` abstraction.
2. Once the set of files are returned from the `filetree.Searcher` the results are filtered down further to return the most unique file results. For example, you may have requested for files by a glob that returns multiple results. These results are filtered down to deduplicate by real files, so if a result contains two references to the same file, say one accessed via symlink and one accessed via the real path, then the real path reference is returned and the symlink reference is filtered out. If both were accessed by symlink then the first (by lexical order) is returned. This is done automatically by the `file.Resolver` abstraction.
3. By the time results reach the `pkg.Cataloger` you are guaranteed to have a set of unique files that exist in the layer(s) of interest (relative to what the resolver supports).
## Testing
+1 -1
View File
@@ -1,4 +1,4 @@
FROM gcr.io/distroless/static-debian11:debug AS build
FROM gcr.io/distroless/static-debian11:debug@sha256:a0a404776dec98be120089ae42bbdfbe48c177921d856937d124d48eb8c0b951 AS build
FROM scratch
# needed for version check HTTPS request
+1 -1
View File
@@ -1,4 +1,4 @@
FROM gcr.io/distroless/static-debian11:debug
FROM gcr.io/distroless/static-debian11:debug@sha256:a0a404776dec98be120089ae42bbdfbe48c177921d856937d124d48eb8c0b951
# create the /tmp dir, which is needed for image content cache
WORKDIR /tmp
+19 -12
View File
@@ -10,15 +10,15 @@ CHRONICLE_CMD = $(TEMP_DIR)/chronicle
GLOW_CMD = $(TEMP_DIR)/glow
# Tool versions #################################
GOLANGCILINT_VERSION := v1.52.2
GOLANGCILINT_VERSION := v1.54.2
GOSIMPORTS_VERSION := v0.3.8
BOUNCER_VERSION := v0.4.0
CHRONICLE_VERSION := v0.6.0
GORELEASER_VERSION := v1.17.1
CHRONICLE_VERSION := v0.8.0
GORELEASER_VERSION := v1.20.0
YAJSV_VERSION := v1.4.1
COSIGN_VERSION := v1.13.1
QUILL_VERSION := v0.2.0
GLOW_VERSION := v1.5.0
COSIGN_VERSION := v2.2.0
QUILL_VERSION := v0.4.1
GLOW_VERSION := v1.5.1
# Formatting variables #################################
BOLD := $(shell tput -T linux bold)
@@ -90,7 +90,7 @@ bootstrap-tools: $(TEMP_DIR)
# the only difference between goimports and gosimports is that gosimports removes extra whitespace between import blocks (see https://github.com/golang/go/issues/20818)
GOBIN="$(realpath $(TEMP_DIR))" go install github.com/rinchsan/gosimports/cmd/gosimports@$(GOSIMPORTS_VERSION)
GOBIN="$(realpath $(TEMP_DIR))" go install github.com/neilpa/yajsv@$(YAJSV_VERSION)
GOBIN="$(realpath $(TEMP_DIR))" go install github.com/sigstore/cosign/cmd/cosign@$(COSIGN_VERSION)
GOBIN="$(realpath $(TEMP_DIR))" go install github.com/sigstore/cosign/v2/cmd/cosign@$(COSIGN_VERSION)
GOBIN="$(realpath $(TEMP_DIR))" go install github.com/charmbracelet/glow@$(GLOW_VERSION)
.PHONY: bootstrap-go
@@ -147,13 +147,14 @@ check-json-schema-drift:
.PHONY: unit
unit: $(TEMP_DIR) fixtures ## Run unit tests (with coverage)
$(call title,Running unit tests)
go test -coverprofile $(TEMP_DIR)/unit-coverage-details.txt $(shell go list ./... | grep -v anchore/syft/test)
go test -race -coverprofile $(TEMP_DIR)/unit-coverage-details.txt $(shell go list ./... | grep -v anchore/syft/test)
@.github/scripts/coverage.py $(COVERAGE_THRESHOLD) $(TEMP_DIR)/unit-coverage-details.txt
.PHONY: integration
integration: ## Run integration tests
$(call title,Running integration tests)
go test -v ./test/integration
go run -race cmd/syft/main.go alpine:latest
.PHONY: validate-cyclonedx-schema
validate-cyclonedx-schema:
@@ -199,7 +200,7 @@ fingerprints:
# for JAVA BUILD test fixtures
cd syft/pkg/cataloger/java/test-fixtures/java-builds && \
make packages.fingerprint
make cache.fingerprint
# for GO BINARY test fixtures
cd syft/pkg/cataloger/golang/test-fixtures/archs && \
@@ -298,22 +299,28 @@ compare-test-rpm-package-install: $(TEMP_DIR) $(SNAPSHOT_DIR)
$(TEMP_DIR)
## Code generation targets #################################
## Code and data generation targets #################################
.PHONY: generate-json-schema
generate-json-schema: ## Generate a new json schema
cd schema/json && go run generate.go
cd syft/internal && go generate . && cd jsonschema && go run .
.PHONY: generate-license-list
generate-license-list: ## Generate an updated spdx license list
go generate ./internal/spdxlicense/...
gofmt -s -w ./internal/spdxlicense
.PHONY: generate-cpe-dictionary-index
generate-cpe-dictionary-index: ## Build the CPE index based off of the latest available CPE dictionary
$(call title,Building CPE index)
go generate ./syft/pkg/cataloger/common/cpe/dictionary
## Build-related targets #################################
.PHONY: build
build: $(SNAPSHOT_DIR) ## Build release snapshot binaries and packages
build:
CGO_ENABLED=0 go build -trimpath -ldflags "$(LDFLAGS)" -o $@ ./cmd/syft
$(SNAPSHOT_DIR): ## Build snapshot release binaries and packages
$(call title,Building snapshot artifacts)
+136 -49
View File
@@ -53,7 +53,7 @@ For commercial support options with Syft or Grype, please [contact Anchore](http
- Red Hat (rpm)
- Ruby (gem)
- Rust (cargo.lock)
- Swift (cocoapods)
- Swift (cocoapods, swift-package-manager)
## Installation
@@ -78,6 +78,12 @@ The chocolatey distribution of syft is community maintained and not distributed
choco install syft -y
```
### Scoop
```powershell
scoop install syft
```
### Homebrew
```bash
brew install syft
@@ -133,6 +139,7 @@ Sources can be explicitly provided with a scheme:
```
docker:yourrepo/yourimage:tag use images from the Docker daemon
podman:yourrepo/yourimage:tag use images from the Podman daemon
containerd:yourrepo/yourimage:tag use images from the Containerd daemon
docker-archive:path/to/yourimage.tar use a tarball from disk for archives created from "docker save"
oci-archive:path/to/yourimage.tar use a tarball from disk for OCI archives (from Skopeo or otherwise)
oci-dir:path/to/yourimage read directly from a path on disk for OCI layout directories (from Skopeo or otherwise)
@@ -150,41 +157,62 @@ This default behavior can be overridden with the `default-image-pull-source` con
### Default Cataloger Configuration by scan type
Syft uses different default sets of catalogers depending on what it is scanning: a container image or a directory on disk. The default catalogers for an image scan assumes that package installation steps have already been completed. For example, Syft will identify Python packages that have egg or wheel metadata files under a site-packages directory, since this indicates software actually installed on an image.
However, if you are scanning a directory, Syft doesn't assume that all relevant software is installed, and will use catalogers that can identify declared dependencies that may not yet be installed on the final system: for example, dependencies listed in a Python requirements.txt.
You can override the list of enabled/disabled catalogers by using the "catalogers" keyword in the [Syft configuration file](https://github.com/anchore/syft#configuration).
##### Image Scanning:
- alpmdb
- rpmdb
- dpkgdb
- apkdb
- portage
- ruby-gemspec
- python-package
- php-composer-installed Cataloger
- javascript-package
- java
- go-module-binary
- binary
- dotnet-deps
- dpkgdb
- go-module-binary
- graalvm-native-image
- java
- javascript-package
- linux-kernel
- nix-store
- php-composer-installed
- portage
- python-package
- rpm-db
- ruby-gemspec
- sbom
##### Directory Scanning:
- alpmdb
- apkdb
- dpkgdb
- portage
- rpmdb
- ruby-gemfile
- python-index
- python-package
- php-composer-lock
- javascript-lock
- java
- java-pom
- go-module-binary
- go-mod-file
- rust-cargo-lock
- dartlang-lock
- dotnet-deps
- binary
- cocoapods
- conan
- hackage
- dartlang-lock
- dotnet-deps
- dpkgdb
- elixir-mix-lock
- erlang-rebar-lock
- go-mod-file
- go-module-binary
- graalvm-native-image
- haskell
- java
- java-gradle-lockfile
- java-pom
- javascript-lock
- linux-kernel
- nix-store
- php-composer-lock
- portage
- python-index
- python-package
- rpm-db
- rpm-file
- ruby-gemfile
- rust-cargo-lock
- sbom
- swift-package-manager
##### Non Default:
- cargo-auditable-binary
@@ -261,6 +289,8 @@ Which would produce output like:
Syft also includes a vast array of utility templating functions from [sprig](http://masterminds.github.io/sprig/) apart from the default Golang [text/template](https://pkg.go.dev/text/template#hdr-Functions) to allow users to customize the output format.
Lastly, Syft has custom templating functions defined in `./syft/format/template/encoder.go` to help parse the passed-in JSON structs.
## Multiple outputs
Syft can also output _multiple_ files in differing formats by appending
@@ -453,6 +483,10 @@ default-image-pull-source: ""
# - "./out/**/*.json"
exclude: []
# allows users to exclude synthetic binary packages from the sbom
# these packages are removed if an overlap with a non-synthetic package is found
exclude-binary-overlap-by-ownership: true
# os and/or architecture to use when referencing container images (e.g. "windows/armv6" or "arm64")
# same as --platform; SYFT_PLATFORM env var
platform: ""
@@ -460,26 +494,40 @@ platform: ""
# set the list of package catalogers to use when generating the SBOM
# default = empty (cataloger set determined automatically by the source type [image or file/directory])
# catalogers:
# - ruby-gemfile
# - ruby-gemspec
# - python-index
# - python-package
# - javascript-lock
# - javascript-package
# - php-composer-installed
# - php-composer-lock
# - alpmdb
# - dpkgdb
# - rpmdb
# - java
# - apkdb
# - go-module-binary
# - go-mod-file
# - dartlang-lock
# - rust
# - dotnet-deps
# rust-audit-binary scans Rust binaries built with https://github.com/Shnatsel/rust-audit
# - rust-audit-binary
# - alpmdb-cataloger
# - apkdb-cataloger
# - binary-cataloger
# - cargo-auditable-binary-cataloger
# - cocoapods-cataloger
# - conan-cataloger
# - dartlang-lock-cataloger
# - dotnet-deps-cataloger
# - dpkgdb-cataloger
# - elixir-mix-lock-cataloger
# - erlang-rebar-lock-cataloger
# - go-mod-file-cataloger
# - go-module-binary-cataloger
# - graalvm-native-image-cataloger
# - haskell-cataloger
# - java-cataloger
# - java-gradle-lockfile-cataloger
# - java-pom-cataloger
# - javascript-lock-cataloger
# - javascript-package-cataloger
# - linux-kernel-cataloger
# - nix-store-cataloger
# - php-composer-installed-cataloger
# - php-composer-lock-cataloger
# - portage-cataloger
# - python-index-cataloger
# - python-package-cataloger
# - rpm-db-cataloger
# - rpm-file-cataloger
# - ruby-gemfile-cataloger
# - ruby-gemspec-cataloger
# - rust-cargo-lock-cataloger
# - sbom-cataloger
# - spm-cataloger
catalogers:
# cataloging packages is exposed through the packages and power-user subcommands
@@ -534,6 +582,13 @@ linux-kernel:
# SYFT_LINUX_KERNEL_CATALOG_MODULES env var
catalog-modules: true
python:
# when running across entries in requirements.txt that do not specify a specific version
# (e.g. "sqlalchemy >= 1.0.0, <= 2.0.0, != 3.0.0, <= 3.0.0"), attempt to guess what the version could
# be based on the version requirements specified (e.g. "1.0.0"). When enabled the lowest expressible version
# when given an arbitrary constraint will be used (even if that version may not be available/published).
guess-unpinned-requirements: false
# cataloging file contents is exposed through the power-user subcommand
file-contents:
cataloger:
@@ -564,7 +619,7 @@ file-metadata:
# SYFT_FILE_METADATA_CATALOGER_SCOPE env var
scope: "squashed"
# the file digest algorithms to use when cataloging files (options: "sha256", "md5", "sha1")
# the file digest algorithms to use when cataloging files (options: "md5", "sha1", "sha224", "sha256", "sha384", "sha512")
# SYFT_FILE_METADATA_DIGESTS env var
digests: ["sha256"]
@@ -602,28 +657,60 @@ secrets:
# SYFT_SECRETS_EXCLUDE_PATTERN_NAMES env var
exclude-pattern-names: []
# options when pulling directly from a registry via the "registry:" scheme
# options that apply to all scan sources
source:
# alias name for the source
# SYFT_SOURCE_NAME env var; --source-name flag
name: ""
# alias version for the source
# SYFT_SOURCE_VERSION env var; --source-version flag
version: ""
# options affecting the file source type
file:
# the file digest algorithms to use on the scanned file (options: "md5", "sha1", "sha224", "sha256", "sha384", "sha512")
digests: ["sha256"]
# options when pulling directly from a registry via the "registry:" or "containerd:" scheme
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
# filepath to a CA certificate (or directory containing *.crt, *.cert, *.pem) used to generate the client certificate
# SYFT_REGISTRY_CA_CERT env var
ca-cert: ""
# credentials for specific registries
auth:
# the URL to the registry (e.g. "docker.io", "localhost:5000", etc.)
# SYFT_REGISTRY_AUTH_AUTHORITY env var
- authority: ""
# SYFT_REGISTRY_AUTH_USERNAME env var
username: ""
# SYFT_REGISTRY_AUTH_PASSWORD env var
password: ""
# note: token and username/password are mutually exclusive
# SYFT_REGISTRY_AUTH_TOKEN env var
token: ""
# - ... # note, more credentials can be provided via config file only
# filepath to the client certificate used for TLS authentication to the registry
# SYFT_REGISTRY_AUTH_TLS_CERT env var
tls-cert: ""
# filepath to the client key used for TLS authentication to the registry
# SYFT_REGISTRY_AUTH_TLS_KEY env var
tls-key: ""
# - ... # note, more credentials can be provided via config file only (not env vars)
# generate an attested SBOM
attest:
-65
View File
@@ -1,65 +0,0 @@
package cli
import (
"fmt"
"log"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/anchore/syft/cmd/syft/cli/attest"
"github.com/anchore/syft/cmd/syft/cli/options"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/config"
)
const (
attestExample = ` {{.appName}} {{.command}} --output [FORMAT] alpine:latest defaults to using images from a Docker daemon. If Docker is not present, the image is pulled directly from the registry
`
attestSchemeHelp = "\n" + indent + schemeHelpHeader + "\n" + imageSchemeHelp
attestHelp = attestExample + attestSchemeHelp
)
func Attest(v *viper.Viper, app *config.Application, ro *options.RootOptions, po *options.PackagesOptions, ao *options.AttestOptions) *cobra.Command {
cmd := &cobra.Command{
Use: "attest --output [FORMAT] <IMAGE>",
Short: "Generate an SBOM as an attestation for the given [SOURCE] container image",
Long: "Generate a packaged-based Software Bill Of Materials (SBOM) from a container image as the predicate of an in-toto attestation that will be uploaded to the image registry",
Example: internal.Tprintf(attestHelp, map[string]interface{}{
"appName": internal.ApplicationName,
"command": "attest",
}),
Args: func(cmd *cobra.Command, args []string) error {
if err := app.LoadAllValues(v, ro.Config); err != nil {
return fmt.Errorf("unable to load configuration: %w", err)
}
newLogWrapper(app)
logApplicationConfig(app)
return validateArgs(cmd, args)
},
SilenceUsage: true,
SilenceErrors: true,
RunE: func(cmd *cobra.Command, args []string) error {
if app.CheckForAppUpdate {
checkForApplicationUpdate()
}
return attest.Run(cmd.Context(), app, args)
},
}
// syft attest is an enhancement of the packages command, so it should have the same flags
err := po.AddFlags(cmd, v)
if err != nil {
log.Fatal(err)
}
// syft attest has its own options not included as part of the packages command
err = ao.AddFlags(cmd, v)
if err != nil {
log.Fatal(err)
}
return cmd
}
-244
View File
@@ -1,244 +0,0 @@
package attest
import (
"context"
"fmt"
"os"
"os/exec"
"strings"
"github.com/wagoodman/go-partybus"
"github.com/wagoodman/go-progress"
"golang.org/x/exp/slices"
"github.com/anchore/stereoscope"
"github.com/anchore/syft/cmd/syft/cli/eventloop"
"github.com/anchore/syft/cmd/syft/cli/options"
"github.com/anchore/syft/cmd/syft/cli/packages"
"github.com/anchore/syft/internal/bus"
"github.com/anchore/syft/internal/config"
"github.com/anchore/syft/internal/ui"
"github.com/anchore/syft/syft"
"github.com/anchore/syft/syft/event"
"github.com/anchore/syft/syft/event/monitor"
"github.com/anchore/syft/syft/formats/syftjson"
"github.com/anchore/syft/syft/formats/table"
"github.com/anchore/syft/syft/sbom"
"github.com/anchore/syft/syft/source"
)
func Run(_ context.Context, app *config.Application, args []string) error {
err := ValidateOutputOptions(app)
if err != nil {
return err
}
writer, err := options.MakeWriter(app.Outputs, app.File, app.OutputTemplatePath)
if err != nil {
return fmt.Errorf("unable to write to report destination: %w", err)
}
defer func() {
if err := writer.Close(); err != nil {
fmt.Printf("unable to close report destination: %+v", err)
}
}()
// could be an image or a directory, with or without a scheme
// TODO: validate that source is image
userInput := args[0]
si, err := source.ParseInputWithName(userInput, app.Platform, app.Name, app.DefaultImagePullSource)
if err != nil {
return fmt.Errorf("could not generate source input for packages command: %w", err)
}
if si.Scheme != source.ImageScheme {
return fmt.Errorf("attestations are only supported for oci images at this time")
}
eventBus := partybus.NewBus()
stereoscope.SetBus(eventBus)
syft.SetBus(eventBus)
subscription := eventBus.Subscribe()
return eventloop.EventLoop(
execWorker(app, *si, writer),
eventloop.SetupSignals(),
subscription,
stereoscope.Cleanup,
ui.Select(options.IsVerbose(app), app.Quiet)...,
)
}
func buildSBOM(app *config.Application, si source.Input, writer sbom.Writer, errs chan error) ([]byte, error) {
src, cleanup, err := source.New(si, app.Registry.ToOptions(), app.Exclusions)
if cleanup != nil {
defer cleanup()
}
if err != nil {
return nil, fmt.Errorf("failed to construct source from user input %q: %w", si.UserInput, err)
}
s, err := packages.GenerateSBOM(src, errs, app)
if err != nil {
return nil, err
}
if s == nil {
return nil, fmt.Errorf("no SBOM produced for %q", si.UserInput)
}
// note: only works for single format no multi writer support
sBytes, err := writer.Bytes(*s)
if err != nil {
return nil, fmt.Errorf("unable to build SBOM bytes: %w", err)
}
return sBytes, nil
}
//nolint:funlen
func execWorker(app *config.Application, si source.Input, writer sbom.Writer) <-chan error {
errs := make(chan error)
go func() {
defer close(errs)
sBytes, err := buildSBOM(app, si, writer, errs)
if err != nil {
errs <- fmt.Errorf("unable to build SBOM: %w", err)
return
}
// TODO: add multi writer support
for _, o := range app.Outputs {
f, err := os.CreateTemp("", o)
if err != nil {
errs <- fmt.Errorf("unable to create temp file: %w", err)
return
}
defer f.Close()
defer os.Remove(f.Name())
if _, err := f.Write(sBytes); err != nil {
errs <- fmt.Errorf("unable to write SBOM to temp file: %w", err)
return
}
// TODO: what other validation here besides binary name?
cmd := "cosign"
if !commandExists(cmd) {
errs <- fmt.Errorf("unable to find cosign in PATH; make sure you have it installed")
return
}
// Select Cosign predicate type based on defined output type
// As orientation, check: https://github.com/sigstore/cosign/blob/main/pkg/cosign/attestation/attestation.go
var predicateType string
switch strings.ToLower(o) {
case "cyclonedx-json":
predicateType = "cyclonedx"
case "spdx-tag-value":
predicateType = "spdx"
case "spdx-json":
predicateType = "spdxjson"
default:
predicateType = "custom"
}
args := []string{"attest", si.UserInput, "--predicate", f.Name(), "--type", predicateType}
if app.Attest.Key != "" {
args = append(args, "--key", app.Attest.Key)
}
execCmd := exec.Command(cmd, args...)
execCmd.Env = os.Environ()
if app.Attest.Key != "" {
execCmd.Env = append(execCmd.Env, fmt.Sprintf("COSIGN_PASSWORD=%s", app.Attest.Password))
} else {
// no key provided, use cosign's keyless mode
execCmd.Env = append(execCmd.Env, "COSIGN_EXPERIMENTAL=1")
}
// bus adapter for ui to hook into stdout via an os pipe
r, w, err := os.Pipe()
if err != nil {
errs <- fmt.Errorf("unable to create os pipe: %w", err)
return
}
defer w.Close()
b := &busWriter{r: r, w: w, mon: progress.NewManual(-1)}
execCmd.Stdout = b
execCmd.Stderr = b
defer b.mon.SetCompleted()
// attest the SBOM
err = execCmd.Run()
if err != nil {
b.mon.SetError(err)
errs <- fmt.Errorf("unable to attest SBOM: %w", err)
return
}
}
bus.Publish(partybus.Event{
Type: event.Exit,
Value: func() error { return nil },
})
}()
return errs
}
func ValidateOutputOptions(app *config.Application) error {
err := packages.ValidateOutputOptions(app)
if err != nil {
return err
}
if len(app.Outputs) > 1 {
return fmt.Errorf("multiple SBOM format is not supported for attest at this time")
}
// cannot use table as default output format when using template output
if slices.Contains(app.Outputs, table.ID.String()) {
app.Outputs = []string{syftjson.ID.String()}
}
return nil
}
type busWriter struct {
w *os.File
r *os.File
hasWritten bool
mon *progress.Manual
}
func (b *busWriter) Write(p []byte) (n int, err error) {
if !b.hasWritten {
b.hasWritten = true
bus.Publish(
partybus.Event{
Type: event.AttestationStarted,
Source: monitor.GenericTask{
Title: monitor.Title{
Default: "Create attestation",
WhileRunning: "Creating attestation",
OnSuccess: "Created attestation",
},
Context: "cosign",
},
Value: &monitor.ShellProgress{
Reader: b.r,
Manual: b.mon,
},
},
)
}
return b.w.Write(p)
}
func commandExists(cmd string) bool {
_, err := exec.LookPath(cmd)
return err == nil
}
+96
View File
@@ -0,0 +1,96 @@
package cli
import (
"os"
cranecmd "github.com/google/go-containerregistry/cmd/crane/cmd"
"github.com/spf13/cobra"
"github.com/anchore/clio"
"github.com/anchore/stereoscope"
"github.com/anchore/syft/cmd/syft/cli/commands"
handler "github.com/anchore/syft/cmd/syft/cli/ui"
"github.com/anchore/syft/cmd/syft/internal/ui"
"github.com/anchore/syft/internal/bus"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/redact"
)
// Application constructs the `syft packages` command, aliases the root command to `syft packages`,
// and constructs the `syft power-user` command. It is also responsible for
// organizing flag usage and injecting the application config for each command.
// It also constructs the syft attest command and the syft version command.
// `RunE` is the earliest that the complete application configuration can be loaded.
func Application(id clio.Identification) clio.Application {
app, _ := create(id)
return app
}
// Command returns the root command for the syft CLI application. This is useful for embedding the entire syft CLI
// into an existing application.
func Command(id clio.Identification) *cobra.Command {
_, cmd := create(id)
return cmd
}
func create(id clio.Identification) (clio.Application, *cobra.Command) {
clioCfg := clio.NewSetupConfig(id).
WithGlobalConfigFlag(). // add persistent -c <path> for reading an application config from
WithGlobalLoggingFlags(). // add persistent -v and -q flags tied to the logging config
WithConfigInRootHelp(). // --help on the root command renders the full application config in the help text
WithUIConstructor(
// select a UI based on the logging configuration and state of stdin (if stdin is a tty)
func(cfg clio.Config) ([]clio.UI, error) {
noUI := ui.None(cfg.Log.Quiet)
if !cfg.Log.AllowUI(os.Stdin) || cfg.Log.Quiet {
return []clio.UI{noUI}, nil
}
return []clio.UI{
ui.New(cfg.Log.Quiet,
handler.New(handler.DefaultHandlerConfig()),
),
noUI,
}, nil
},
).
WithInitializers(
func(state *clio.State) error {
// clio is setting up and providing the bus, redact store, and logger to the application. Once loaded,
// we can hoist them into the internal packages for global use.
stereoscope.SetBus(state.Bus)
bus.Set(state.Bus)
redact.Set(state.RedactStore)
log.Set(state.Logger)
stereoscope.SetLogger(state.Logger)
return nil
},
).
WithPostRuns(func(state *clio.State, err error) {
stereoscope.Cleanup()
})
app := clio.New(*clioCfg)
// since root is aliased as the packages cmd we need to construct this command first
// we also need the command to have information about the `root` options because of this alias
packagesCmd := commands.Packages(app)
// rootCmd is currently an alias for the packages command
rootCmd := commands.Root(app, packagesCmd)
// add sub-commands
rootCmd.AddCommand(
packagesCmd,
commands.PowerUser(app),
commands.Attest(app),
commands.Convert(app),
clio.VersionCommand(id),
cranecmd.NewCmdAuthLogin(id.Name), // syft login uses the same command as crane
)
return app, rootCmd
}
-167
View File
@@ -1,167 +0,0 @@
package cli
import (
"fmt"
"strings"
cranecmd "github.com/google/go-containerregistry/cmd/crane/cmd"
"github.com/gookit/color"
logrusUpstream "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/wagoodman/go-partybus"
"github.com/anchore/go-logger/adapter/logrus"
"github.com/anchore/stereoscope"
"github.com/anchore/syft/cmd/syft/cli/options"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/bus"
"github.com/anchore/syft/internal/config"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/version"
"github.com/anchore/syft/syft"
"github.com/anchore/syft/syft/event"
)
const indent = " "
// New constructs the `syft packages` command, aliases the root command to `syft packages`,
// and constructs the `syft power-user` command. It is also responsible for
// organizing flag usage and injecting the application config for each command.
// It also constructs the syft attest command and the syft version command.
// Because of how the `cobra` library behaves, the application's configuration is initialized
// at this level. Values from the config should only be used after `app.LoadAllValues` has been called.
// Cobra does not have knowledge of the user provided flags until the `RunE` block of each command.
// `RunE` is the earliest that the complete application configuration can be loaded.
func New() (*cobra.Command, error) {
app := &config.Application{}
// allow for nested options to be specified via environment variables
// e.g. pod.context = APPNAME_POD_CONTEXT
v := viper.NewWithOptions(viper.EnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_")))
// since root is aliased as the packages cmd we need to construct this command first
// we also need the command to have information about the `root` options because of this alias
ro := &options.RootOptions{}
po := &options.PackagesOptions{}
ao := &options.AttestOptions{}
packagesCmd := Packages(v, app, ro, po)
// root options are also passed to the attestCmd so that a user provided config location can be discovered
poweruserCmd := PowerUser(v, app, ro)
convertCmd := Convert(v, app, ro, po)
attestCmd := Attest(v, app, ro, po, ao)
// rootCmd is currently an alias for the packages command
rootCmd := &cobra.Command{
Use: fmt.Sprintf("%s [SOURCE]", internal.ApplicationName),
Short: packagesCmd.Short,
Long: packagesCmd.Long,
Args: packagesCmd.Args,
Example: packagesCmd.Example,
SilenceUsage: true,
SilenceErrors: true,
RunE: packagesCmd.RunE,
Version: version.FromBuild().Version,
}
rootCmd.SetVersionTemplate(fmt.Sprintf("%s {{.Version}}\n", internal.ApplicationName))
// start adding flags to all the commands
err := ro.AddFlags(rootCmd, v)
if err != nil {
return nil, err
}
// package flags need to be decorated onto the rootCmd so that rootCmd can function as a packages alias
err = po.AddFlags(rootCmd, v)
if err != nil {
return nil, err
}
// poweruser also uses the packagesCmd flags since it is a specialized version of the command
err = po.AddFlags(poweruserCmd, v)
if err != nil {
return nil, err
}
// commands to add to root
cmds := []*cobra.Command{
packagesCmd,
poweruserCmd,
convertCmd,
attestCmd,
Version(v, app),
cranecmd.NewCmdAuthLogin("syft"), // syft login uses the same command as crane
}
// Add sub-commands.
for _, cmd := range cmds {
rootCmd.AddCommand(cmd)
}
return rootCmd, err
}
func validateArgs(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 checkForApplicationUpdate() {
log.Debugf("checking if a new version of %s is available", internal.ApplicationName)
isAvailable, newVersion, err := version.IsUpdateAvailable()
if err != nil {
// this should never stop the application
log.Errorf(err.Error())
}
if isAvailable {
log.Infof("new version of %s is available: %s (current version is %s)", internal.ApplicationName, newVersion, version.FromBuild().Version)
bus.Publish(partybus.Event{
Type: event.AppUpdateAvailable,
Value: newVersion,
})
} else {
log.Debugf("no new %s update available", internal.ApplicationName)
}
}
func logApplicationConfig(app *config.Application) {
versionInfo := version.FromBuild()
log.Infof("%s version: %+v", internal.ApplicationName, versionInfo.Version)
log.Debugf("application config:\n%+v", color.Magenta.Sprint(app.String()))
}
func newLogWrapper(app *config.Application) {
cfg := logrus.Config{
EnableConsole: (app.Log.FileLocation == "" || app.Verbosity > 0) && !app.Quiet,
FileLocation: app.Log.FileLocation,
Level: app.Log.Level,
}
if app.Log.Structured {
cfg.Formatter = &logrusUpstream.JSONFormatter{
TimestampFormat: "2006-01-02 15:04:05",
DisableTimestamp: false,
DisableHTMLEscape: false,
PrettyPrint: false,
}
}
logWrapper, err := logrus.New(cfg)
if err != nil {
// this is kinda circular, but we can't return an error... ¯\_(ツ)_/¯
// I'm going to leave this here in case we one day have a different default logger other than the "discard" logger
log.Error("unable to initialize logger: %+v", err)
return
}
syft.SetLogger(logWrapper)
stereoscope.SetLogger(logWrapper.Nested("from-lib", "stereoscope"))
}
+258
View File
@@ -0,0 +1,258 @@
package commands
import (
"fmt"
"os"
"os/exec"
"strings"
"github.com/spf13/cobra"
"github.com/wagoodman/go-partybus"
"github.com/wagoodman/go-progress"
"github.com/anchore/clio"
"github.com/anchore/stereoscope/pkg/image"
"github.com/anchore/syft/cmd/syft/cli/options"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/bus"
"github.com/anchore/syft/internal/file"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/event"
"github.com/anchore/syft/syft/event/monitor"
"github.com/anchore/syft/syft/formats"
"github.com/anchore/syft/syft/formats/github"
"github.com/anchore/syft/syft/formats/syftjson"
"github.com/anchore/syft/syft/formats/table"
"github.com/anchore/syft/syft/formats/template"
"github.com/anchore/syft/syft/formats/text"
"github.com/anchore/syft/syft/sbom"
"github.com/anchore/syft/syft/source"
)
const (
attestExample = ` {{.appName}} {{.command}} --output [FORMAT] alpine:latest defaults to using images from a Docker daemon. If Docker is not present, the image is pulled directly from the registry
`
attestSchemeHelp = "\n " + schemeHelpHeader + "\n" + imageSchemeHelp
attestHelp = attestExample + attestSchemeHelp
)
type attestOptions struct {
options.Config `yaml:",inline" mapstructure:",squash"`
options.SingleOutput `yaml:",inline" mapstructure:",squash"`
options.UpdateCheck `yaml:",inline" mapstructure:",squash"`
options.Catalog `yaml:",inline" mapstructure:",squash"`
options.Attest `yaml:",inline" mapstructure:",squash"`
}
func Attest(app clio.Application) *cobra.Command {
id := app.ID()
var allowableOutputs []string
for _, f := range formats.AllIDs() {
switch f {
case table.ID, text.ID, github.ID, template.ID:
continue
}
allowableOutputs = append(allowableOutputs, f.String())
}
opts := &attestOptions{
UpdateCheck: options.DefaultUpdateCheck(),
SingleOutput: options.SingleOutput{
AllowableOptions: allowableOutputs,
Output: syftjson.ID.String(),
},
Catalog: options.DefaultCatalog(),
}
return app.SetupCommand(&cobra.Command{
Use: "attest --output [FORMAT] <IMAGE>",
Short: "Generate an SBOM as an attestation for the given [SOURCE] container image",
Long: "Generate a packaged-based Software Bill Of Materials (SBOM) from a container image as the predicate of an in-toto attestation that will be uploaded to the image registry",
Example: internal.Tprintf(attestHelp, map[string]interface{}{
"appName": id.Name,
"command": "attest",
}),
Args: validatePackagesArgs,
PreRunE: applicationUpdateCheck(id, &opts.UpdateCheck),
RunE: func(cmd *cobra.Command, args []string) error {
return runAttest(id, opts, args[0])
},
}, opts)
}
//nolint:funlen
func runAttest(id clio.Identification, opts *attestOptions, userInput string) error {
_, err := exec.LookPath("cosign")
if err != nil {
// when cosign is not installed the error will be rendered like so:
// 2023/06/30 08:31:52 error during command execution: 'syft attest' requires cosign to be installed: exec: "cosign": executable file not found in $PATH
return fmt.Errorf("'syft attest' requires cosign to be installed: %w", err)
}
s, err := buildSBOM(id, &opts.Catalog, userInput)
if err != nil {
return fmt.Errorf("unable to build SBOM: %w", err)
}
o := opts.Output
f, err := os.CreateTemp("", o)
if err != nil {
return fmt.Errorf("unable to create temp file: %w", err)
}
defer os.Remove(f.Name())
writer, err := opts.SBOMWriter(f.Name())
if err != nil {
return fmt.Errorf("unable to create SBOM writer: %w", err)
}
if err := writer.Write(*s); err != nil {
return fmt.Errorf("unable to write SBOM to temp file: %w", err)
}
// TODO: what other validation here besides binary name?
cmd := "cosign"
if !commandExists(cmd) {
return fmt.Errorf("unable to find cosign in PATH; make sure you have it installed")
}
// Select Cosign predicate type based on defined output type
// As orientation, check: https://github.com/sigstore/cosign/blob/main/pkg/cosign/attestation/attestation.go
var predicateType string
switch strings.ToLower(o) {
case "cyclonedx-json":
predicateType = "cyclonedx"
case "spdx-tag-value", "spdx-tv":
predicateType = "spdx"
case "spdx-json", "json":
predicateType = "spdxjson"
default:
predicateType = "custom"
}
args := []string{"attest", userInput, "--predicate", f.Name(), "--type", predicateType}
if opts.Attest.Key != "" {
args = append(args, "--key", opts.Attest.Key.String())
}
execCmd := exec.Command(cmd, args...)
execCmd.Env = os.Environ()
if opts.Attest.Key != "" {
execCmd.Env = append(execCmd.Env, fmt.Sprintf("COSIGN_PASSWORD=%s", opts.Attest.Password))
} else {
// no key provided, use cosign's keyless mode
execCmd.Env = append(execCmd.Env, "COSIGN_EXPERIMENTAL=1")
}
log.WithFields("cmd", strings.Join(execCmd.Args, " ")).Trace("creating attestation")
// bus adapter for ui to hook into stdout via an os pipe
r, w, err := os.Pipe()
if err != nil {
return fmt.Errorf("unable to create os pipe: %w", err)
}
defer w.Close()
mon := progress.NewManual(-1)
bus.Publish(
partybus.Event{
Type: event.AttestationStarted,
Source: monitor.GenericTask{
Title: monitor.Title{
Default: "Create attestation",
WhileRunning: "Creating attestation",
OnSuccess: "Created attestation",
},
Context: "cosign",
},
Value: &monitor.ShellProgress{
Reader: r,
Progressable: mon,
},
},
)
execCmd.Stdout = w
execCmd.Stderr = w
// attest the SBOM
err = execCmd.Run()
if err != nil {
mon.SetError(err)
return fmt.Errorf("unable to attest SBOM: %w", err)
}
mon.SetCompleted()
return nil
}
func buildSBOM(id clio.Identification, opts *options.Catalog, userInput string) (*sbom.SBOM, error) {
cfg := source.DetectConfig{
DefaultImageSource: opts.DefaultImagePullSource,
}
detection, err := source.Detect(userInput, cfg)
if err != nil {
return nil, fmt.Errorf("could not deteremine source: %w", err)
}
if detection.IsContainerImage() {
return nil, fmt.Errorf("attestations are only supported for oci images at this time")
}
var platform *image.Platform
if opts.Platform != "" {
platform, err = image.NewPlatform(opts.Platform)
if err != nil {
return nil, fmt.Errorf("invalid platform: %w", err)
}
}
hashers, err := file.Hashers(opts.Source.File.Digests...)
if err != nil {
return nil, fmt.Errorf("invalid hash: %w", err)
}
src, err := detection.NewSource(
source.DetectionSourceConfig{
Alias: source.Alias{
Name: opts.Source.Name,
Version: opts.Source.Version,
},
RegistryOptions: opts.Registry.ToOptions(),
Platform: platform,
Exclude: source.ExcludeConfig{
Paths: opts.Exclusions,
},
DigestAlgorithms: hashers,
BasePath: opts.BasePath,
},
)
if src != nil {
defer src.Close()
}
if err != nil {
return nil, fmt.Errorf("failed to construct source from user input %q: %w", userInput, err)
}
s, err := generateSBOM(id, src, opts)
if err != nil {
return nil, err
}
if s == nil {
return nil, fmt.Errorf("no SBOM produced for %q", userInput)
}
return s, nil
}
func commandExists(cmd string) bool {
_, err := exec.LookPath(cmd)
return err == nil
}
+95
View File
@@ -0,0 +1,95 @@
package commands
import (
"fmt"
"io"
"os"
"github.com/spf13/cobra"
"github.com/anchore/clio"
"github.com/anchore/syft/cmd/syft/cli/options"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/formats"
)
const (
convertExample = ` {{.appName}} {{.command}} img.syft.json -o spdx-json convert a syft SBOM to spdx-json, output goes to stdout
{{.appName}} {{.command}} img.syft.json -o cyclonedx-json=img.cdx.json convert a syft SBOM to CycloneDX, output is written to the file "img.cdx.json""
{{.appName}} {{.command}} - -o spdx-json convert an SBOM from STDIN to spdx-json
`
)
type ConvertOptions struct {
options.Config `yaml:",inline" mapstructure:",squash"`
options.MultiOutput `yaml:",inline" mapstructure:",squash"`
options.UpdateCheck `yaml:",inline" mapstructure:",squash"`
}
//nolint:dupl
func Convert(app clio.Application) *cobra.Command {
id := app.ID()
opts := &ConvertOptions{
UpdateCheck: options.DefaultUpdateCheck(),
}
return app.SetupCommand(&cobra.Command{
Use: "convert [SOURCE-SBOM] -o [FORMAT]",
Short: "Convert between SBOM formats",
Long: "[Experimental] Convert SBOM files to, and from, SPDX, CycloneDX and Syft's format. For more info about data loss between formats see https://github.com/anchore/syft#format-conversion-experimental",
Example: internal.Tprintf(convertExample, map[string]interface{}{
"appName": id.Name,
"command": "convert",
}),
Args: validateConvertArgs,
PreRunE: applicationUpdateCheck(id, &opts.UpdateCheck),
RunE: func(cmd *cobra.Command, args []string) error {
return RunConvert(opts, args[0])
},
}, opts)
}
func validateConvertArgs(cmd *cobra.Command, args []string) error {
return validateArgs(cmd, args, "an SBOM argument is required")
}
func RunConvert(opts *ConvertOptions, userInput string) error {
log.Warn("convert is an experimental feature, run `syft convert -h` for help")
writer, err := opts.SBOMWriter()
if err != nil {
return err
}
var reader io.ReadCloser
if userInput == "-" {
reader = os.Stdin
} else {
f, err := os.Open(userInput)
if err != nil {
return fmt.Errorf("failed to open SBOM file: %w", err)
}
defer func() {
_ = f.Close()
}()
reader = f
}
s, _, err := formats.Decode(reader)
if err != nil {
return fmt.Errorf("failed to decode SBOM: %w", err)
}
if s == nil {
return fmt.Errorf("no SBOM produced")
}
if err := writer.Write(*s); err != nil {
return fmt.Errorf("failed to write SBOM: %w", err)
}
return nil
}
+253
View File
@@ -0,0 +1,253 @@
package commands
import (
"fmt"
"github.com/hashicorp/go-multierror"
"github.com/spf13/cobra"
"github.com/anchore/clio"
"github.com/anchore/stereoscope/pkg/image"
"github.com/anchore/syft/cmd/syft/cli/eventloop"
"github.com/anchore/syft/cmd/syft/cli/options"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/file"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/formats/template"
"github.com/anchore/syft/syft/sbom"
"github.com/anchore/syft/syft/source"
)
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 formatted SBOM
{{.appName}} {{.command}} alpine:latest -o cyclonedx-json show a CycloneDX JSON formatted SBOM
{{.appName}} {{.command}} alpine:latest -o spdx show a SPDX 2.3 Tag-Value formatted SBOM
{{.appName}} {{.command}} alpine:latest -o spdx@2.2 show a SPDX 2.2 Tag-Value formatted SBOM
{{.appName}} {{.command}} alpine:latest -o spdx-json show a SPDX 2.3 JSON formatted SBOM
{{.appName}} {{.command}} alpine:latest -o spdx-json@2.2 show a SPDX 2.2 JSON formatted SBOM
{{.appName}} {{.command}} alpine:latest -vv show verbose debug information
{{.appName}} {{.command}} alpine:latest -o template -t my_format.tmpl show a SBOM formatted according to given template file
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, SIF container, or generic filesystem directory
`
schemeHelpHeader = "You can also explicitly specify the scheme to use:"
imageSchemeHelp = ` {{.appName}} {{.command}} docker:yourrepo/yourimage:tag explicitly use the Docker daemon
{{.appName}} {{.command}} podman:yourrepo/yourimage:tag explicitly use the Podman daemon
{{.appName}} {{.command}} registry:yourrepo/yourimage:tag pull image directly from a registry (no container runtime required)
{{.appName}} {{.command}} docker-archive:path/to/yourimage.tar use a tarball from disk for archives created from "docker save"
{{.appName}} {{.command}} oci-archive:path/to/yourimage.tar use a tarball from disk for OCI archives (from Skopeo or otherwise)
{{.appName}} {{.command}} oci-dir:path/to/yourimage read directly from a path on disk for OCI layout directories (from Skopeo or otherwise)
{{.appName}} {{.command}} singularity:path/to/yourimage.sif read directly from a Singularity Image Format (SIF) container on disk
`
nonImageSchemeHelp = ` {{.appName}} {{.command}} dir:path/to/yourproject read directly from a path on disk (any directory)
{{.appName}} {{.command}} file:path/to/yourproject/file read directly from a path on disk (any single file)
`
packagesSchemeHelp = "\n " + schemeHelpHeader + "\n" + imageSchemeHelp + nonImageSchemeHelp
packagesHelp = packagesExample + packagesSchemeHelp
)
type packagesOptions struct {
options.Config `yaml:",inline" mapstructure:",squash"`
options.MultiOutput `yaml:",inline" mapstructure:",squash"`
options.UpdateCheck `yaml:",inline" mapstructure:",squash"`
options.Catalog `yaml:",inline" mapstructure:",squash"`
}
func defaultPackagesOptions() *packagesOptions {
return &packagesOptions{
MultiOutput: options.DefaultOutput(),
UpdateCheck: options.DefaultUpdateCheck(),
Catalog: options.DefaultCatalog(),
}
}
//nolint:dupl
func Packages(app clio.Application) *cobra.Command {
id := app.ID()
opts := defaultPackagesOptions()
return app.SetupCommand(&cobra.Command{
Use: "packages [SOURCE]",
Short: "Generate a package SBOM",
Long: "Generate a packaged-based Software Bill Of Materials (SBOM) from container images and filesystems",
Example: internal.Tprintf(packagesHelp, map[string]interface{}{
"appName": id.Name,
"command": "packages",
}),
Args: validatePackagesArgs,
PreRunE: applicationUpdateCheck(id, &opts.UpdateCheck),
RunE: func(cmd *cobra.Command, args []string) error {
return runPackages(id, opts, args[0])
},
}, opts)
}
func validatePackagesArgs(cmd *cobra.Command, args []string) error {
return validateArgs(cmd, args, "an image/directory argument is required")
}
func validateArgs(cmd *cobra.Command, args []string, error 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(error)
}
return cobra.MaximumNArgs(1)(cmd, args)
}
// nolint:funlen
func runPackages(id clio.Identification, opts *packagesOptions, userInput string) error {
err := validatePackageOutputOptions(&opts.MultiOutput)
if err != nil {
return err
}
writer, err := opts.SBOMWriter()
if err != nil {
return err
}
detection, err := source.Detect(
userInput,
source.DetectConfig{
DefaultImageSource: opts.DefaultImagePullSource,
},
)
if err != nil {
return fmt.Errorf("could not deteremine source: %w", err)
}
var platform *image.Platform
if opts.Platform != "" {
platform, err = image.NewPlatform(opts.Platform)
if err != nil {
return fmt.Errorf("invalid platform: %w", err)
}
}
hashers, err := file.Hashers(opts.Source.File.Digests...)
if err != nil {
return fmt.Errorf("invalid hash: %w", err)
}
src, err := detection.NewSource(
source.DetectionSourceConfig{
Alias: source.Alias{
Name: opts.Source.Name,
Version: opts.Source.Version,
},
RegistryOptions: opts.Registry.ToOptions(),
Platform: platform,
Exclude: source.ExcludeConfig{
Paths: opts.Exclusions,
},
DigestAlgorithms: hashers,
BasePath: opts.BasePath,
},
)
if err != nil {
return fmt.Errorf("failed to construct source from user input %q: %w", userInput, err)
}
defer func() {
if src != nil {
if err := src.Close(); err != nil {
log.Tracef("unable to close source: %+v", err)
}
}
}()
s, err := generateSBOM(id, src, &opts.Catalog)
if err != nil {
return err
}
if s == nil {
return fmt.Errorf("no SBOM produced for %q", userInput)
}
if err := writer.Write(*s); err != nil {
return fmt.Errorf("failed to write SBOM: %w", err)
}
return nil
}
func generateSBOM(id clio.Identification, src source.Source, opts *options.Catalog) (*sbom.SBOM, error) {
tasks, err := eventloop.Tasks(opts)
if err != nil {
return nil, err
}
s := sbom.SBOM{
Source: src.Describe(),
Descriptor: sbom.Descriptor{
Name: id.Name,
Version: id.Version,
Configuration: opts,
},
}
err = buildRelationships(&s, src, tasks)
return &s, err
}
func buildRelationships(s *sbom.SBOM, src source.Source, tasks []eventloop.Task) error {
var errs error
var relationships []<-chan artifact.Relationship
for _, task := range tasks {
c := make(chan artifact.Relationship)
relationships = append(relationships, c)
go func(task eventloop.Task) {
err := eventloop.RunTask(task, &s.Artifacts, src, c)
if err != nil {
errs = multierror.Append(errs, err)
}
}(task)
}
s.Relationships = append(s.Relationships, mergeRelationships(relationships...)...)
return errs
}
func mergeRelationships(cs ...<-chan artifact.Relationship) (relationships []artifact.Relationship) {
for _, c := range cs {
for n := range c {
relationships = append(relationships, n)
}
}
return relationships
}
func validatePackageOutputOptions(cfg *options.MultiOutput) error {
var usesTemplateOutput bool
for _, o := range cfg.Outputs {
if o == template.ID.String() {
usesTemplateOutput = true
break
}
}
if usesTemplateOutput && cfg.OutputTemplatePath == "" {
return fmt.Errorf(`must specify path to template file when using "template" output format`)
}
return nil
}
+154
View File
@@ -0,0 +1,154 @@
package commands
import (
"fmt"
"os"
"github.com/gookit/color"
"github.com/hashicorp/go-multierror"
"github.com/spf13/cobra"
"github.com/anchore/clio"
"github.com/anchore/stereoscope/pkg/image"
"github.com/anchore/syft/cmd/syft/cli/eventloop"
"github.com/anchore/syft/cmd/syft/cli/options"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/formats/syftjson"
"github.com/anchore/syft/syft/sbom"
"github.com/anchore/syft/syft/source"
)
const powerUserExample = ` {{.appName}} {{.command}} <image>
DEPRECATED - THIS COMMAND WILL BE REMOVED in v1.0.0
Template outputs are not supported.
All behavior is controlled via application configuration and environment variables (see https://github.com/anchore/syft#configuration)
`
type powerUserOptions struct {
options.Config `yaml:",inline" mapstructure:",squash"`
options.OutputFile `yaml:",inline" mapstructure:",squash"`
options.UpdateCheck `yaml:",inline" mapstructure:",squash"`
options.Catalog `yaml:",inline" mapstructure:",squash"`
}
func PowerUser(app clio.Application) *cobra.Command {
id := app.ID()
pkgs := options.DefaultCatalog()
pkgs.Secrets.Cataloger.Enabled = true
pkgs.FileMetadata.Cataloger.Enabled = true
pkgs.FileContents.Cataloger.Enabled = true
pkgs.FileClassification.Cataloger.Enabled = true
opts := &powerUserOptions{
Catalog: pkgs,
}
return app.SetupCommand(&cobra.Command{
Use: "power-user [IMAGE]",
Short: "Run bulk operations on container images",
Example: internal.Tprintf(powerUserExample, map[string]interface{}{
"appName": id.Name,
"command": "power-user",
}),
Args: validatePackagesArgs,
Hidden: true,
PreRunE: applicationUpdateCheck(id, &opts.UpdateCheck),
RunE: func(cmd *cobra.Command, args []string) error {
return runPowerUser(id, opts, args[0])
},
}, opts)
}
//nolint:funlen
func runPowerUser(id clio.Identification, opts *powerUserOptions, userInput string) error {
writer, err := opts.SBOMWriter(syftjson.Format())
if err != nil {
return err
}
defer func() {
// inform user at end of run that command will be removed
deprecated := color.Style{color.Red, color.OpBold}.Sprint("DEPRECATED: This command will be removed in v1.0.0")
fmt.Fprintln(os.Stderr, deprecated)
}()
tasks, err := eventloop.Tasks(&opts.Catalog)
if err != nil {
return err
}
detection, err := source.Detect(
userInput,
source.DetectConfig{
DefaultImageSource: opts.DefaultImagePullSource,
},
)
if err != nil {
return fmt.Errorf("could not deteremine source: %w", err)
}
var platform *image.Platform
if opts.Platform != "" {
platform, err = image.NewPlatform(opts.Platform)
if err != nil {
return fmt.Errorf("invalid platform: %w", err)
}
}
src, err := detection.NewSource(
source.DetectionSourceConfig{
Alias: source.Alias{
Name: opts.Source.Name,
Version: opts.Source.Version,
},
RegistryOptions: opts.Registry.ToOptions(),
Platform: platform,
Exclude: source.ExcludeConfig{
Paths: opts.Exclusions,
},
DigestAlgorithms: nil,
BasePath: opts.BasePath,
},
)
if src != nil {
defer src.Close()
}
if err != nil {
return fmt.Errorf("failed to construct source from user input %q: %w", userInput, err)
}
s := sbom.SBOM{
Source: src.Describe(),
Descriptor: sbom.Descriptor{
Name: id.Name,
Version: id.Version,
Configuration: opts,
},
}
var errs error
var relationships []<-chan artifact.Relationship
for _, task := range tasks {
c := make(chan artifact.Relationship)
relationships = append(relationships, c)
go func(task eventloop.Task) {
err := eventloop.RunTask(task, &s.Artifacts, src, c)
errs = multierror.Append(errs, err)
}(task)
}
if errs != nil {
return errs
}
s.Relationships = append(s.Relationships, mergeRelationships(relationships...)...)
if err := writer.Write(s); err != nil {
return fmt.Errorf("failed to write sbom: %w", err)
}
return nil
}
+27
View File
@@ -0,0 +1,27 @@
package commands
import (
"fmt"
"github.com/spf13/cobra"
"github.com/anchore/clio"
)
func Root(app clio.Application, packagesCmd *cobra.Command) *cobra.Command {
id := app.ID()
opts := defaultPackagesOptions()
return app.SetupRootCommand(&cobra.Command{
Use: fmt.Sprintf("%s [SOURCE]", app.ID().Name),
Short: packagesCmd.Short,
Long: packagesCmd.Long,
Args: packagesCmd.Args,
Example: packagesCmd.Example,
PreRunE: applicationUpdateCheck(id, &opts.UpdateCheck),
RunE: func(cmd *cobra.Command, args []string) error {
return runPackages(id, opts, args[0])
},
}, opts)
}
+121
View File
@@ -0,0 +1,121 @@
package commands
import (
"fmt"
"io"
"net/http"
"strings"
"github.com/spf13/cobra"
"github.com/wagoodman/go-partybus"
"github.com/anchore/clio"
hashiVersion "github.com/anchore/go-version"
"github.com/anchore/syft/cmd/syft/cli/options"
"github.com/anchore/syft/cmd/syft/internal"
"github.com/anchore/syft/internal/bus"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/event"
"github.com/anchore/syft/syft/event/parsers"
)
var latestAppVersionURL = struct {
host string
path string
}{
host: "https://toolbox-data.anchore.io",
path: "/syft/releases/latest/VERSION",
}
func applicationUpdateCheck(id clio.Identification, check *options.UpdateCheck) func(cmd *cobra.Command, args []string) error {
return func(cmd *cobra.Command, args []string) error {
if check.CheckForAppUpdate {
checkForApplicationUpdate(id)
}
return nil
}
}
func checkForApplicationUpdate(id clio.Identification) {
log.Debugf("checking if a new version of %s is available", id.Name)
isAvailable, newVersion, err := isUpdateAvailable(id)
if err != nil {
// this should never stop the application
log.Errorf(err.Error())
}
if isAvailable {
log.Infof("new version of %s is available: %s (current version is %s)", id.Name, newVersion, id.Version)
bus.Publish(partybus.Event{
Type: event.CLIAppUpdateAvailable,
Value: parsers.UpdateCheck{
New: newVersion,
Current: id.Version,
},
})
} else {
log.Debugf("no new %s update available", id.Name)
}
}
// isUpdateAvailable indicates if there is a newer application version available, and if so, what the new version is.
func isUpdateAvailable(id clio.Identification) (bool, string, error) {
if !isProductionBuild(id.Version) {
// don't allow for non-production builds to check for a version.
return false, "", nil
}
currentVersion, err := hashiVersion.NewVersion(id.Version)
if err != nil {
return false, "", fmt.Errorf("failed to parse current application version: %w", err)
}
latestVersion, err := fetchLatestApplicationVersion(id)
if err != nil {
return false, "", err
}
if latestVersion.GreaterThan(currentVersion) {
return true, latestVersion.String(), nil
}
return false, "", nil
}
func isProductionBuild(version string) bool {
if strings.Contains(version, "SNAPSHOT") || strings.Contains(version, internal.NotProvided) {
return false
}
return true
}
func fetchLatestApplicationVersion(id clio.Identification) (*hashiVersion.Version, error) {
req, err := http.NewRequest(http.MethodGet, latestAppVersionURL.host+latestAppVersionURL.path, nil)
if err != nil {
return nil, fmt.Errorf("failed to create request for latest version: %w", err)
}
req.Header.Add("User-Agent", fmt.Sprintf("%v %v", id.Name, id.Version))
client := http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to fetch latest version: %w", err)
}
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("HTTP %d on fetching latest version: %s", resp.StatusCode, resp.Status)
}
versionBytes, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read latest version: %w", err)
}
versionStr := strings.TrimSuffix(string(versionBytes), "\n")
if len(versionStr) > 50 {
return nil, fmt.Errorf("version too long: %q", versionStr[:50])
}
return hashiVersion.NewVersion(versionStr)
}
@@ -1,11 +1,13 @@
package version
package commands
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/anchore/clio"
hashiVersion "github.com/anchore/go-version"
"github.com/anchore/syft/cmd/syft/internal"
)
func TestIsUpdateAvailable(t *testing.T) {
@@ -74,7 +76,7 @@ func TestIsUpdateAvailable(t *testing.T) {
},
{
name: "NoBuildVersion",
buildVersion: valueNotProvided,
buildVersion: internal.NotProvided,
latestVersion: "1.0.0",
code: 200,
isAvailable: false,
@@ -105,7 +107,7 @@ func TestIsUpdateAvailable(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
// setup mocks
// local...
version = test.buildVersion
id := clio.Identification{Name: "Syft", Version: test.buildVersion}
// remote...
handler := http.NewServeMux()
handler.HandleFunc(latestAppVersionURL.path, func(w http.ResponseWriter, r *http.Request) {
@@ -116,7 +118,7 @@ func TestIsUpdateAvailable(t *testing.T) {
latestAppVersionURL.host = mockSrv.URL
defer mockSrv.Close()
isAvailable, newVersion, err := IsUpdateAvailable()
isAvailable, newVersion, err := isUpdateAvailable(id)
if err != nil && !test.err {
t.Fatalf("got error but expected none: %+v", err)
} else if err == nil && test.err {
@@ -137,52 +139,67 @@ func TestIsUpdateAvailable(t *testing.T) {
func TestFetchLatestApplicationVersion(t *testing.T) {
tests := []struct {
name string
response string
code int
err bool
expected *hashiVersion.Version
name string
response string
code int
err bool
id clio.Identification
expected *hashiVersion.Version
expectedHeaders map[string]string
}{
{
name: "gocase",
response: "1.0.0",
code: 200,
expected: hashiVersion.Must(hashiVersion.NewVersion("1.0.0")),
name: "gocase",
response: "1.0.0",
code: 200,
id: clio.Identification{Name: "Syft", Version: "0.0.0"},
expected: hashiVersion.Must(hashiVersion.NewVersion("1.0.0")),
expectedHeaders: map[string]string{"User-Agent": "Syft 0.0.0"},
err: false,
},
{
name: "garbage",
response: "garbage",
code: 200,
expected: nil,
err: true,
name: "garbage",
response: "garbage",
code: 200,
id: clio.Identification{Name: "Syft", Version: "0.0.0"},
expected: nil,
expectedHeaders: nil,
err: true,
},
{
name: "http 500",
response: "1.0.0",
code: 500,
expected: nil,
err: true,
name: "http 500",
response: "1.0.0",
code: 500,
id: clio.Identification{Name: "Syft", Version: "0.0.0"},
expected: nil,
expectedHeaders: nil,
err: true,
},
{
name: "http 404",
response: "1.0.0",
code: 404,
expected: nil,
err: true,
name: "http 404",
response: "1.0.0",
code: 404,
id: clio.Identification{Name: "Syft", Version: "0.0.0"},
expected: nil,
expectedHeaders: nil,
err: true,
},
{
name: "empty",
response: "",
code: 200,
expected: nil,
err: true,
name: "empty",
response: "",
code: 200,
id: clio.Identification{Name: "Syft", Version: "0.0.0"},
expected: nil,
expectedHeaders: nil,
err: true,
},
{
name: "too long",
response: "this is really long this is really long this is really long this is really long this is really long this is really long this is really long this is really long ",
code: 200,
expected: nil,
err: true,
name: "too long",
response: "this is really long this is really long this is really long this is really long this is really long this is really long this is really long this is really long ",
code: 200,
id: clio.Identification{Name: "Syft", Version: "0.0.0"},
expected: nil,
expectedHeaders: nil,
err: true,
},
}
@@ -191,6 +208,15 @@ func TestFetchLatestApplicationVersion(t *testing.T) {
// setup mock
handler := http.NewServeMux()
handler.HandleFunc(latestAppVersionURL.path, func(w http.ResponseWriter, r *http.Request) {
if test.expectedHeaders != nil {
for headerName, headerValue := range test.expectedHeaders {
actualHeader := r.Header.Get(headerName)
if actualHeader != headerValue {
t.Fatalf("expected header %v=%v but got %v", headerName, headerValue, actualHeader)
}
}
}
w.WriteHeader(test.code)
_, _ = w.Write([]byte(test.response))
})
@@ -198,7 +224,7 @@ func TestFetchLatestApplicationVersion(t *testing.T) {
latestAppVersionURL.host = mockSrv.URL
defer mockSrv.Close()
actual, err := fetchLatestApplicationVersion()
actual, err := fetchLatestApplicationVersion(test.id)
if err != nil && !test.err {
t.Fatalf("got error but expected none: %+v", err)
} else if err == nil && test.err {
-57
View File
@@ -1,57 +0,0 @@
package cli
import (
"fmt"
"log"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/anchore/syft/cmd/syft/cli/convert"
"github.com/anchore/syft/cmd/syft/cli/options"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/config"
)
const (
convertExample = ` {{.appName}} {{.command}} img.syft.json -o spdx-json convert a syft SBOM to spdx-json, output goes to stdout
{{.appName}} {{.command}} img.syft.json -o cyclonedx-json=img.cdx.json convert a syft SBOM to CycloneDX, output is written to the file "img.cdx.json""
{{.appName}} {{.command}} - -o spdx-json convert an SBOM from STDIN to spdx-json
`
)
//nolint:dupl
func Convert(v *viper.Viper, app *config.Application, ro *options.RootOptions, po *options.PackagesOptions) *cobra.Command {
cmd := &cobra.Command{
Use: "convert [SOURCE-SBOM] -o [FORMAT]",
Short: "Convert between SBOM formats",
Long: "[Experimental] Convert SBOM files to, and from, SPDX, CycloneDX and Syft's format. For more info about data loss between formats see https://github.com/anchore/syft#format-conversion-experimental",
Example: internal.Tprintf(convertExample, map[string]interface{}{
"appName": internal.ApplicationName,
"command": "convert",
}),
Args: func(cmd *cobra.Command, args []string) error {
if err := app.LoadAllValues(v, ro.Config); err != nil {
return fmt.Errorf("invalid application config: %w", err)
}
newLogWrapper(app)
logApplicationConfig(app)
return validateArgs(cmd, args)
},
SilenceUsage: true,
SilenceErrors: true,
RunE: func(cmd *cobra.Command, args []string) error {
if app.CheckForAppUpdate {
checkForApplicationUpdate()
}
return convert.Run(cmd.Context(), app, args)
},
}
err := po.AddFlags(cmd, v)
if err != nil {
log.Fatal(err)
}
return cmd
}
-52
View File
@@ -1,52 +0,0 @@
package convert
import (
"context"
"fmt"
"io"
"os"
"github.com/anchore/syft/cmd/syft/cli/options"
"github.com/anchore/syft/internal/config"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/formats"
)
func Run(_ context.Context, app *config.Application, args []string) error {
log.Warn("convert is an experimental feature, run `syft convert -h` for help")
writer, err := options.MakeWriter(app.Outputs, app.File, app.OutputTemplatePath)
if err != nil {
return err
}
defer func() {
if err := writer.Close(); err != nil {
log.Warnf("unable to write to report destination: %w", err)
}
}()
// this can only be a SBOM file
userInput := args[0]
var reader io.ReadCloser
if userInput == "-" {
reader = os.Stdin
} else {
f, err := os.Open(userInput)
if err != nil {
return fmt.Errorf("failed to open SBOM file: %w", err)
}
defer func() {
_ = f.Close()
}()
reader = f
}
sbom, _, err := formats.Decode(reader)
if err != nil {
return fmt.Errorf("failed to decode SBOM: %w", err)
}
return writer.Write(*sbom)
}
-98
View File
@@ -1,98 +0,0 @@
package eventloop
import (
"errors"
"fmt"
"os"
"github.com/hashicorp/go-multierror"
"github.com/wagoodman/go-partybus"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/ui"
)
// eventLoop listens to worker errors (from execution path), worker events (from a partybus subscription), and
// signal interrupts. Is responsible for handling each event relative to a given UI an to coordinate eventing until
// an eventual graceful exit.
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
var ux ui.UI
if ux, err = setupUI(subscription.Unsubscribe, uxs...); err != nil {
return err
}
var retErr error
var forceTeardown bool
for {
if workerErrs == nil && events == nil {
break
}
select {
case err, isOpen := <-workerErrs:
if !isOpen {
workerErrs = nil
continue
}
if err != nil {
// capture the error from the worker and unsubscribe to complete a graceful shutdown
retErr = multierror.Append(retErr, err)
_ = subscription.Unsubscribe()
// 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 {
events = nil
continue
}
if err := ux.Handle(e); err != nil {
if errors.Is(err, partybus.ErrUnsubscribe) {
events = nil
} else {
retErr = multierror.Append(retErr, err)
// TODO: should we unsubscribe? should we try to halt execution? or continue?
}
}
case <-signals:
// ignore further results from any event source and exit ASAP, but ensure that all cache is cleaned up.
// we ignore further errors since cleaning up the tmp directories will affect running catalogers that are
// reading/writing from/to their nested temp dirs. This is acceptable since we are bailing without result.
// TODO: potential future improvement would be to pass context into workers with a cancel function that is
// to the event loop. In this way we can have a more controlled shutdown even at the most nested levels
// of processing.
events = nil
workerErrs = nil
forceTeardown = true
}
}
if err := ux.Teardown(forceTeardown); err != nil {
retErr = multierror.Append(retErr, err)
}
return retErr
}
// 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 {
log.Warnf("unable to setup given UI, falling back to alternative UI: %+v", err)
continue
}
return ux, nil
}
return nil, fmt.Errorf("unable to setup any UI")
}
-456
View File
@@ -1,456 +0,0 @@
package eventloop
import (
"fmt"
"os"
"syscall"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/wagoodman/go-partybus"
"github.com/anchore/syft/internal/ui"
"github.com/anchore/syft/syft/event"
)
var _ ui.UI = (*uiMock)(nil)
type uiMock struct {
t *testing.T
finalEvent partybus.Event
unsubscribe func() error
mock.Mock
}
func (u *uiMock) Setup(unsubscribe func() error) error {
u.t.Logf("UI Setup called")
u.unsubscribe = unsubscribe
return u.Called(unsubscribe).Error(0)
}
func (u *uiMock) Handle(event partybus.Event) error {
u.t.Logf("UI Handle called: %+v", event.Type)
if event == u.finalEvent {
assert.NoError(u.t, u.unsubscribe())
}
return u.Called(event).Error(0)
}
func (u *uiMock) Teardown(_ bool) error {
u.t.Logf("UI Teardown called")
return u.Called().Error(0)
}
func Test_EventLoop_gracefulExit(t *testing.T) {
test := func(t *testing.T) {
testBus := partybus.NewBus()
subscription := testBus.Subscribe()
t.Cleanup(testBus.Close)
finalEvent := partybus.Event{
Type: event.Exit,
}
worker := func() <-chan error {
ret := make(chan error)
go func() {
t.Log("worker running")
// send an empty item (which is ignored) ensuring we've entered the select statement,
// then close (a partial shutdown).
ret <- nil
t.Log("worker sent nothing")
close(ret)
t.Log("worker closed")
// do the other half of the shutdown
testBus.Publish(finalEvent)
t.Log("worker published final event")
}()
return ret
}
signaler := func() <-chan os.Signal {
return nil
}
ux := &uiMock{
t: t,
finalEvent: finalEvent,
}
// ensure the mock sees at least the final event
ux.On("Handle", finalEvent).Return(nil)
// ensure the mock sees basic setup/teardown events
ux.On("Setup", mock.AnythingOfType("func() error")).Return(nil)
ux.On("Teardown").Return(nil)
var cleanupCalled bool
cleanupFn := func() {
t.Log("cleanup called")
cleanupCalled = true
}
assert.NoError(t,
EventLoop(
worker(),
signaler(),
subscription,
cleanupFn,
ux,
),
)
assert.True(t, cleanupCalled, "cleanup function not called")
ux.AssertExpectations(t)
}
// if there is a bug, then there is a risk of the event loop never returning
testWithTimeout(t, 5*time.Second, test)
}
func Test_EventLoop_workerError(t *testing.T) {
test := func(t *testing.T) {
testBus := partybus.NewBus()
subscription := testBus.Subscribe()
t.Cleanup(testBus.Close)
workerErr := fmt.Errorf("worker error")
worker := func() <-chan error {
ret := make(chan error)
go func() {
t.Log("worker running")
// send an empty item (which is ignored) ensuring we've entered the select statement,
// then close (a partial shutdown).
ret <- nil
t.Log("worker sent nothing")
ret <- workerErr
t.Log("worker sent error")
close(ret)
t.Log("worker closed")
// note: NO final event is fired
}()
return ret
}
signaler := func() <-chan os.Signal {
return nil
}
ux := &uiMock{
t: t,
}
// ensure the mock sees basic setup/teardown events
ux.On("Setup", mock.AnythingOfType("func() error")).Return(nil)
ux.On("Teardown").Return(nil)
var cleanupCalled bool
cleanupFn := func() {
t.Log("cleanup called")
cleanupCalled = true
}
// ensure we see an error returned
assert.ErrorIs(t,
EventLoop(
worker(),
signaler(),
subscription,
cleanupFn,
ux,
),
workerErr,
"should have seen a worker error, but did not",
)
assert.True(t, cleanupCalled, "cleanup function not called")
ux.AssertExpectations(t)
}
// if there is a bug, then there is a risk of the event loop never returning
testWithTimeout(t, 5*time.Second, test)
}
func Test_EventLoop_unsubscribeError(t *testing.T) {
test := func(t *testing.T) {
testBus := partybus.NewBus()
subscription := testBus.Subscribe()
t.Cleanup(testBus.Close)
finalEvent := partybus.Event{
Type: event.Exit,
}
worker := func() <-chan error {
ret := make(chan error)
go func() {
t.Log("worker running")
// send an empty item (which is ignored) ensuring we've entered the select statement,
// then close (a partial shutdown).
ret <- nil
t.Log("worker sent nothing")
close(ret)
t.Log("worker closed")
// do the other half of the shutdown
testBus.Publish(finalEvent)
t.Log("worker published final event")
}()
return ret
}
signaler := func() <-chan os.Signal {
return nil
}
ux := &uiMock{
t: t,
finalEvent: finalEvent,
}
// ensure the mock sees at least the final event... note the unsubscribe error here
ux.On("Handle", finalEvent).Return(partybus.ErrUnsubscribe)
// ensure the mock sees basic setup/teardown events
ux.On("Setup", mock.AnythingOfType("func() error")).Return(nil)
ux.On("Teardown").Return(nil)
var cleanupCalled bool
cleanupFn := func() {
t.Log("cleanup called")
cleanupCalled = true
}
// unsubscribe errors should be handled and ignored, not propagated. We are additionally asserting that
// this case is handled as a controlled shutdown (this test should not timeout)
assert.NoError(t,
EventLoop(
worker(),
signaler(),
subscription,
cleanupFn,
ux,
),
)
assert.True(t, cleanupCalled, "cleanup function not called")
ux.AssertExpectations(t)
}
// if there is a bug, then there is a risk of the event loop never returning
testWithTimeout(t, 5*time.Second, test)
}
func Test_EventLoop_handlerError(t *testing.T) {
test := func(t *testing.T) {
testBus := partybus.NewBus()
subscription := testBus.Subscribe()
t.Cleanup(testBus.Close)
finalEvent := partybus.Event{
Type: event.Exit,
Error: fmt.Errorf("an exit error occured"),
}
worker := func() <-chan error {
ret := make(chan error)
go func() {
t.Log("worker running")
// send an empty item (which is ignored) ensuring we've entered the select statement,
// then close (a partial shutdown).
ret <- nil
t.Log("worker sent nothing")
close(ret)
t.Log("worker closed")
// do the other half of the shutdown
testBus.Publish(finalEvent)
t.Log("worker published final event")
}()
return ret
}
signaler := func() <-chan os.Signal {
return nil
}
ux := &uiMock{
t: t,
finalEvent: finalEvent,
}
// ensure the mock sees at least the final event... note the event error is propagated
ux.On("Handle", finalEvent).Return(finalEvent.Error)
// ensure the mock sees basic setup/teardown events
ux.On("Setup", mock.AnythingOfType("func() error")).Return(nil)
ux.On("Teardown").Return(nil)
var cleanupCalled bool
cleanupFn := func() {
t.Log("cleanup called")
cleanupCalled = true
}
// handle errors SHOULD propagate the event loop. We are additionally asserting that this case is
// handled as a controlled shutdown (this test should not timeout)
assert.ErrorIs(t,
EventLoop(
worker(),
signaler(),
subscription,
cleanupFn,
ux,
),
finalEvent.Error,
"should have seen a event error, but did not",
)
assert.True(t, cleanupCalled, "cleanup function not called")
ux.AssertExpectations(t)
}
// if there is a bug, then there is a risk of the event loop never returning
testWithTimeout(t, 5*time.Second, test)
}
func Test_EventLoop_signalsStopExecution(t *testing.T) {
test := func(t *testing.T) {
testBus := partybus.NewBus()
subscription := testBus.Subscribe()
t.Cleanup(testBus.Close)
worker := func() <-chan error {
// the worker will never return work and the event loop will always be waiting...
return make(chan error)
}
signaler := func() <-chan os.Signal {
ret := make(chan os.Signal)
go func() {
ret <- syscall.SIGINT
// note: we do NOT close the channel to ensure the event loop does not depend on that behavior to exit
}()
return ret
}
ux := &uiMock{
t: t,
}
// ensure the mock sees basic setup/teardown events
ux.On("Setup", mock.AnythingOfType("func() error")).Return(nil)
ux.On("Teardown").Return(nil)
var cleanupCalled bool
cleanupFn := func() {
t.Log("cleanup called")
cleanupCalled = true
}
assert.NoError(t,
EventLoop(
worker(),
signaler(),
subscription,
cleanupFn,
ux,
),
)
assert.True(t, cleanupCalled, "cleanup function not called")
ux.AssertExpectations(t)
}
// if there is a bug, then there is a risk of the event loop never returning
testWithTimeout(t, 5*time.Second, test)
}
func Test_EventLoop_uiTeardownError(t *testing.T) {
test := func(t *testing.T) {
testBus := partybus.NewBus()
subscription := testBus.Subscribe()
t.Cleanup(testBus.Close)
finalEvent := partybus.Event{
Type: event.Exit,
}
worker := func() <-chan error {
ret := make(chan error)
go func() {
t.Log("worker running")
// send an empty item (which is ignored) ensuring we've entered the select statement,
// then close (a partial shutdown).
ret <- nil
t.Log("worker sent nothing")
close(ret)
t.Log("worker closed")
// do the other half of the shutdown
testBus.Publish(finalEvent)
t.Log("worker published final event")
}()
return ret
}
signaler := func() <-chan os.Signal {
return nil
}
ux := &uiMock{
t: t,
finalEvent: finalEvent,
}
teardownError := fmt.Errorf("sorry, dave, the UI doesn't want to be torn down")
// ensure the mock sees at least the final event... note the event error is propagated
ux.On("Handle", finalEvent).Return(nil)
// ensure the mock sees basic setup/teardown events
ux.On("Setup", mock.AnythingOfType("func() error")).Return(nil)
ux.On("Teardown").Return(teardownError)
var cleanupCalled bool
cleanupFn := func() {
t.Log("cleanup called")
cleanupCalled = true
}
// ensure we see an error returned
assert.ErrorIs(t,
EventLoop(
worker(),
signaler(),
subscription,
cleanupFn,
ux,
),
teardownError,
"should have seen a UI teardown error, but did not",
)
assert.True(t, cleanupCalled, "cleanup function not called")
ux.AssertExpectations(t)
}
// if there is a bug, then there is a risk of the event loop never returning
testWithTimeout(t, 5*time.Second, test)
}
func testWithTimeout(t *testing.T, timeout time.Duration, test func(*testing.T)) {
done := make(chan bool)
go func() {
test(t)
done <- true
}()
select {
case <-time.After(timeout):
t.Fatal("test timed out")
case <-done:
}
}
-20
View File
@@ -1,20 +0,0 @@
package eventloop
import (
"os"
"os/signal"
"syscall"
)
func SetupSignals() <-chan os.Signal {
c := make(chan os.Signal, 1) // Note: A buffered channel is recommended for this; see https://golang.org/pkg/os/signal/#Notify
interruptions := []os.Signal{
syscall.SIGINT,
syscall.SIGTERM,
}
signal.Notify(c, interruptions...)
return c
}
+42 -57
View File
@@ -1,23 +1,24 @@
package eventloop
import (
"crypto"
"fmt"
"github.com/anchore/syft/internal/config"
"github.com/anchore/syft/cmd/syft/cli/options"
"github.com/anchore/syft/internal/file"
"github.com/anchore/syft/syft"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/file/cataloger/filecontent"
"github.com/anchore/syft/syft/file/cataloger/filedigest"
"github.com/anchore/syft/syft/file/cataloger/filemetadata"
"github.com/anchore/syft/syft/file/cataloger/secrets"
"github.com/anchore/syft/syft/sbom"
"github.com/anchore/syft/syft/source"
)
type Task func(*sbom.Artifacts, *source.Source) ([]artifact.Relationship, error)
type Task func(*sbom.Artifacts, source.Source) ([]artifact.Relationship, error)
func Tasks(app *config.Application) ([]Task, error) {
func Tasks(opts *options.Catalog) ([]Task, error) {
var tasks []Task
generators := []func(app *config.Application) (Task, error){
generators := []func(opts *options.Catalog) (Task, error){
generateCatalogPackagesTask,
generateCatalogFileMetadataTask,
generateCatalogFileDigestsTask,
@@ -26,7 +27,7 @@ func Tasks(app *config.Application) ([]Task, error) {
}
for _, generator := range generators {
task, err := generator(app)
task, err := generator(opts)
if err != nil {
return nil, err
}
@@ -39,15 +40,15 @@ func Tasks(app *config.Application) ([]Task, error) {
return tasks, nil
}
func generateCatalogPackagesTask(app *config.Application) (Task, error) {
if !app.Package.Cataloger.Enabled {
func generateCatalogPackagesTask(opts *options.Catalog) (Task, error) {
if !opts.Package.Cataloger.Enabled {
return nil, nil
}
task := func(results *sbom.Artifacts, src *source.Source) ([]artifact.Relationship, error) {
packageCatalog, relationships, theDistro, err := syft.CatalogPackages(src, app.ToCatalogerConfig())
task := func(results *sbom.Artifacts, src source.Source) ([]artifact.Relationship, error) {
packageCatalog, relationships, theDistro, err := syft.CatalogPackages(src, opts.ToCatalogerConfig())
results.PackageCatalog = packageCatalog
results.Packages = packageCatalog
results.LinuxDistribution = theDistro
return relationships, err
@@ -56,15 +57,15 @@ func generateCatalogPackagesTask(app *config.Application) (Task, error) {
return task, nil
}
func generateCatalogFileMetadataTask(app *config.Application) (Task, error) {
if !app.FileMetadata.Cataloger.Enabled {
func generateCatalogFileMetadataTask(opts *options.Catalog) (Task, error) {
if !opts.FileMetadata.Cataloger.Enabled {
return nil, nil
}
metadataCataloger := file.NewMetadataCataloger()
metadataCataloger := filemetadata.NewCataloger()
task := func(results *sbom.Artifacts, src *source.Source) ([]artifact.Relationship, error) {
resolver, err := src.FileResolver(app.FileMetadata.Cataloger.ScopeOpt)
task := func(results *sbom.Artifacts, src source.Source) ([]artifact.Relationship, error) {
resolver, err := src.FileResolver(opts.FileMetadata.Cataloger.GetScope())
if err != nil {
return nil, err
}
@@ -80,37 +81,20 @@ func generateCatalogFileMetadataTask(app *config.Application) (Task, error) {
return task, nil
}
func generateCatalogFileDigestsTask(app *config.Application) (Task, error) {
if !app.FileMetadata.Cataloger.Enabled {
func generateCatalogFileDigestsTask(opts *options.Catalog) (Task, error) {
if !opts.FileMetadata.Cataloger.Enabled {
return nil, nil
}
supportedHashAlgorithms := make(map[string]crypto.Hash)
for _, h := range []crypto.Hash{
crypto.MD5,
crypto.SHA1,
crypto.SHA256,
} {
supportedHashAlgorithms[file.DigestAlgorithmName(h)] = h
}
var hashes []crypto.Hash
for _, hashStr := range app.FileMetadata.Digests {
name := file.CleanDigestAlgorithmName(hashStr)
hashObj, ok := supportedHashAlgorithms[name]
if !ok {
return nil, fmt.Errorf("unsupported hash algorithm: %s", hashStr)
}
hashes = append(hashes, hashObj)
}
digestsCataloger, err := file.NewDigestsCataloger(hashes)
hashes, err := file.Hashers(opts.FileMetadata.Digests...)
if err != nil {
return nil, err
}
task := func(results *sbom.Artifacts, src *source.Source) ([]artifact.Relationship, error) {
resolver, err := src.FileResolver(app.FileMetadata.Cataloger.ScopeOpt)
digestsCataloger := filedigest.NewCataloger(hashes)
task := func(results *sbom.Artifacts, src source.Source) ([]artifact.Relationship, error) {
resolver, err := src.FileResolver(opts.FileMetadata.Cataloger.GetScope())
if err != nil {
return nil, err
}
@@ -126,23 +110,23 @@ func generateCatalogFileDigestsTask(app *config.Application) (Task, error) {
return task, nil
}
func generateCatalogSecretsTask(app *config.Application) (Task, error) {
if !app.Secrets.Cataloger.Enabled {
func generateCatalogSecretsTask(opts *options.Catalog) (Task, error) {
if !opts.Secrets.Cataloger.Enabled {
return nil, nil
}
patterns, err := file.GenerateSearchPatterns(file.DefaultSecretsPatterns, app.Secrets.AdditionalPatterns, app.Secrets.ExcludePatternNames)
patterns, err := secrets.GenerateSearchPatterns(secrets.DefaultSecretsPatterns, opts.Secrets.AdditionalPatterns, opts.Secrets.ExcludePatternNames)
if err != nil {
return nil, err
}
secretsCataloger, err := file.NewSecretsCataloger(patterns, app.Secrets.RevealValues, app.Secrets.SkipFilesAboveSize)
secretsCataloger, err := secrets.NewCataloger(patterns, opts.Secrets.RevealValues, opts.Secrets.SkipFilesAboveSize) //nolint:staticcheck
if err != nil {
return nil, err
}
task := func(results *sbom.Artifacts, src *source.Source) ([]artifact.Relationship, error) {
resolver, err := src.FileResolver(app.Secrets.Cataloger.ScopeOpt)
task := func(results *sbom.Artifacts, src source.Source) ([]artifact.Relationship, error) {
resolver, err := src.FileResolver(opts.Secrets.Cataloger.GetScope())
if err != nil {
return nil, err
}
@@ -158,18 +142,18 @@ func generateCatalogSecretsTask(app *config.Application) (Task, error) {
return task, nil
}
func generateCatalogContentsTask(app *config.Application) (Task, error) {
if !app.FileContents.Cataloger.Enabled {
func generateCatalogContentsTask(opts *options.Catalog) (Task, error) {
if !opts.FileContents.Cataloger.Enabled {
return nil, nil
}
contentsCataloger, err := file.NewContentsCataloger(app.FileContents.Globs, app.FileContents.SkipFilesAboveSize)
contentsCataloger, err := filecontent.NewCataloger(opts.FileContents.Globs, opts.FileContents.SkipFilesAboveSize) //nolint:staticcheck
if err != nil {
return nil, err
}
task := func(results *sbom.Artifacts, src *source.Source) ([]artifact.Relationship, error) {
resolver, err := src.FileResolver(app.FileContents.Cataloger.ScopeOpt)
task := func(results *sbom.Artifacts, src source.Source) ([]artifact.Relationship, error) {
resolver, err := src.FileResolver(opts.FileContents.Cataloger.GetScope())
if err != nil {
return nil, err
}
@@ -185,16 +169,17 @@ func generateCatalogContentsTask(app *config.Application) (Task, error) {
return task, nil
}
func RunTask(t Task, a *sbom.Artifacts, src *source.Source, c chan<- artifact.Relationship, errs chan<- error) {
func RunTask(t Task, a *sbom.Artifacts, src source.Source, c chan<- artifact.Relationship) error {
defer close(c)
relationships, err := t(a, src)
if err != nil {
errs <- err
return
return err
}
for _, relationship := range relationships {
c <- relationship
}
return nil
}
+8 -17
View File
@@ -1,26 +1,17 @@
package options
import (
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/spf13/viper"
"github.com/anchore/clio"
)
type AttestOptions struct {
Key string
type Attest struct {
// IMPORTANT: do not show the attestation key/password in any YAML/JSON output (sensitive information)
Key secret `yaml:"key" json:"key" mapstructure:"key"`
Password secret `yaml:"password" json:"password" mapstructure:"password"`
}
var _ Interface = (*AttestOptions)(nil)
var _ clio.FlagAdder = (*Attest)(nil)
func (o AttestOptions) AddFlags(cmd *cobra.Command, v *viper.Viper) error {
cmd.Flags().StringVarP(&o.Key, "key", "k", "", "the key to use for the attestation")
return bindAttestConfigOptions(cmd.Flags(), v)
}
//nolint:revive
func bindAttestConfigOptions(flags *pflag.FlagSet, v *viper.Viper) error {
if err := v.BindPFlag("attest.key", flags.Lookup("key")); err != nil {
return err
}
return nil
func (o Attest) AddFlags(flags clio.FlagSet) {
flags.StringVarP((*string)(&o.Key), "key", "k", "the key to use for the attestation")
}
+168
View File
@@ -0,0 +1,168 @@
package options
import (
"fmt"
"sort"
"strings"
"github.com/iancoleman/strcase"
"github.com/mitchellh/go-homedir"
"github.com/anchore/clio"
"github.com/anchore/fangs"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/pkg/cataloger"
golangCataloger "github.com/anchore/syft/syft/pkg/cataloger/golang"
"github.com/anchore/syft/syft/pkg/cataloger/kernel"
pythonCataloger "github.com/anchore/syft/syft/pkg/cataloger/python"
"github.com/anchore/syft/syft/source"
)
type Catalog struct {
Catalogers []string `yaml:"catalogers" json:"catalogers" mapstructure:"catalogers"`
Package pkg `yaml:"package" json:"package" mapstructure:"package"`
Golang golang `yaml:"golang" json:"golang" mapstructure:"golang"`
LinuxKernel linuxKernel `yaml:"linux-kernel" json:"linux-kernel" mapstructure:"linux-kernel"`
Python python `yaml:"python" json:"python" mapstructure:"python"`
FileMetadata fileMetadata `yaml:"file-metadata" json:"file-metadata" mapstructure:"file-metadata"`
FileClassification fileClassification `yaml:"file-classification" json:"file-classification" mapstructure:"file-classification"`
FileContents fileContents `yaml:"file-contents" json:"file-contents" mapstructure:"file-contents"`
Secrets secrets `yaml:"secrets" json:"secrets" mapstructure:"secrets"`
Registry registry `yaml:"registry" json:"registry" mapstructure:"registry"`
Exclusions []string `yaml:"exclude" json:"exclude" mapstructure:"exclude"`
Platform string `yaml:"platform" json:"platform" mapstructure:"platform"`
Name string `yaml:"name" json:"name" mapstructure:"name"`
Source sourceCfg `yaml:"source" json:"source" mapstructure:"source"`
Parallelism int `yaml:"parallelism" json:"parallelism" mapstructure:"parallelism"` // the number of catalog workers to run in parallel
DefaultImagePullSource string `yaml:"default-image-pull-source" json:"default-image-pull-source" mapstructure:"default-image-pull-source"` // specify default image pull source
BasePath string `yaml:"base-path" json:"base-path" mapstructure:"base-path"` // specify base path for all file paths
ExcludeBinaryOverlapByOwnership bool `yaml:"exclude-binary-overlap-by-ownership" json:"exclude-binary-overlap-by-ownership" mapstructure:"exclude-binary-overlap-by-ownership"` // exclude synthetic binary packages owned by os package files
}
var _ interface {
clio.FlagAdder
clio.PostLoader
} = (*Catalog)(nil)
func DefaultCatalog() Catalog {
return Catalog{
Package: defaultPkg(),
LinuxKernel: defaultLinuxKernel(),
FileMetadata: defaultFileMetadata(),
FileClassification: defaultFileClassification(),
FileContents: defaultFileContents(),
Secrets: defaultSecrets(),
Source: defaultSourceCfg(),
Parallelism: 1,
ExcludeBinaryOverlapByOwnership: true,
}
}
func (cfg *Catalog) AddFlags(flags clio.FlagSet) {
var validScopeValues []string
for _, scope := range source.AllScopes {
validScopeValues = append(validScopeValues, strcase.ToDelimited(string(scope), '-'))
}
flags.StringVarP(&cfg.Package.Cataloger.Scope, "scope", "s",
fmt.Sprintf("selection of layers to catalog, options=%v", validScopeValues))
flags.StringVarP(&cfg.Platform, "platform", "",
"an optional platform specifier for container image sources (e.g. 'linux/arm64', 'linux/arm64/v8', 'arm64', 'linux')")
flags.StringArrayVarP(&cfg.Exclusions, "exclude", "",
"exclude paths from being scanned using a glob expression")
flags.StringArrayVarP(&cfg.Catalogers, "catalogers", "",
"enable one or more package catalogers")
flags.StringVarP(&cfg.Source.Name, "name", "",
"set the name of the target being analyzed")
if pfp, ok := flags.(fangs.PFlagSetProvider); ok {
flagSet := pfp.PFlagSet()
flagSet.Lookup("name").Deprecated = "use: source-name"
}
flags.StringVarP(&cfg.Source.Name, "source-name", "",
"set the name of the target being analyzed")
flags.StringVarP(&cfg.Source.Version, "source-version", "",
"set the version of the target being analyzed")
flags.StringVarP(&cfg.BasePath, "base-path", "",
"base directory for scanning, no links will be followed above this directory, and all paths will be reported relative to this directory")
}
func (cfg *Catalog) PostLoad() error {
// parse options on this struct
var catalogers []string
for _, c := range cfg.Catalogers {
for _, f := range strings.Split(c, ",") {
catalogers = append(catalogers, strings.TrimSpace(f))
}
}
sort.Strings(catalogers)
cfg.Catalogers = catalogers
if err := checkDefaultSourceValues(cfg.DefaultImagePullSource); err != nil {
return err
}
if cfg.Name != "" {
log.Warnf("name parameter is deprecated. please use: source-name. name will be removed in a future version")
if cfg.Source.Name == "" {
cfg.Source.Name = cfg.Name
}
}
return nil
}
func (cfg Catalog) ToCatalogerConfig() cataloger.Config {
return cataloger.Config{
Search: cataloger.SearchConfig{
IncludeIndexedArchives: cfg.Package.SearchIndexedArchives,
IncludeUnindexedArchives: cfg.Package.SearchUnindexedArchives,
Scope: cfg.Package.Cataloger.GetScope(),
},
Catalogers: cfg.Catalogers,
Parallelism: cfg.Parallelism,
Golang: golangCataloger.NewGoCatalogerOpts().
WithSearchLocalModCacheLicenses(cfg.Golang.SearchLocalModCacheLicenses).
WithLocalModCacheDir(cfg.Golang.LocalModCacheDir).
WithSearchRemoteLicenses(cfg.Golang.SearchRemoteLicenses).
WithProxy(cfg.Golang.Proxy).
WithNoProxy(cfg.Golang.NoProxy),
LinuxKernel: kernel.LinuxCatalogerConfig{
CatalogModules: cfg.LinuxKernel.CatalogModules,
},
Python: pythonCataloger.CatalogerConfig{
GuessUnpinnedRequirements: cfg.Python.GuessUnpinnedRequirements,
},
ExcludeBinaryOverlapByOwnership: cfg.ExcludeBinaryOverlapByOwnership,
}
}
var validDefaultSourceValues = []string{"registry", "docker", "podman", ""}
func checkDefaultSourceValues(source string) error {
validValues := internal.NewStringSet(validDefaultSourceValues...)
if !validValues.Contains(source) {
validValuesString := strings.Join(validDefaultSourceValues, ", ")
return fmt.Errorf("%s is not a valid default source; please use one of the following: %s''", source, validValuesString)
}
return nil
}
func expandFilePath(file string) (string, error) {
if file != "" {
expandedPath, err := homedir.Expand(file)
if err != nil {
return "", fmt.Errorf("unable to expand file path=%q: %w", file, err)
}
file = expandedPath
}
return file, nil
}
+6
View File
@@ -0,0 +1,6 @@
package options
// Config holds a reference to the specific config file that was used to load application configuration
type Config struct {
ConfigFile string `yaml:"config" json:"config" mapstructure:"config"`
}
@@ -0,0 +1,17 @@
package options
import (
"github.com/anchore/syft/syft/source"
)
type fileClassification struct {
Cataloger scope `yaml:"cataloger" json:"cataloger" mapstructure:"cataloger"`
}
func defaultFileClassification() fileClassification {
return fileClassification{
Cataloger: scope{
Scope: source.SquashedScope.String(),
},
}
}
+21
View File
@@ -0,0 +1,21 @@
package options
import (
"github.com/anchore/syft/internal/file"
"github.com/anchore/syft/syft/source"
)
type fileContents struct {
Cataloger scope `yaml:"cataloger" json:"cataloger" mapstructure:"cataloger"`
SkipFilesAboveSize int64 `yaml:"skip-files-above-size" json:"skip-files-above-size" mapstructure:"skip-files-above-size"`
Globs []string `yaml:"globs" json:"globs" mapstructure:"globs"`
}
func defaultFileContents() fileContents {
return fileContents{
Cataloger: scope{
Scope: source.SquashedScope.String(),
},
SkipFilesAboveSize: 1 * file.MB,
}
}
+19
View File
@@ -0,0 +1,19 @@
package options
import (
"github.com/anchore/syft/syft/source"
)
type fileMetadata struct {
Cataloger scope `yaml:"cataloger" json:"cataloger" mapstructure:"cataloger"`
Digests []string `yaml:"digests" json:"digests" mapstructure:"digests"`
}
func defaultFileMetadata() fileMetadata {
return fileMetadata{
Cataloger: scope{
Scope: source.SquashedScope.String(),
},
Digests: []string{"sha256"},
}
}
-49
View File
@@ -1,49 +0,0 @@
package options
import (
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/spf13/viper"
)
const defaultFulcioURL = "https://fulcio.sigstore.dev"
// FulcioOptions is the wrapper for Fulcio related options.
type FulcioOptions struct {
URL string
IdentityToken string
InsecureSkipFulcioVerify bool
}
var _ Interface = (*FulcioOptions)(nil)
// AddFlags implements Interface
func (o *FulcioOptions) AddFlags(cmd *cobra.Command, v *viper.Viper) error {
// TODO: change this back to api.SigstorePublicServerURL after the v1 migration is complete.
cmd.Flags().StringVar(&o.URL, "fulcio-url", defaultFulcioURL,
"address of sigstore PKI server")
cmd.Flags().StringVar(&o.IdentityToken, "identity-token", "",
"identity token to use for certificate from fulcio")
cmd.Flags().BoolVar(&o.InsecureSkipFulcioVerify, "insecure-skip-verify", false,
"skip verifying fulcio certificat and the SCT (Signed Certificate Timestamp) (this should only be used for testing).")
return bindFulcioConfigOptions(cmd.Flags(), v)
}
//nolint:revive
func bindFulcioConfigOptions(flags *pflag.FlagSet, v *viper.Viper) error {
if err := v.BindPFlag("attest.fulcio-url", flags.Lookup("fulcio-url")); err != nil {
return err
}
if err := v.BindPFlag("attest.fulcio-identity-token", flags.Lookup("identity-token")); err != nil {
return err
}
if err := v.BindPFlag("attest.insecure-skip-verify", flags.Lookup("insecure-skip-verify")); err != nil {
return err
}
return nil
}
@@ -1,6 +1,4 @@
package config
import "github.com/spf13/viper"
package options
type golang struct {
SearchLocalModCacheLicenses bool `json:"search-local-mod-cache-licenses" yaml:"search-local-mod-cache-licenses" mapstructure:"search-local-mod-cache-licenses"`
@@ -9,11 +7,3 @@ type golang struct {
Proxy string `json:"proxy" yaml:"proxy" mapstructure:"proxy"`
NoProxy string `json:"no-proxy" yaml:"no-proxy" mapstructure:"no-proxy"`
}
func (cfg golang) loadDefaultValues(v *viper.Viper) {
v.SetDefault("golang.search-local-mod-cache-licenses", false)
v.SetDefault("golang.local-mod-cache-dir", "")
v.SetDefault("golang.search-remote-licenses", false)
v.SetDefault("golang.proxy", "")
v.SetDefault("golang.no-proxy", "")
}
+11
View File
@@ -0,0 +1,11 @@
package options
type linuxKernel struct {
CatalogModules bool `json:"catalog-modules" yaml:"catalog-modules" mapstructure:"catalog-modules"`
}
func defaultLinuxKernel() linuxKernel {
return linuxKernel{
CatalogModules: true,
}
}
-49
View File
@@ -1,49 +0,0 @@
package options
import (
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/spf13/viper"
)
const DefaultOIDCIssuerURL = "https://oauth2.sigstore.dev/auth"
// OIDCOptions is the wrapper for OIDC related options.
type OIDCOptions struct {
Issuer string
ClientID string
RedirectURL string
}
var _ Interface = (*OIDCOptions)(nil)
// AddFlags implements Interface
func (o *OIDCOptions) AddFlags(cmd *cobra.Command, v *viper.Viper) error {
cmd.Flags().StringVar(&o.Issuer, "oidc-issuer", DefaultOIDCIssuerURL,
"OIDC provider to be used to issue ID token")
cmd.Flags().StringVar(&o.ClientID, "oidc-client-id", "sigstore",
"OIDC client ID for application")
cmd.Flags().StringVar(&o.RedirectURL, "oidc-redirect-url", "",
"OIDC redirect URL (Optional)")
return bindOIDCConfigOptions(cmd.Flags(), v)
}
//nolint:revive
func bindOIDCConfigOptions(flags *pflag.FlagSet, v *viper.Viper) error {
if err := v.BindPFlag("attest.oidc-issuer", flags.Lookup("oidc-issuer")); err != nil {
return err
}
if err := v.BindPFlag("attest.oidc-client-id", flags.Lookup("oidc-client-id")); err != nil {
return err
}
if err := v.BindPFlag("attest.oidc-redirect-url", flags.Lookup("oidc-redirect-url")); err != nil {
return err
}
return nil
}
-11
View File
@@ -1,11 +0,0 @@
package options
import (
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
type Interface interface {
// AddFlags adds this options' flags to the cobra command.
AddFlags(cmd *cobra.Command, v *viper.Viper) error
}
+94
View File
@@ -0,0 +1,94 @@
package options
import (
"fmt"
"slices"
"github.com/anchore/clio"
"github.com/anchore/syft/syft/formats"
"github.com/anchore/syft/syft/formats/table"
"github.com/anchore/syft/syft/formats/template"
"github.com/anchore/syft/syft/sbom"
)
// MultiOutput has the standard output options syft accepts: multiple -o, --file, --template
type MultiOutput struct {
Outputs []string `yaml:"output" json:"output" mapstructure:"output"` // -o, the format to use for output
OutputFile `yaml:",inline" json:"" mapstructure:",squash"`
OutputTemplatePath string `yaml:"output-template-path" json:"output-template-path" mapstructure:"output-template-path"` // -t template file to use for output
}
var _ interface {
clio.FlagAdder
} = (*MultiOutput)(nil)
func DefaultOutput() MultiOutput {
return MultiOutput{
Outputs: []string{string(table.ID)},
}
}
func (o *MultiOutput) AddFlags(flags clio.FlagSet) {
flags.StringArrayVarP(&o.Outputs, "output", "o",
fmt.Sprintf("report output format, options=%v", formats.AllIDs()))
flags.StringVarP(&o.OutputTemplatePath, "template", "t",
"specify the path to a Go template file")
}
func (o *MultiOutput) SBOMWriter() (sbom.Writer, error) {
return makeSBOMWriter(o.Outputs, o.File, o.OutputTemplatePath)
}
// SingleOutput allows only 1 output to be specified, with a user able to set what options are allowed by setting AllowableOptions
type SingleOutput struct {
AllowableOptions []string `yaml:"-" json:"-" mapstructure:"-"`
Output string `yaml:"output" json:"output" mapstructure:"output"`
OutputTemplatePath string `yaml:"output-template-path" json:"output-template-path" mapstructure:"output-template-path"` // -t template file to use for output
}
var _ clio.FlagAdder = (*SingleOutput)(nil)
func (o *SingleOutput) AddFlags(flags clio.FlagSet) {
flags.StringVarP(&o.Output, "output", "o",
fmt.Sprintf("report output format, options=%v", o.AllowableOptions))
if slices.Contains(o.AllowableOptions, template.ID.String()) {
flags.StringVarP(&o.OutputTemplatePath, "template", "t",
"specify the path to a Go template file")
}
}
func (o *SingleOutput) SBOMWriter(file string) (sbom.Writer, error) {
return makeSBOMWriter([]string{o.Output}, file, o.OutputTemplatePath)
}
// OutputFile is only the --file argument
type OutputFile struct {
File string `yaml:"file" json:"file" mapstructure:"file"` // --file, the file to write report output to
}
var _ interface {
clio.FlagAdder
clio.PostLoader
} = (*OutputFile)(nil)
func (o *OutputFile) AddFlags(flags clio.FlagSet) {
flags.StringVarP(&o.File, "file", "",
"file to write the default report output to (default is STDOUT)")
}
func (o *OutputFile) PostLoad() error {
if o.File != "" {
file, err := expandFilePath(o.File)
if err != nil {
return err
}
o.File = file
}
return nil
}
func (o *OutputFile) SBOMWriter(f sbom.Format) (sbom.Writer, error) {
return makeSBOMWriterForFormat(f, o.File)
}
-94
View File
@@ -1,94 +0,0 @@
package options
import (
"fmt"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/spf13/viper"
"github.com/anchore/syft/syft/formats"
"github.com/anchore/syft/syft/formats/table"
"github.com/anchore/syft/syft/pkg/cataloger"
"github.com/anchore/syft/syft/source"
)
type PackagesOptions struct {
Scope string
Output []string
OutputTemplatePath string
File string
Platform string
Exclude []string
Catalogers []string
Name string
}
var _ Interface = (*PackagesOptions)(nil)
func (o *PackagesOptions) AddFlags(cmd *cobra.Command, v *viper.Viper) error {
cmd.Flags().StringVarP(&o.Scope, "scope", "s", cataloger.DefaultSearchConfig().Scope.String(),
fmt.Sprintf("selection of layers to catalog, options=%v", source.AllScopes))
cmd.Flags().StringArrayVarP(&o.Output, "output", "o", []string{string(table.ID)},
fmt.Sprintf("report output format, options=%v", formats.AllIDs()))
cmd.Flags().StringVarP(&o.File, "file", "", "",
"file to write the default report output to (default is STDOUT)")
cmd.Flags().StringVarP(&o.OutputTemplatePath, "template", "t", "",
"specify the path to a Go template file")
cmd.Flags().StringVarP(&o.Platform, "platform", "", "",
"an optional platform specifier for container image sources (e.g. 'linux/arm64', 'linux/arm64/v8', 'arm64', 'linux')")
cmd.Flags().StringArrayVarP(&o.Exclude, "exclude", "", nil,
"exclude paths from being scanned using a glob expression")
cmd.Flags().StringArrayVarP(&o.Catalogers, "catalogers", "", nil,
"enable one or more package catalogers")
cmd.Flags().StringVarP(&o.Name, "name", "", "",
"set the name of the target being analyzed")
return bindPackageConfigOptions(cmd.Flags(), v)
}
//nolint:revive
func bindPackageConfigOptions(flags *pflag.FlagSet, v *viper.Viper) error {
// Formatting & Input options //////////////////////////////////////////////
if err := v.BindPFlag("package.cataloger.scope", flags.Lookup("scope")); err != nil {
return err
}
if err := v.BindPFlag("file", flags.Lookup("file")); err != nil {
return err
}
if err := v.BindPFlag("exclude", flags.Lookup("exclude")); err != nil {
return err
}
if err := v.BindPFlag("catalogers", flags.Lookup("catalogers")); err != nil {
return err
}
if err := v.BindPFlag("name", flags.Lookup("name")); err != nil {
return err
}
if err := v.BindPFlag("output", flags.Lookup("output")); err != nil {
return err
}
if err := v.BindPFlag("output-template-path", flags.Lookup("template")); err != nil {
return err
}
if err := v.BindPFlag("platform", flags.Lookup("platform")); err != nil {
return err
}
return nil
}
+23
View File
@@ -0,0 +1,23 @@
package options
import (
"github.com/anchore/syft/syft/pkg/cataloger"
)
type pkg struct {
Cataloger scope `yaml:"cataloger" json:"cataloger" mapstructure:"cataloger"`
SearchUnindexedArchives bool `yaml:"search-unindexed-archives" json:"search-unindexed-archives" mapstructure:"search-unindexed-archives"`
SearchIndexedArchives bool `yaml:"search-indexed-archives" json:"search-indexed-archives" mapstructure:"search-indexed-archives"`
}
func defaultPkg() pkg {
c := cataloger.DefaultSearchConfig()
return pkg{
SearchIndexedArchives: c.IncludeIndexedArchives,
SearchUnindexedArchives: c.IncludeUnindexedArchives,
Cataloger: scope{
Enabled: true,
Scope: c.Scope.String(),
},
}
}
+5
View File
@@ -0,0 +1,5 @@
package options
type python struct {
GuessUnpinnedRequirements bool `json:"guess-unpinned-requirements" yaml:"guess-unpinned-requirements" mapstructure:"guess-unpinned-requirements"`
}
+84
View File
@@ -0,0 +1,84 @@
package options
import (
"os"
"github.com/anchore/clio"
"github.com/anchore/stereoscope/pkg/image"
)
type RegistryCredentials struct {
Authority string `yaml:"authority" json:"authority" mapstructure:"authority"`
// IMPORTANT: do not show any credential information, use secret type to automatically redact the values
Username secret `yaml:"username" json:"username" mapstructure:"username"`
Password secret `yaml:"password" json:"password" mapstructure:"password"`
Token secret `yaml:"token" json:"token" mapstructure:"token"`
TLSCert string `yaml:"tls-cert,omitempty" json:"tls-cert,omitempty" mapstructure:"tls-cert"`
TLSKey string `yaml:"tls-key,omitempty" json:"tls-key,omitempty" mapstructure:"tls-key"`
}
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"`
CACert string `yaml:"ca-cert" json:"ca-cert" mapstructure:"ca-cert"`
}
var _ clio.PostLoader = (*registry)(nil)
func (cfg *registry) PostLoad() error {
// there may be additional credentials provided by env var that should be appended to the set of credentials
authority, username, password, token, tlsCert, tlsKey :=
os.Getenv("SYFT_REGISTRY_AUTH_AUTHORITY"),
os.Getenv("SYFT_REGISTRY_AUTH_USERNAME"),
os.Getenv("SYFT_REGISTRY_AUTH_PASSWORD"),
os.Getenv("SYFT_REGISTRY_AUTH_TOKEN"),
os.Getenv("SYFT_REGISTRY_AUTH_TLS_CERT"),
os.Getenv("SYFT_REGISTRY_AUTH_TLS_KEY")
if hasNonEmptyCredentials(username, password, token, tlsCert, tlsKey) {
// note: we prepend the credentials such that the environment variables take precedence over on-disk configuration.
// since this PostLoad is called before the PostLoad on the Auth credentials list,
// all appropriate redactions will be added
cfg.Auth = append([]RegistryCredentials{
{
Authority: authority,
Username: secret(username),
Password: secret(password),
Token: secret(token),
TLSCert: tlsCert,
TLSKey: tlsKey,
},
}, cfg.Auth...)
}
return nil
}
func hasNonEmptyCredentials(username, password, token, tlsCert, tlsKey string) bool {
hasUserPass := username != "" && password != ""
hasToken := token != ""
hasTLSMaterial := tlsCert != "" && tlsKey != ""
return hasUserPass || hasToken || hasTLSMaterial
}
func (cfg *registry) ToOptions() *image.RegistryOptions {
var auth = make([]image.RegistryCredentials, len(cfg.Auth))
for i, a := range cfg.Auth {
auth[i] = image.RegistryCredentials{
Authority: a.Authority,
Username: a.Username.String(),
Password: a.Password.String(),
Token: a.Token.String(),
ClientCert: a.TLSCert,
ClientKey: a.TLSKey,
}
}
return &image.RegistryOptions{
InsecureSkipTLSVerify: cfg.InsecureSkipTLSVerify,
InsecureUseHTTP: cfg.InsecureUseHTTP,
Credentials: auth,
CAFileOrDir: cfg.CACert,
}
}
@@ -1,4 +1,4 @@
package config
package options
import (
"fmt"
@@ -11,48 +11,60 @@ import (
func TestHasNonEmptyCredentials(t *testing.T) {
tests := []struct {
username, password, token string
expected bool
username, password, token, cert, key string
expected bool
}{
{
"", "", "",
"", "", "", "", "",
false,
},
{
"user", "", "",
"user", "", "", "", "",
false,
},
{
"", "pass", "",
"", "pass", "", "", "",
false,
},
{
"", "pass", "tok",
"", "pass", "tok", "", "",
true,
},
{
"user", "", "tok",
"user", "", "tok", "", "",
true,
},
{
"", "", "tok",
"", "", "tok", "", "",
true,
},
{
"user", "pass", "tok",
"user", "pass", "tok", "", "",
true,
},
{
"user", "pass", "",
"user", "pass", "", "", "",
true,
},
{
"", "", "", "cert", "key",
true,
},
{
"", "", "", "cert", "",
false,
},
{
"", "", "", "", "key",
false,
},
}
for _, test := range tests {
t.Run(fmt.Sprintf("%+v", test), func(t *testing.T) {
assert.Equal(t, test.expected, hasNonEmptyCredentials(test.username, test.password, test.token))
assert.Equal(t, test.expected, hasNonEmptyCredentials(test.username, test.password, test.token, test.cert, test.key))
})
}
}
@@ -102,6 +114,29 @@ func Test_registry_ToOptions(t *testing.T) {
Credentials: []image.RegistryCredentials{},
},
},
{
name: "provide all tls configuration",
input: registry{
CACert: "ca.crt",
InsecureSkipTLSVerify: true,
Auth: []RegistryCredentials{
{
TLSCert: "client.crt",
TLSKey: "client.key",
},
},
},
expected: image.RegistryOptions{
CAFileOrDir: "ca.crt",
InsecureSkipTLSVerify: true,
Credentials: []image.RegistryCredentials{
{
ClientCert: "client.crt",
ClientKey: "client.key",
},
},
},
},
}
for _, test := range tests {
-33
View File
@@ -1,33 +0,0 @@
package options
import (
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/spf13/viper"
)
const DefaultRekorURL = "https://rekor.sigstore.dev"
// RekorOptions is the wrapper for Rekor related options.
type RekorOptions struct {
URL string
}
var _ Interface = (*RekorOptions)(nil)
// AddFlags implements Interface
func (o *RekorOptions) AddFlags(cmd *cobra.Command, v *viper.Viper) error {
cmd.Flags().StringVar(&o.URL, "rekor-url", DefaultRekorURL,
"address of rekor STL server")
return bindRekorConfigOptions(cmd.Flags(), v)
}
//nolint:revive
func bindRekorConfigOptions(flags *pflag.FlagSet, v *viper.Viper) error {
// TODO: config re-design
if err := v.BindPFlag("attest.rekor-url", flags.Lookup("rekor-url")); err != nil {
return err
}
return nil
}
-37
View File
@@ -1,37 +0,0 @@
package options
import (
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/spf13/viper"
)
type RootOptions struct {
Config string
Quiet bool
Verbose int
}
var _ Interface = (*RootOptions)(nil)
func (o *RootOptions) AddFlags(cmd *cobra.Command, v *viper.Viper) error {
cmd.PersistentFlags().StringVarP(&o.Config, "config", "c", "", "application config file")
cmd.PersistentFlags().CountVarP(&o.Verbose, "verbose", "v", "increase verbosity (-v = info, -vv = debug)")
cmd.PersistentFlags().BoolVarP(&o.Quiet, "quiet", "q", false, "suppress all logging output")
return bindRootConfigOptions(cmd.PersistentFlags(), v)
}
//nolint:revive
func bindRootConfigOptions(flags *pflag.FlagSet, v *viper.Viper) error {
if err := v.BindPFlag("config", flags.Lookup("config")); err != nil {
return err
}
if err := v.BindPFlag("verbosity", flags.Lookup("verbose")); err != nil {
return err
}
if err := v.BindPFlag("quiet", flags.Lookup("quiet")); err != nil {
return err
}
return nil
}
+27
View File
@@ -0,0 +1,27 @@
package options
import (
"fmt"
"github.com/anchore/clio"
"github.com/anchore/syft/syft/source"
)
type scope struct {
Enabled bool `yaml:"enabled" json:"enabled" mapstructure:"enabled"`
Scope string `yaml:"scope" json:"scope" mapstructure:"scope"`
}
var _ clio.PostLoader = (*scope)(nil)
func (opt *scope) PostLoad() error {
s := opt.GetScope()
if s == source.UnknownScope {
return fmt.Errorf("bad scope value %v", opt.Scope)
}
return nil
}
func (opt scope) GetScope() source.Scope {
return source.ParseScope(opt.Scope)
}
+25
View File
@@ -0,0 +1,25 @@
package options
import (
"fmt"
"github.com/anchore/clio"
"github.com/anchore/syft/internal/redact"
)
type secret string
var _ interface {
fmt.Stringer
clio.PostLoader
} = (*secret)(nil)
// PostLoad needs to use a pointer receiver, even if it's not modifying the value
func (r *secret) PostLoad() error {
redact.Add(string(*r))
return nil
}
func (r secret) String() string {
return string(r)
}
@@ -1,29 +1,23 @@
package config
package options
import (
"github.com/spf13/viper"
"github.com/anchore/syft/internal/file"
"github.com/anchore/syft/syft/source"
)
type secrets struct {
Cataloger catalogerOptions `yaml:"cataloger" json:"cataloger" mapstructure:"cataloger"`
Cataloger scope `yaml:"cataloger" json:"cataloger" mapstructure:"cataloger"`
AdditionalPatterns map[string]string `yaml:"additional-patterns" json:"additional-patterns" mapstructure:"additional-patterns"`
ExcludePatternNames []string `yaml:"exclude-pattern-names" json:"exclude-pattern-names" mapstructure:"exclude-pattern-names"`
RevealValues bool `yaml:"reveal-values" json:"reveal-values" mapstructure:"reveal-values"`
SkipFilesAboveSize int64 `yaml:"skip-files-above-size" json:"skip-files-above-size" mapstructure:"skip-files-above-size"`
}
func (cfg secrets) loadDefaultValues(v *viper.Viper) {
v.SetDefault("secrets.cataloger.enabled", catalogerEnabledDefault)
v.SetDefault("secrets.cataloger.scope", source.AllLayersScope)
v.SetDefault("secrets.reveal-values", false)
v.SetDefault("secrets.skip-files-above-size", 1*file.MB)
v.SetDefault("secrets.additional-patterns", map[string]string{})
v.SetDefault("secrets.exclude-pattern-names", []string{})
}
func (cfg *secrets) parseConfigValues() error {
return cfg.Cataloger.parseConfigValues()
func defaultSecrets() secrets {
return secrets{
Cataloger: scope{
Scope: source.AllLayersScope.String(),
},
SkipFilesAboveSize: 1 * file.MB,
}
}
+19
View File
@@ -0,0 +1,19 @@
package options
type sourceCfg struct {
Name string `json:"name" yaml:"name" mapstructure:"name"`
Version string `json:"version" yaml:"version" mapstructure:"version"`
File fileSource `json:"file" yaml:"file" mapstructure:"file"`
}
type fileSource struct {
Digests []string `json:"digests" yaml:"digests" mapstructure:"digests"`
}
func defaultSourceCfg() sourceCfg {
return sourceCfg{
File: fileSource{
Digests: []string{"sha256"},
},
}
}
+11
View File
@@ -0,0 +1,11 @@
package options
type UpdateCheck struct {
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
}
func DefaultUpdateCheck() UpdateCheck {
return UpdateCheck{
CheckForAppUpdate: true,
}
}
-18
View File
@@ -1,18 +0,0 @@
package options
import (
"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/config"
"github.com/anchore/syft/internal/log"
)
func IsVerbose(app *config.Application) (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 app.Verbosity > 0 || isPipedInput
}
-17
View File
@@ -1,17 +0,0 @@
package options
import (
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
type VersionOptions struct {
Output string
}
var _ Interface = (*VersionOptions)(nil)
func (o *VersionOptions) AddFlags(cmd *cobra.Command, _ *viper.Viper) error {
cmd.Flags().StringVarP(&o.Output, "output", "o", "text", "format to show version information (available=[text, json])")
return nil
}
+144 -7
View File
@@ -1,26 +1,40 @@
package options
import (
"bytes"
"fmt"
"io"
"os"
"path"
"strings"
"github.com/hashicorp/go-multierror"
"github.com/mitchellh/go-homedir"
"github.com/anchore/syft/internal/bus"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/formats"
"github.com/anchore/syft/syft/formats/table"
"github.com/anchore/syft/syft/formats/template"
"github.com/anchore/syft/syft/sbom"
)
// makeWriter creates a sbom.Writer for output or returns an error. this will either return a valid writer
var _ sbom.Writer = (*sbomMultiWriter)(nil)
var _ interface {
io.Closer
sbom.Writer
} = (*sbomStreamWriter)(nil)
// makeSBOMWriter creates a sbom.Writer for output or returns an error. this will either return a valid writer
// or an error but neither both and if there is no error, sbom.Writer.Close() should be called
func MakeWriter(outputs []string, defaultFile, templateFilePath string) (sbom.Writer, error) {
outputOptions, err := parseOutputs(outputs, defaultFile, templateFilePath)
func makeSBOMWriter(outputs []string, defaultFile, templateFilePath string) (sbom.Writer, error) {
outputOptions, err := parseSBOMOutputFlags(outputs, defaultFile, templateFilePath)
if err != nil {
return nil, err
}
writer, err := sbom.NewWriter(outputOptions...)
writer, err := newSBOMMultiWriter(outputOptions...)
if err != nil {
return nil, err
}
@@ -28,8 +42,18 @@ func MakeWriter(outputs []string, defaultFile, templateFilePath string) (sbom.Wr
return writer, nil
}
// parseOptions utility to parse command-line option strings and retain the existing behavior of default format and file
func parseOutputs(outputs []string, defaultFile, templateFilePath string) (out []sbom.WriterOption, errs error) {
// makeSBOMWriterForFormat creates a sbom.Writer for for the given format or returns an error.
func makeSBOMWriterForFormat(format sbom.Format, path string) (sbom.Writer, error) {
writer, err := newSBOMMultiWriter(newSBOMWriterDescription(format, path))
if err != nil {
return nil, err
}
return writer, nil
}
// parseSBOMOutputFlags utility to parse command-line option strings and retain the existing behavior of default format and file
func parseSBOMOutputFlags(outputs []string, defaultFile, templateFilePath string) (out []sbomWriterDescription, errs error) {
// always should have one option -- we generally get the default of "table", but just make sure
if len(outputs) == 0 {
outputs = append(outputs, table.ID.String())
@@ -63,7 +87,120 @@ func parseOutputs(outputs []string, defaultFile, templateFilePath string) (out [
format = tmpl
}
out = append(out, sbom.NewWriterOption(format, file))
out = append(out, newSBOMWriterDescription(format, file))
}
return out, errs
}
// sbomWriterDescription Format and path strings used to create sbom.Writer
type sbomWriterDescription struct {
Format sbom.Format
Path string
}
func newSBOMWriterDescription(f sbom.Format, p string) sbomWriterDescription {
expandedPath, err := homedir.Expand(p)
if err != nil {
log.Warnf("could not expand given writer output path=%q: %w", p, err)
// ignore errors
expandedPath = p
}
return sbomWriterDescription{
Format: f,
Path: expandedPath,
}
}
// sbomMultiWriter holds a list of child sbom.Writers to apply all Write and Close operations to
type sbomMultiWriter struct {
writers []sbom.Writer
}
// newSBOMMultiWriter create all report writers from input options; if a file is not specified the given defaultWriter is used
func newSBOMMultiWriter(options ...sbomWriterDescription) (_ *sbomMultiWriter, err error) {
if len(options) == 0 {
return nil, fmt.Errorf("no output options provided")
}
out := &sbomMultiWriter{}
for _, option := range options {
switch len(option.Path) {
case 0:
out.writers = append(out.writers, &sbomPublisher{
format: option.Format,
})
default:
// create any missing subdirectories
dir := path.Dir(option.Path)
if dir != "" {
s, err := os.Stat(dir)
if err != nil {
err = os.MkdirAll(dir, 0755) // maybe should be os.ModePerm ?
if err != nil {
return nil, err
}
} else if !s.IsDir() {
return nil, fmt.Errorf("output path does not contain a valid directory: %s", option.Path)
}
}
fileOut, err := os.OpenFile(option.Path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
return nil, fmt.Errorf("unable to create report file: %w", err)
}
out.writers = append(out.writers, &sbomStreamWriter{
format: option.Format,
out: fileOut,
})
}
}
return out, nil
}
// Write writes the SBOM to all writers
func (m *sbomMultiWriter) Write(s sbom.SBOM) (errs error) {
for _, w := range m.writers {
err := w.Write(s)
if err != nil {
errs = multierror.Append(errs, fmt.Errorf("unable to write SBOM: %w", err))
}
}
return errs
}
// sbomStreamWriter implements sbom.Writer for a given format and io.Writer, also providing a close function for cleanup
type sbomStreamWriter struct {
format sbom.Format
out io.Writer
}
// Write the provided SBOM to the data stream
func (w *sbomStreamWriter) Write(s sbom.SBOM) error {
defer w.Close()
return w.format.Encode(w.out, s)
}
// Close any resources, such as open files
func (w *sbomStreamWriter) Close() error {
if closer, ok := w.out.(io.Closer); ok {
return closer.Close()
}
return nil
}
// sbomPublisher implements sbom.Writer that publishes results to the event bus
type sbomPublisher struct {
format sbom.Format
}
// Write the provided SBOM to the data stream
func (w *sbomPublisher) Write(s sbom.SBOM) error {
buf := &bytes.Buffer{}
if err := w.format.Encode(buf, s); err != nil {
return fmt.Errorf("unable to encode SBOM: %w", err)
}
bus.Report(buf.String())
return nil
}
+201 -2
View File
@@ -1,12 +1,18 @@
package options
import (
"io"
"path/filepath"
"strings"
"testing"
"github.com/docker/docker/pkg/homedir"
"github.com/stretchr/testify/assert"
"github.com/anchore/syft/syft/sbom"
)
func TestIsSupportedFormat(t *testing.T) {
func Test_MakeSBOMWriter(t *testing.T) {
tests := []struct {
outputs []string
wantErr assert.ErrorAssertionFunc
@@ -28,7 +34,200 @@ func TestIsSupportedFormat(t *testing.T) {
}
for _, tt := range tests {
_, err := MakeWriter(tt.outputs, "", "")
_, err := makeSBOMWriter(tt.outputs, "", "")
tt.wantErr(t, err)
}
}
func dummyEncoder(io.Writer, sbom.SBOM) error {
return nil
}
func dummyFormat(name string) sbom.Format {
return sbom.NewFormat(sbom.AnyVersion, dummyEncoder, nil, nil, sbom.FormatID(name))
}
func Test_newSBOMMultiWriter(t *testing.T) {
type writerConfig struct {
format string
file string
}
tmp := t.TempDir()
testName := func(options []sbomWriterDescription, err bool) string {
var out []string
for _, opt := range options {
out = append(out, string(opt.Format.ID())+"="+opt.Path)
}
errs := ""
if err {
errs = "(err)"
}
return strings.Join(out, ", ") + errs
}
tests := []struct {
outputs []sbomWriterDescription
err bool
expected []writerConfig
}{
{
outputs: []sbomWriterDescription{},
err: true,
},
{
outputs: []sbomWriterDescription{
{
Format: dummyFormat("table"),
Path: "",
},
},
expected: []writerConfig{
{
format: "table",
},
},
},
{
outputs: []sbomWriterDescription{
{
Format: dummyFormat("json"),
},
},
expected: []writerConfig{
{
format: "json",
},
},
},
{
outputs: []sbomWriterDescription{
{
Format: dummyFormat("json"),
Path: "test-2.json",
},
},
expected: []writerConfig{
{
format: "json",
file: "test-2.json",
},
},
},
{
outputs: []sbomWriterDescription{
{
Format: dummyFormat("json"),
Path: "test-3/1.json",
},
{
Format: dummyFormat("spdx-json"),
Path: "test-3/2.json",
},
},
expected: []writerConfig{
{
format: "json",
file: "test-3/1.json",
},
{
format: "spdx-json",
file: "test-3/2.json",
},
},
},
{
outputs: []sbomWriterDescription{
{
Format: dummyFormat("text"),
},
{
Format: dummyFormat("spdx-json"),
Path: "test-4.json",
},
},
expected: []writerConfig{
{
format: "text",
},
{
format: "spdx-json",
file: "test-4.json",
},
},
},
}
for _, test := range tests {
t.Run(testName(test.outputs, test.err), func(t *testing.T) {
outputs := test.outputs
for i := range outputs {
if outputs[i].Path != "" {
outputs[i].Path = tmp + outputs[i].Path
}
}
mw, err := newSBOMMultiWriter(outputs...)
if test.err {
assert.Error(t, err)
return
} else {
assert.NoError(t, err)
}
assert.Len(t, mw.writers, len(test.expected))
for i, e := range test.expected {
switch w := mw.writers[i].(type) {
case *sbomStreamWriter:
assert.Equal(t, string(w.format.ID()), e.format)
if e.file != "" {
assert.NotNil(t, w.out)
} else {
assert.NotNil(t, w.out)
}
if e.file != "" {
assert.FileExists(t, tmp+e.file)
}
case *sbomPublisher:
assert.Equal(t, string(w.format.ID()), e.format)
default:
t.Fatalf("unknown writer type: %T", w)
}
}
})
}
}
func Test_newSBOMWriterDescription(t *testing.T) {
tests := []struct {
name string
path string
expected string
}{
{
name: "expand home dir",
path: "~/place.txt",
expected: filepath.Join(homedir.Get(), "place.txt"),
},
{
name: "passthrough other paths",
path: "/other/place.txt",
expected: "/other/place.txt",
},
{
name: "no path",
path: "",
expected: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
o := newSBOMWriterDescription(dummyFormat("table"), tt.path)
assert.Equal(t, tt.expected, o.Path)
})
}
}
-85
View File
@@ -1,85 +0,0 @@
package cli
import (
"fmt"
"log"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/anchore/syft/cmd/syft/cli/options"
"github.com/anchore/syft/cmd/syft/cli/packages"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/config"
)
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 formatted SBOM
{{.appName}} {{.command}} alpine:latest -o cyclonedx-json show a CycloneDX JSON formatted SBOM
{{.appName}} {{.command}} alpine:latest -o spdx show a SPDX 2.3 Tag-Value formatted SBOM
{{.appName}} {{.command}} alpine:latest -o spdx@2.2 show a SPDX 2.2 Tag-Value formatted SBOM
{{.appName}} {{.command}} alpine:latest -o spdx-json show a SPDX 2.3 JSON formatted SBOM
{{.appName}} {{.command}} alpine:latest -o spdx-json@2.2 show a SPDX 2.2 JSON formatted SBOM
{{.appName}} {{.command}} alpine:latest -vv show verbose debug information
{{.appName}} {{.command}} alpine:latest -o template -t my_format.tmpl show a SBOM formatted according to given template file
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, SIF container, or generic filesystem directory
`
schemeHelpHeader = "You can also explicitly specify the scheme to use:"
imageSchemeHelp = ` {{.appName}} {{.command}} docker:yourrepo/yourimage:tag explicitly use the Docker daemon
{{.appName}} {{.command}} podman:yourrepo/yourimage:tag explicitly use the Podman daemon
{{.appName}} {{.command}} registry:yourrepo/yourimage:tag pull image directly from a registry (no container runtime required)
{{.appName}} {{.command}} docker-archive:path/to/yourimage.tar use a tarball from disk for archives created from "docker save"
{{.appName}} {{.command}} oci-archive:path/to/yourimage.tar use a tarball from disk for OCI archives (from Skopeo or otherwise)
{{.appName}} {{.command}} oci-dir:path/to/yourimage read directly from a path on disk for OCI layout directories (from Skopeo or otherwise)
{{.appName}} {{.command}} singularity:path/to/yourimage.sif read directly from a Singularity Image Format (SIF) container on disk
`
nonImageSchemeHelp = ` {{.appName}} {{.command}} dir:path/to/yourproject read directly from a path on disk (any directory)
{{.appName}} {{.command}} file:path/to/yourproject/file read directly from a path on disk (any single file)
`
packagesSchemeHelp = "\n" + indent + schemeHelpHeader + "\n" + imageSchemeHelp + nonImageSchemeHelp
packagesHelp = packagesExample + packagesSchemeHelp
)
//nolint:dupl
func Packages(v *viper.Viper, app *config.Application, ro *options.RootOptions, po *options.PackagesOptions) *cobra.Command {
cmd := &cobra.Command{
Use: "packages [SOURCE]",
Short: "Generate a package SBOM",
Long: "Generate a packaged-based Software Bill Of Materials (SBOM) from container images and filesystems",
Example: internal.Tprintf(packagesHelp, map[string]interface{}{
"appName": internal.ApplicationName,
"command": "packages",
}),
Args: func(cmd *cobra.Command, args []string) error {
if err := app.LoadAllValues(v, ro.Config); err != nil {
return fmt.Errorf("invalid application config: %w", err)
}
// configure logging for command
newLogWrapper(app)
logApplicationConfig(app)
return validateArgs(cmd, args)
},
SilenceUsage: true,
SilenceErrors: true,
RunE: func(cmd *cobra.Command, args []string) error {
if app.CheckForAppUpdate {
checkForApplicationUpdate()
}
return packages.Run(cmd.Context(), app, args)
},
}
err := po.AddFlags(cmd, v)
if err != nil {
log.Fatal(err)
}
return cmd
}
-151
View File
@@ -1,151 +0,0 @@
package packages
import (
"context"
"fmt"
"github.com/wagoodman/go-partybus"
"github.com/anchore/stereoscope"
"github.com/anchore/syft/cmd/syft/cli/eventloop"
"github.com/anchore/syft/cmd/syft/cli/options"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/bus"
"github.com/anchore/syft/internal/config"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/ui"
"github.com/anchore/syft/internal/version"
"github.com/anchore/syft/syft"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/event"
"github.com/anchore/syft/syft/formats/template"
"github.com/anchore/syft/syft/sbom"
"github.com/anchore/syft/syft/source"
)
func Run(_ context.Context, app *config.Application, args []string) error {
err := ValidateOutputOptions(app)
if err != nil {
return err
}
writer, err := options.MakeWriter(app.Outputs, app.File, app.OutputTemplatePath)
if err != nil {
return err
}
defer func() {
if err := writer.Close(); err != nil {
log.Warnf("unable to write to report destination: %w", err)
}
}()
// could be an image or a directory, with or without a scheme
userInput := args[0]
si, err := source.ParseInputWithName(userInput, app.Platform, app.Name, app.DefaultImagePullSource)
if err != nil {
return fmt.Errorf("could not generate source input for packages command: %w", err)
}
eventBus := partybus.NewBus()
stereoscope.SetBus(eventBus)
syft.SetBus(eventBus)
subscription := eventBus.Subscribe()
return eventloop.EventLoop(
execWorker(app, *si, writer),
eventloop.SetupSignals(),
subscription,
stereoscope.Cleanup,
ui.Select(options.IsVerbose(app), app.Quiet)...,
)
}
func execWorker(app *config.Application, si source.Input, writer sbom.Writer) <-chan error {
errs := make(chan error)
go func() {
defer close(errs)
src, cleanup, err := source.New(si, app.Registry.ToOptions(), app.Exclusions)
if cleanup != nil {
defer cleanup()
}
if err != nil {
errs <- fmt.Errorf("failed to construct source from user input %q: %w", si.UserInput, err)
return
}
s, err := GenerateSBOM(src, errs, app)
if err != nil {
errs <- err
return
}
if s == nil {
errs <- fmt.Errorf("no SBOM produced for %q", si.UserInput)
}
bus.Publish(partybus.Event{
Type: event.Exit,
Value: func() error { return writer.Write(*s) },
})
}()
return errs
}
func GenerateSBOM(src *source.Source, errs chan error, app *config.Application) (*sbom.SBOM, error) {
tasks, err := eventloop.Tasks(app)
if err != nil {
return nil, err
}
s := sbom.SBOM{
Source: src.Metadata,
Descriptor: sbom.Descriptor{
Name: internal.ApplicationName,
Version: version.FromBuild().Version,
Configuration: app,
},
}
buildRelationships(&s, src, tasks, errs)
return &s, nil
}
func buildRelationships(s *sbom.SBOM, src *source.Source, tasks []eventloop.Task, errs chan error) {
var relationships []<-chan artifact.Relationship
for _, task := range tasks {
c := make(chan artifact.Relationship)
relationships = append(relationships, c)
go eventloop.RunTask(task, &s.Artifacts, src, c, errs)
}
s.Relationships = append(s.Relationships, MergeRelationships(relationships...)...)
}
func MergeRelationships(cs ...<-chan artifact.Relationship) (relationships []artifact.Relationship) {
for _, c := range cs {
for n := range c {
relationships = append(relationships, n)
}
}
return relationships
}
func ValidateOutputOptions(app *config.Application) error {
var usesTemplateOutput bool
for _, o := range app.Outputs {
if o == template.ID.String() {
usesTemplateOutput = true
break
}
}
if usesTemplateOutput && app.OutputTemplatePath == "" {
return fmt.Errorf(`must specify path to template file when using "template" output format`)
}
return nil
}
-50
View File
@@ -1,50 +0,0 @@
package cli
import (
"fmt"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/anchore/syft/cmd/syft/cli/options"
"github.com/anchore/syft/cmd/syft/cli/poweruser"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/config"
)
const powerUserExample = ` {{.appName}} {{.command}} <image>
DEPRECATED - THIS COMMAND WILL BE REMOVED in v1.0.0
Only image sources are supported (e.g. docker: , podman: , docker-archive: , oci: , etc.), the directory source (dir:) is not supported, template outputs are not supported.
All behavior is controlled via application configuration and environment variables (see https://github.com/anchore/syft#configuration)
`
func PowerUser(v *viper.Viper, app *config.Application, ro *options.RootOptions) *cobra.Command {
cmd := &cobra.Command{
Use: "power-user [IMAGE]",
Short: "Run bulk operations on container images",
Example: internal.Tprintf(powerUserExample, map[string]interface{}{
"appName": internal.ApplicationName,
"command": "power-user",
}),
Args: func(cmd *cobra.Command, args []string) error {
if err := app.LoadAllValues(v, ro.Config); err != nil {
return fmt.Errorf("invalid application config: %w", err)
}
// configure logging for command
newLogWrapper(app)
logApplicationConfig(app)
return validateArgs(cmd, args)
},
Hidden: true,
SilenceUsage: true,
SilenceErrors: true,
RunE: func(cmd *cobra.Command, args []string) error {
if app.CheckForAppUpdate {
checkForApplicationUpdate()
}
return poweruser.Run(cmd.Context(), app, args)
},
}
return cmd
}
-119
View File
@@ -1,119 +0,0 @@
package poweruser
import (
"context"
"fmt"
"os"
"github.com/gookit/color"
"github.com/wagoodman/go-partybus"
"github.com/anchore/stereoscope"
"github.com/anchore/syft/cmd/syft/cli/eventloop"
"github.com/anchore/syft/cmd/syft/cli/options"
"github.com/anchore/syft/cmd/syft/cli/packages"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/bus"
"github.com/anchore/syft/internal/config"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/ui"
"github.com/anchore/syft/internal/version"
"github.com/anchore/syft/syft"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/event"
"github.com/anchore/syft/syft/formats/syftjson"
"github.com/anchore/syft/syft/sbom"
"github.com/anchore/syft/syft/source"
)
func Run(_ context.Context, app *config.Application, args []string) error {
f := syftjson.Format()
writer, err := sbom.NewWriter(sbom.WriterOption{
Format: f,
Path: app.File,
})
if err != nil {
return err
}
defer func() {
if err := writer.Close(); err != nil {
log.Warnf("unable to write to report destination: %+v", err)
}
// inform user at end of run that command will be removed
deprecated := color.Style{color.Red, color.OpBold}.Sprint("DEPRECATED: This command will be removed in v1.0.0")
fmt.Fprintln(os.Stderr, deprecated)
}()
userInput := args[0]
si, err := source.ParseInputWithName(userInput, app.Platform, app.Name, app.DefaultImagePullSource)
if err != nil {
return fmt.Errorf("could not generate source input for packages command: %w", err)
}
eventBus := partybus.NewBus()
stereoscope.SetBus(eventBus)
syft.SetBus(eventBus)
subscription := eventBus.Subscribe()
return eventloop.EventLoop(
execWorker(app, *si, writer),
eventloop.SetupSignals(),
subscription,
stereoscope.Cleanup,
ui.Select(options.IsVerbose(app), app.Quiet)...,
)
}
func execWorker(app *config.Application, si source.Input, writer sbom.Writer) <-chan error {
errs := make(chan error)
go func() {
defer close(errs)
app.Secrets.Cataloger.Enabled = true
app.FileMetadata.Cataloger.Enabled = true
app.FileContents.Cataloger.Enabled = true
app.FileClassification.Cataloger.Enabled = true
tasks, err := eventloop.Tasks(app)
if err != nil {
errs <- err
return
}
src, cleanup, err := source.New(si, app.Registry.ToOptions(), app.Exclusions)
if err != nil {
errs <- err
return
}
if cleanup != nil {
defer cleanup()
}
s := sbom.SBOM{
Source: src.Metadata,
Descriptor: sbom.Descriptor{
Name: internal.ApplicationName,
Version: version.FromBuild().Version,
Configuration: app,
},
}
var relationships []<-chan artifact.Relationship
for _, task := range tasks {
c := make(chan artifact.Relationship)
relationships = append(relationships, c)
go eventloop.RunTask(task, &s.Artifacts, src, c, errs)
}
s.Relationships = append(s.Relationships, packages.MergeRelationships(relationships...)...)
bus.Publish(partybus.Event{
Type: event.Exit,
Value: func() error { return writer.Write(s) },
})
}()
return errs
}
+19
View File
@@ -0,0 +1,19 @@
[TestHandler_handleAttestationStarted/attesting_in_progress/task_line - 1]
⠋ Creating a thing running a thing
---
[TestHandler_handleAttestationStarted/attesting_in_progress/log - 1]
░░ contents
░░ of
░░ stuff!
---
[TestHandler_handleAttestationStarted/attesting_complete/task_line - 1]
✔ Created a thing running a thing
---
[TestHandler_handleAttestationStarted/attesting_complete/log - 1]
---
@@ -0,0 +1,16 @@
[TestHandler_handleCatalogerTaskStarted/cataloging_task_in_progress - 1]
some task title [some value]
---
[TestHandler_handleCatalogerTaskStarted/cataloging_sub_task_in_progress - 1]
└── some task title [some value]
---
[TestHandler_handleCatalogerTaskStarted/cataloging_sub_task_complete - 1]
✔ └── some task done [some value]
---
[TestHandler_handleCatalogerTaskStarted/cataloging_sub_task_complete_with_removal - 1]
---
@@ -0,0 +1,8 @@
[TestHandler_handleFetchImage/fetch_image_in_progress - 1]
⠋ Loading image ━━━━━━━━━━━━━━━━━━━━ [current] the-image
---
[TestHandler_handleFetchImage/fetch_image_complete - 1]
✔ Loaded image the-image
---
@@ -0,0 +1,8 @@
[TestHandler_handleFileDigestsCatalogerStarted/cataloging_in_progress - 1]
⠋ Cataloging file digests ━━━━━━━━━━━━━━━━━━━━ [current]
---
[TestHandler_handleFileDigestsCatalogerStarted/cataloging_complete - 1]
✔ Cataloged file digests
---
@@ -0,0 +1,8 @@
[TestHandler_handleFileIndexingStarted/cataloging_in_progress - 1]
⠋ Indexing file system ━━━━━━━━━━━━━━━━━━━━ [current] /some/path
---
[TestHandler_handleFileIndexingStarted/cataloging_complete - 1]
✔ Indexed file system /some/path
---
@@ -0,0 +1,8 @@
[TestHandler_handleFileMetadataCatalogerStarted/cataloging_in_progress - 1]
⠋ Cataloging file metadata ━━━━━━━━━━━━━━━━━━━━ [current]
---
[TestHandler_handleFileMetadataCatalogerStarted/cataloging_complete - 1]
✔ Cataloged file metadata
---
@@ -0,0 +1,16 @@
[TestHandler_handlePackageCatalogerStarted/cataloging_in_progress - 1]
⠋ Cataloging packages [50 packages]
---
[TestHandler_handlePackageCatalogerStarted/cataloging_only_files_complete - 1]
⠋ Cataloging packages [50 packages]
---
[TestHandler_handlePackageCatalogerStarted/cataloging_only_packages_complete - 1]
⠋ Cataloging packages [100 packages]
---
[TestHandler_handlePackageCatalogerStarted/cataloging_complete - 1]
✔ Cataloged packages [100 packages]
---
@@ -0,0 +1,12 @@
[Test_dockerPullStatusFormatter_Render/pulling - 1]
3 Layers▕▅▃ ▏[12 B / 30 B]
---
[Test_dockerPullStatusFormatter_Render/download_complete - 1]
3 Layers▕█▃ ▏[30 B] Extracting...
---
[Test_dockerPullStatusFormatter_Render/complete - 1]
3 Layers▕███▏[30 B] Extracting...
---
@@ -0,0 +1,8 @@
[TestHandler_handleReadImage/read_image_in_progress - 1]
⠋ Parsing image ━━━━━━━━━━━━━━━━━━━━ id
---
[TestHandler_handleReadImage/read_image_complete - 1]
✔ Parsed image id
---
@@ -0,0 +1,8 @@
[TestHandler_handleSecretsCatalogerStarted/cataloging_in_progress - 1]
⠋ Cataloging secrets ━━━━━━━━━━━━━━━━━━━━ [64 secrets]
---
[TestHandler_handleSecretsCatalogerStarted/cataloging_complete - 1]
✔ Cataloged secrets [64 secrets]
---
+255
View File
@@ -0,0 +1,255 @@
package ui
import (
"bufio"
"fmt"
"io"
"strings"
"sync"
"time"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/google/uuid"
"github.com/wagoodman/go-partybus"
"github.com/wagoodman/go-progress"
"github.com/zyedidia/generic/queue"
"github.com/anchore/bubbly/bubbles/taskprogress"
"github.com/anchore/syft/internal/log"
syftEventParsers "github.com/anchore/syft/syft/event/parsers"
)
var (
_ tea.Model = (*attestLogFrame)(nil)
)
type attestLogFrame struct {
reader *backgroundLineReader
prog progress.Progressable
lines []string
completed bool
failed bool
windowSize tea.WindowSizeMsg
id uint32
sequence int
updateDuration time.Duration
borderStype lipgloss.Style
}
// attestLogFrameTickMsg indicates that the timer has ticked and we should render a frame.
type attestLogFrameTickMsg struct {
Time time.Time
Sequence int
ID uint32
}
type backgroundLineReader struct {
limit int
lines *queue.Queue[string]
lock *sync.RWMutex
// This is added specifically for tests to assert when the background reader is done.
// The main UI uses the global ui wait group from the handler to otherwise block
// Shared concerns among multiple model made it difficult to test using the global wait group
// so this is added to allow tests to assert when the background reader is done.
running *sync.WaitGroup
}
func (m *Handler) handleAttestationStarted(e partybus.Event) []tea.Model {
reader, prog, taskInfo, err := syftEventParsers.ParseAttestationStartedEvent(e)
if err != nil {
log.WithFields("error", err).Warn("unable to parse event")
return nil
}
stage := progress.Stage{}
tsk := m.newTaskProgress(
taskprogress.Title{
Default: taskInfo.Title.Default,
Running: taskInfo.Title.WhileRunning,
Success: taskInfo.Title.OnSuccess,
},
taskprogress.WithStagedProgressable(
struct {
progress.Progressable
progress.Stager
}{
Progressable: prog,
Stager: &stage,
},
),
)
tsk.HideStageOnSuccess = false
if taskInfo.Context != "" {
tsk.Context = []string{taskInfo.Context}
}
borderStyle := tsk.HintStyle
return []tea.Model{
tsk,
newLogFrame(newBackgroundLineReader(m.Running, reader, &stage), prog, borderStyle),
}
}
func newLogFrame(reader *backgroundLineReader, prog progress.Progressable, borderStyle lipgloss.Style) attestLogFrame {
return attestLogFrame{
reader: reader,
prog: prog,
id: uuid.Must(uuid.NewUUID()).ID(),
updateDuration: 250 * time.Millisecond,
borderStype: borderStyle,
}
}
func newBackgroundLineReader(wg *sync.WaitGroup, reader io.Reader, stage *progress.Stage) *backgroundLineReader {
r := &backgroundLineReader{
limit: 7,
lock: &sync.RWMutex{},
lines: queue.New[string](),
running: &sync.WaitGroup{},
}
// tracks the background reader for the global handler wait group
wg.Add(1)
// tracks the background reader for the local wait group (used in tests to decouple from the global handler wait group)
r.running.Add(1)
go func() {
r.read(reader, stage)
wg.Done()
r.running.Done()
}()
return r
}
func (l *backgroundLineReader) read(reader io.Reader, stage *progress.Stage) {
s := bufio.NewScanner(reader)
for s.Scan() {
l.lock.Lock()
text := s.Text()
l.lines.Enqueue(text)
if strings.Contains(text, "tlog entry created with index") {
fields := strings.SplitN(text, ":", 2)
present := text
if len(fields) == 2 {
present = fmt.Sprintf("transparency log index: %s", fields[1])
}
stage.Current = present
} else if strings.Contains(text, "WARNING: skipping transparency log upload") {
stage.Current = "transparency log upload skipped"
}
// only show the last X lines of the shell output
for l.lines.Len() > l.limit {
l.lines.Dequeue()
}
l.lock.Unlock()
}
}
func (l backgroundLineReader) Lines() []string {
l.lock.RLock()
defer l.lock.RUnlock()
var lines []string
l.lines.Each(func(line string) {
lines = append(lines, line)
})
return lines
}
func (l attestLogFrame) Init() tea.Cmd {
// this is the periodic update of state information
return func() tea.Msg {
return attestLogFrameTickMsg{
// The time at which the tick occurred.
Time: time.Now(),
// The ID of the log frame that this message belongs to. This can be
// helpful when routing messages, however bear in mind that log frames
// will ignore messages that don't contain ID by default.
ID: l.id,
Sequence: l.sequence,
}
}
}
func (l attestLogFrame) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.WindowSizeMsg:
l.windowSize = msg
return l, nil
case attestLogFrameTickMsg:
l.lines = l.reader.Lines()
l.completed = progress.IsCompleted(l.prog)
err := l.prog.Error()
l.failed = err != nil && !progress.IsErrCompleted(err)
tickCmd := l.handleTick(msg)
return l, tickCmd
}
return l, nil
}
func (l attestLogFrame) View() string {
if l.completed && !l.failed {
return ""
}
sb := strings.Builder{}
for _, line := range l.lines {
sb.WriteString(fmt.Sprintf(" %s %s\n", l.borderStype.Render("░░"), line))
}
return sb.String()
}
func (l attestLogFrame) queueNextTick() tea.Cmd {
return tea.Tick(l.updateDuration, func(t time.Time) tea.Msg {
return attestLogFrameTickMsg{
Time: t,
ID: l.id,
Sequence: l.sequence,
}
})
}
func (l *attestLogFrame) handleTick(msg attestLogFrameTickMsg) tea.Cmd {
// If an ID is set, and the ID doesn't belong to this log frame, reject the message.
if msg.ID > 0 && msg.ID != l.id {
return nil
}
// If a sequence is set, and it's not the one we expect, reject the message.
// This prevents the log frame from receiving too many messages and
// thus updating too frequently.
if msg.Sequence > 0 && msg.Sequence != l.sequence {
return nil
}
l.sequence++
// note: even if the log is completed we should still respond to stage changes and window size events
return l.queueNextTick()
}
+133
View File
@@ -0,0 +1,133 @@
package ui
import (
"strings"
"testing"
"time"
tea "github.com/charmbracelet/bubbletea"
"github.com/gkampitakis/go-snaps/snaps"
"github.com/stretchr/testify/require"
"github.com/wagoodman/go-partybus"
"github.com/wagoodman/go-progress"
"github.com/anchore/bubbly/bubbles/taskprogress"
syftEvent "github.com/anchore/syft/syft/event"
"github.com/anchore/syft/syft/event/monitor"
)
func TestHandler_handleAttestationStarted(t *testing.T) {
tests := []struct {
name string
eventFn func(*testing.T) partybus.Event
iterations int
}{
{
name: "attesting in progress",
// note: this model depends on a background reader. Multiple iterations ensures that the
// reader has time to at least start and process the test fixture before the runModel
// test harness completes (which is a fake event loop anyway).
iterations: 1,
eventFn: func(t *testing.T) partybus.Event {
reader := strings.NewReader("contents\nof\nstuff!")
src := monitor.GenericTask{
Title: monitor.Title{
Default: "Create a thing",
WhileRunning: "Creating a thing",
OnSuccess: "Created a thing",
},
Context: "running a thing",
}
mon := progress.NewManual(-1)
mon.Set(50)
value := &monitor.ShellProgress{
Reader: reader,
Progressable: mon,
}
return partybus.Event{
Type: syftEvent.AttestationStarted,
Source: src,
Value: value,
}
},
},
{
name: "attesting complete",
// note: this model depends on a background reader. Multiple iterations ensures that the
// reader has time to at least start and process the test fixture before the runModel
// test harness completes (which is a fake event loop anyway).
iterations: 1,
eventFn: func(t *testing.T) partybus.Event {
reader := strings.NewReader("contents\nof\nstuff!")
src := monitor.GenericTask{
Title: monitor.Title{
Default: "Create a thing",
WhileRunning: "Creating a thing",
OnSuccess: "Created a thing",
},
Context: "running a thing",
}
mon := progress.NewManual(-1)
mon.Set(50)
mon.SetCompleted()
value := &monitor.ShellProgress{
Reader: reader,
Progressable: mon,
}
return partybus.Event{
Type: syftEvent.AttestationStarted,
Source: src,
Value: value,
}
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
event := tt.eventFn(t)
handler := New(DefaultHandlerConfig())
handler.WindowSize = tea.WindowSizeMsg{
Width: 100,
Height: 80,
}
models := handler.Handle(event)
require.Len(t, models, 2)
t.Run("task line", func(t *testing.T) {
tsk, ok := models[0].(taskprogress.Model)
require.True(t, ok)
got := runModel(t, tsk, tt.iterations, taskprogress.TickMsg{
Time: time.Now(),
Sequence: tsk.Sequence(),
ID: tsk.ID(),
})
t.Log(got)
snaps.MatchSnapshot(t, got)
})
t.Run("log", func(t *testing.T) {
log, ok := models[1].(attestLogFrame)
require.True(t, ok)
got := runModel(t, log, tt.iterations, attestLogFrameTickMsg{
Time: time.Now(),
Sequence: log.sequence,
ID: log.id,
}, log.reader.running)
t.Log(got)
snaps.MatchSnapshot(t, got)
})
})
}
}
+72
View File
@@ -0,0 +1,72 @@
package ui
import (
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/wagoodman/go-partybus"
"github.com/wagoodman/go-progress"
"github.com/anchore/bubbly/bubbles/taskprogress"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/event/monitor"
syftEventParsers "github.com/anchore/syft/syft/event/parsers"
)
var _ progress.Stager = (*catalogerTaskStageAdapter)(nil)
type catalogerTaskStageAdapter struct {
mon *monitor.CatalogerTask
}
func newCatalogerTaskStageAdapter(mon *monitor.CatalogerTask) *catalogerTaskStageAdapter {
return &catalogerTaskStageAdapter{
mon: mon,
}
}
func (c catalogerTaskStageAdapter) Stage() string {
return c.mon.GetValue()
}
func (m *Handler) handleCatalogerTaskStarted(e partybus.Event) []tea.Model {
mon, err := syftEventParsers.ParseCatalogerTaskStarted(e)
if err != nil {
log.WithFields("error", err).Warn("unable to parse event")
return nil
}
var prefix string
if mon.SubStatus {
// TODO: support list of sub-statuses, not just a single leaf
prefix = "└── "
}
tsk := m.newTaskProgress(
taskprogress.Title{
// TODO: prefix should not be part of the title, but instead a separate field that is aware of the tree structure
Default: prefix + mon.Title,
Running: prefix + mon.Title,
Success: prefix + mon.TitleOnCompletion,
},
taskprogress.WithStagedProgressable(
struct {
progress.Stager
progress.Progressable
}{
Progressable: mon.GetMonitor(),
Stager: newCatalogerTaskStageAdapter(mon),
},
),
)
// TODO: this isn't ideal since the model stays around after it is no longer needed, but it works for now
tsk.HideOnSuccess = mon.RemoveOnCompletion
tsk.HideStageOnSuccess = false
tsk.HideProgressOnSuccess = false
tsk.TitleStyle = lipgloss.NewStyle()
// TODO: this is a hack to get the spinner to not show up, but ideally the component would support making the spinner optional
tsk.Spinner.Spinner.Frames = []string{" "}
return []tea.Model{tsk}
}
@@ -0,0 +1,123 @@
package ui
import (
"testing"
"time"
tea "github.com/charmbracelet/bubbletea"
"github.com/gkampitakis/go-snaps/snaps"
"github.com/stretchr/testify/require"
"github.com/wagoodman/go-partybus"
"github.com/anchore/bubbly/bubbles/taskprogress"
syftEvent "github.com/anchore/syft/syft/event"
"github.com/anchore/syft/syft/event/monitor"
)
func TestHandler_handleCatalogerTaskStarted(t *testing.T) {
tests := []struct {
name string
eventFn func(*testing.T) partybus.Event
iterations int
}{
{
name: "cataloging task in progress",
eventFn: func(t *testing.T) partybus.Event {
src := &monitor.CatalogerTask{
SubStatus: false,
RemoveOnCompletion: false,
Title: "some task title",
TitleOnCompletion: "some task done",
}
src.SetValue("some value")
return partybus.Event{
Type: syftEvent.CatalogerTaskStarted,
Source: src,
}
},
},
{
name: "cataloging sub task in progress",
eventFn: func(t *testing.T) partybus.Event {
src := &monitor.CatalogerTask{
SubStatus: true,
RemoveOnCompletion: false,
Title: "some task title",
TitleOnCompletion: "some task done",
}
src.SetValue("some value")
return partybus.Event{
Type: syftEvent.CatalogerTaskStarted,
Source: src,
}
},
},
{
name: "cataloging sub task complete",
eventFn: func(t *testing.T) partybus.Event {
src := &monitor.CatalogerTask{
SubStatus: true,
RemoveOnCompletion: false,
Title: "some task title",
TitleOnCompletion: "some task done",
}
src.SetValue("some value")
src.SetCompleted()
return partybus.Event{
Type: syftEvent.CatalogerTaskStarted,
Source: src,
}
},
},
{
name: "cataloging sub task complete with removal",
eventFn: func(t *testing.T) partybus.Event {
src := &monitor.CatalogerTask{
SubStatus: true,
RemoveOnCompletion: true,
Title: "some task title",
TitleOnCompletion: "some task done",
}
src.SetValue("some value")
src.SetCompleted()
return partybus.Event{
Type: syftEvent.CatalogerTaskStarted,
Source: src,
}
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
event := tt.eventFn(t)
handler := New(DefaultHandlerConfig())
handler.WindowSize = tea.WindowSizeMsg{
Width: 100,
Height: 80,
}
models := handler.Handle(event)
require.Len(t, models, 1)
model := models[0]
tsk, ok := model.(taskprogress.Model)
require.True(t, ok)
got := runModel(t, tsk, tt.iterations, taskprogress.TickMsg{
Time: time.Now(),
Sequence: tsk.Sequence(),
ID: tsk.ID(),
})
t.Log(got)
snaps.MatchSnapshot(t, got)
})
}
}
+32
View File
@@ -0,0 +1,32 @@
package ui
import (
tea "github.com/charmbracelet/bubbletea"
"github.com/wagoodman/go-partybus"
"github.com/anchore/bubbly/bubbles/taskprogress"
stereoEventParsers "github.com/anchore/stereoscope/pkg/event/parsers"
"github.com/anchore/syft/internal/log"
)
func (m *Handler) handleFetchImage(e partybus.Event) []tea.Model {
imgName, prog, err := stereoEventParsers.ParseFetchImage(e)
if err != nil {
log.WithFields("error", err).Warn("unable to parse event")
return nil
}
tsk := m.newTaskProgress(
taskprogress.Title{
Default: "Load image",
Running: "Loading image",
Success: "Loaded image",
},
taskprogress.WithStagedProgressable(prog),
)
if imgName != "" {
tsk.Context = []string{imgName}
}
return []tea.Model{tsk}
}
@@ -0,0 +1,99 @@
package ui
import (
"testing"
"time"
tea "github.com/charmbracelet/bubbletea"
"github.com/gkampitakis/go-snaps/snaps"
"github.com/stretchr/testify/require"
"github.com/wagoodman/go-partybus"
"github.com/wagoodman/go-progress"
"github.com/anchore/bubbly/bubbles/taskprogress"
stereoscopeEvent "github.com/anchore/stereoscope/pkg/event"
)
func TestHandler_handleFetchImage(t *testing.T) {
tests := []struct {
name string
eventFn func(*testing.T) partybus.Event
iterations int
}{
{
name: "fetch image in progress",
eventFn: func(t *testing.T) partybus.Event {
prog := &progress.Manual{}
prog.SetTotal(100)
prog.Set(50)
mon := struct {
progress.Progressable
progress.Stager
}{
Progressable: prog,
Stager: &progress.Stage{
Current: "current",
},
}
return partybus.Event{
Type: stereoscopeEvent.FetchImage,
Source: "the-image",
Value: mon,
}
},
},
{
name: "fetch image complete",
eventFn: func(t *testing.T) partybus.Event {
prog := &progress.Manual{}
prog.SetTotal(100)
prog.Set(100)
prog.SetCompleted()
mon := struct {
progress.Progressable
progress.Stager
}{
Progressable: prog,
Stager: &progress.Stage{
Current: "current",
},
}
return partybus.Event{
Type: stereoscopeEvent.FetchImage,
Source: "the-image",
Value: mon,
}
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
event := tt.eventFn(t)
handler := New(DefaultHandlerConfig())
handler.WindowSize = tea.WindowSizeMsg{
Width: 100,
Height: 80,
}
models := handler.Handle(event)
require.Len(t, models, 1)
model := models[0]
tsk, ok := model.(taskprogress.Model)
require.True(t, ok)
got := runModel(t, tsk, tt.iterations, taskprogress.TickMsg{
Time: time.Now(),
Sequence: tsk.Sequence(),
ID: tsk.ID(),
})
t.Log(got)
snaps.MatchSnapshot(t, got)
})
}
}
@@ -0,0 +1,28 @@
package ui
import (
tea "github.com/charmbracelet/bubbletea"
"github.com/wagoodman/go-partybus"
"github.com/anchore/bubbly/bubbles/taskprogress"
"github.com/anchore/syft/internal/log"
syftEventParsers "github.com/anchore/syft/syft/event/parsers"
)
func (m *Handler) handleFileDigestsCatalogerStarted(e partybus.Event) []tea.Model {
prog, err := syftEventParsers.ParseFileDigestsCatalogingStarted(e)
if err != nil {
log.WithFields("error", err).Warn("unable to parse event")
return nil
}
tsk := m.newTaskProgress(
taskprogress.Title{
Default: "Catalog file digests",
Running: "Cataloging file digests",
Success: "Cataloged file digests",
}, taskprogress.WithStagedProgressable(prog),
)
return []tea.Model{tsk}
}
@@ -0,0 +1,97 @@
package ui
import (
"testing"
"time"
tea "github.com/charmbracelet/bubbletea"
"github.com/gkampitakis/go-snaps/snaps"
"github.com/stretchr/testify/require"
"github.com/wagoodman/go-partybus"
"github.com/wagoodman/go-progress"
"github.com/anchore/bubbly/bubbles/taskprogress"
syftEvent "github.com/anchore/syft/syft/event"
)
func TestHandler_handleFileDigestsCatalogerStarted(t *testing.T) {
tests := []struct {
name string
eventFn func(*testing.T) partybus.Event
iterations int
}{
{
name: "cataloging in progress",
eventFn: func(t *testing.T) partybus.Event {
prog := &progress.Manual{}
prog.SetTotal(100)
prog.Set(50)
mon := struct {
progress.Progressable
progress.Stager
}{
Progressable: prog,
Stager: &progress.Stage{
Current: "current",
},
}
return partybus.Event{
Type: syftEvent.FileDigestsCatalogerStarted,
Value: mon,
}
},
},
{
name: "cataloging complete",
eventFn: func(t *testing.T) partybus.Event {
prog := &progress.Manual{}
prog.SetTotal(100)
prog.Set(100)
prog.SetCompleted()
mon := struct {
progress.Progressable
progress.Stager
}{
Progressable: prog,
Stager: &progress.Stage{
Current: "current",
},
}
return partybus.Event{
Type: syftEvent.FileDigestsCatalogerStarted,
Value: mon,
}
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
event := tt.eventFn(t)
handler := New(DefaultHandlerConfig())
handler.WindowSize = tea.WindowSizeMsg{
Width: 100,
Height: 80,
}
models := handler.Handle(event)
require.Len(t, models, 1)
model := models[0]
tsk, ok := model.(taskprogress.Model)
require.True(t, ok)
got := runModel(t, tsk, tt.iterations, taskprogress.TickMsg{
Time: time.Now(),
Sequence: tsk.Sequence(),
ID: tsk.ID(),
})
t.Log(got)
snaps.MatchSnapshot(t, got)
})
}
}
+31
View File
@@ -0,0 +1,31 @@
package ui
import (
tea "github.com/charmbracelet/bubbletea"
"github.com/wagoodman/go-partybus"
"github.com/anchore/bubbly/bubbles/taskprogress"
"github.com/anchore/syft/internal/log"
syftEventParsers "github.com/anchore/syft/syft/event/parsers"
)
func (m *Handler) handleFileIndexingStarted(e partybus.Event) []tea.Model {
path, prog, err := syftEventParsers.ParseFileIndexingStarted(e)
if err != nil {
log.WithFields("error", err).Warn("unable to parse event")
return nil
}
tsk := m.newTaskProgress(
taskprogress.Title{
Default: "Index files system",
Running: "Indexing file system",
Success: "Indexed file system",
},
taskprogress.WithStagedProgressable(prog),
)
tsk.Context = []string{path}
return []tea.Model{tsk}
}
@@ -0,0 +1,99 @@
package ui
import (
"testing"
"time"
tea "github.com/charmbracelet/bubbletea"
"github.com/gkampitakis/go-snaps/snaps"
"github.com/stretchr/testify/require"
"github.com/wagoodman/go-partybus"
"github.com/wagoodman/go-progress"
"github.com/anchore/bubbly/bubbles/taskprogress"
syftEvent "github.com/anchore/syft/syft/event"
)
func TestHandler_handleFileIndexingStarted(t *testing.T) {
tests := []struct {
name string
eventFn func(*testing.T) partybus.Event
iterations int
}{
{
name: "cataloging in progress",
eventFn: func(t *testing.T) partybus.Event {
prog := &progress.Manual{}
prog.SetTotal(100)
prog.Set(50)
mon := struct {
progress.Progressable
progress.Stager
}{
Progressable: prog,
Stager: &progress.Stage{
Current: "current",
},
}
return partybus.Event{
Type: syftEvent.FileIndexingStarted,
Source: "/some/path",
Value: mon,
}
},
},
{
name: "cataloging complete",
eventFn: func(t *testing.T) partybus.Event {
prog := &progress.Manual{}
prog.SetTotal(100)
prog.Set(100)
prog.SetCompleted()
mon := struct {
progress.Progressable
progress.Stager
}{
Progressable: prog,
Stager: &progress.Stage{
Current: "current",
},
}
return partybus.Event{
Type: syftEvent.FileIndexingStarted,
Source: "/some/path",
Value: mon,
}
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
event := tt.eventFn(t)
handler := New(DefaultHandlerConfig())
handler.WindowSize = tea.WindowSizeMsg{
Width: 100,
Height: 80,
}
models := handler.Handle(event)
require.Len(t, models, 1)
model := models[0]
tsk, ok := model.(taskprogress.Model)
require.True(t, ok)
got := runModel(t, tsk, tt.iterations, taskprogress.TickMsg{
Time: time.Now(),
Sequence: tsk.Sequence(),
ID: tsk.ID(),
})
t.Log(got)
snaps.MatchSnapshot(t, got)
})
}
}
@@ -0,0 +1,29 @@
package ui
import (
tea "github.com/charmbracelet/bubbletea"
"github.com/wagoodman/go-partybus"
"github.com/anchore/bubbly/bubbles/taskprogress"
"github.com/anchore/syft/internal/log"
syftEventParsers "github.com/anchore/syft/syft/event/parsers"
)
func (m *Handler) handleFileMetadataCatalogerStarted(e partybus.Event) []tea.Model {
prog, err := syftEventParsers.ParseFileMetadataCatalogingStarted(e)
if err != nil {
log.WithFields("error", err).Warn("unable to parse event")
return nil
}
tsk := m.newTaskProgress(
taskprogress.Title{
Default: "Catalog file metadata",
Running: "Cataloging file metadata",
Success: "Cataloged file metadata",
},
taskprogress.WithStagedProgressable(prog),
)
return []tea.Model{tsk}
}
@@ -0,0 +1,97 @@
package ui
import (
"testing"
"time"
tea "github.com/charmbracelet/bubbletea"
"github.com/gkampitakis/go-snaps/snaps"
"github.com/stretchr/testify/require"
"github.com/wagoodman/go-partybus"
"github.com/wagoodman/go-progress"
"github.com/anchore/bubbly/bubbles/taskprogress"
syftEvent "github.com/anchore/syft/syft/event"
)
func TestHandler_handleFileMetadataCatalogerStarted(t *testing.T) {
tests := []struct {
name string
eventFn func(*testing.T) partybus.Event
iterations int
}{
{
name: "cataloging in progress",
eventFn: func(t *testing.T) partybus.Event {
prog := &progress.Manual{}
prog.SetTotal(100)
prog.Set(50)
mon := struct {
progress.Progressable
progress.Stager
}{
Progressable: prog,
Stager: &progress.Stage{
Current: "current",
},
}
return partybus.Event{
Type: syftEvent.FileMetadataCatalogerStarted,
Value: mon,
}
},
},
{
name: "cataloging complete",
eventFn: func(t *testing.T) partybus.Event {
prog := &progress.Manual{}
prog.SetTotal(100)
prog.Set(100)
prog.SetCompleted()
mon := struct {
progress.Progressable
progress.Stager
}{
Progressable: prog,
Stager: &progress.Stage{
Current: "current",
},
}
return partybus.Event{
Type: syftEvent.FileMetadataCatalogerStarted,
Value: mon,
}
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
event := tt.eventFn(t)
handler := New(DefaultHandlerConfig())
handler.WindowSize = tea.WindowSizeMsg{
Width: 100,
Height: 80,
}
models := handler.Handle(event)
require.Len(t, models, 1)
model := models[0]
tsk, ok := model.(taskprogress.Model)
require.True(t, ok)
got := runModel(t, tsk, tt.iterations, taskprogress.TickMsg{
Time: time.Now(),
Sequence: tsk.Sequence(),
ID: tsk.ID(),
})
t.Log(got)
snaps.MatchSnapshot(t, got)
})
}
}
@@ -0,0 +1,87 @@
package ui
import (
"fmt"
tea "github.com/charmbracelet/bubbletea"
"github.com/wagoodman/go-partybus"
"github.com/wagoodman/go-progress"
"github.com/anchore/bubbly/bubbles/taskprogress"
"github.com/anchore/syft/internal/log"
syftEventParsers "github.com/anchore/syft/syft/event/parsers"
"github.com/anchore/syft/syft/pkg/cataloger"
)
var _ progress.StagedProgressable = (*packageCatalogerProgressAdapter)(nil)
type packageCatalogerProgressAdapter struct {
monitor *cataloger.Monitor
monitors []progress.Monitorable
}
func newPackageCatalogerProgressAdapter(monitor *cataloger.Monitor) packageCatalogerProgressAdapter {
return packageCatalogerProgressAdapter{
monitor: monitor,
monitors: []progress.Monitorable{
monitor.FilesProcessed,
monitor.PackagesDiscovered,
},
}
}
func (p packageCatalogerProgressAdapter) Stage() string {
return fmt.Sprintf("%d packages", p.monitor.PackagesDiscovered.Current())
}
func (p packageCatalogerProgressAdapter) Current() int64 {
return p.monitor.PackagesDiscovered.Current()
}
func (p packageCatalogerProgressAdapter) Error() error {
completedMonitors := 0
for _, monitor := range p.monitors {
err := monitor.Error()
if err == nil {
continue
}
if progress.IsErrCompleted(err) {
completedMonitors++
continue
}
// something went wrong
return err
}
if completedMonitors == len(p.monitors) && len(p.monitors) > 0 {
return p.monitors[0].Error()
}
return nil
}
func (p packageCatalogerProgressAdapter) Size() int64 {
// this is an inherently unknown value (indeterminate total number of packages to discover)
return -1
}
func (m *Handler) handlePackageCatalogerStarted(e partybus.Event) []tea.Model {
monitor, err := syftEventParsers.ParsePackageCatalogerStarted(e)
if err != nil {
log.WithFields("error", err).Warn("unable to parse event")
return nil
}
tsk := m.newTaskProgress(
taskprogress.Title{
Default: "Catalog packages",
Running: "Cataloging packages",
Success: "Cataloged packages",
},
taskprogress.WithStagedProgressable(
newPackageCatalogerProgressAdapter(monitor),
),
)
tsk.HideStageOnSuccess = false
return []tea.Model{tsk}
}
@@ -0,0 +1,133 @@
package ui
import (
"testing"
"time"
tea "github.com/charmbracelet/bubbletea"
"github.com/gkampitakis/go-snaps/snaps"
"github.com/stretchr/testify/require"
"github.com/wagoodman/go-partybus"
"github.com/wagoodman/go-progress"
"github.com/anchore/bubbly/bubbles/taskprogress"
syftEvent "github.com/anchore/syft/syft/event"
"github.com/anchore/syft/syft/pkg/cataloger"
)
func TestHandler_handlePackageCatalogerStarted(t *testing.T) {
tests := []struct {
name string
eventFn func(*testing.T) partybus.Event
iterations int
}{
{
name: "cataloging in progress",
eventFn: func(t *testing.T) partybus.Event {
prog := &progress.Manual{}
prog.SetTotal(100)
prog.Set(50)
mon := cataloger.Monitor{
FilesProcessed: progress.NewManual(-1),
PackagesDiscovered: prog,
}
return partybus.Event{
Type: syftEvent.PackageCatalogerStarted,
Value: mon,
}
},
},
{
name: "cataloging only files complete",
eventFn: func(t *testing.T) partybus.Event {
prog := &progress.Manual{}
prog.SetTotal(100)
prog.Set(50)
files := progress.NewManual(-1)
files.SetCompleted()
mon := cataloger.Monitor{
FilesProcessed: files,
PackagesDiscovered: prog,
}
return partybus.Event{
Type: syftEvent.PackageCatalogerStarted,
Value: mon,
}
},
},
{
name: "cataloging only packages complete",
eventFn: func(t *testing.T) partybus.Event {
prog := &progress.Manual{}
prog.SetTotal(100)
prog.Set(100)
prog.SetCompleted()
files := progress.NewManual(-1)
mon := cataloger.Monitor{
FilesProcessed: files,
PackagesDiscovered: prog,
}
return partybus.Event{
Type: syftEvent.PackageCatalogerStarted,
Value: mon,
}
},
},
{
name: "cataloging complete",
eventFn: func(t *testing.T) partybus.Event {
prog := &progress.Manual{}
prog.SetTotal(100)
prog.Set(100)
prog.SetCompleted()
files := progress.NewManual(-1)
files.SetCompleted()
mon := cataloger.Monitor{
FilesProcessed: files,
PackagesDiscovered: prog,
}
return partybus.Event{
Type: syftEvent.PackageCatalogerStarted,
Value: mon,
}
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
event := tt.eventFn(t)
handler := New(DefaultHandlerConfig())
handler.WindowSize = tea.WindowSizeMsg{
Width: 100,
Height: 80,
}
models := handler.Handle(event)
require.Len(t, models, 1)
model := models[0]
tsk, ok := model.(taskprogress.Model)
require.True(t, ok)
got := runModel(t, tsk, tt.iterations, taskprogress.TickMsg{
Time: time.Now(),
Sequence: tsk.Sequence(),
ID: tsk.ID(),
})
t.Log(got)
snaps.MatchSnapshot(t, got)
})
}
}
@@ -0,0 +1,190 @@
package ui
import (
"fmt"
"strings"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/dustin/go-humanize"
"github.com/wagoodman/go-partybus"
"github.com/wagoodman/go-progress"
"github.com/anchore/bubbly/bubbles/taskprogress"
stereoscopeParsers "github.com/anchore/stereoscope/pkg/event/parsers"
"github.com/anchore/stereoscope/pkg/image/containerd"
"github.com/anchore/syft/internal/log"
)
var _ interface {
progress.Stager
progress.Progressable
} = (*containerdPullProgressAdapter)(nil)
type containerdPullStatus interface {
Complete() bool
Layers() []containerd.LayerID
Current(containerd.LayerID) progress.Progressable
}
type containerdPullProgressAdapter struct {
status containerdPullStatus
formatter containerdPullStatusFormatter
}
type containerdPullStatusFormatter struct {
auxInfoStyle lipgloss.Style
pullCompletedStyle lipgloss.Style
pullDownloadStyle lipgloss.Style
pullStageChars []string
layerCaps []string
}
func (m *Handler) handlePullContainerdImage(e partybus.Event) []tea.Model {
_, pullStatus, err := stereoscopeParsers.ParsePullContainerdImage(e)
if err != nil {
log.WithFields("error", err).Warn("unable to parse event")
return nil
}
if pullStatus == nil {
return nil
}
tsk := m.newTaskProgress(
taskprogress.Title{
Default: "Pull image",
Running: "Pulling image",
Success: "Pulled image",
},
taskprogress.WithStagedProgressable(
newContainerdPullProgressAdapter(pullStatus),
),
)
tsk.HintStyle = lipgloss.NewStyle()
tsk.HintEndCaps = nil
return []tea.Model{tsk}
}
func newContainerdPullProgressAdapter(status *containerd.PullStatus) *containerdPullProgressAdapter {
return &containerdPullProgressAdapter{
status: status,
formatter: newContainerdPullStatusFormatter(),
}
}
func newContainerdPullStatusFormatter() containerdPullStatusFormatter {
return containerdPullStatusFormatter{
auxInfoStyle: lipgloss.NewStyle().Foreground(lipgloss.Color("#777777")),
pullCompletedStyle: lipgloss.NewStyle().Foreground(lipgloss.Color("#fcba03")),
pullDownloadStyle: lipgloss.NewStyle().Foreground(lipgloss.Color("#777777")),
pullStageChars: strings.Split("▁▃▄▅▆▇█", ""),
layerCaps: strings.Split("▕▏", ""),
}
}
func (d containerdPullProgressAdapter) Size() int64 {
return -1
}
func (d containerdPullProgressAdapter) Current() int64 {
return 1
}
func (d containerdPullProgressAdapter) Error() error {
if d.status.Complete() {
return progress.ErrCompleted
}
// TODO: return intermediate error indications
return nil
}
func (d containerdPullProgressAdapter) Stage() string {
return d.formatter.Render(d.status)
}
// Render crafts the given docker image pull status summarized into a single line.
func (f containerdPullStatusFormatter) Render(pullStatus containerdPullStatus) string {
var size, current uint64
layers := pullStatus.Layers()
status := make(map[containerd.LayerID]progress.Progressable)
completed := make([]string, len(layers))
// fetch the current state
for idx, layer := range layers {
completed[idx] = " "
status[layer] = pullStatus.Current(layer)
}
numCompleted := 0
for idx, layer := range layers {
prog := status[layer]
curN := prog.Current()
curSize := prog.Size()
if progress.IsCompleted(prog) {
input := f.pullStageChars[len(f.pullStageChars)-1]
completed[idx] = f.formatPullPhase(prog.Error() != nil, input)
} else if curN != 0 {
var ratio float64
switch {
case curN == 0 || curSize < 0:
ratio = 0
case curN >= curSize:
ratio = 1
default:
ratio = float64(curN) / float64(curSize)
}
i := int(ratio * float64(len(f.pullStageChars)-1))
input := f.pullStageChars[i]
completed[idx] = f.formatPullPhase(status[layer].Error() != nil, input)
}
if progress.IsErrCompleted(status[layer].Error()) {
numCompleted++
}
}
for _, layer := range layers {
prog := status[layer]
size += uint64(prog.Size())
current += uint64(prog.Current())
}
var progStr, auxInfo string
if len(layers) > 0 {
render := strings.Join(completed, "")
prefix := f.pullCompletedStyle.Render(fmt.Sprintf("%d Layers", len(layers)))
auxInfo = f.auxInfoStyle.Render(fmt.Sprintf("[%s / %s]", humanize.Bytes(current), humanize.Bytes(size)))
if len(layers) == numCompleted {
auxInfo = f.auxInfoStyle.Render(fmt.Sprintf("[%s] Extracting...", humanize.Bytes(size)))
}
progStr = fmt.Sprintf("%s%s%s%s", prefix, f.layerCap(false), render, f.layerCap(true))
}
return progStr + auxInfo
}
// formatPullPhase returns a single character that represents the status of a layer pull.
func (f containerdPullStatusFormatter) formatPullPhase(completed bool, inputStr string) string {
if completed {
return f.pullCompletedStyle.Render(f.pullStageChars[len(f.pullStageChars)-1])
}
return f.pullDownloadStyle.Render(inputStr)
}
func (f containerdPullStatusFormatter) layerCap(end bool) string {
l := len(f.layerCaps)
if l == 0 {
return ""
}
if end {
return f.layerCaps[l-1]
}
return f.layerCaps[0]
}
+201
View File
@@ -0,0 +1,201 @@
package ui
import (
"fmt"
"strings"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/dustin/go-humanize"
"github.com/wagoodman/go-partybus"
"github.com/wagoodman/go-progress"
"github.com/anchore/bubbly/bubbles/taskprogress"
stereoscopeParsers "github.com/anchore/stereoscope/pkg/event/parsers"
"github.com/anchore/stereoscope/pkg/image/docker"
"github.com/anchore/syft/internal/log"
)
var _ interface {
progress.Stager
progress.Progressable
} = (*dockerPullProgressAdapter)(nil)
type dockerPullStatus interface {
Complete() bool
Layers() []docker.LayerID
Current(docker.LayerID) docker.LayerState
}
type dockerPullProgressAdapter struct {
status dockerPullStatus
formatter dockerPullStatusFormatter
}
type dockerPullStatusFormatter struct {
auxInfoStyle lipgloss.Style
dockerPullCompletedStyle lipgloss.Style
dockerPullDownloadStyle lipgloss.Style
dockerPullExtractStyle lipgloss.Style
dockerPullStageChars []string
layerCaps []string
}
func (m *Handler) handlePullDockerImage(e partybus.Event) []tea.Model {
_, pullStatus, err := stereoscopeParsers.ParsePullDockerImage(e)
if err != nil {
log.WithFields("error", err).Warn("unable to parse event")
return nil
}
tsk := m.newTaskProgress(
taskprogress.Title{
Default: "Pull image",
Running: "Pulling image",
Success: "Pulled image",
},
taskprogress.WithStagedProgressable(
newDockerPullProgressAdapter(pullStatus),
),
)
tsk.HintStyle = lipgloss.NewStyle()
tsk.HintEndCaps = nil
return []tea.Model{tsk}
}
func newDockerPullProgressAdapter(status dockerPullStatus) *dockerPullProgressAdapter {
return &dockerPullProgressAdapter{
status: status,
formatter: newDockerPullStatusFormatter(),
}
}
func newDockerPullStatusFormatter() dockerPullStatusFormatter {
return dockerPullStatusFormatter{
auxInfoStyle: lipgloss.NewStyle().Foreground(lipgloss.Color("#777777")),
dockerPullCompletedStyle: lipgloss.NewStyle().Foreground(lipgloss.Color("#fcba03")),
dockerPullDownloadStyle: lipgloss.NewStyle().Foreground(lipgloss.Color("#777777")),
dockerPullExtractStyle: lipgloss.NewStyle().Foreground(lipgloss.Color("#ffffff")),
dockerPullStageChars: strings.Split("▁▃▄▅▆▇█", ""),
layerCaps: strings.Split("▕▏", ""),
}
}
func (d dockerPullProgressAdapter) Size() int64 {
return -1
}
func (d dockerPullProgressAdapter) Current() int64 {
return 1
}
func (d dockerPullProgressAdapter) Error() error {
if d.status.Complete() {
return progress.ErrCompleted
}
// TODO: return intermediate error indications
return nil
}
func (d dockerPullProgressAdapter) Stage() string {
return d.formatter.Render(d.status)
}
// Render crafts the given docker image pull status summarized into a single line.
func (f dockerPullStatusFormatter) Render(pullStatus dockerPullStatus) string {
var size, current uint64
layers := pullStatus.Layers()
status := make(map[docker.LayerID]docker.LayerState)
completed := make([]string, len(layers))
// fetch the current state
for idx, layer := range layers {
completed[idx] = " "
status[layer] = pullStatus.Current(layer)
}
numCompleted := 0
for idx, layer := range layers {
prog := status[layer].PhaseProgress
curN := prog.Current()
curSize := prog.Size()
if progress.IsCompleted(prog) {
input := f.dockerPullStageChars[len(f.dockerPullStageChars)-1]
completed[idx] = f.formatDockerPullPhase(status[layer].Phase, input)
} else if curN != 0 {
var ratio float64
switch {
case curN == 0 || curSize < 0:
ratio = 0
case curN >= curSize:
ratio = 1
default:
ratio = float64(curN) / float64(curSize)
}
i := int(ratio * float64(len(f.dockerPullStageChars)-1))
input := f.dockerPullStageChars[i]
completed[idx] = f.formatDockerPullPhase(status[layer].Phase, input)
}
if progress.IsErrCompleted(status[layer].DownloadProgress.Error()) {
numCompleted++
}
}
for _, layer := range layers {
prog := status[layer].DownloadProgress
size += uint64(prog.Size())
current += uint64(prog.Current())
}
var progStr, auxInfo string
if len(layers) > 0 {
render := strings.Join(completed, "")
prefix := f.dockerPullCompletedStyle.Render(fmt.Sprintf("%d Layers", len(layers)))
auxInfo = f.auxInfoStyle.Render(fmt.Sprintf("[%s / %s]", humanize.Bytes(current), humanize.Bytes(size)))
if len(layers) == numCompleted {
auxInfo = f.auxInfoStyle.Render(fmt.Sprintf("[%s] Extracting...", humanize.Bytes(size)))
}
progStr = fmt.Sprintf("%s%s%s%s", prefix, f.layerCap(false), render, f.layerCap(true))
}
return progStr + auxInfo
}
// formatDockerPullPhase returns a single character that represents the status of a layer pull.
func (f dockerPullStatusFormatter) formatDockerPullPhase(phase docker.PullPhase, inputStr string) string {
switch phase {
case docker.WaitingPhase:
// ignore any progress related to waiting
return " "
case docker.PullingFsPhase, docker.DownloadingPhase:
return f.dockerPullDownloadStyle.Render(inputStr)
case docker.DownloadCompletePhase:
return f.dockerPullDownloadStyle.Render(f.dockerPullStageChars[len(f.dockerPullStageChars)-1])
case docker.ExtractingPhase:
return f.dockerPullExtractStyle.Render(inputStr)
case docker.VerifyingChecksumPhase, docker.PullCompletePhase:
return f.dockerPullCompletedStyle.Render(inputStr)
case docker.AlreadyExistsPhase:
return f.dockerPullCompletedStyle.Render(f.dockerPullStageChars[len(f.dockerPullStageChars)-1])
default:
return inputStr
}
}
func (f dockerPullStatusFormatter) layerCap(end bool) string {
l := len(f.layerCaps)
if l == 0 {
return ""
}
if end {
return f.layerCaps[l-1]
}
return f.layerCaps[0]
}
@@ -0,0 +1,163 @@
package ui
import (
"testing"
"github.com/gkampitakis/go-snaps/snaps"
"github.com/wagoodman/go-progress"
"github.com/anchore/stereoscope/pkg/image/docker"
)
var _ dockerPullStatus = (*mockDockerPullStatus)(nil)
type mockDockerPullStatus struct {
complete bool
layers []docker.LayerID
current map[docker.LayerID]docker.LayerState
}
func (m mockDockerPullStatus) Complete() bool {
return m.complete
}
func (m mockDockerPullStatus) Layers() []docker.LayerID {
return m.layers
}
func (m mockDockerPullStatus) Current(id docker.LayerID) docker.LayerState {
return m.current[id]
}
func Test_dockerPullStatusFormatter_Render(t *testing.T) {
tests := []struct {
name string
status dockerPullStatus
}{
{
name: "pulling",
status: func() dockerPullStatus {
complete := progress.NewManual(10)
complete.Set(10)
complete.SetCompleted()
quarter := progress.NewManual(10)
quarter.Set(2)
half := progress.NewManual(10)
half.Set(6)
empty := progress.NewManual(10)
return mockDockerPullStatus{
complete: false,
layers: []docker.LayerID{
"sha256:1",
"sha256:2",
"sha256:3",
},
current: map[docker.LayerID]docker.LayerState{
"sha256:1": {
Phase: docker.ExtractingPhase,
PhaseProgress: half,
DownloadProgress: complete,
},
"sha256:2": {
Phase: docker.DownloadingPhase,
PhaseProgress: quarter,
DownloadProgress: quarter,
},
"sha256:3": {
Phase: docker.WaitingPhase,
PhaseProgress: empty,
DownloadProgress: empty,
},
},
}
}(),
},
{
name: "download complete",
status: func() dockerPullStatus {
complete := progress.NewManual(10)
complete.Set(10)
complete.SetCompleted()
quarter := progress.NewManual(10)
quarter.Set(2)
half := progress.NewManual(10)
half.Set(6)
empty := progress.NewManual(10)
return mockDockerPullStatus{
complete: false,
layers: []docker.LayerID{
"sha256:1",
"sha256:2",
"sha256:3",
},
current: map[docker.LayerID]docker.LayerState{
"sha256:1": {
Phase: docker.ExtractingPhase,
PhaseProgress: complete,
DownloadProgress: complete,
},
"sha256:2": {
Phase: docker.ExtractingPhase,
PhaseProgress: quarter,
DownloadProgress: complete,
},
"sha256:3": {
Phase: docker.ExtractingPhase,
PhaseProgress: empty,
DownloadProgress: complete,
},
},
}
}(),
},
{
name: "complete",
status: func() dockerPullStatus {
complete := progress.NewManual(10)
complete.Set(10)
complete.SetCompleted()
return mockDockerPullStatus{
complete: true,
layers: []docker.LayerID{
"sha256:1",
"sha256:2",
"sha256:3",
},
current: map[docker.LayerID]docker.LayerState{
"sha256:1": {
Phase: docker.PullCompletePhase,
PhaseProgress: complete,
DownloadProgress: complete,
},
"sha256:2": {
Phase: docker.PullCompletePhase,
PhaseProgress: complete,
DownloadProgress: complete,
},
"sha256:3": {
Phase: docker.PullCompletePhase,
PhaseProgress: complete,
DownloadProgress: complete,
},
},
}
}(),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
f := newDockerPullStatusFormatter()
snaps.MatchSnapshot(t, f.Render(tt.status))
})
}
}
+33
View File
@@ -0,0 +1,33 @@
package ui
import (
tea "github.com/charmbracelet/bubbletea"
"github.com/wagoodman/go-partybus"
"github.com/anchore/bubbly/bubbles/taskprogress"
stereoEventParsers "github.com/anchore/stereoscope/pkg/event/parsers"
"github.com/anchore/syft/internal/log"
)
func (m *Handler) handleReadImage(e partybus.Event) []tea.Model {
imgMetadata, prog, err := stereoEventParsers.ParseReadImage(e)
if err != nil {
log.WithFields("error", err).Warn("unable to parse event")
return nil
}
tsk := m.newTaskProgress(
taskprogress.Title{
Default: "Parse image",
Running: "Parsing image",
Success: "Parsed image",
},
taskprogress.WithProgress(prog),
)
if imgMetadata != nil {
tsk.Context = []string{imgMetadata.ID}
}
return []tea.Model{tsk}
}
+117
View File
@@ -0,0 +1,117 @@
package ui
import (
"testing"
"time"
tea "github.com/charmbracelet/bubbletea"
"github.com/gkampitakis/go-snaps/snaps"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/stretchr/testify/require"
"github.com/wagoodman/go-partybus"
"github.com/wagoodman/go-progress"
"github.com/anchore/bubbly/bubbles/taskprogress"
stereoscopeEvent "github.com/anchore/stereoscope/pkg/event"
"github.com/anchore/stereoscope/pkg/image"
)
func TestHandler_handleReadImage(t *testing.T) {
tests := []struct {
name string
eventFn func(*testing.T) partybus.Event
iterations int
}{
{
name: "read image in progress",
eventFn: func(t *testing.T) partybus.Event {
prog := &progress.Manual{}
prog.SetTotal(100)
prog.Set(50)
src := image.Metadata{
ID: "id",
Size: 42,
Config: v1.ConfigFile{
Architecture: "arch",
Author: "auth",
Container: "cont",
OS: "os",
OSVersion: "os-ver",
Variant: "vari",
},
MediaType: "media",
ManifestDigest: "digest",
Architecture: "arch",
Variant: "var",
OS: "os",
}
return partybus.Event{
Type: stereoscopeEvent.ReadImage,
Source: src,
Value: prog,
}
},
},
{
name: "read image complete",
eventFn: func(t *testing.T) partybus.Event {
prog := &progress.Manual{}
prog.SetTotal(100)
prog.Set(100)
prog.SetCompleted()
src := image.Metadata{
ID: "id",
Size: 42,
Config: v1.ConfigFile{
Architecture: "arch",
Author: "auth",
Container: "cont",
OS: "os",
OSVersion: "os-ver",
Variant: "vari",
},
MediaType: "media",
ManifestDigest: "digest",
Architecture: "arch",
Variant: "var",
OS: "os",
}
return partybus.Event{
Type: stereoscopeEvent.ReadImage,
Source: src,
Value: prog,
}
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
event := tt.eventFn(t)
handler := New(DefaultHandlerConfig())
handler.WindowSize = tea.WindowSizeMsg{
Width: 100,
Height: 80,
}
models := handler.Handle(event)
require.Len(t, models, 1)
model := models[0]
tsk, ok := model.(taskprogress.Model)
require.True(t, ok)
got := runModel(t, tsk, tt.iterations, taskprogress.TickMsg{
Time: time.Now(),
Sequence: tsk.Sequence(),
ID: tsk.ID(),
})
t.Log(got)
snaps.MatchSnapshot(t, got)
})
}
}
@@ -0,0 +1,57 @@
package ui
import (
"fmt"
tea "github.com/charmbracelet/bubbletea"
"github.com/wagoodman/go-partybus"
"github.com/wagoodman/go-progress"
"github.com/anchore/bubbly/bubbles/taskprogress"
"github.com/anchore/syft/internal/log"
syftEventParsers "github.com/anchore/syft/syft/event/parsers"
"github.com/anchore/syft/syft/file/cataloger/secrets"
)
var _ progress.StagedProgressable = (*secretsCatalogerProgressAdapter)(nil)
// Deprecated: will be removed in syft 1.0
type secretsCatalogerProgressAdapter struct {
*secrets.Monitor
}
// Deprecated: will be removed in syft 1.0
func newSecretsCatalogerProgressAdapter(monitor *secrets.Monitor) secretsCatalogerProgressAdapter {
return secretsCatalogerProgressAdapter{
Monitor: monitor,
}
}
func (s secretsCatalogerProgressAdapter) Stage() string {
return fmt.Sprintf("%d secrets", s.Monitor.SecretsDiscovered.Current())
}
// Deprecated: will be removed in syft 1.0
func (m *Handler) handleSecretsCatalogerStarted(e partybus.Event) []tea.Model {
mon, err := syftEventParsers.ParseSecretsCatalogingStarted(e)
if err != nil {
log.WithFields("error", err).Warn("unable to parse event")
return nil
}
tsk := m.newTaskProgress(
taskprogress.Title{
Default: "Catalog secrets",
Running: "Cataloging secrets",
Success: "Cataloged secrets",
},
taskprogress.WithStagedProgressable(
newSecretsCatalogerProgressAdapter(mon),
),
)
tsk.HideStageOnSuccess = false
return []tea.Model{tsk}
}

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