Compare commits

...
49 Commits
Author SHA1 Message Date
Christopher Angelo Phillips 6f36b586ba chore: update release workflow to persist credentials for git tag step (#4069)
Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
2025-07-21 19:23:14 +00:00
anchore-actions-token-generator[bot]andspiffcs a620baff90 chore(deps): update anchore dependencies (#4068)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: spiffcs <32073428+spiffcs@users.noreply.github.com>
2025-07-21 14:12:47 -04:00
dependabot[bot] 5b14d160cf chore(deps): bump pygments (#4064)
Bumps [pygments](https://github.com/pygments/pygments) from 1.6 to 2.15.0.
- [Release notes](https://github.com/pygments/pygments/releases)
- [Changelog](https://github.com/pygments/pygments/blob/master/CHANGES)
- [Commits](https://github.com/pygments/pygments/compare/1.6...2.15.0)

---
updated-dependencies:
- dependency-name: pygments
  dependency-version: 2.15.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-21 13:07:27 -04:00
anchore-actions-token-generator[bot]andspiffcs 0a9567e88c chore(deps): update tools to latest versions (#4065)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: spiffcs <32073428+spiffcs@users.noreply.github.com>
2025-07-21 13:07:18 -04:00
dependabot[bot] af787d685c chore(deps): bump sigstore/cosign-installer from 3.9.1 to 3.9.2 (#4066)
Bumps [sigstore/cosign-installer](https://github.com/sigstore/cosign-installer) from 3.9.1 to 3.9.2.
- [Release notes](https://github.com/sigstore/cosign-installer/releases)
- [Commits](https://github.com/sigstore/cosign-installer/compare/398d4b0eeef1380460a10c8013a76f728fb906ac...d58896d6a1865668819e1d91763c7751a165e159)

---
updated-dependencies:
- dependency-name: sigstore/cosign-installer
  dependency-version: 3.9.2
  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>
2025-07-21 13:06:59 -04:00
anchore-actions-token-generator[bot]andwagoodman 64b62c086c chore(deps): update CPE dictionary index (#4067)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: wagoodman <590471+wagoodman@users.noreply.github.com>
2025-07-21 07:57:39 -04:00
c491dab35b feat: add parsing for uv.lock (#3763)
* feat: add parsing for uv.lock (#3268)

Signed-off-by: Joshua Kugler <tek30584@adobe.com>

* Still no tests, but much more complete

Next up: start writing tests! :)

Signed-off-by: Joshua Kugler <tek30584@adobe.com>

* chore: finish out functionality and write tests

Signed-off-by: Joshua Kugler <tek30584@adobe.com>

* Merge the .NET deps.json and PE binary catalogers (#3563)

* add combined deps.json + pe binary cataloger

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

* deprecate pe and deps standalone catalogers

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

* parse resource names + add tests

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

* fix integration and CLI tests

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

* add some helpful code comments

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

* allow for dropping Dep packages that are missing DLLs

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

* migrate json schema changes to 24

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

* keep application configuration

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

* correct config help

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

* [wip] detect claims of dlls within deps.json

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

* [wip] fix tests

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

* add assembly repack detection

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

* .net package count is lower due to dll claim requirement

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

---------

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

* better .NET cpe generation (#3764)

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

* Better represent .NET runtime packages (#3768)

* clean up .NET runtime packages

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

* add runtime relationships

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

* remove runtime references from binary package name

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

---------

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

* chore(deps): update CPE dictionary index (#3769)

Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: wagoodman <590471+wagoodman@users.noreply.github.com>
Signed-off-by: Joshua Kugler <tek30584@adobe.com>

* chore(deps): bump modernc.org/sqlite from 1.36.1 to 1.37.0 (#3771)

Bumps [modernc.org/sqlite](https://gitlab.com/cznic/sqlite) from 1.36.1 to 1.37.0.
- [Commits](https://gitlab.com/cznic/sqlite/compare/v1.36.1...v1.37.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>
Signed-off-by: Joshua Kugler <tek30584@adobe.com>

* chore(deps): bump 8398a7/action-slack from 3.16.2 to 3.18.0 (#3767)

Bumps [8398a7/action-slack](https://github.com/8398a7/action-slack) from 3.16.2 to 3.18.0.
- [Release notes](https://github.com/8398a7/action-slack/releases)
- [Commits](https://github.com/8398a7/action-slack/compare/28ba43ae48961b90635b50953d216767a6bea486...1750b5085f3ec60384090fb7c52965ef822e869e)

---
updated-dependencies:
- dependency-name: 8398a7/action-slack
  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>
Signed-off-by: Joshua Kugler <tek30584@adobe.com>

* chore(deps): bump golang.org/x/net from 0.37.0 to 0.38.0 (#3766)

Bumps [golang.org/x/net](https://github.com/golang/net) from 0.37.0 to 0.38.0.
- [Commits](https://github.com/golang/net/compare/v0.37.0...v0.38.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>
Signed-off-by: Joshua Kugler <tek30584@adobe.com>

* chore: move/modify code for lint issues

Signed-off-by: Joshua Kugler <tek30584@adobe.com>

* chore: make sure private structs are not exported

Signed-off-by: Joshua Kugler <tek30584@adobe.com>

* fix tests

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

* generate json schema

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

* chore: update readme to include uv

Signed-off-by: Joshua Kugler <tek30584@adobe.com>

* chore: use uv as the package manager name

Signed-off-by: Joshua Kugler <tek30584@adobe.com>

---------

Signed-off-by: Joshua Kugler <tek30584@adobe.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
Co-authored-by: anchore-actions-token-generator[bot] <102182147+anchore-actions-token-generator[bot]@users.noreply.github.com>
Co-authored-by: wagoodman <590471+wagoodman@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-17 18:26:56 +00:00
dependabot[bot] 0e5db45aad chore(deps): bump marocchino/sticky-pull-request-comment (#4063)
Bumps [marocchino/sticky-pull-request-comment](https://github.com/marocchino/sticky-pull-request-comment) from 2.9.3 to 2.9.4.
- [Release notes](https://github.com/marocchino/sticky-pull-request-comment/releases)
- [Commits](https://github.com/marocchino/sticky-pull-request-comment/compare/d2ad0de260ae8b0235ce059e63f2949ba9e05943...773744901bac0e8cbb5a0dc842800d45e9b2b405)

---
updated-dependencies:
- dependency-name: marocchino/sticky-pull-request-comment
  dependency-version: 2.9.4
  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>
2025-07-17 09:27:02 -04:00
Will Murphy 9cda2de2ad chore: lint gh actions with zizmor (#4062)
Signed-off-by: Will Murphy <willmurphyscode@users.noreply.github.com>
2025-07-16 17:12:38 -04:00
anchore-actions-token-generator[bot]andspiffcs 37c182d5be chore(deps): update tools to latest versions (#4060)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: spiffcs <32073428+spiffcs@users.noreply.github.com>
2025-07-15 11:32:27 -04:00
dependabot[bot] cef2a38117 chore(deps): bump github.com/go-viper/mapstructure/v2 (#4061)
Bumps [github.com/go-viper/mapstructure/v2](https://github.com/go-viper/mapstructure) from 2.3.0 to 2.4.0.
- [Release notes](https://github.com/go-viper/mapstructure/releases)
- [Changelog](https://github.com/go-viper/mapstructure/blob/main/CHANGELOG.md)
- [Commits](https://github.com/go-viper/mapstructure/compare/v2.3.0...v2.4.0)

---
updated-dependencies:
- dependency-name: github.com/go-viper/mapstructure/v2
  dependency-version: 2.4.0
  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>
2025-07-15 11:32:11 -04:00
dependabot[bot] 75eda3976d chore(deps): bump github.com/bmatcuk/doublestar/v4 from 4.8.1 to 4.9.0 (#4059)
Bumps [github.com/bmatcuk/doublestar/v4](https://github.com/bmatcuk/doublestar) from 4.8.1 to 4.9.0.
- [Release notes](https://github.com/bmatcuk/doublestar/releases)
- [Commits](https://github.com/bmatcuk/doublestar/compare/v4.8.1...v4.9.0)

---
updated-dependencies:
- dependency-name: github.com/bmatcuk/doublestar/v4
  dependency-version: 4.9.0
  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>
2025-07-14 17:14:11 +00:00
dependabot[bot] 2b1710b009 chore(deps): bump golang.org/x/mod from 0.25.0 to 0.26.0 (#4054)
Bumps [golang.org/x/mod](https://github.com/golang/mod) from 0.25.0 to 0.26.0.
- [Commits](https://github.com/golang/mod/compare/v0.25.0...v0.26.0)

---
updated-dependencies:
- dependency-name: golang.org/x/mod
  dependency-version: 0.26.0
  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>
2025-07-14 14:43:04 +00:00
mikey strauss 9caad26ee5 Pkg Metadata type unmarshal bug (#4043)
Signed-off-by: houdini91 <mdstrauss91@gmail.com>
2025-07-14 10:28:38 -04:00
anchore-actions-token-generator[bot]andspiffcs d88ad07855 chore(deps): update tools to latest versions (#4053)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: spiffcs <32073428+spiffcs@users.noreply.github.com>
2025-07-14 10:27:40 -04:00
dependabot[bot] 13986b7cea chore(deps): bump golang.org/x/net from 0.41.0 to 0.42.0 (#4056)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.41.0 to 0.42.0.
- [Commits](https://github.com/golang/net/compare/v0.41.0...v0.42.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-version: 0.42.0
  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>
2025-07-14 10:27:10 -04:00
anchore-actions-token-generator[bot]andwagoodman 75db6527bc chore(deps): update CPE dictionary index (#4058)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: wagoodman <590471+wagoodman@users.noreply.github.com>
2025-07-14 10:27:01 -04:00
dependabot[bot] 1c0ed133a3 chore(deps): bump github.com/olekukonko/tablewriter from 1.0.7 to 1.0.8 (#4049)
Bumps [github.com/olekukonko/tablewriter](https://github.com/olekukonko/tablewriter) from 1.0.7 to 1.0.8.
- [Commits](https://github.com/olekukonko/tablewriter/compare/v1.0.7...v1.0.8)

---
updated-dependencies:
- dependency-name: github.com/olekukonko/tablewriter
  dependency-version: 1.0.8
  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>
2025-07-08 15:27:02 -04:00
anchore-actions-token-generator[bot]andwagoodman 9928386d38 chore(deps): update CPE dictionary index (#4050)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: wagoodman <590471+wagoodman@users.noreply.github.com>
2025-07-08 15:26:50 -04:00
dependabot[bot] 9dd06981b4 chore(deps): bump github.com/hashicorp/hcl/v2 from 2.23.0 to 2.24.0 (#4051)
Bumps [github.com/hashicorp/hcl/v2](https://github.com/hashicorp/hcl) from 2.23.0 to 2.24.0.
- [Release notes](https://github.com/hashicorp/hcl/releases)
- [Changelog](https://github.com/hashicorp/hcl/blob/main/CHANGELOG.md)
- [Commits](https://github.com/hashicorp/hcl/compare/v2.23.0...v2.24.0)

---
updated-dependencies:
- dependency-name: github.com/hashicorp/hcl/v2
  dependency-version: 2.24.0
  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>
2025-07-08 15:26:34 -04:00
dependabot[bot] f88be457ef chore(deps): bump github.com/charmbracelet/bubbletea from 1.3.5 to 1.3.6 (#4052)
Bumps [github.com/charmbracelet/bubbletea](https://github.com/charmbracelet/bubbletea) from 1.3.5 to 1.3.6.
- [Release notes](https://github.com/charmbracelet/bubbletea/releases)
- [Changelog](https://github.com/charmbracelet/bubbletea/blob/main/.goreleaser.yml)
- [Commits](https://github.com/charmbracelet/bubbletea/compare/v1.3.5...v1.3.6)

---
updated-dependencies:
- dependency-name: github.com/charmbracelet/bubbletea
  dependency-version: 1.3.6
  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>
2025-07-08 15:26:26 -04:00
Keith ZantowandAlex Goodman 02703d5c80 feat: RHEL EUS detection (#4023)
* feat: rhel eus detection

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

* chore: update tests

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

* chore: update more tests

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

* rename feature detection functions

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

---------

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>
2025-07-07 14:11:20 +00:00
dependabot[bot] 9cbd52bdd7 chore(deps): bump anchore/sbom-action from 0.20.1 to 0.20.2 (#4048)
Bumps [anchore/sbom-action](https://github.com/anchore/sbom-action) from 0.20.1 to 0.20.2.
- [Release notes](https://github.com/anchore/sbom-action/releases)
- [Changelog](https://github.com/anchore/sbom-action/blob/main/RELEASE.md)
- [Commits](https://github.com/anchore/sbom-action/compare/9246b90769f852b3a8921f330c59e0b3f439d6e9...cee1b8e05ae5b2593a75e197229729eabaa9f8ec)

---
updated-dependencies:
- dependency-name: anchore/sbom-action
  dependency-version: 0.20.2
  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>
2025-07-03 15:00:51 -04:00
anchore-actions-token-generator[bot]andwagoodman e8b62ab9ac chore(deps): update anchore dependencies (#4047)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: wagoodman <590471+wagoodman@users.noreply.github.com>
2025-07-02 16:09:46 +00:00
anchore-actions-token-generator[bot]andwagoodman 2af1bca83f chore(deps): update anchore dependencies (#4045)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: wagoodman <590471+wagoodman@users.noreply.github.com>
2025-07-02 11:50:29 -04:00
Carlos Tadeu Panato JuniorandAlex Goodman 2111d4d0e4 chore: upgrade tablewriter dependency to use new API (#3990)
* upgrade tablewriter

Signed-off-by: Carlos Panato <ctadeu@gmail.com>

* remove header line whitespace

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

* fix static analysis

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

---------

Signed-off-by: Carlos Panato <ctadeu@gmail.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
2025-07-01 16:16:16 -04:00
dependabot[bot]andAlex Goodman 179cc70a36 chore(deps): bump github.com/Masterminds/semver/v3 from 3.3.1 to 3.4.0 (#4040)
* chore(deps): bump github.com/Masterminds/semver/v3 from 3.3.1 to 3.4.0

Bumps [github.com/Masterminds/semver/v3](https://github.com/Masterminds/semver) from 3.3.1 to 3.4.0.
- [Release notes](https://github.com/Masterminds/semver/releases)
- [Changelog](https://github.com/Masterminds/semver/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Masterminds/semver/compare/v3.3.1...v3.4.0)

---
updated-dependencies:
- dependency-name: github.com/Masterminds/semver/v3
  dependency-version: 3.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

* update error message expectations

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

---------

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
2025-07-01 19:21:02 +00:00
Christopher Angelo Phillips 1e3d2a2927 chore: update tests to read from latest test-fixture-cache and fix cache publish (#4042)
* feat: update integration test with correct package for httpd

Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>

* chore: update integration and cli tests with new upstream expectations

- php interpreter 8.3.21 => 8.3.22
- runCycloneDXInDocker update for local arm64 qemu emulation CycloneDX
- getSyftBinaryLocationByOS update to detect arm64 v8.0 artifact path

Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>

* chore: add snalshot to test command for fixture builds

Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>

* chore: update cdx in docker for all GOOS

Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>

---------

Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
2025-07-01 14:11:36 +00:00
dependabot[bot] 421afac532 chore(deps): bump github.com/mholt/archives from 0.1.2 to 0.1.3 (#4032)
Bumps [github.com/mholt/archives](https://github.com/mholt/archives) from 0.1.2 to 0.1.3.
- [Release notes](https://github.com/mholt/archives/releases)
- [Commits](https://github.com/mholt/archives/compare/v0.1.2...v0.1.3)

---
updated-dependencies:
- dependency-name: github.com/mholt/archives
  dependency-version: 0.1.3
  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>
2025-06-30 17:58:41 -04:00
dependabot[bot] b0b10acb40 chore(deps): bump marocchino/sticky-pull-request-comment (#4019)
Bumps [marocchino/sticky-pull-request-comment](https://github.com/marocchino/sticky-pull-request-comment) from 2.9.2 to 2.9.3.
- [Release notes](https://github.com/marocchino/sticky-pull-request-comment/releases)
- [Commits](https://github.com/marocchino/sticky-pull-request-comment/compare/67d0dec7b07ed060a405f9b2a64b8ab319fdd7db...d2ad0de260ae8b0235ce059e63f2949ba9e05943)

---
updated-dependencies:
- dependency-name: marocchino/sticky-pull-request-comment
  dependency-version: 2.9.3
  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>
2025-06-30 17:58:32 -04:00
dependabot[bot] b90028bd1f chore(deps): bump sigstore/cosign-installer from 3.9.0 to 3.9.1 (#4022)
Bumps [sigstore/cosign-installer](https://github.com/sigstore/cosign-installer) from 3.9.0 to 3.9.1.
- [Release notes](https://github.com/sigstore/cosign-installer/releases)
- [Commits](https://github.com/sigstore/cosign-installer/compare/v3.9.0...v3.9.1)

---
updated-dependencies:
- dependency-name: sigstore/cosign-installer
  dependency-version: 3.9.1
  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>
2025-06-30 17:58:23 -04:00
anchore-actions-token-generator[bot]andspiffcs b5a1b309ca chore(deps): update tools to latest versions (#4035)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: spiffcs <32073428+spiffcs@users.noreply.github.com>
2025-06-30 17:17:49 -04:00
anchore-actions-token-generator[bot]andwagoodman 841f963e70 chore(deps): update CPE dictionary index (#4037)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: wagoodman <590471+wagoodman@users.noreply.github.com>
2025-06-30 17:17:34 -04:00
dependabot[bot] ba59f57bfe chore(deps): bump github/codeql-action from 3.29.0 to 3.29.2 (#4039)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.29.0 to 3.29.2.
- [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/ce28f5bb42b7a9f2c824e633a3f6ee835bab6858...181d5eefc20863364f96762470ba6f862bdef56b)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 3.29.2
  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>
2025-06-30 17:17:20 -04:00
Alex Goodman 2bda086423 Add ability to scan snaps (as a source) (#3929) 2025-06-25 16:53:35 -04:00
anchore-actions-token-generator[bot]andwagoodman 4eb8ba4575 chore(deps): update CPE dictionary index (#4021)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: wagoodman <590471+wagoodman@users.noreply.github.com>
2025-06-23 11:30:19 -04:00
anchore-actions-token-generator[bot]andspiffcs 49115355d4 chore(deps): update tools to latest versions (#4016)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: spiffcs <32073428+spiffcs@users.noreply.github.com>
2025-06-19 14:02:03 -04:00
anchore-actions-token-generator[bot]andspiffcs d9eb1d7c1b chore(deps): update tools to latest versions (#4012)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: spiffcs <32073428+spiffcs@users.noreply.github.com>
2025-06-17 11:35:22 -04:00
dependabot[bot] 32a30f76c6 chore(deps): bump github.com/go-viper/mapstructure/v2 (#4014)
Bumps [github.com/go-viper/mapstructure/v2](https://github.com/go-viper/mapstructure) from 2.2.1 to 2.3.0.
- [Release notes](https://github.com/go-viper/mapstructure/releases)
- [Changelog](https://github.com/go-viper/mapstructure/blob/main/CHANGELOG.md)
- [Commits](https://github.com/go-viper/mapstructure/compare/v2.2.1...v2.3.0)

---
updated-dependencies:
- dependency-name: github.com/go-viper/mapstructure/v2
  dependency-version: 2.3.0
  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>
2025-06-17 11:35:09 -04:00
dependabot[bot] b52b13c03c chore(deps): bump sigstore/cosign-installer from 3.8.2 to 3.9.0 (#4015)
Bumps [sigstore/cosign-installer](https://github.com/sigstore/cosign-installer) from 3.8.2 to 3.9.0.
- [Release notes](https://github.com/sigstore/cosign-installer/releases)
- [Commits](https://github.com/sigstore/cosign-installer/compare/v3.8.2...v3.9.0)

---
updated-dependencies:
- dependency-name: sigstore/cosign-installer
  dependency-version: 3.9.0
  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>
2025-06-17 11:34:58 -04:00
anchore-actions-token-generator[bot]andwagoodman 0bfda2c514 chore(deps): update CPE dictionary index (#4007)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: wagoodman <590471+wagoodman@users.noreply.github.com>
2025-06-16 11:15:50 -04:00
dependabot[bot] 0b57d03958 chore(deps): bump anchore/sbom-action from 0.20.0 to 0.20.1 (#4008)
Bumps [anchore/sbom-action](https://github.com/anchore/sbom-action) from 0.20.0 to 0.20.1.
- [Release notes](https://github.com/anchore/sbom-action/releases)
- [Changelog](https://github.com/anchore/sbom-action/blob/main/RELEASE.md)
- [Commits](https://github.com/anchore/sbom-action/compare/e11c554f704a0b820cbf8c51673f6945e0731532...9246b90769f852b3a8921f330c59e0b3f439d6e9)

---
updated-dependencies:
- dependency-name: anchore/sbom-action
  dependency-version: 0.20.1
  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>
2025-06-16 11:15:37 -04:00
dependabot[bot] 72f9c42562 chore(deps): bump github.com/google/go-containerregistry (#4009)
Bumps [github.com/google/go-containerregistry](https://github.com/google/go-containerregistry) from 0.20.5 to 0.20.6.
- [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.20.5...v0.20.6)

---
updated-dependencies:
- dependency-name: github.com/google/go-containerregistry
  dependency-version: 0.20.6
  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>
2025-06-16 11:15:22 -04:00
anchore-actions-token-generator[bot]andspiffcs 181e180284 chore(deps): update tools to latest versions (#3992)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: spiffcs <32073428+spiffcs@users.noreply.github.com>
2025-06-13 10:38:44 -04:00
dependabot[bot] c19558dd73 chore(deps): bump github/codeql-action from 3.28.19 to 3.29.0 (#4000)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.19 to 3.29.0.
- [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/fca7ace96b7d713c7035871441bd52efbe39e27e...ce28f5bb42b7a9f2c824e633a3f6ee835bab6858)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 3.29.0
  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>
2025-06-12 10:37:53 -04:00
Keith Zantow 10f0631710 fix: provide separate nonroot image (#3998)
Signed-off-by: Keith Zantow <kzantow@gmail.com>
2025-06-11 17:00:55 -04:00
Alex Goodman 96c34ffc43 account for non-import shapes (#3997)
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2025-06-11 13:11:40 -04:00
Alex Goodman 79b6d5daa4 Allow decoding of anchorectl json files (#3973)
* allow decoding of import sbom file shape

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

* address formatting

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

* add file mode and type processing

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

* use type to interpret the raw value

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

* safe mode convert should use uint32

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

* simpler decoder type

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

---------

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2025-06-10 15:03:50 -04:00
dependabot[bot] cfa7cc5be9 chore(deps): bump github.com/anchore/stereoscope (#3991)
Bumps [github.com/anchore/stereoscope](https://github.com/anchore/stereoscope) from 0.1.5-0.20250604132324-344e29f37f05 to 0.1.5.
- [Release notes](https://github.com/anchore/stereoscope/releases)
- [Changelog](https://github.com/anchore/stereoscope/blob/main/RELEASE.md)
- [Commits](https://github.com/anchore/stereoscope/commits/v0.1.5)

---
updated-dependencies:
- dependency-name: github.com/anchore/stereoscope
  dependency-version: 0.1.5
  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>
2025-06-10 11:47:40 -04:00
140 changed files with 15198 additions and 458 deletions
+5 -5
View File
@@ -26,7 +26,7 @@ tools:
# used for linting
- name: golangci-lint
version:
want: v2.1.6
want: v2.2.2
method: github-release
with:
repo: golangci/golangci-lint
@@ -42,7 +42,7 @@ tools:
# used for signing the checksums file at release
- name: cosign
version:
want: v2.5.0
want: v2.5.3
method: github-release
with:
repo: sigstore/cosign
@@ -58,7 +58,7 @@ tools:
# used to release all artifacts
- name: goreleaser
version:
want: v2.10.2
want: v2.11.0
method: github-release
with:
repo: goreleaser/goreleaser
@@ -98,7 +98,7 @@ tools:
# used for triggering a release
- name: gh
version:
want: v2.74.0
want: v2.76.0
method: github-release
with:
repo: cli/cli
@@ -114,7 +114,7 @@ tools:
# used to upload test fixture cache
- name: yq
version:
want: v4.45.4
want: v4.46.1
method: github-release
with:
repo: mikefarah/yq
+4 -1
View File
@@ -55,8 +55,11 @@ runs:
- name: Install apt packages
if: inputs.bootstrap-apt-packages != ''
shell: bash
env:
APT_PACKAGES: ${{ inputs.bootstrap-apt-packages }}
run: |
DEBIAN_FRONTEND=noninteractive sudo apt update && sudo -E apt install -y ${{ inputs.bootstrap-apt-packages }}
IFS=' ' read -ra packages <<< "$APT_PACKAGES"
DEBIAN_FRONTEND=noninteractive sudo apt update && sudo -E apt install -y "${packages[@]}"
- name: Restore ORAS cache from github actions
if: inputs.download-test-fixture-cache == 'true'
+5 -3
View File
@@ -37,6 +37,8 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
with:
persist-credentials: false
- name: Install Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 #v5.5.0
@@ -45,7 +47,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@fca7ace96b7d713c7035871441bd52efbe39e27e #v3.28.19
uses: github/codeql-action/init@181d5eefc20863364f96762470ba6f862bdef56b #v3.29.2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -56,7 +58,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@fca7ace96b7d713c7035871441bd52efbe39e27e #v3.28.19
uses: github/codeql-action/autobuild@181d5eefc20863364f96762470ba6f862bdef56b #v3.29.2
# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@@ -70,4 +72,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@fca7ace96b7d713c7035871441bd52efbe39e27e #v3.28.19
uses: github/codeql-action/analyze@181d5eefc20863364f96762470ba6f862bdef56b #v3.29.2
+8 -2
View File
@@ -28,9 +28,15 @@ jobs:
label:
name: "Label changes"
runs-on: ubuntu-22.04
permissions:
contents: read
pull-requests: write
issues: write
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
with:
persist-credentials: false
- run: python .github/scripts/labeler.py
env:
@@ -40,7 +46,7 @@ jobs:
- name: Delete existing comment
if: ${{ hashFiles( env.CI_COMMENT_FILE ) == '' }}
uses: marocchino/sticky-pull-request-comment@67d0dec7b07ed060a405f9b2a64b8ab319fdd7db #v2.9.2
uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 #v2.9.4
with:
header: ${{ env.COMMENT_HEADER }}
hide: true
@@ -48,7 +54,7 @@ jobs:
- name: Add comment
if: ${{ hashFiles( env.CI_COMMENT_FILE ) != '' }}
uses: marocchino/sticky-pull-request-comment@67d0dec7b07ed060a405f9b2a64b8ab319fdd7db #v2.9.2
uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 #v2.9.4
with:
header: ${{ env.COMMENT_HEADER }}
path: ${{ env.CI_COMMENT_FILE }}
@@ -14,13 +14,22 @@ on:
type: string
description: release version to update the version file with (prefixed with v)
required: true
secrets:
TOOLBOX_AWS_ACCESS_KEY_ID:
required: true
TOOLBOX_AWS_SECRET_ACCESS_KEY:
required: true
jobs:
release:
runs-on: ubuntu-24.04
permissions:
contents: read
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
with:
persist-credentials: false
- name: Update version file
run: make ci-release-version-file
+26 -11
View File
@@ -16,6 +16,8 @@ jobs:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
with:
persist-credentials: false
- name: Check if running on main
if: github.ref != 'refs/heads/main'
@@ -28,8 +30,10 @@ jobs:
- name: Check if tag already exists
# note: this will fail if the tag already exists
run: |
[[ "${{ github.event.inputs.version }}" == v* ]] || (echo "version '${{ github.event.inputs.version }}' does not have a 'v' prefix" && exit 1)
git tag ${{ github.event.inputs.version }}
[[ "$VERSION" == v* ]] || (echo "version '$VERSION' does not have a 'v' prefix" && exit 1)
git tag "$VERSION"
env:
VERSION: ${{ github.event.inputs.version }}
- name: Check static analysis results
uses: fountainhead/action-wait-for-check@5a908a24814494009c4bb27c242ea38c93c593be # v1.2.0
@@ -87,13 +91,20 @@ jobs:
- name: Quality gate
if: steps.static-analysis.outputs.conclusion != 'success' || steps.unit.outputs.conclusion != 'success' || steps.integration.outputs.conclusion != 'success' || steps.cli-linux.outputs.conclusion != 'success' || steps.acceptance-linux.outputs.conclusion != 'success' || steps.acceptance-mac.outputs.conclusion != 'success'
env:
STATIC_ANALYSIS_STATUS: ${{ steps.static-analysis.conclusion }}
UNIT_TEST_STATUS: ${{ steps.unit.outputs.conclusion }}
INTEGRATION_TEST_STATUS: ${{ steps.integration.outputs.conclusion }}
ACCEPTANCE_LINUX_STATUS: ${{ steps.acceptance-linux.outputs.conclusion }}
ACCEPTANCE_MAC_STATUS: ${{ steps.acceptance-mac.outputs.conclusion }}
CLI_LINUX_STATUS: ${{ steps.cli-linux.outputs.conclusion }}
run: |
echo "Static Analysis Status: ${{ steps.static-analysis.conclusion }}"
echo "Unit Test Status: ${{ steps.unit.outputs.conclusion }}"
echo "Integration Test Status: ${{ steps.integration.outputs.conclusion }}"
echo "Acceptance Test (Linux) Status: ${{ steps.acceptance-linux.outputs.conclusion }}"
echo "Acceptance Test (Mac) Status: ${{ steps.acceptance-mac.outputs.conclusion }}"
echo "CLI Test (Linux) Status: ${{ steps.cli-linux.outputs.conclusion }}"
echo "Static Analysis Status: $STATIC_ANALYSIS_STATUS"
echo "Unit Test Status: $UNIT_TEST_STATUS"
echo "Integration Test Status: $INTEGRATION_TEST_STATUS"
echo "Acceptance Test (Linux) Status: $ACCEPTANCE_LINUX_STATUS"
echo "Acceptance Test (Mac) Status: $ACCEPTANCE_MAC_STATUS"
echo "CLI Test (Linux) Status: $CLI_LINUX_STATUS"
false
release:
@@ -108,6 +119,7 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
with:
fetch-depth: 0
persist-credentials: true
- name: Bootstrap environment
uses: ./.github/actions/bootstrap
@@ -129,10 +141,11 @@ jobs:
run: |
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 tag -a "$VERSION" -m "Release $VERSION"
git push origin --tags
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ github.event.inputs.version }}
- name: Build & publish release artifacts
run: make ci-release
@@ -148,7 +161,7 @@ jobs:
# for updating brew formula in anchore/homebrew-syft
GITHUB_BREW_TOKEN: ${{ secrets.ANCHOREOPS_GITHUB_OSS_WRITE_TOKEN }}
- uses: anchore/sbom-action@e11c554f704a0b820cbf8c51673f6945e0731532 #v0.20.0
- uses: anchore/sbom-action@cee1b8e05ae5b2593a75e197229729eabaa9f8ec #v0.20.2
continue-on-error: true
with:
file: go.mod
@@ -169,4 +182,6 @@ jobs:
uses: ./.github/workflows/release-version-file.yaml
with:
version: ${{ github.event.inputs.version }}
secrets: inherit
secrets:
TOOLBOX_AWS_ACCESS_KEY_ID: ${{ secrets.TOOLBOX_AWS_ACCESS_KEY_ID }}
TOOLBOX_AWS_SECRET_ACCESS_KEY: ${{ secrets.TOOLBOX_AWS_SECRET_ACCESS_KEY }}
@@ -6,6 +6,10 @@ on:
jobs:
run:
permissions:
contents: read
issues: write
pull-requests: write
uses: "anchore/workflows/.github/workflows/remove-awaiting-response-label.yaml@main"
secrets:
token: ${{ secrets.OSS_PROJECT_GH_TOKEN }}
@@ -20,6 +20,8 @@ jobs:
packages: write
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
with:
persist-credentials: false
- name: Bootstrap environment
uses: ./.github/actions/bootstrap
@@ -34,7 +36,9 @@ jobs:
DOWNLOAD_TEST_FIXTURE_CACHE: "false"
- name: Login to GitHub Container Registry (ORAS)
run: echo "${{ secrets.GITHUB_TOKEN }}" | .tool/oras login ghcr.io -u ${{ github.actor }} --password-stdin
run: echo "${{ secrets.GITHUB_TOKEN }}" | .tool/oras login ghcr.io -u "$ACTOR" --password-stdin
env:
ACTOR: ${{ github.actor }}
- name: Publish test fixture cache
run: make upload-test-fixture-cache
@@ -16,6 +16,8 @@ jobs:
if: github.repository_owner == 'anchore' # only run for main repo (not forks)
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
with:
persist-credentials: false
- name: Bootstrap environment
uses: ./.github/actions/bootstrap
@@ -14,6 +14,8 @@ jobs:
if: github.repository == 'anchore/syft' # only run for main repo
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
with:
persist-credentials: false
- name: Bootstrap environment
uses: ./.github/actions/bootstrap
@@ -17,6 +17,8 @@ jobs:
if: github.repository == 'anchore/syft' # only run for main repo
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
with:
persist-credentials: false
- name: Bootstrap environment
uses: ./.github/actions/bootstrap
@@ -0,0 +1,35 @@
name: "Validate GitHub Actions"
on:
pull_request:
paths:
- '.github/workflows/**'
- '.github/actions/**'
push:
branches:
- main
paths:
- '.github/workflows/**'
- '.github/actions/**'
permissions:
contents: read
jobs:
zizmor:
name: "Lint"
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write # for uploading SARIF results
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- name: "Run zizmor"
uses: zizmorcore/zizmor-action@f52a838cfabf134edcbaa7c8b3677dde20045018 # v0.1.1
with:
config-file: .github/zizmor.yml
sarif-upload: true
inputs: .github
+19 -1
View File
@@ -18,6 +18,8 @@ jobs:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
with:
persist-credentials: false
- name: Bootstrap environment
uses: ./.github/actions/bootstrap
@@ -33,6 +35,8 @@ jobs:
runs-on: ubuntu-22.04-4core-16gb
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
with:
persist-credentials: false
- name: Bootstrap environment
uses: ./.github/actions/bootstrap
@@ -49,6 +53,8 @@ jobs:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
with:
persist-credentials: false
- name: Bootstrap environment
uses: ./.github/actions/bootstrap
@@ -67,6 +73,8 @@ jobs:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
with:
persist-credentials: false
- name: Bootstrap environment
uses: ./.github/actions/bootstrap
@@ -100,6 +108,8 @@ jobs:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
with:
persist-credentials: false
- name: Bootstrap environment
uses: ./.github/actions/bootstrap
@@ -155,6 +165,8 @@ jobs:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
with:
persist-credentials: false
- name: Bootstrap environment
uses: ./.github/actions/bootstrap
@@ -198,9 +210,11 @@ jobs:
runs-on: macos-latest
steps:
- name: Install Cosign
uses: sigstore/cosign-installer@v3.8.2
uses: sigstore/cosign-installer@d58896d6a1865668819e1d91763c7751a165e159 # v3.9.2
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
with:
persist-credentials: false
- name: Bootstrap environment
uses: ./.github/actions/bootstrap
@@ -238,6 +252,8 @@ jobs:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
with:
persist-credentials: false
- name: Bootstrap environment
uses: ./.github/actions/bootstrap
@@ -276,6 +292,8 @@ jobs:
- Upload-Snapshot-Artifacts
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
with:
persist-credentials: false
- name: Delete snapshot cache
run: gh cache delete "snapshot-build-${{ github.run_id }}"
+11
View File
@@ -0,0 +1,11 @@
rules:
unpinned-uses:
ignore:
# Allow unpinned uses of trusted internal anchore/workflows actions
- update-anchore-dependencies.yml
dangerous-triggers:
ignore:
# Safe use of pull_request_target - only runs trusted scripts from base repo,
# never checks out PR code, needs secrets for labeling PRs from forks
- detect-schema-changes.yaml
+104 -23
View File
@@ -126,6 +126,59 @@ dockers:
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
# nonroot images...
- image_templates:
- anchore/syft:{{.Tag}}-nonroot-amd64
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-amd64
goarch: amd64
dockerfile: Dockerfile.nonroot
use: buildx
build_flag_templates:
- "--platform=linux/amd64"
- "--build-arg=BUILD_DATE={{.Date}}"
- "--build-arg=BUILD_VERSION={{.Version}}"
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
- image_templates:
- anchore/syft:{{.Tag}}-nonroot-arm64v8
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-arm64v8
goarch: arm64
dockerfile: Dockerfile.nonroot
use: buildx
build_flag_templates:
- "--platform=linux/arm64/v8"
- "--build-arg=BUILD_DATE={{.Date}}"
- "--build-arg=BUILD_VERSION={{.Version}}"
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
- image_templates:
- anchore/syft:{{.Tag}}-nonroot-ppc64le
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-ppc64le
goarch: ppc64le
dockerfile: Dockerfile.nonroot
use: buildx
build_flag_templates:
- "--platform=linux/ppc64le"
- "--build-arg=BUILD_DATE={{.Date}}"
- "--build-arg=BUILD_VERSION={{.Version}}"
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
- image_templates:
- anchore/syft:{{.Tag}}-nonroot-s390x
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-s390x
goarch: s390x
dockerfile: Dockerfile.nonroot
use: buildx
build_flag_templates:
- "--platform=linux/s390x"
- "--build-arg=BUILD_DATE={{.Date}}"
- "--build-arg=BUILD_VERSION={{.Version}}"
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
# debug images...
- image_templates:
- anchore/syft:{{.Tag}}-debug-amd64
@@ -180,7 +233,6 @@ dockers:
- "--build-arg=VCS_URL={{.GitURL}}"
docker_manifests:
# anchore/syft manifests...
- name_template: anchore/syft:latest
image_templates:
- anchore/syft:{{.Tag}}-amd64
@@ -188,6 +240,13 @@ docker_manifests:
- anchore/syft:{{.Tag}}-ppc64le
- anchore/syft:{{.Tag}}-s390x
- name_template: ghcr.io/anchore/syft:latest
image_templates:
- ghcr.io/anchore/syft:{{.Tag}}-amd64
- ghcr.io/anchore/syft:{{.Tag}}-arm64v8
- ghcr.io/anchore/syft:{{.Tag}}-ppc64le
- ghcr.io/anchore/syft:{{.Tag}}-s390x
- name_template: anchore/syft:{{.Tag}}
image_templates:
- anchore/syft:{{.Tag}}-amd64
@@ -195,28 +254,6 @@ docker_manifests:
- anchore/syft:{{.Tag}}-ppc64le
- anchore/syft:{{.Tag}}-s390x
- name_template: anchore/syft:debug
image_templates:
- anchore/syft:{{.Tag}}-debug-amd64
- anchore/syft:{{.Tag}}-debug-arm64v8
- anchore/syft:{{.Tag}}-debug-ppc64le
- anchore/syft:{{.Tag}}-debug-s390x
- name_template: anchore/syft:{{.Tag}}-debug
image_templates:
- anchore/syft:{{.Tag}}-debug-amd64
- anchore/syft:{{.Tag}}-debug-arm64v8
- anchore/syft:{{.Tag}}-debug-ppc64le
- anchore/syft:{{.Tag}}-debug-s390x
# ghcr.io/anchore/syft manifests...
- name_template: ghcr.io/anchore/syft:latest
image_templates:
- ghcr.io/anchore/syft:{{.Tag}}-amd64
- ghcr.io/anchore/syft:{{.Tag}}-arm64v8
- ghcr.io/anchore/syft:{{.Tag}}-ppc64le
- ghcr.io/anchore/syft:{{.Tag}}-s390x
- name_template: ghcr.io/anchore/syft:{{.Tag}}
image_templates:
- ghcr.io/anchore/syft:{{.Tag}}-amd64
@@ -224,6 +261,43 @@ docker_manifests:
- ghcr.io/anchore/syft:{{.Tag}}-ppc64le
- ghcr.io/anchore/syft:{{.Tag}}-s390x
# nonroot images...
- name_template: anchore/syft:nonroot
image_templates:
- anchore/syft:{{.Tag}}-nonroot-amd64
- anchore/syft:{{.Tag}}-nonroot-arm64v8
- anchore/syft:{{.Tag}}-nonroot-ppc64le
- anchore/syft:{{.Tag}}-nonroot-s390x
- name_template: ghcr.io/anchore/syft:nonroot
image_templates:
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-amd64
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-arm64v8
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-ppc64le
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-s390x
- name_template: anchore/syft:{{.Tag}}-nonroot
image_templates:
- anchore/syft:{{.Tag}}-nonroot-amd64
- anchore/syft:{{.Tag}}-nonroot-arm64v8
- anchore/syft:{{.Tag}}-nonroot-ppc64le
- anchore/syft:{{.Tag}}-nonroot-s390x
- name_template: ghcr.io/anchore/syft:{{.Tag}}-nonroot
image_templates:
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-amd64
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-arm64v8
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-ppc64le
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-s390x
# debug images...
- name_template: anchore/syft:debug
image_templates:
- anchore/syft:{{.Tag}}-debug-amd64
- anchore/syft:{{.Tag}}-debug-arm64v8
- anchore/syft:{{.Tag}}-debug-ppc64le
- anchore/syft:{{.Tag}}-debug-s390x
- name_template: ghcr.io/anchore/syft:debug
image_templates:
- ghcr.io/anchore/syft:{{.Tag}}-debug-amd64
@@ -231,6 +305,13 @@ docker_manifests:
- ghcr.io/anchore/syft:{{.Tag}}-debug-ppc64le
- ghcr.io/anchore/syft:{{.Tag}}-debug-s390x
- name_template: anchore/syft:{{.Tag}}-debug
image_templates:
- anchore/syft:{{.Tag}}-debug-amd64
- anchore/syft:{{.Tag}}-debug-arm64v8
- anchore/syft:{{.Tag}}-debug-ppc64le
- anchore/syft:{{.Tag}}-debug-s390x
- name_template: ghcr.io/anchore/syft:{{.Tag}}-debug
image_templates:
- ghcr.io/anchore/syft:{{.Tag}}-debug-amd64
+5 -3
View File
@@ -1,12 +1,14 @@
FROM gcr.io/distroless/static-debian12:nonroot
FROM gcr.io/distroless/static-debian12:latest AS build
FROM scratch
# needed for version check HTTPS request
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
# create the /tmp dir, which is needed for image content cache
WORKDIR /tmp
COPY syft /
USER nonroot
ARG BUILD_DATE
ARG BUILD_VERSION
ARG VCS_REF
+27
View File
@@ -0,0 +1,27 @@
FROM gcr.io/distroless/static-debian12:nonroot
# create the /tmp dir, which is needed for image content cache
WORKDIR /tmp
COPY syft /
USER nonroot
ARG BUILD_DATE
ARG BUILD_VERSION
ARG VCS_REF
ARG VCS_URL
LABEL org.opencontainers.image.created=$BUILD_DATE
LABEL org.opencontainers.image.title="syft"
LABEL org.opencontainers.image.description="CLI tool and library for generating a Software Bill of Materials from container images and filesystems"
LABEL org.opencontainers.image.source=$VCS_URL
LABEL org.opencontainers.image.revision=$VCS_REF
LABEL org.opencontainers.image.vendor="Anchore, Inc."
LABEL org.opencontainers.image.version=$BUILD_VERSION
LABEL org.opencontainers.image.licenses="Apache-2.0"
LABEL io.artifacthub.package.readme-url="https://raw.githubusercontent.com/anchore/syft/main/README.md"
LABEL io.artifacthub.package.logo-url="https://user-images.githubusercontent.com/5199289/136844524-1527b09f-c5cb-4aa9-be54-5aa92a6086c1.png"
LABEL io.artifacthub.package.license="Apache-2.0"
ENTRYPOINT ["/syft"]
+5 -5
View File
@@ -37,7 +37,7 @@ Syft development is sponsored by [Anchore](https://anchore.com/), and is release
Syft binaries are provided for Linux, macOS and Windows.
### Recommended
> ```bash
> ```bash
> curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
> ```
@@ -141,7 +141,7 @@ Note that flags using the @<version> can be used for earlier versions of each sp
- Linux kernel modules (ko)
- Nix (outputs in /nix/store)
- PHP (composer, PECL, Pear)
- Python (wheel, egg, poetry, requirements.txt)
- Python (wheel, egg, poetry, requirements.txt, uv)
- Red Hat (rpm)
- Ruby (gem)
- Rust (cargo.lock, auditable binary)
@@ -157,7 +157,7 @@ Our [wiki](https://github.com/anchore/syft/wiki) contains further details on the
* [File Selection](https://github.com/anchore/syft/wiki/file-selection)
* [Excluding file paths](https://github.com/anchore/syft/wiki/excluding-file-paths)
* [Output formats](https://github.com/anchore/syft/wiki/output-formats)
* [Package Cataloger Selection](https://github.com/anchore/syft/wiki/package-cataloger-selection)
* [Package Cataloger Selection](https://github.com/anchore/syft/wiki/package-cataloger-selection)
* [Concepts](https://github.com/anchore/syft/wiki/package-cataloger-selection#concepts)
* [Examples](https://github.com/anchore/syft/wiki/package-cataloger-selection#examples)
* [Using templates](https://github.com/anchore/syft/wiki/using-templates)
@@ -177,8 +177,8 @@ Check out our [contributing](/CONTRIBUTING.md) guide and [developer](/DEVELOPING
## Syft Team Meetings
The Syft Team hold regular community meetings online. All are welcome to join to bring topics for discussion.
- Check the [calendar](https://calendar.google.com/calendar/u/0/r?cid=Y182OTM4dGt0MjRtajI0NnNzOThiaGtnM29qNEBncm91cC5jYWxlbmRhci5nb29nbGUuY29t) for the next meeting date.
The Syft Team hold regular community meetings online. All are welcome to join to bring topics for discussion.
- Check the [calendar](https://calendar.google.com/calendar/u/0/r?cid=Y182OTM4dGt0MjRtajI0NnNzOThiaGtnM29qNEBncm91cC5jYWxlbmRhci5nb29nbGUuY29t) for the next meeting date.
- Add items to the [agenda](https://docs.google.com/document/d/1ZtSAa6fj2a6KRWviTn3WoJm09edvrNUp4Iz_dOjjyY8/edit?usp=sharing) (join [this group](https://groups.google.com/g/anchore-oss-community) for write access to the [agenda](https://docs.google.com/document/d/1ZtSAa6fj2a6KRWviTn3WoJm09edvrNUp4Iz_dOjjyY8/edit?usp=sharing))
- See you there!
+21 -20
View File
@@ -78,6 +78,7 @@ tasks:
- task: integration
- task: validate-cyclonedx-schema
- task: test-utils
- task: snapshot
- task: cli
- task: check-docker-cache
@@ -264,12 +265,12 @@ tasks:
YELLOW='\033[0;33m'
RESET='\033[0m'
echo -e "${YELLOW}creating fingerprint files for non-docker fixtures...${RESET}"
echo -e "${YELLOW}creating fingerprint files for non-docker fixtures...${RESET}"
for dir in $(find . -type d -name 'test-fixtures'); do
if [ -f "$dir/Makefile" ]; then
# for debugging...
#echo -e "${YELLOW}• calculating fingerprints in $dir... ${RESET}"
(make -C "$dir" fingerprint)
fi
done
@@ -298,18 +299,18 @@ tasks:
BOLD='\033[1m'
PURPLE='\033[0;35m'
RESET='\033[0m'
# if DOWNLOAD_TEST_FIXTURE_CACHE is set to 'false', then skip the cache download and always build
if [ "$DOWNLOAD_TEST_FIXTURE_CACHE" = "false" ]; then
echo -e "${BOLD}${PURPLE}skipping cache download, rebuilding cache...${RESET}"
{{ .TASK }} build-fixtures
exit 0
fi
LATEST_FINGERPRINT=$(docker manifest inspect {{ .CACHE_IMAGE }} | {{ .YQ }} -r '.annotations.fingerprint')
echo "latest cache: $LATEST_FINGERPRINT"
if [ -f {{ .LAST_CACHE_PULL_FILE }} ]; then
LAST_PULL_FINGERPRINT=$(cat {{ .LAST_CACHE_PULL_FILE }} | {{ .YQ }} -r '.digest')
else
@@ -317,41 +318,41 @@ tasks:
{{ .TASK }} download-test-fixture-cache
exit 0
fi
{{ .TASK }} fingerprints
WANT_FINGERPRINT=$(cat {{ .CACHE_PATHS_FILE }} | {{ .YQ }} -r '.digest')
WANT_FINGERPRINT=$(cat {{ .CACHE_PATHS_FILE }} | {{ .YQ }} -r '.digest')
echo "desired cache: $WANT_FINGERPRINT"
echo "last pulled cache: $LAST_PULL_FINGERPRINT"
# if we already have the latest cache, skip the refresh
if [ "$LAST_PULL_FINGERPRINT" = "$WANT_FINGERPRINT" ]; then
echo -e "${BOLD}${PURPLE}already have the latest cache (skipping cache download)${RESET}"
exit 0
fi
# at this point we only refresh the cache if we want the same cache that is currently available.
# we don't by default refresh the cache if the cache if it is simply different from what we have,
# because we may be working on a code change that doesn't require a cache refresh (but could trigger one,
# which would be annoying to deal with in a development workflow).
if [ "$LATEST_FINGERPRINT" = "$WANT_FINGERPRINT" ]; then
echo -e "${BOLD}${PURPLE}found newer cache! downloading cache...${RESET}"
{{ .TASK }} download-test-fixture-cache
else
echo -e "${BOLD}${PURPLE}found different cache, but isn't clear if it's newer (skipping cache download and manually building)${RESET}"
{{ .YQ }} eval '.paths[] | "\(.digest) \(.path)"' {{ .LAST_CACHE_PULL_FILE }} > .tmp/last_cache_lines
{{ .YQ }} eval '.paths[] | "\(.digest) \(.path)"' {{ .CACHE_PATHS_FILE }} > .tmp/cache_lines
diff .tmp/last_cache_lines .tmp/cache_lines || true
echo -e "${BOLD}${PURPLE}diff with more context...${RESET}"
diff -U10000 {{ .LAST_CACHE_PULL_FILE }} {{ .CACHE_PATHS_FILE }} || true
echo -e "${BOLD}${PURPLE}detected changes to input material, manually building fixtures...${RESET}"
{{ .TASK }} build-fixtures
fi
@@ -364,7 +365,7 @@ tasks:
BOLD='\033[1m'
YELLOW='\033[0;33m'
RESET='\033[0m'
# Use a for loop with command substitution to avoid subshell issues
for dir in $(find . -type d -name 'test-fixtures'); do
if [ -f "$dir/Makefile" ]; then
@@ -402,15 +403,15 @@ tasks:
set -eu
oras_command="{{ .ORAS }} push {{ .CACHE_IMAGE }}"
paths=$(cat {{ .CACHE_PATHS_FILE }} | {{ .YQ }} -r '.paths[].path')
paths=$(cat {{ .CACHE_PATHS_FILE }} | {{ .YQ }} -r '.paths[].path')
for path in $paths; do
oras_command+=" $path"
done
oras_command+=" {{ .CACHE_PATHS_FILE }}"
oras_command+=" --annotation org.opencontainers.image.source=https://github.com/{{ .OWNER }}/{{ .PROJECT }}"
oras_command+=" --annotation fingerprint=$(cat {{ .CACHE_PATHS_FILE }} | {{ .YQ }} -r '.digest')"
echo "Executing: $oras_command"
eval $oras_command
@@ -640,7 +641,7 @@ tasks:
BOLD='\033[1m'
YELLOW='\033[0;33m'
RESET='\033[0m'
# Use a for loop with command substitution to avoid subshell issues
for dir in $(find . -type d -name 'test-fixtures'); do
if [ -f "$dir/Makefile" ]; then
@@ -0,0 +1,8 @@
[TestHandler_handlePullSourceStarted/snap_download_in_progress - 1]
⠋ Downloading snap file... ━━━━━━━━━━━━━━━━━━━━ example-app_1.0_amd64.snap
---
[TestHandler_handlePullSourceStarted/snap_download_complete - 1]
✔ Snap downloaded successfully example-app_1.0_amd64.snap
---
@@ -26,7 +26,7 @@ func TestHandler_handleCatalogerTaskStarted(t *testing.T) {
{
name: "cataloging task in progress",
eventFn: func(t *testing.T) partybus.Event {
value := &monitor.CatalogerTaskProgress{
value := &monitor.TaskProgress{
AtomicStage: progress.NewAtomicStage("some stage"),
Manual: progress.NewManual(100),
}
@@ -48,7 +48,7 @@ func TestHandler_handleCatalogerTaskStarted(t *testing.T) {
{
name: "cataloging sub task in progress",
eventFn: func(t *testing.T) partybus.Event {
value := &monitor.CatalogerTaskProgress{
value := &monitor.TaskProgress{
AtomicStage: progress.NewAtomicStage("some stage"),
Manual: progress.NewManual(100),
}
@@ -71,7 +71,7 @@ func TestHandler_handleCatalogerTaskStarted(t *testing.T) {
{
name: "cataloging sub task complete",
eventFn: func(t *testing.T) partybus.Event {
value := &monitor.CatalogerTaskProgress{
value := &monitor.TaskProgress{
AtomicStage: progress.NewAtomicStage("some stage"),
Manual: progress.NewManual(100),
}
@@ -94,7 +94,7 @@ func TestHandler_handleCatalogerTaskStarted(t *testing.T) {
{
name: "cataloging sub task complete -- hide stage",
eventFn: func(t *testing.T) partybus.Event {
value := &monitor.CatalogerTaskProgress{
value := &monitor.TaskProgress{
AtomicStage: progress.NewAtomicStage("some stage"),
Manual: progress.NewManual(100),
}
@@ -117,7 +117,7 @@ func TestHandler_handleCatalogerTaskStarted(t *testing.T) {
{
name: "cataloging sub task complete with removal",
eventFn: func(t *testing.T) partybus.Event {
value := &monitor.CatalogerTaskProgress{
value := &monitor.TaskProgress{
AtomicStage: progress.NewAtomicStage("some stage"),
Manual: progress.NewManual(100),
}
@@ -162,7 +162,7 @@ func TestHandler_handleCatalogerTaskStarted(t *testing.T) {
}
// note: this line / event is not under test, only needed to show a sub status
kickoffEvent := &monitor.CatalogerTaskProgress{
kickoffEvent := &monitor.TaskProgress{
AtomicStage: progress.NewAtomicStage(""),
Manual: progress.NewManual(-1),
}
+37
View File
@@ -0,0 +1,37 @@
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) handlePullSourceStarted(e partybus.Event) []tea.Model {
prog, info, err := syftEventParsers.ParsePullSourceStarted(e)
if err != nil {
log.WithFields("error", err).Debug("unable to parse event")
return nil
}
tsk := m.newTaskProgress(
taskprogress.Title{
Default: info.Title.Default,
Running: info.Title.WhileRunning,
Success: info.Title.OnSuccess,
},
taskprogress.WithStagedProgressable(prog),
)
tsk.HideOnSuccess = info.HideOnSuccess
tsk.HideStageOnSuccess = info.HideStageOnSuccess
tsk.HideProgressOnSuccess = true
if info.Context != "" {
tsk.Context = []string{info.Context}
}
return []tea.Model{tsk}
}
+121
View File
@@ -0,0 +1,121 @@
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"
"github.com/anchore/syft/syft/event"
"github.com/anchore/syft/syft/event/monitor"
)
func TestHandler_handlePullSourceStarted(t *testing.T) {
tests := []struct {
name string
eventFn func(*testing.T) partybus.Event
iterations int
}{
{
name: "snap download in progress",
eventFn: func(t *testing.T) partybus.Event {
stage := progress.NewAtomicStage("")
manual := progress.NewManual(0)
manual.SetTotal(1000000) // 1MB file
manual.Set(250000) // 25% downloaded
taskProg := &monitor.TaskProgress{
AtomicStage: stage,
Manual: manual,
}
genericTask := monitor.GenericTask{
Title: monitor.Title{
Default: "Downloading snap",
WhileRunning: "Downloading snap file...",
OnSuccess: "Snap downloaded",
},
Context: "example-app_1.0_amd64.snap",
HideOnSuccess: false,
HideStageOnSuccess: true,
ID: "snap-download-123",
}
return partybus.Event{
Type: event.PullSourceStarted,
Source: genericTask,
Value: taskProg,
}
},
iterations: 5,
},
{
name: "snap download complete",
eventFn: func(t *testing.T) partybus.Event {
stage := progress.NewAtomicStage("")
manual := progress.NewManual(0)
manual.SetTotal(1000000) // 1MB file
manual.Set(1000000) // 100% downloaded
manual.SetCompleted()
taskProg := &monitor.TaskProgress{
AtomicStage: stage,
Manual: manual,
}
genericTask := monitor.GenericTask{
Title: monitor.Title{
Default: "Downloading snap",
WhileRunning: "Downloading snap file...",
OnSuccess: "Snap downloaded successfully",
},
Context: "example-app_1.0_amd64.snap",
HideOnSuccess: false,
HideStageOnSuccess: true,
ID: "snap-download-123",
}
return partybus.Event{
Type: event.PullSourceStarted,
Source: genericTask,
Value: taskProg,
}
},
iterations: 3,
},
}
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.handlePullSourceStarted(event)
require.Len(t, models, 1)
model := models[0]
tsk, ok := model.(taskprogress.Model)
require.True(t, ok)
gotModel := runModel(t, tsk, tt.iterations, taskprogress.TickMsg{
Time: time.Now(),
Sequence: tsk.Sequence(),
ID: tsk.ID(),
})
got := gotModel.View()
t.Log(got)
snaps.MatchSnapshot(t, got)
})
}
}
+1
View File
@@ -57,6 +57,7 @@ func New(cfg HandlerConfig) *Handler {
stereoscopeEvent.FetchImage: simpleHandler(h.handleFetchImage),
syftEvent.FileIndexingStarted: simpleHandler(h.handleFileIndexingStarted),
syftEvent.AttestationStarted: simpleHandler(h.handleAttestationStarted),
syftEvent.PullSourceStarted: simpleHandler(h.handlePullSourceStarted),
syftEvent.CatalogerTaskStarted: h.handleCatalogerTaskStarted,
})
+1 -1
View File
@@ -44,7 +44,7 @@ func AppClioSetupConfig(id clio.Identification, out io.Writer) *clio.SetupConfig
redact.Set(state.RedactStore)
log.Set(state.Logger)
stereoscope.SetLogger(state.Logger)
stereoscope.SetLogger(state.Logger.Nested("from", "stereoscope"))
return nil
},
).
@@ -33,7 +33,7 @@ func TestPackageDeduplication(t *testing.T) {
"curl-minimal-7.76.1-31.el9": 3, // curl upgrade layer + all above layers
"wget-1.21.1-8.el9_4": 4, // wget + all above layers
"vsftpd-3.0.5-6.el9": 2, // vsftpd + all above layers
"httpd-2.4.62-1.el9_5.2": 1, // last layer
"httpd-2.4.62-4.el9": 1, // last layer
},
},
{
@@ -51,7 +51,7 @@ func TestPackageDeduplication(t *testing.T) {
"curl-minimal-7.76.1-31.el9": 1, // upgrade
"wget-1.21.1-8.el9_4": 1,
"vsftpd-3.0.5-6.el9": 1,
"httpd-2.4.62-1.el9_5.2": 1,
"httpd-2.4.62-4.el9": 1,
},
},
}
+61 -31
View File
@@ -5,7 +5,7 @@ go 1.24.1
require (
github.com/BurntSushi/toml v1.5.0
github.com/CycloneDX/cyclonedx-go v0.9.2
github.com/Masterminds/semver/v3 v3.3.1
github.com/Masterminds/semver/v3 v3.4.0
github.com/Masterminds/sprig/v3 v3.3.0
github.com/OneOfOne/xxhash v1.2.8
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
@@ -24,17 +24,18 @@ require (
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b
github.com/anchore/packageurl-go v0.1.1-0.20250220190351-d62adb6e1115
github.com/anchore/stereoscope v0.1.5-0.20250604132324-344e29f37f05
github.com/anchore/stereoscope v0.1.7-0.20250716200927-94c6f92877d4
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be
github.com/aquasecurity/go-pep440-version v0.0.1
github.com/bitnami/go-version v0.0.0-20250131085805-b1f57a8634ef
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb
github.com/bmatcuk/doublestar/v4 v4.8.1
github.com/bmatcuk/doublestar/v4 v4.9.0
github.com/charmbracelet/bubbles v0.21.0
github.com/charmbracelet/bubbletea v1.3.5
github.com/charmbracelet/bubbletea v1.3.6
github.com/charmbracelet/lipgloss v1.1.0
github.com/dave/jennifer v1.7.1
github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da
github.com/diskfs/go-diskfs v1.6.1-0.20250601133945-2af1c7ece24c
github.com/distribution/reference v0.6.0
github.com/dustin/go-humanize v1.0.1
github.com/elliotchance/phpserialize v1.4.0
@@ -44,25 +45,27 @@ require (
github.com/go-git/go-billy/v5 v5.6.2
github.com/go-git/go-git/v5 v5.16.2
github.com/go-test/deep v1.1.1
github.com/go-viper/mapstructure/v2 v2.2.1
github.com/go-viper/mapstructure/v2 v2.4.0
github.com/gohugoio/hashstructure v0.5.0
github.com/google/go-cmp v0.7.0
github.com/google/go-containerregistry v0.20.5
github.com/google/go-containerregistry v0.20.6
github.com/google/licensecheck v0.3.1
github.com/google/uuid v1.6.0
github.com/gookit/color v1.5.4
github.com/hashicorp/go-cleanhttp v0.5.2
github.com/hashicorp/go-getter v1.7.8
github.com/hashicorp/go-multierror v1.1.1
github.com/hashicorp/hcl/v2 v2.23.0
github.com/hashicorp/hcl/v2 v2.24.0
github.com/iancoleman/strcase v0.3.0
github.com/invopop/jsonschema v0.7.0
github.com/jedib0t/go-pretty/v6 v6.6.7
github.com/jinzhu/copier v0.4.0
github.com/kastenhq/goversion v0.0.0-20230811215019-93b2f8823953
github.com/magiconair/properties v1.8.10
github.com/mholt/archives v0.1.2
github.com/mholt/archives v0.1.3
github.com/moby/sys/mountinfo v0.7.2
github.com/nix-community/go-nix v0.0.0-20250101154619-4bdde671e0a1
github.com/olekukonko/tablewriter v0.0.5
github.com/olekukonko/tablewriter v1.0.8
github.com/opencontainers/go-digest v1.0.0
github.com/pelletier/go-toml v1.9.5
github.com/quasilyte/go-ruleguard/dsl v0.3.22
@@ -86,13 +89,19 @@ require (
github.com/zyedidia/generic v1.2.2-0.20230320175451-4410d2372cb1
go.uber.org/goleak v1.3.0
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0
golang.org/x/mod v0.25.0
golang.org/x/net v0.41.0
golang.org/x/mod v0.26.0
golang.org/x/net v0.42.0
gopkg.in/yaml.v3 v3.0.1
modernc.org/sqlite v1.38.0
)
require (
cloud.google.com/go v0.116.0 // indirect
cloud.google.com/go/auth v0.9.9 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect
cloud.google.com/go/compute/metadata v0.7.0 // indirect
cloud.google.com/go/iam v1.2.2 // indirect
cloud.google.com/go/storage v1.43.0 // indirect
dario.cat/mergo v1.0.1 // indirect
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect
github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0 // indirect
@@ -103,20 +112,22 @@ require (
github.com/ProtonMail/go-crypto v1.2.0 // indirect
github.com/STARRY-S/zip v0.2.1 // indirect
github.com/agext/levenshtein v1.2.1 // indirect; indirectt
github.com/anchore/go-lzo v0.1.0 // indirect
github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 // indirect
github.com/andybalholm/brotli v1.1.2-0.20250424173009-453214e765f3 // indirect
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/aquasecurity/go-version v0.0.1 // indirect
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aws/aws-sdk-go v1.44.122 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/becheran/wildmatch-go v1.0.0 // indirect
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
github.com/bodgit/plumbing v1.3.0 // indirect
github.com/bodgit/sevenzip v1.6.0 // indirect
github.com/bodgit/windows v1.0.1 // indirect
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
github.com/charmbracelet/harmonica v0.2.0 // indirect
github.com/charmbracelet/x/ansi v0.8.0 // indirect
github.com/charmbracelet/x/ansi v0.9.3 // indirect
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
github.com/charmbracelet/x/term v0.2.1 // indirect
github.com/cloudflare/circl v1.6.1 // indirect
@@ -124,18 +135,19 @@ require (
github.com/containerd/containerd v1.7.27 // indirect
github.com/containerd/containerd/api v1.8.0 // indirect
github.com/containerd/continuity v0.4.4 // indirect
github.com/containerd/errdefs v0.3.0 // indirect
github.com/containerd/errdefs v1.0.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/containerd/fifo v1.1.0 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/containerd/platforms v0.2.1 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.16.3 // indirect
github.com/containerd/ttrpc v1.2.7 // indirect
github.com/containerd/typeurl/v2 v2.1.1 // indirect
github.com/containerd/typeurl/v2 v2.2.0 // indirect
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/docker/cli v28.2.2+incompatible // indirect
github.com/docker/cli v28.3.0+incompatible // indirect
github.com/docker/distribution v2.8.3+incompatible // indirect
github.com/docker/docker v28.1.1+incompatible // indirect
github.com/docker/docker v28.2.2+incompatible // indirect
github.com/docker/docker-credential-helpers v0.9.3 // indirect
github.com/docker/go-connections v0.5.0 // indirect
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect
@@ -143,6 +155,7 @@ require (
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/fatih/color v1.17.0 // indirect
github.com/felixge/fgprof v0.9.5 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.8.0 // indirect
@@ -150,20 +163,26 @@ require (
github.com/gkampitakis/ciinfo v0.3.2 // indirect
github.com/gkampitakis/go-diff v1.3.2 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-restruct/restruct v1.2.0-alpha // indirect
github.com/goccy/go-yaml v1.18.0 // indirect
github.com/goccy/go-yaml v1.18.0
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e // indirect
github.com/google/s2a-go v0.1.8 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
github.com/googleapis/gax-go/v2 v2.13.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-safetemp v1.0.0 // indirect
github.com/hashicorp/go-version v1.6.0 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/huandu/xstrings v1.5.0 // indirect
github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
@@ -177,9 +196,11 @@ require (
github.com/mattn/go-localereader v0.0.2-0.20220822084749-2491eb6c1c75 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/mikelolasagasti/xz v1.0.1 // indirect
github.com/minio/minlz v1.0.0 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
@@ -194,6 +215,8 @@ require (
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/nwaples/rardecode v1.1.3 // indirect
github.com/nwaples/rardecode/v2 v2.1.0 // indirect
github.com/olekukonko/errors v0.0.0-20250405072817-4e6d85265da6 // indirect
github.com/olekukonko/ll v0.0.8 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/opencontainers/runtime-spec v1.1.0 // indirect
github.com/opencontainers/selinux v1.11.0 // indirect
@@ -203,6 +226,7 @@ require (
github.com/pjbgf/sha1cd v0.3.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pkg/profile v1.7.0 // indirect
github.com/pkg/xattr v0.4.9 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rivo/uniseg v0.4.7 // indirect
@@ -210,13 +234,14 @@ require (
github.com/sagikazarmark/locafero v0.7.0 // indirect
github.com/sahilm/fuzzy v0.1.1 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af // indirect
github.com/skeema/knownhosts v1.3.1 // indirect
github.com/sorairolake/lzip-go v0.3.5 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/cast v1.7.1 // indirect
github.com/spf13/pflag v1.0.6 // indirect
github.com/spf13/viper v1.20.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/sylabs/sif/v2 v2.21.1 // indirect
github.com/sylabs/squashfs v1.0.6 // indirect
@@ -232,24 +257,29 @@ require (
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/zclconf/go-cty v1.13.0 // indirect
github.com/zclconf/go-cty v1.16.3 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
go.opentelemetry.io/otel v1.35.0 // indirect
go.opentelemetry.io/otel/metric v1.35.0 // indirect
go.opentelemetry.io/otel/trace v1.35.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
go.opentelemetry.io/otel v1.36.0 // indirect
go.opentelemetry.io/otel/metric v1.36.0 // indirect
go.opentelemetry.io/otel/trace v1.36.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
golang.org/x/crypto v0.39.0 // indirect
golang.org/x/sync v0.15.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/term v0.32.0 // indirect
golang.org/x/text v0.26.0 // indirect
golang.org/x/tools v0.33.0 // indirect
golang.org/x/crypto v0.40.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/sync v0.16.0 // indirect
golang.org/x/sys v0.34.0 // indirect
golang.org/x/term v0.33.0 // indirect
golang.org/x/text v0.27.0 // indirect
golang.org/x/time v0.7.0 // indirect
golang.org/x/tools v0.34.0 // indirect
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
google.golang.org/api v0.203.0 // indirect
google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20241113202542-65e8d215514f // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 // indirect
google.golang.org/grpc v1.67.3 // indirect
google.golang.org/protobuf v1.36.4 // indirect
+1087 -67
View File
File diff suppressed because it is too large Load Diff
+32 -2
View File
@@ -36,8 +36,8 @@ func Notify(message string) {
})
}
func StartCatalogerTask(info monitor.GenericTask, size int64, initialStage string) *monitor.CatalogerTaskProgress {
t := &monitor.CatalogerTaskProgress{
func StartCatalogerTask(info monitor.GenericTask, size int64, initialStage string) *monitor.TaskProgress {
t := &monitor.TaskProgress{
AtomicStage: progress.NewAtomicStage(initialStage),
Manual: progress.NewManual(size),
}
@@ -50,3 +50,33 @@ func StartCatalogerTask(info monitor.GenericTask, size int64, initialStage strin
return t
}
func StartPullSourceTask(info monitor.GenericTask, size int64, initialStage string) *monitor.TaskProgress {
t := &monitor.TaskProgress{
AtomicStage: progress.NewAtomicStage(initialStage),
Manual: progress.NewManual(size),
}
Publish(partybus.Event{
Type: event.PullSourceStarted,
Source: info,
Value: progress.StagedProgressable(t),
})
return t
}
func StartIndexingFiles(path string) *monitor.TaskProgress {
t := &monitor.TaskProgress{
AtomicStage: progress.NewAtomicStage(""),
Manual: progress.NewManual(-1),
}
Publish(partybus.Event{
Type: event.FileIndexingStarted,
Source: path,
Value: progress.StagedProgressable(t),
})
return t
}
+1 -1
View File
@@ -3,5 +3,5 @@ package internal
const (
// JSONSchemaVersion is the current schema version output by the JSON encoder
// This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment.
JSONSchemaVersion = "16.0.34"
JSONSchemaVersion = "16.0.36"
)
+154
View File
@@ -0,0 +1,154 @@
package file
import (
"fmt"
"io"
"net/http"
"github.com/hashicorp/go-getter"
"github.com/hashicorp/go-getter/helper/url"
"github.com/wagoodman/go-progress"
"github.com/anchore/clio"
"github.com/anchore/stereoscope/pkg/file"
"github.com/anchore/syft/internal"
)
var (
archiveExtensions = getterDecompressorNames()
ErrNonArchiveSource = fmt.Errorf("non-archive sources are not supported for directory destinations")
)
type Getter interface {
// GetFile downloads the give URL into the given path. The URL must reference a single file.
GetFile(dst, src string, monitor ...*progress.Manual) error
// GetToDir downloads the resource found at the `src` URL into the given `dst` directory.
// The directory must already exist, and the remote resource MUST BE AN ARCHIVE (e.g. `.tar.gz`).
GetToDir(dst, src string, monitor ...*progress.Manual) error
}
type HashiGoGetter struct {
httpGetter getter.HttpGetter
}
// NewGetter creates and returns a new Getter. Providing an http.Client is optional. If one is provided,
// it will be used for all HTTP(S) getting; otherwise, go-getter's default getters will be used.
func NewGetter(id clio.Identification, httpClient *http.Client) *HashiGoGetter {
return &HashiGoGetter{
httpGetter: getter.HttpGetter{
Client: httpClient,
Header: http.Header{
"User-Agent": []string{fmt.Sprintf("%v %v", id.Name, id.Version)},
},
},
}
}
func (g HashiGoGetter) GetFile(dst, src string, monitors ...*progress.Manual) error {
if len(monitors) > 1 {
return fmt.Errorf("multiple monitors provided, which is not allowed")
}
return getterClient(dst, src, false, g.httpGetter, monitors).Get()
}
func (g HashiGoGetter) GetToDir(dst, src string, monitors ...*progress.Manual) error {
// though there are multiple getters, only the http/https getter requires extra validation
if err := validateHTTPSource(src); err != nil {
return err
}
if len(monitors) > 1 {
return fmt.Errorf("multiple monitors provided, which is not allowed")
}
return getterClient(dst, src, true, g.httpGetter, monitors).Get()
}
func validateHTTPSource(src string) error {
// we are ignoring any sources that are not destined to use the http getter object
if !internal.HasAnyOfPrefixes(src, "http://", "https://") {
return nil
}
u, err := url.Parse(src)
if err != nil {
return fmt.Errorf("bad URL provided %q: %w", src, err)
}
// only allow for sources with archive extensions
if !internal.HasAnyOfSuffixes(u.Path, archiveExtensions...) {
return ErrNonArchiveSource
}
return nil
}
func getterClient(dst, src string, dir bool, httpGetter getter.HttpGetter, monitors []*progress.Manual) *getter.Client {
client := &getter.Client{
Src: src,
Dst: dst,
Dir: dir,
Getters: map[string]getter.Getter{
"http": &httpGetter,
"https": &httpGetter,
// note: these are the default getters from https://github.com/hashicorp/go-getter/blob/v1.5.9/get.go#L68-L74
// it is possible that other implementations need to account for custom httpclient injection, however,
// that has not been accounted for at this time.
"file": new(getter.FileGetter),
"git": new(getter.GitGetter),
"gcs": new(getter.GCSGetter),
"hg": new(getter.HgGetter),
"s3": new(getter.S3Getter),
},
Options: mapToGetterClientOptions(monitors),
}
return client
}
func withProgress(monitor *progress.Manual) func(client *getter.Client) error {
return getter.WithProgress(
&progressAdapter{monitor: monitor},
)
}
func mapToGetterClientOptions(monitors []*progress.Manual) []getter.ClientOption {
var result []getter.ClientOption
for _, monitor := range monitors {
result = append(result, withProgress(monitor))
}
// derived from https://github.com/hashicorp/go-getter/blob/v2.2.3/decompress.go#L23-L63
fileSizeLimit := int64(5 * file.GB)
dec := getter.LimitedDecompressors(0, fileSizeLimit)
result = append(result, getter.WithDecompressors(dec))
return result
}
type readCloser struct {
progress.Reader
}
func (c *readCloser) Close() error { return nil }
type progressAdapter struct {
monitor *progress.Manual
}
func (a *progressAdapter) TrackProgress(_ string, currentSize, totalSize int64, stream io.ReadCloser) io.ReadCloser {
a.monitor.Set(currentSize)
a.monitor.SetTotal(totalSize)
return &readCloser{
Reader: *progress.NewProxyReader(stream, a.monitor),
}
}
func getterDecompressorNames() (names []string) {
for name := range getter.Decompressors {
names = append(names, name)
}
return names
}
+268
View File
@@ -0,0 +1,268 @@
package file
import (
"archive/tar"
"bytes"
"context"
"crypto/x509"
"fmt"
"net"
"net/http"
"net/http/httptest"
"net/url"
"path"
"testing"
"github.com/stretchr/testify/assert"
"github.com/anchore/clio"
)
func TestGetter_GetFile(t *testing.T) {
testCases := []struct {
name string
prepareClient func(*http.Client)
assert assert.ErrorAssertionFunc
}{
{
name: "client trusts server's CA",
assert: assert.NoError,
},
{
name: "client doesn't trust server's CA",
prepareClient: removeTrustedCAs,
assert: assertUnknownAuthorityError,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
requestPath := "/foo"
server := newTestServer(t, withResponseForPath(t, requestPath, testFileContent))
t.Cleanup(server.Close)
httpClient := getClient(t, server)
if tc.prepareClient != nil {
tc.prepareClient(httpClient)
}
getter := NewGetter(testID, httpClient)
requestURL := createRequestURL(t, server, requestPath)
tempDir := t.TempDir()
tempFile := path.Join(tempDir, "some-destination-file")
err := getter.GetFile(tempFile, requestURL)
tc.assert(t, err)
})
}
}
func TestGetter_GetToDir_FilterNonArchivesWired(t *testing.T) {
testCases := []struct {
name string
source string
assert assert.ErrorAssertionFunc
}{
{
name: "error out on non-archive sources",
source: "http://localhost/something.txt",
assert: assertErrNonArchiveSource,
},
}
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
test.assert(t, NewGetter(testID, nil).GetToDir(t.TempDir(), test.source))
})
}
}
func TestGetter_validateHttpSource(t *testing.T) {
testCases := []struct {
name string
source string
assert assert.ErrorAssertionFunc
}{
{
name: "error out on non-archive sources",
source: "http://localhost/something.txt",
assert: assertErrNonArchiveSource,
},
{
name: "filter out non-archive sources with get param",
source: "https://localhost/vulnerability-db_v3_2021-11-21T08:15:44Z.txt?checksum=sha256%3Ac402d01fa909a3fa85a5c6733ef27a3a51a9105b6c62b9152adbd24c08358911",
assert: assertErrNonArchiveSource,
},
{
name: "ignore non http-https input",
source: "s3://bucket/something.txt",
assert: assert.NoError,
},
}
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
test.assert(t, validateHTTPSource(test.source))
})
}
}
func TestGetter_GetToDir_CertConcerns(t *testing.T) {
testCases := []struct {
name string
prepareClient func(*http.Client)
assert assert.ErrorAssertionFunc
}{
{
name: "client trusts server's CA",
assert: assert.NoError,
},
{
name: "client doesn't trust server's CA",
prepareClient: removeTrustedCAs,
assert: assertUnknownAuthorityError,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
requestPath := "/foo.tar"
tarball := createTarball("foo", testFileContent)
server := newTestServer(t, withResponseForPath(t, requestPath, tarball))
t.Cleanup(server.Close)
httpClient := getClient(t, server)
if tc.prepareClient != nil {
tc.prepareClient(httpClient)
}
getter := NewGetter(testID, httpClient)
requestURL := createRequestURL(t, server, requestPath)
tempDir := t.TempDir()
err := getter.GetToDir(tempDir, requestURL)
tc.assert(t, err)
})
}
}
func assertUnknownAuthorityError(t assert.TestingT, err error, _ ...interface{}) bool {
return assert.ErrorAs(t, err, &x509.UnknownAuthorityError{})
}
func assertErrNonArchiveSource(t assert.TestingT, err error, _ ...interface{}) bool {
return assert.ErrorIs(t, err, ErrNonArchiveSource)
}
func removeTrustedCAs(client *http.Client) {
client.Transport.(*http.Transport).TLSClientConfig.RootCAs = x509.NewCertPool()
}
// createTarball makes a single-file tarball and returns it as a byte slice.
func createTarball(filename string, content []byte) []byte {
tarBuffer := new(bytes.Buffer)
tarWriter := tar.NewWriter(tarBuffer)
tarWriter.WriteHeader(&tar.Header{
Name: filename,
Size: int64(len(content)),
Mode: 0600,
})
tarWriter.Write(content)
tarWriter.Close()
return tarBuffer.Bytes()
}
type muxOption func(mux *http.ServeMux)
func withResponseForPath(t *testing.T, path string, response []byte) muxOption {
t.Helper()
return func(mux *http.ServeMux) {
mux.HandleFunc(path, func(w http.ResponseWriter, req *http.Request) {
t.Logf("server handling request: %s %s", req.Method, req.URL)
_, err := w.Write(response)
if err != nil {
t.Fatal(err)
}
})
}
}
var testID = clio.Identification{
Name: "test-app",
Version: "v0.5.3",
}
func newTestServer(t *testing.T, muxOptions ...muxOption) *httptest.Server {
t.Helper()
mux := http.NewServeMux()
for _, option := range muxOptions {
option(mux)
}
server := httptest.NewTLSServer(mux)
t.Logf("new TLS server listening at %s", getHost(t, server))
return server
}
func createRequestURL(t *testing.T, server *httptest.Server, path string) string {
t.Helper()
// TODO: Figure out how to get this value from the server without hardcoding it here
const testServerCertificateName = "example.com"
serverURL, err := url.Parse(server.URL)
if err != nil {
t.Fatal(err)
}
// Set URL hostname to value from TLS certificate
serverURL.Host = fmt.Sprintf("%s:%s", testServerCertificateName, serverURL.Port())
serverURL.Path = path
return serverURL.String()
}
// getClient returns an http.Client that can be used to contact the test TLS server.
func getClient(t *testing.T, server *httptest.Server) *http.Client {
t.Helper()
httpClient := server.Client()
transport := httpClient.Transport.(*http.Transport)
serverHost := getHost(t, server)
transport.DialContext = func(_ context.Context, _, addr string) (net.Conn, error) {
t.Logf("client dialing %q for host %q", serverHost, addr)
// Ensure the client dials our test server
return net.Dial("tcp", serverHost)
}
return httpClient
}
// getHost extracts the host value from a server URL string.
// e.g. given a server with URL "http://1.2.3.4:5000/foo", getHost returns "1.2.3.4:5000"
func getHost(t *testing.T, server *httptest.Server) string {
t.Helper()
u, err := url.Parse(server.URL)
if err != nil {
t.Fatal(err)
}
return u.Hostname() + ":" + u.Port()
}
var testFileContent = []byte("This is the content of a test file!\n")
+89
View File
@@ -0,0 +1,89 @@
package file
import (
"errors"
"io/fs"
"os"
"path/filepath"
"github.com/diskfs/go-diskfs/filesystem"
)
type WalkDiskDirFunc func(fsys filesystem.FileSystem, path string, d os.FileInfo, err error) error
// WalkDiskDir walks the file tree within the go-diskfs filesystem at root, calling fn for each file or directory in the tree, including root.
// This is meant to mimic the behavior of fs.WalkDir in the standard library.
func WalkDiskDir(fsys filesystem.FileSystem, root string, fn WalkDiskDirFunc) error {
infos, err := fsys.ReadDir(root)
if err != nil {
return err
}
if len(infos) == 0 {
return nil
}
for _, info := range infos {
p := filepath.Join(root, info.Name())
err = walkDiskDir(fsys, p, info, fn)
if err != nil {
if errors.Is(err, fs.SkipDir) {
continue
}
if errors.Is(err, fs.SkipAll) {
return nil
}
return err
}
}
return err
}
func walkDiskDir(fsys filesystem.FileSystem, name string, d os.FileInfo, walkDirFn WalkDiskDirFunc) error {
if err := walkDirFn(fsys, name, d, nil); err != nil {
if errors.Is(err, fs.SkipDir) && (d == nil || d.IsDir()) {
return nil
}
return err
}
isDir := d != nil && d.IsDir()
if d == nil {
_, err := fsys.ReadDir(name)
if err != nil {
return nil
}
isDir = true
}
if !isDir {
return nil
}
dirs, err := fsys.ReadDir(name)
if err != nil {
err = walkDirFn(fsys, name, d, err)
if err != nil {
if errors.Is(err, fs.SkipDir) {
return nil
}
return err
}
}
for _, d1 := range dirs {
name1 := filepath.Join(name, d1.Name())
if err := walkDiskDir(fsys, name1, d1, walkDirFn); err != nil {
if errors.Is(err, fs.SkipDir) {
break
}
if errors.Is(err, fs.SkipAll) {
return err
}
return err
}
}
return nil
}
+250
View File
@@ -0,0 +1,250 @@
package file
import (
"io/fs"
"os"
"path/filepath"
"testing"
"github.com/diskfs/go-diskfs/backend/file"
"github.com/diskfs/go-diskfs/filesystem"
"github.com/diskfs/go-diskfs/filesystem/squashfs"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func createTestFS(t *testing.T) filesystem.FileSystem {
dir := t.TempDir()
filename := "test.squashfs"
f, err := os.Create(filepath.Join(dir, filename))
require.NoError(t, err)
b := file.New(f, false)
fsys, err := squashfs.Create(b, 0, 0, 4096)
require.NoError(t, err)
testFiles := []struct {
path string
content string
isDir bool
}{
{"/file1.txt", "content of file1", false},
{"/file2.txt", "content of file2", false},
{"/dir1", "", true},
{"/dir1/subfile1.txt", "content of subfile1", false},
{"/dir1/subfile2.txt", "content of subfile2", false},
{"/dir1/subdir1", "", true},
{"/dir1/subdir1/deepfile.txt", "deep content", false},
{"/dir2", "", true},
{"/dir2/anotherfile.txt", "another content", false},
{"/emptydir", "", true},
}
for _, tf := range testFiles {
if tf.isDir {
err := fsys.Mkdir(tf.path)
require.NoError(t, err)
} else {
f, err := fsys.OpenFile(tf.path, os.O_CREATE|os.O_RDWR)
require.NoError(t, err)
_, err = f.Write([]byte(tf.content))
require.NoError(t, err)
f.Close()
}
}
return fsys
}
func TestWalkDiskDir_CompleteTraversal(t *testing.T) {
fsys := createTestFS(t)
var visitedPaths []string
err := WalkDiskDir(fsys, "/", func(fsys filesystem.FileSystem, path string, d os.FileInfo, err error) error {
require.NoError(t, err)
visitedPaths = append(visitedPaths, path)
return nil
})
require.NoError(t, err)
expectedPaths := []string{
"/file1.txt",
"/file2.txt",
"/dir1",
"/dir1/subfile1.txt",
"/dir1/subfile2.txt",
"/dir1/subdir1",
"/dir1/subdir1/deepfile.txt",
"/dir2",
"/dir2/anotherfile.txt",
"/emptydir",
}
assert.ElementsMatch(t, expectedPaths, visitedPaths)
}
func TestWalkDiskDir_FileInfoCorrect(t *testing.T) {
fsys := createTestFS(t)
var fileInfos []struct {
path string
isDir bool
name string
}
err := WalkDiskDir(fsys, "/", func(fsys filesystem.FileSystem, path string, d os.FileInfo, err error) error {
require.NoError(t, err)
require.NotNil(t, d)
fileInfos = append(fileInfos, struct {
path string
isDir bool
name string
}{
path: path,
isDir: d.IsDir(),
name: d.Name(),
})
return nil
})
require.NoError(t, err)
for _, fi := range fileInfos {
expectedName := filepath.Base(fi.path)
assert.Equal(t, expectedName, fi.name)
if fi.path == "/dir1" || fi.path == "/dir2" || fi.path == "/emptydir" || fi.path == "/dir1/subdir1" {
assert.True(t, fi.isDir, "Expected %s to be directory", fi.path)
} else {
assert.False(t, fi.isDir, "Expected %s to be file", fi.path)
}
}
}
func TestWalkDiskDir_SkipDir(t *testing.T) {
fsys := createTestFS(t)
var visitedPaths []string
err := WalkDiskDir(fsys, "/", func(fsys filesystem.FileSystem, path string, d os.FileInfo, err error) error {
require.NoError(t, err)
visitedPaths = append(visitedPaths, path)
if path == "/dir1" {
return fs.SkipDir
}
return nil
})
require.NoError(t, err)
assert.Contains(t, visitedPaths, "/dir1")
assert.NotContains(t, visitedPaths, "/dir1/subfile1.txt")
assert.NotContains(t, visitedPaths, "/dir1/subfile2.txt")
assert.NotContains(t, visitedPaths, "/dir1/subdir1")
assert.NotContains(t, visitedPaths, "/dir1/subdir1/deepfile.txt")
assert.Contains(t, visitedPaths, "/dir2")
assert.Contains(t, visitedPaths, "/dir2/anotherfile.txt")
}
func TestWalkDiskDir_SkipAll(t *testing.T) {
fsys := createTestFS(t)
var visitedPaths []string
err := WalkDiskDir(fsys, "/", func(fsys filesystem.FileSystem, path string, d os.FileInfo, err error) error {
require.NoError(t, err)
visitedPaths = append(visitedPaths, path)
if path == "/dir1" {
return fs.SkipAll
}
return nil
})
require.NoError(t, err)
assert.Contains(t, visitedPaths, "/dir1")
assert.NotContains(t, visitedPaths, "/file1.txt")
assert.NotContains(t, visitedPaths, "/file2.txt")
assert.NotContains(t, visitedPaths, "/dir1/subfile1.txt")
assert.NotContains(t, visitedPaths, "/dir2")
assert.NotContains(t, visitedPaths, "/dir2/anotherfile.txt")
assert.NotContains(t, visitedPaths, "/emptydir")
}
func TestWalkDiskDir_EmptyDirectory(t *testing.T) {
fs := createTestFS(t)
var visitedPaths []string
err := WalkDiskDir(fs, "/emptydir", func(fsys filesystem.FileSystem, path string, d os.FileInfo, err error) error {
require.NoError(t, err)
visitedPaths = append(visitedPaths, path)
return nil
})
require.NoError(t, err)
assert.Empty(t, visitedPaths)
}
func TestWalkDiskDir_NonexistentPath(t *testing.T) {
fs := createTestFS(t)
err := WalkDiskDir(fs, "/nonexistent", func(fsys filesystem.FileSystem, path string, d os.FileInfo, err error) error {
return nil
})
assert.Error(t, err)
}
func TestWalkDiskDir_WalkFunctionError(t *testing.T) {
fs := createTestFS(t)
customErr := assert.AnError
err := WalkDiskDir(fs, "/", func(fsys filesystem.FileSystem, path string, d os.FileInfo, err error) error {
if path == "/file1.txt" {
return customErr
}
return nil
})
assert.Error(t, err)
assert.Equal(t, customErr, err)
}
func TestWalkDiskDir_SubdirectoryTraversal(t *testing.T) {
fs := createTestFS(t)
var visitedPaths []string
err := WalkDiskDir(fs, "/dir1", func(fsys filesystem.FileSystem, path string, d os.FileInfo, err error) error {
require.NoError(t, err)
visitedPaths = append(visitedPaths, path)
return nil
})
require.NoError(t, err)
expectedPaths := []string{
"/dir1/subfile1.txt",
"/dir1/subfile2.txt",
"/dir1/subdir1",
"/dir1/subdir1/deepfile.txt",
}
assert.ElementsMatch(t, expectedPaths, visitedPaths)
}
func TestWalkDiskDir_SingleFile(t *testing.T) {
fs := createTestFS(t)
var visitedPaths []string
err := WalkDiskDir(fs, "/file1.txt", func(fsys filesystem.FileSystem, path string, d os.FileInfo, err error) error {
require.NoError(t, err)
visitedPaths = append(visitedPaths, path)
return nil
})
// we are providing a file path, not a directory
require.Error(t, err)
assert.Empty(t, visitedPaths)
}
+61
View File
@@ -0,0 +1,61 @@
package os
import (
"context"
"fmt"
"regexp"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/sbomsync"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/linux"
"github.com/anchore/syft/syft/sbom"
)
func DetectFeatures(_ context.Context, resolver file.Resolver, builder sbomsync.Builder) error {
builder.(sbomsync.Accessor).WriteToSBOM(func(s *sbom.SBOM) {
if s.Artifacts.LinuxDistribution == nil {
return
}
err := findRhelFeatures(resolver, s.Artifacts.LinuxDistribution)
if err != nil {
log.WithFields("error", err, "release", s.Artifacts.LinuxDistribution).Trace("error searching for extended support")
}
})
return nil
}
func findRhelFeatures(resolver file.Resolver, release *linux.Release) error {
if release == nil || release.ID != "rhel" {
return nil
}
contentManifestFiles, err := resolver.FilesByGlob("/root/buildinfo/content_manifests/*.json")
if err != nil {
return fmt.Errorf("unable to find content manifests: %w", err)
}
for _, contentManifestFile := range contentManifestFiles {
found, err := hasRhelExtendedSupportInContentManifest(resolver, contentManifestFile)
if err != nil {
return fmt.Errorf("unable to read content manifest from %s: %w", contentManifestFile.String(), err)
}
if found {
release.ExtendedSupport = true
break
}
}
return nil
}
func hasRhelExtendedSupportInContentManifest(resolver file.Resolver, contentManifestFile file.Location) (bool, error) {
contents, err := resolver.FileContentsByLocation(contentManifestFile)
if err != nil {
return false, fmt.Errorf("unable to read content manifest from %s: %w", contentManifestFile.String(), err)
}
defer internal.CloseAndLogError(contents, "content-manifest")
patterns := []*regexp.Regexp{regexp.MustCompile(`baseos-eus`), regexp.MustCompile(`baseos-eus|appstream-eus`)}
return internal.MatchAnyFromReader(contents, patterns...)
}
+97
View File
@@ -0,0 +1,97 @@
package os_test
import (
"context"
"testing"
"github.com/stretchr/testify/require"
"github.com/anchore/stereoscope/pkg/imagetest"
"github.com/anchore/syft/internal/os"
"github.com/anchore/syft/internal/sbomsync"
"github.com/anchore/syft/internal/task"
"github.com/anchore/syft/syft"
"github.com/anchore/syft/syft/linux"
"github.com/anchore/syft/syft/sbom"
"github.com/anchore/syft/syft/source"
)
func Test_EnvironmentTask(t *testing.T) {
tests := []struct {
name string
expected linux.Release
}{
{
name: "not_rhel",
expected: linux.Release{
PrettyName: "Red Hat Enterprise Linux 9.4 (Plow)",
Name: "Red Hat Enterprise Linux",
ID: "not-rhel",
IDLike: []string{
"fedora",
},
Version: "9.4 (Plow)",
VersionID: "9.4",
HomeURL: "https://www.redhat.com/",
BugReportURL: "https://issues.redhat.com/",
CPEName: "cpe:/o:redhat:enterprise_linux:9::baseos",
ExtendedSupport: false, // important
},
},
{
name: "rhel_content_manifests",
expected: linux.Release{
PrettyName: "Red Hat Enterprise Linux 9.4 (Plow)",
Name: "Red Hat Enterprise Linux",
ID: "rhel",
IDLike: []string{
"fedora",
},
Version: "9.4 (Plow)",
VersionID: "9.4",
HomeURL: "https://www.redhat.com/",
BugReportURL: "https://issues.redhat.com/",
CPEName: "cpe:/o:redhat:enterprise_linux:9::baseos",
ExtendedSupport: true, // important
},
},
{
name: "rhel_no_manifests",
expected: linux.Release{
PrettyName: "Red Hat Enterprise Linux 9.4 (Plow)",
Name: "Red Hat Enterprise Linux",
ID: "rhel",
IDLike: []string{
"fedora",
},
Version: "9.4 (Plow)",
VersionID: "9.4",
HomeURL: "https://www.redhat.com/",
BugReportURL: "https://issues.redhat.com/",
CPEName: "cpe:/o:redhat:enterprise_linux:9::baseos",
ExtendedSupport: false, // important
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
tarPath := imagetest.GetFixtureImageTarPath(t, test.name)
// get the source
theSource, err := syft.GetSource(context.Background(), tarPath, syft.DefaultGetSourceConfig().WithSources("docker-archive"))
require.NoError(t, err)
resolver, err := theSource.FileResolver(source.SquashedScope)
require.NoError(t, err)
s := sbom.SBOM{}
err = task.NewEnvironmentTask().Execute(context.Background(), resolver, sbomsync.NewBuilder(&s))
require.NoError(t, err)
err = os.DetectFeatures(context.Background(), resolver, sbomsync.NewBuilder(&s))
require.NoError(t, err)
require.Equal(t, &test.expected, s.Artifacts.LinuxDistribution)
})
}
}
@@ -0,0 +1,4 @@
FROM scratch
COPY etc/os-release /etc/
COPY root/buildinfo/content_manifests/*.json /root/buildinfo/content_manifests/
@@ -0,0 +1,18 @@
NAME="Red Hat Enterprise Linux"
VERSION="9.4 (Plow)"
ID="not-rhel"
ID_LIKE="fedora"
VERSION_ID="9.4"
PLATFORM_ID="platform:el9"
PRETTY_NAME="Red Hat Enterprise Linux 9.4 (Plow)"
ANSI_COLOR="0;31"
LOGO="fedora-logo-icon"
CPE_NAME="cpe:/o:redhat:enterprise_linux:9::baseos"
HOME_URL="https://www.redhat.com/"
DOCUMENTATION_URL="https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/9"
BUG_REPORT_URL="https://issues.redhat.com/"
REDHAT_BUGZILLA_PRODUCT="Red Hat Enterprise Linux 9"
REDHAT_BUGZILLA_PRODUCT_VERSION=9.4
REDHAT_SUPPORT_PRODUCT="Red Hat Enterprise Linux"
REDHAT_SUPPORT_PRODUCT_VERSION="9.4"
@@ -0,0 +1,14 @@
{
"image_contents": [],
"metadata": {
"icm_spec": "https://raw.githubusercontent.com/containerbuildsystem/atomic-reactor/f4abcfdaf8247a6b074f94fa84f3846f82d781c6/atomic_reactor/schemas/content_manifest.json",
"icm_version": 1,
"image_layer_index": 3
},
"content_sets": [
"rhel-9-for-x86_64-appstream-eus-rpms__9_DOT_4",
"rhel-9-for-x86_64-baseos-eus-rpms__9_DOT_4",
"rhel-9-for-x86_64-rt-rpms__9_DOT_4",
"rhocp-4.16-for-rhel-9-x86_64-rpms"
]
}
@@ -0,0 +1,4 @@
FROM scratch
COPY etc/os-release /etc/
COPY root/buildinfo/content_manifests/*.json /root/buildinfo/content_manifests/
@@ -0,0 +1,18 @@
NAME="Red Hat Enterprise Linux"
VERSION="9.4 (Plow)"
ID="rhel"
ID_LIKE="fedora"
VERSION_ID="9.4"
PLATFORM_ID="platform:el9"
PRETTY_NAME="Red Hat Enterprise Linux 9.4 (Plow)"
ANSI_COLOR="0;31"
LOGO="fedora-logo-icon"
CPE_NAME="cpe:/o:redhat:enterprise_linux:9::baseos"
HOME_URL="https://www.redhat.com/"
DOCUMENTATION_URL="https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/9"
BUG_REPORT_URL="https://issues.redhat.com/"
REDHAT_BUGZILLA_PRODUCT="Red Hat Enterprise Linux 9"
REDHAT_BUGZILLA_PRODUCT_VERSION=9.4
REDHAT_SUPPORT_PRODUCT="Red Hat Enterprise Linux"
REDHAT_SUPPORT_PRODUCT_VERSION="9.4"
@@ -0,0 +1,14 @@
{
"image_contents": [],
"metadata": {
"icm_spec": "https://raw.githubusercontent.com/containerbuildsystem/atomic-reactor/f4abcfdaf8247a6b074f94fa84f3846f82d781c6/atomic_reactor/schemas/content_manifest.json",
"icm_version": 1,
"image_layer_index": 3
},
"content_sets": [
"rhel-9-for-x86_64-appstream-eus-rpms__9_DOT_4",
"rhel-9-for-x86_64-baseos-eus-rpms__9_DOT_4",
"rhel-9-for-x86_64-rt-rpms__9_DOT_4",
"rhocp-4.16-for-rhel-9-x86_64-rpms"
]
}
@@ -0,0 +1,13 @@
{
"image_contents": [],
"metadata": {
"icm_spec": "https://raw.githubusercontent.com/containerbuildsystem/atomic-reactor/f4abcfdaf8247a6b074f94fa84f3846f82d781c6/atomic_reactor/schemas/content_manifest.json",
"icm_version": 1,
"image_layer_index": 1
},
"content_sets": [
"rhel-9-for-x86_64-appstream-eus-rpms__9_DOT_4",
"rhel-9-for-x86_64-baseos-eus-rpms__9_DOT_4",
"rhocp-4.16-for-rhel-9-x86_64-rpms"
]
}
@@ -0,0 +1,3 @@
FROM scratch
COPY ./etc/os-release /etc/
@@ -0,0 +1,18 @@
NAME="Red Hat Enterprise Linux"
VERSION="9.4 (Plow)"
ID="rhel"
ID_LIKE="fedora"
VERSION_ID="9.4"
PLATFORM_ID="platform:el9"
PRETTY_NAME="Red Hat Enterprise Linux 9.4 (Plow)"
ANSI_COLOR="0;31"
LOGO="fedora-logo-icon"
CPE_NAME="cpe:/o:redhat:enterprise_linux:9::baseos"
HOME_URL="https://www.redhat.com/"
DOCUMENTATION_URL="https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/9"
BUG_REPORT_URL="https://issues.redhat.com/"
REDHAT_BUGZILLA_PRODUCT="Red Hat Enterprise Linux 9"
REDHAT_BUGZILLA_PRODUCT_VERSION=9.4
REDHAT_SUPPORT_PRODUCT="Red Hat Enterprise Linux"
REDHAT_SUPPORT_PRODUCT_VERSION="9.4"
+11
View File
@@ -13,6 +13,17 @@ func HasAnyOfPrefixes(input string, prefixes ...string) bool {
return false
}
// HasAnyOfSuffixes returns an indication if the given string has any of the given suffixes.
func HasAnyOfSuffixes(input string, suffixes ...string) bool {
for _, suffix := range suffixes {
if strings.HasSuffix(input, suffix) {
return true
}
}
return false
}
func TruncateMiddleEllipsis(input string, maxLen int) string {
if len(input) <= maxLen {
return input
+57
View File
@@ -7,6 +7,63 @@ import (
"github.com/stretchr/testify/assert"
)
func TestHasAnyOfSuffixes(t *testing.T) {
tests := []struct {
name string
input string
suffixes []string
expected bool
}{
{
name: "go case",
input: "this has something",
suffixes: []string{
"has something",
"has NOT something",
},
expected: true,
},
{
name: "no match",
input: "this has something",
suffixes: []string{
"has NOT something",
},
expected: false,
},
{
name: "empty",
input: "this has something",
suffixes: []string{},
expected: false,
},
{
name: "positive match last",
input: "this has something",
suffixes: []string{
"that does not have",
"something",
},
expected: true,
},
{
name: "empty input",
input: "",
suffixes: []string{
"that does not have",
"this has",
},
expected: false,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
assert.Equal(t, test.expected, HasAnyOfSuffixes(test.input, test.suffixes...))
})
}
}
func TestHasAnyOfPrefixes(t *testing.T) {
tests := []struct {
name string
+1 -1
View File
@@ -15,7 +15,7 @@ import (
"github.com/anchore/syft/syft/sbom"
)
func RunTask(ctx context.Context, tsk Task, resolver file.Resolver, s sbomsync.Builder, prog *monitor.CatalogerTaskProgress) error {
func RunTask(ctx context.Context, tsk Task, resolver file.Resolver, s sbomsync.Builder, prog *monitor.TaskProgress) error {
err := runTaskSafely(ctx, tsk, resolver, s)
unknowns, remainingErrors := unknown.ExtractCoordinateErrors(err)
if len(unknowns) > 0 {
+1 -1
View File
@@ -17,7 +17,7 @@ func Test_TaskExecutor_PanicHandling(t *testing.T) {
panic("something bad happened")
})
err := RunTask(context.Background(), tsk, nil, nil, &monitor.CatalogerTaskProgress{
err := RunTask(context.Background(), tsk, nil, nil, &monitor.TaskProgress{
Manual: progress.NewManual(-1),
})
+7
View File
@@ -0,0 +1,7 @@
package task
import "github.com/anchore/syft/internal/os"
func NewOSFeatureDetectionTask() Task {
return NewTask("os-feature-detection", os.DetectFeatures)
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+73 -1
View File
@@ -1,6 +1,6 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "anchore.io/schema/syft/json/16.0.34/document",
"$id": "anchore.io/schema/syft/json/16.0.36/document",
"$ref": "#/$defs/Document",
"$defs": {
"AlpmDbEntry": {
@@ -1628,6 +1628,9 @@
},
"supportEnd": {
"type": "string"
},
"extendedSupport": {
"type": "boolean"
}
},
"type": "object"
@@ -2001,6 +2004,9 @@
{
"$ref": "#/$defs/PythonPoetryLockEntry"
},
{
"$ref": "#/$defs/PythonUvLockEntry"
},
{
"$ref": "#/$defs/RDescription"
},
@@ -2594,6 +2600,72 @@
"dependencies"
]
},
"PythonUvLockDependencyEntry": {
"properties": {
"name": {
"type": "string"
},
"optional": {
"type": "boolean"
},
"markers": {
"type": "string"
},
"extras": {
"items": {
"type": "string"
},
"type": "array"
}
},
"type": "object",
"required": [
"name",
"optional"
]
},
"PythonUvLockEntry": {
"properties": {
"index": {
"type": "string"
},
"dependencies": {
"items": {
"$ref": "#/$defs/PythonUvLockDependencyEntry"
},
"type": "array"
},
"extras": {
"items": {
"$ref": "#/$defs/PythonUvLockExtraEntry"
},
"type": "array"
}
},
"type": "object",
"required": [
"index",
"dependencies"
]
},
"PythonUvLockExtraEntry": {
"properties": {
"name": {
"type": "string"
},
"dependencies": {
"items": {
"type": "string"
},
"type": "array"
}
},
"type": "object",
"required": [
"name",
"dependencies"
]
},
"RDescription": {
"properties": {
"title": {
+3 -3
View File
@@ -144,14 +144,14 @@ func setContextExecutors(ctx context.Context, cfg *CreateSBOMConfig) context.Con
return ctx
}
func monitorPackageCount(prog *monitor.CatalogerTaskProgress) func(s *sbom.SBOM) {
func monitorPackageCount(prog *monitor.TaskProgress) func(s *sbom.SBOM) {
return func(s *sbom.SBOM) {
count := humanize.Comma(int64(s.Artifacts.Packages.PackageCount()))
prog.AtomicStage.Set(fmt.Sprintf("%s packages", count))
}
}
func monitorPackageCatalogingTask() *monitor.CatalogerTaskProgress {
func monitorPackageCatalogingTask() *monitor.TaskProgress {
info := monitor.GenericTask{
Title: monitor.Title{
Default: "Packages",
@@ -164,7 +164,7 @@ func monitorPackageCatalogingTask() *monitor.CatalogerTaskProgress {
return bus.StartCatalogerTask(info, -1, "")
}
func monitorCatalogingTask(srcID artifact.ID, tasks [][]task.Task) *monitor.CatalogerTaskProgress {
func monitorCatalogingTask(srcID artifact.ID, tasks [][]task.Task) *monitor.TaskProgress {
info := monitor.GenericTask{
Title: monitor.Title{
Default: "Catalog contents",
+19
View File
@@ -191,6 +191,7 @@ func (c *CreateSBOMConfig) makeTaskGroups(src source.Description) ([][]task.Task
scopeTasks := c.scopeTasks()
relationshipsTasks := c.relationshipTasks(src)
unknownTasks := c.unknownsTasks()
osFeatureDetectionTasks := c.osFeatureDetectionTasks()
pkgTasks, fileTasks, selectionEvidence, err := c.selectTasks(src)
if err != nil {
@@ -220,6 +221,11 @@ func (c *CreateSBOMConfig) makeTaskGroups(src source.Description) ([][]task.Task
taskGroups = append(taskGroups, unknownTasks)
}
// osFeatureDetectionTasks should happen after package scanning is complete
if len(osFeatureDetectionTasks) > 0 {
taskGroups = append(taskGroups, osFeatureDetectionTasks)
}
// identifying the environment (i.e. the linux release) must be done first as this is required for package cataloging
taskGroups = append(
[][]task.Task{
@@ -444,6 +450,17 @@ func (c *CreateSBOMConfig) unknownsTasks() []task.Task {
return tasks
}
// osFeatureDetectionTasks returns a set of tasks that perform post-processing feature detection and update the SBOM accordingly
func (c *CreateSBOMConfig) osFeatureDetectionTasks() []task.Task {
var tasks []task.Task
if t := task.NewOSFeatureDetectionTask(); t != nil {
tasks = append(tasks, t)
}
return tasks
}
func (c *CreateSBOMConfig) validate() error {
if c.Relationships.ExcludeBinaryPackagesWithFileOwnershipOverlap {
if !c.Relationships.PackageFileOwnershipOverlap {
@@ -464,6 +481,8 @@ func findDefaultTags(src source.Description) ([]string, error) {
return []string{pkgcataloging.ImageTag, filecataloging.FileTag}, nil
case source.FileMetadata, source.DirectoryMetadata:
return []string{pkgcataloging.DirectoryTag, filecataloging.FileTag}, nil
case source.SnapMetadata:
return []string{pkgcataloging.InstalledTag, filecataloging.FileTag}, nil
default:
return nil, fmt.Errorf("unable to determine default cataloger tag for source type=%T", m)
}
+15
View File
@@ -91,6 +91,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
fileCatalogerNames(),
relationshipCatalogerNames(),
unknownsTaskNames(),
osFeatureDetectionTaskNames(),
},
wantManifest: &catalogerManifest{
Requested: cataloging.SelectionRequest{
@@ -110,6 +111,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
fileCatalogerNames(),
relationshipCatalogerNames(),
unknownsTaskNames(),
osFeatureDetectionTaskNames(),
},
wantManifest: &catalogerManifest{
Requested: cataloging.SelectionRequest{
@@ -130,6 +132,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
fileCatalogerNames(),
relationshipCatalogerNames(),
unknownsTaskNames(),
osFeatureDetectionTaskNames(),
},
wantManifest: &catalogerManifest{
Requested: cataloging.SelectionRequest{
@@ -149,6 +152,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
fileCatalogerNames("file-metadata", "content", "binary-metadata"),
relationshipCatalogerNames(),
unknownsTaskNames(),
osFeatureDetectionTaskNames(),
},
wantManifest: &catalogerManifest{
Requested: cataloging.SelectionRequest{
@@ -169,6 +173,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
nil, // note: there is a file cataloging group, with no items in it
relationshipCatalogerNames(),
unknownsTaskNames(),
osFeatureDetectionTaskNames(),
},
wantManifest: &catalogerManifest{
Requested: cataloging.SelectionRequest{
@@ -192,6 +197,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
),
relationshipCatalogerNames(),
unknownsTaskNames(),
osFeatureDetectionTaskNames(),
},
wantManifest: &catalogerManifest{
Requested: cataloging.SelectionRequest{
@@ -213,6 +219,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
fileCatalogerNames(),
relationshipCatalogerNames(),
unknownsTaskNames(),
osFeatureDetectionTaskNames(),
},
wantManifest: &catalogerManifest{
Requested: cataloging.SelectionRequest{
@@ -234,6 +241,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
fileCatalogerNames(),
relationshipCatalogerNames(),
unknownsTaskNames(),
osFeatureDetectionTaskNames(),
},
wantManifest: &catalogerManifest{
Requested: cataloging.SelectionRequest{
@@ -255,6 +263,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
fileCatalogerNames(),
relationshipCatalogerNames(),
unknownsTaskNames(),
osFeatureDetectionTaskNames(),
},
wantManifest: &catalogerManifest{
Requested: cataloging.SelectionRequest{
@@ -277,6 +286,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
fileCatalogerNames(),
relationshipCatalogerNames(),
unknownsTaskNames(),
osFeatureDetectionTaskNames(),
},
wantManifest: &catalogerManifest{
Requested: cataloging.SelectionRequest{
@@ -298,6 +308,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
fileCatalogerNames(),
relationshipCatalogerNames(),
unknownsTaskNames(),
osFeatureDetectionTaskNames(),
},
wantManifest: &catalogerManifest{
Requested: cataloging.SelectionRequest{
@@ -430,6 +441,10 @@ func unknownsTaskNames() []string {
return []string{"unknowns-labeler"}
}
func osFeatureDetectionTaskNames() []string {
return []string{"os-feature-detection"}
}
func environmentCatalogerNames() []string {
return []string{"environment-cataloger"}
}
+4
View File
@@ -23,6 +23,10 @@ const (
// CatalogerTaskStarted is a partybus event that occurs when starting a task within a cataloger
CatalogerTaskStarted partybus.EventType = typePrefix + "-cataloger-task-started"
// PullSourceStarted is a partybus event that occurs when starting to pull a source (does not overlap with stereoscope image pull events,
// this covers any additional sources such as snap and git repos).
PullSourceStarted partybus.EventType = typePrefix + "-pull-source-started"
// Events exclusively for the CLI
// CLIAppUpdateAvailable is a partybus event that occurs when an application update is available
@@ -1,15 +0,0 @@
package monitor
import (
"github.com/wagoodman/go-progress"
)
const (
TopLevelCatalogingTaskID = "cataloging"
PackageCatalogingTaskID = "package-cataloging"
)
type CatalogerTaskProgress struct {
*progress.AtomicStage
*progress.Manual
}
+10
View File
@@ -6,6 +6,11 @@ import (
"github.com/wagoodman/go-progress"
)
const (
TopLevelCatalogingTaskID = "cataloging"
PackageCatalogingTaskID = "package-cataloging"
)
type ShellProgress struct {
io.Reader
progress.Progressable
@@ -34,3 +39,8 @@ type GenericTask struct {
ParentID string
Context string
}
type TaskProgress struct {
*progress.AtomicStage
*progress.Manual
}
+20
View File
@@ -77,6 +77,26 @@ func ParseCatalogerTaskStarted(e partybus.Event) (progress.StagedProgressable, *
return mon, &source, nil
}
func ParsePullSourceStarted(e partybus.Event) (progress.StagedProgressable, *monitor.GenericTask, error) {
if err := checkEventType(e.Type, event.PullSourceStarted); err != nil {
return nil, nil, err
}
var mon progress.StagedProgressable
source, ok := e.Source.(monitor.GenericTask)
if !ok {
return nil, nil, newPayloadErr(e.Type, "Source", e.Source)
}
mon, ok = e.Value.(progress.StagedProgressable)
if !ok {
mon = nil
}
return mon, &source, nil
}
func ParseAttestationStartedEvent(e partybus.Event) (io.Reader, progress.Progressable, *monitor.GenericTask, error) {
if err := checkEventType(e.Type, event.AttestationStarted); err != nil {
return nil, nil, nil, err
+1 -1
View File
@@ -106,7 +106,7 @@ func processExecutableLocation(loc file.Location, resolver file.Resolver) (*file
return processExecutable(loc, uReader)
}
func catalogingProgress(locations int64) *monitor.CatalogerTaskProgress {
func catalogingProgress(locations int64) *monitor.TaskProgress {
info := monitor.GenericTask{
Title: monitor.Title{
Default: "Executables",
+1 -1
View File
@@ -113,7 +113,7 @@ func (i *Cataloger) catalogLocation(resolver file.Resolver, location file.Locati
return buf.String(), nil
}
func catalogingProgress(locations int64) *monitor.CatalogerTaskProgress {
func catalogingProgress(locations int64) *monitor.TaskProgress {
info := monitor.GenericTask{
Title: monitor.Title{
Default: "File contents",
+1 -1
View File
@@ -112,7 +112,7 @@ func (i *Cataloger) catalogLocation(ctx context.Context, resolver file.Resolver,
return digests, nil
}
func catalogingProgress(locations int64) *monitor.CatalogerTaskProgress {
func catalogingProgress(locations int64) *monitor.TaskProgress {
info := monitor.GenericTask{
Title: monitor.Title{
Default: "File digests",
@@ -76,7 +76,7 @@ func (i *Cataloger) Catalog(ctx context.Context, resolver file.Resolver, coordin
return results, errs
}
func catalogingProgress(locations int64) *monitor.CatalogerTaskProgress {
func catalogingProgress(locations int64) *monitor.TaskProgress {
info := monitor.GenericTask{
Title: monitor.Title{
Default: "File metadata",
@@ -480,6 +480,10 @@ func Test_toOsComponent(t *testing.T) {
Version: "myVersion",
},
Properties: &[]cyclonedx.Property{
{
Name: "syft:distro:extendedSupport",
Value: "false",
},
{
Name: "syft:distro:id",
Value: "myLinux",
@@ -37,6 +37,7 @@ const (
prefixImage = "Image"
prefixDirectory = "Directory"
prefixFile = "File"
prefixSnap = "Snap"
prefixUnknown = "Unknown"
)
@@ -228,6 +229,18 @@ func toRootPackage(s source.Description) *spdx.Package {
Value: d.Value,
})
}
case source.SnapMetadata:
prefix = prefixSnap
purpose = spdxPrimaryPurposeContainer
for _, d := range m.Digests {
checksums = append(checksums, spdx.Checksum{
Algorithm: toChecksumAlgorithm(d.Algorithm),
Value: d.Value,
})
}
default:
prefix = prefixUnknown
purpose = spdxPrimaryPurposeOther
@@ -237,6 +237,82 @@ func Test_toFormatModel(t *testing.T) {
},
},
},
{
name: "snap",
in: sbom.SBOM{
Source: source.Description{
Name: "etcd",
Version: "3.4.36",
Metadata: source.SnapMetadata{
Summary: "Distributed reliable key-value store",
Base: "core18",
Grade: "stable",
Confinement: "strict",
Architectures: []string{
"amd64",
},
Digests: []file.Digest{
{
Algorithm: "sha256",
Value: "d34db33f",
},
},
},
},
Artifacts: sbom.Artifacts{
Packages: pkg.NewCollection(pkg.Package{
Name: "pkg-1",
Version: "version-1",
}),
},
},
expected: &spdx.Document{
SPDXIdentifier: "DOCUMENT",
SPDXVersion: spdx.Version,
DataLicense: spdx.DataLicense,
DocumentName: "etcd",
Packages: []*spdx.Package{
{
PackageSPDXIdentifier: "Package-pkg-1-pkg-1",
PackageName: "pkg-1",
PackageVersion: "version-1",
PackageSupplier: &spdx.Supplier{
Supplier: "NOASSERTION",
},
},
{
PackageSPDXIdentifier: "DocumentRoot-Snap-etcd",
PackageName: "etcd",
PackageVersion: "3.4.36",
PrimaryPackagePurpose: "CONTAINER",
PackageChecksums: []spdx.Checksum{{Algorithm: "SHA256", Value: "d34db33f"}},
PackageSupplier: &spdx.Supplier{
Supplier: "NOASSERTION",
},
},
},
Relationships: []*spdx.Relationship{
{
RefA: spdx.DocElementID{
ElementRefID: "DocumentRoot-Snap-etcd",
},
RefB: spdx.DocElementID{
ElementRefID: "Package-pkg-1-pkg-1",
},
Relationship: spdx.RelationshipContains,
},
{
RefA: spdx.DocElementID{
ElementRefID: "DOCUMENT",
},
RefB: spdx.DocElementID{
ElementRefID: "DocumentRoot-Snap-etcd",
},
Relationship: spdx.RelationshipDescribes,
},
},
},
},
}
for _, test := range tests {
@@ -102,6 +102,10 @@
"version": "1.2.3"
},
"properties": [
{
"name": "syft:distro:extendedSupport",
"value": "false"
},
{
"name": "syft:distro:id",
"value": "debian"
@@ -111,6 +111,10 @@
"version": "1.2.3"
},
"properties": [
{
"name": "syft:distro:extendedSupport",
"value": "false"
},
{
"name": "syft:distro:id",
"value": "debian"
@@ -53,6 +53,7 @@
<description>debian</description>
<swid tagId="debian" name="debian" version="1.2.3"></swid>
<properties>
<property name="syft:distro:extendedSupport">false</property>
<property name="syft:distro:id">debian</property>
<property name="syft:distro:idLike:0">like!</property>
<property name="syft:distro:prettyName">debian</property>
@@ -11,7 +11,7 @@
</component>
</components>
</tools>
<component bom-ref="f28a4ba3ddfdddad" type="container">
<component bom-ref="redacted" type="container">
<name>user-image-input</name>
<version>sha256:redacted</version>
</component>
@@ -56,6 +56,7 @@
<description>debian</description>
<swid tagId="debian" name="debian" version="1.2.3"></swid>
<properties>
<property name="syft:distro:extendedSupport">false</property>
<property name="syft:distro:id">debian</property>
<property name="syft:distro:idLike:0">like!</property>
<property name="syft:distro:prettyName">debian</property>
@@ -130,6 +130,11 @@ func toPath(s source.Description, p pkg.Package) string {
return fmt.Sprintf("%s/%s", path, packagePath)
}
return packagePath
case source.SnapMetadata:
if inputPath != "" {
return fmt.Sprintf("%s:/%s", inputPath, packagePath)
}
return packagePath
}
}
return inputPath
@@ -173,6 +173,11 @@ func Test_toGithubModel(t *testing.T) {
metadata: source.FileMetadata{Path: "./archive.tar.gz"},
testPath: "archive.tar.gz:/etc",
},
{
name: "snap",
metadata: source.SnapMetadata{},
testPath: "name:/etc",
},
}
for _, test := range tests {
@@ -180,6 +185,7 @@ func Test_toGithubModel(t *testing.T) {
s := sbomFixture()
if test.metadata != nil {
s.Source.Name = "name"
s.Source.Metadata = test.metadata
}
actual := ToGithubModel(&s)
@@ -45,6 +45,15 @@ func Test_DocumentName(t *testing.T) {
},
expected: "some/path/to/place",
},
{
name: "snap",
srcMetadata: source.Description{
Name: "some/name",
// there is nothing in the snap metadata that indicates a name
Metadata: source.SnapMetadata{},
},
expected: "some/name",
},
{
name: "named",
srcMetadata: source.Description{
@@ -16,6 +16,7 @@ const (
InputImage = "image"
InputDirectory = "dir"
InputFile = "file"
InputSnap = "snap"
)
func DocumentNameAndNamespace(src source.Description, desc sbom.Descriptor) (string, string) {
@@ -33,6 +34,8 @@ func DocumentNamespace(name string, src source.Description, desc sbom.Descriptor
input = InputDirectory
case source.FileMetadata:
input = InputFile
case source.SnapMetadata:
input = InputSnap
}
uniqueID := uuid.Must(uuid.NewRandom())
@@ -12,7 +12,7 @@ import (
"github.com/anchore/syft/syft/source"
)
func Test_documentNamespace(t *testing.T) {
func Test_DocumentNamespace(t *testing.T) {
tracker := sourcemetadata.NewCompletionTester(t)
tests := []struct {
@@ -53,6 +53,14 @@ func Test_documentNamespace(t *testing.T) {
},
expected: "https://anchore.com/syft/file/my-name-",
},
{
name: "snap",
inputName: "my-name",
src: source.Description{
Metadata: source.SnapMetadata{},
},
expected: "https://anchore.com/syft/snap/my-name-",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
@@ -42,6 +42,7 @@ func Test_OriginatorSupplier(t *testing.T) {
pkg.PythonPipfileLockEntry{},
pkg.PythonRequirementsEntry{},
pkg.PythonPoetryLockEntry{},
pkg.PythonUvLockEntry{},
pkg.RustBinaryAuditEntry{},
pkg.RustCargoLockEntry{},
pkg.SwiftPackageManagerResolvedEntry{},
+4 -4
View File
@@ -68,15 +68,15 @@ func AssertEncoderAgainstGoldenSnapshot(t *testing.T, cfg EncoderSnapshotTestCon
if cfg.IsJSON {
require.JSONEq(t, string(expected), string(actual))
} else {
requireEqual(t, expected, actual)
requireEqual(t, string(expected), string(actual))
}
}
func requireEqual(t *testing.T, expected any, actual any) {
func requireEqual(t *testing.T, expected string, actual string) {
if diff := cmp.Diff(expected, actual); diff != "" {
// uncomment to debug
// t.Logf("expected: %s", expected)
// t.Logf("actual: %s", actual)
// t.Logf("expected:\n%s", expected)
// t.Logf("actual:\n%s", actual)
t.Fatalf("mismatched output: %s", diff)
}
}
+2 -2
View File
@@ -114,12 +114,12 @@ func TestOutOfDateParser(t *testing.T) {
name: "warning when document version is unparseable",
documentVersion: "some-nonsense",
parserVersion: "3.1.0",
want: fmt.Errorf("error comparing document schema version with parser schema version: %w", errors.New("Invalid Semantic Version")),
want: fmt.Errorf("error comparing document schema version with parser schema version: %w", errors.New("invalid semantic version")),
}, {
name: "warning when parser version is unparseable",
documentVersion: "7.1.0",
parserVersion: "some-nonsense",
want: fmt.Errorf("error comparing document schema version with parser schema version: %w", errors.New("Invalid Semantic Version")),
want: fmt.Errorf("error comparing document schema version with parser schema version: %w", errors.New("invalid semantic version")),
}}
for _, tt := range tests {
+24
View File
@@ -1,5 +1,10 @@
package model
import (
"encoding/json"
"fmt"
)
// Document represents the syft cataloging findings as a JSON document
type Document struct {
Artifacts []Package `json:"artifacts"` // Artifacts is the list of packages discovered and placed into the catalog
@@ -11,6 +16,25 @@ type Document struct {
Schema Schema `json:"schema"` // Schema is a block reserved for defining the version for the shape of this JSON document and where to find the schema document to validate the shape
}
func (d *Document) UnmarshalJSON(data []byte) error {
type Alias *Document
aux := Alias(d)
if err := json.Unmarshal(data, aux); err != nil {
return fmt.Errorf("could not unmarshal syft JSON document: %w", err)
}
// in previous versions of anchorectl, the file modes were stored as decimal values instead of octal.
if d.Schema.Version == "1.0.0" && d.Descriptor.Name == "anchorectl" {
// convert all file modes from decimal to octal
for i := range d.Files {
d.Files[i].Metadata.Mode = convertBase10ToBase8(d.Files[i].Metadata.Mode)
}
}
return nil
}
// Descriptor describes what created the document as well as surrounding metadata
type Descriptor struct {
Name string `json:"name"`
@@ -0,0 +1,99 @@
package model
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestDocumentUnmarshalJSON_SchemaDetection(t *testing.T) {
tests := []struct {
name string
jsonData string
modes []int
}{
{
name: "schema version 1.0.0 + anchorectl",
jsonData: `{
"files": [
{"metadata": {"mode": 493}},
{"metadata": {"mode": 420}}
],
"schema": {"version": "1.0.0"},
"descriptor": {
"name": "anchorectl"
}
}`,
modes: []int{755, 644},
},
{
name: "schema version 1.0.0 + syft",
jsonData: `{
"files": [
{"metadata": {"mode": 755}},
{"metadata": {"mode": 644}}
],
"schema": {"version": "1.0.0"},
"descriptor": {
"name": "syft"
}
}`,
modes: []int{755, 644},
},
{
name: "schema version 2.0.0 + anchorectl",
jsonData: `{
"files": [
{"metadata": {"mode": 755}},
{"metadata": {"mode": 644}}
],
"schema": {"version": "2.0.0"},
"descriptor": {
"name": "anchorectl"
}
}`,
modes: []int{755, 644},
},
{
name: "missing schema version should not convert modes",
jsonData: `{
"files": [
{"metadata": {"mode": 755}}
],
"schema": {}
}`,
modes: []int{755},
},
{
name: "empty files array with version 1.0.0",
jsonData: `{
"files": [],
"schema": {"version": "1.0.0"},
"descriptor": {
"name": "anchorectl"
}
}`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var doc Document
err := json.Unmarshal([]byte(tt.jsonData), &doc)
if err != nil {
t.Fatalf("Failed to unmarshal JSON: %v", err)
}
var modes []int
for _, file := range doc.Files {
modes = append(modes, file.Metadata.Mode)
}
require.Len(t, doc.Files, len(tt.modes), "Unexpected number of files")
assert.Equal(t, tt.modes, modes, "File modes do not match expected values")
})
}
}
+92
View File
@@ -1,6 +1,11 @@
package model
import (
"encoding/json"
"fmt"
"strconv"
stereoFile "github.com/anchore/stereoscope/pkg/file"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/license"
)
@@ -26,6 +31,57 @@ type FileMetadataEntry struct {
Size int64 `json:"size"`
}
type auxFileMetadataEntry FileMetadataEntry
type fileMetadataEntryWithLegacyHint struct {
*auxFileMetadataEntry `json:",inline"`
LegacyHint any `json:"FileInfo"`
}
func (f *FileMetadataEntry) UnmarshalJSON(data []byte) error {
aux := fileMetadataEntryWithLegacyHint{
auxFileMetadataEntry: (*auxFileMetadataEntry)(f),
}
if err := json.Unmarshal(data, &aux); err == nil {
fieldsSpecified := f.Mode != 0 || f.Type != "" || f.LinkDestination != "" ||
f.UserID != 0 || f.GroupID != 0 || f.MIMEType != "" || f.Size != 0
if aux.LegacyHint == nil && fieldsSpecified {
// we should have at least one field set to a non-zero value... (this is not a legacy shape)
return nil
}
}
var legacy sbomImportLegacyFileMetadataEntry
if err := json.Unmarshal(data, &legacy); err != nil {
return err
}
if !legacy.Type.WasInt {
// this occurs for document shapes from a non-import path and indicates that the mode has already been converted to octal.
// That being said, we want to handle all legacy shapes the same, so we will convert this to base 10 for consistency.
legacy.Mode = convertBase8ToBase10(legacy.Mode)
}
f.Mode = legacy.Mode
f.Type = legacy.Type.Value
f.LinkDestination = legacy.LinkDestination
f.UserID = legacy.UserID
f.GroupID = legacy.GroupID
f.MIMEType = legacy.MIMEType
f.Size = legacy.Size
return nil
}
type sbomImportLegacyFileMetadataEntry struct {
Mode int `json:"Mode"`
Type intOrStringFileType `json:"Type"`
LinkDestination string `json:"LinkDestination"`
UserID int `json:"UserID"`
GroupID int `json:"GroupID"`
MIMEType string `json:"MIMEType"`
Size int64 `json:"Size"`
}
type FileLicense struct {
Value string `json:"value"`
SPDXExpression string `json:"spdxExpression"`
@@ -38,3 +94,39 @@ type FileLicenseEvidence struct {
Offset int `json:"offset"`
Extent int `json:"extent"`
}
type intOrStringFileType struct {
Value string
WasInt bool
}
func (lt *intOrStringFileType) UnmarshalJSON(data []byte) error {
var str string
if err := json.Unmarshal(data, &str); err == nil {
lt.Value = str
return nil
}
var num stereoFile.Type
if err := json.Unmarshal(data, &num); err != nil {
return fmt.Errorf("file.Type must be either string or int, got: %s", string(data))
}
lt.Value = num.String()
lt.WasInt = true
return nil
}
func convertBase10ToBase8(rawMode int) int {
octalStr := fmt.Sprintf("%o", rawMode)
// we don't need to check that this is a valid octal string since the input is always an integer
result, _ := strconv.Atoi(octalStr)
return result
}
func convertBase8ToBase10(octalMode int) int {
octalStr := strconv.Itoa(octalMode)
result, _ := strconv.ParseInt(octalStr, 8, 64)
return int(result)
}
+320
View File
@@ -0,0 +1,320 @@
package model
import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_FileMetadataEntry_UnmarshalJSON(t *testing.T) {
tests := []struct {
name string
jsonData []byte
expected FileMetadataEntry
}{
{
name: "unmarshal current format",
jsonData: []byte(`{
"mode": 644,
"type": "RegularFile",
"linkDestination": "/usr/bin/python3",
"userID": 1000,
"groupID": 1000,
"mimeType": "text/plain",
"size": 10174
}`),
expected: FileMetadataEntry{
Mode: 644,
Type: "RegularFile",
LinkDestination: "/usr/bin/python3",
UserID: 1000,
GroupID: 1000,
MIMEType: "text/plain",
Size: 10174,
},
},
{
name: "unmarshal legacy image add internal document format",
jsonData: []byte(`{
"FileInfo": {},
"Mode": 644,
"Type": "RegularFile",
"LinkDestination": "/usr/bin/python3",
"UserID": 1000,
"GroupID": 1000,
"MIMEType": "text/plain",
"Size": 10174
}`),
expected: FileMetadataEntry{
Mode: 420, // important! we convert this to base 10 so that all documents are consistent
Type: "RegularFile",
LinkDestination: "/usr/bin/python3",
UserID: 1000,
GroupID: 1000,
MIMEType: "text/plain",
Size: 10174,
},
},
{
name: "unmarshal legacy sbom import format",
jsonData: []byte(`{
"FileInfo": {},
"Mode": 644,
"Type": 0,
"LinkDestination": "/usr/bin/python3",
"UserID": 1000,
"GroupID": 1000,
"MIMEType": "text/plain",
"Size": 10174
}`),
expected: FileMetadataEntry{
Mode: 644,
Type: "RegularFile",
LinkDestination: "/usr/bin/python3",
UserID: 1000,
GroupID: 1000,
MIMEType: "text/plain",
Size: 10174,
},
},
{
name: "unmarshal minimal current format",
jsonData: []byte(`{
"mode": 0,
"type": "RegularFile",
"userID": 0,
"groupID": 0,
"size": 0
}`),
expected: FileMetadataEntry{
Type: "RegularFile",
},
},
{
name: "unmarshal minimal legacy format",
jsonData: []byte(`{
"FileInfo": {},
"Mode": 0,
"Type": "RegularFile",
"UserID": 0,
"GroupID": 0,
"Size": 0
}`),
expected: FileMetadataEntry{
Type: "RegularFile",
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
var actual FileMetadataEntry
err := actual.UnmarshalJSON(test.jsonData)
require.NoError(t, err)
if diff := cmp.Diff(test.expected, actual); diff != "" {
t.Errorf("FileMetadataEntry mismatch (-expected +actual):\n%s", diff)
}
})
}
}
func Test_intOrStringFileType_UnmarshalJSON(t *testing.T) {
tests := []struct {
name string
jsonData []byte
expected string
expectedWasInt bool
wantErr require.ErrorAssertionFunc
}{
// string inputs - should pass through unchanged
{
name: "string RegularFile",
jsonData: []byte(`"RegularFile"`),
expected: "RegularFile",
},
{
name: "string HardLink",
jsonData: []byte(`"HardLink"`),
expected: "HardLink",
},
{
name: "string Directory",
jsonData: []byte(`"Directory"`),
expected: "Directory",
},
{
name: "string custom value",
jsonData: []byte(`"CustomFileType"`),
expected: "CustomFileType",
},
// integer inputs - should convert to string representation
{
name: "int 0 (TypeRegular)",
jsonData: []byte(`0`),
expected: "RegularFile",
expectedWasInt: true,
},
{
name: "int 1 (TypeHardLink)",
jsonData: []byte(`1`),
expected: "HardLink",
expectedWasInt: true,
},
{
name: "int 2 (TypeSymLink)",
jsonData: []byte(`2`),
expected: "SymbolicLink",
expectedWasInt: true,
},
{
name: "int 3 (TypeCharacterDevice)",
jsonData: []byte(`3`),
expected: "CharacterDevice",
expectedWasInt: true,
},
{
name: "int 4 (TypeBlockDevice)",
jsonData: []byte(`4`),
expected: "BlockDevice",
expectedWasInt: true,
},
{
name: "int 5 (TypeDirectory)",
jsonData: []byte(`5`),
expected: "Directory",
expectedWasInt: true,
},
{
name: "int 6 (TypeFIFO)",
jsonData: []byte(`6`),
expected: "FIFONode",
expectedWasInt: true,
},
{
name: "int 7 (TypeSocket)",
jsonData: []byte(`7`),
expected: "Socket",
expectedWasInt: true,
},
{
name: "int 8 (TypeIrregular)",
jsonData: []byte(`8`),
expected: "IrregularFile",
expectedWasInt: true,
},
{
name: "unknown int",
jsonData: []byte(`99`),
expected: "Unknown",
expectedWasInt: true,
},
{
name: "negative int",
jsonData: []byte(`-1`),
expected: "Unknown",
expectedWasInt: true,
},
{
name: "null value",
jsonData: []byte(`null`),
},
{
name: "invalid JSON",
jsonData: []byte(`{`),
wantErr: require.Error,
},
{
name: "boolean value",
jsonData: []byte(`true`),
wantErr: require.Error,
},
{
name: "array value",
jsonData: []byte(`[]`),
wantErr: require.Error,
},
{
name: "object value",
jsonData: []byte(`{}`),
wantErr: require.Error,
},
{
name: "float value",
jsonData: []byte(`1.5`),
wantErr: require.Error,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if test.wantErr == nil {
test.wantErr = require.NoError
}
var ft intOrStringFileType
err := ft.UnmarshalJSON(test.jsonData)
test.wantErr(t, err)
if err != nil {
return
}
assert.Equal(t, test.expected, ft.Value)
assert.Equal(t, test.expectedWasInt, ft.WasInt)
})
}
}
func Test_convertBase10ToBase8(t *testing.T) {
tests := []struct {
name string
input int
expected int
}{
{
name: "no permissions",
input: 0,
expected: 0,
},
{
name: "symlink + rwxrwxrwx",
input: 134218239,
expected: 1000000777,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
actual := convertBase10ToBase8(tt.input)
require.Equal(t, tt.expected, actual)
})
}
}
func Test_convertBase8ToBase10(t *testing.T) {
tests := []struct {
name string
input int
expected int
}{
{
name: "no permissions",
input: 0,
expected: 0,
},
{
name: "symlink + rwxrwxrwx",
input: 1000000777,
expected: 134218239,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
actual := convertBase8ToBase10(tt.input)
require.Equal(t, tt.expected, actual)
})
}
}
@@ -25,6 +25,7 @@ type LinuxRelease struct {
PrivacyPolicyURL string `json:"privacyPolicyURL,omitempty"`
CPEName string `json:"cpeName,omitempty"`
SupportEnd string `json:"supportEnd,omitempty"`
ExtendedSupport bool `json:"extendedSupport,omitempty"`
}
func (s *IDLikes) UnmarshalJSON(data []byte) error {
+1
View File
@@ -204,5 +204,6 @@ func unpackPkgMetadata(p *Package, unpacker packageMetadataUnpacker) error {
}
}
p.Metadata = reflect.ValueOf(val).Elem().Interface()
p.MetadataType = ty
return nil
}
+45
View File
@@ -145,6 +145,51 @@ func TestSource_UnmarshalJSON(t *testing.T) {
},
},
},
{
name: "snap",
input: []byte(`{
"id": "foobar",
"name": "etcd",
"version": "3.4.36",
"type": "snap",
"metadata": {
"summary": "Distributed reliable key-value store",
"base": "core18",
"grade": "stable",
"confinement": "strict",
"architectures": [
"amd64"
],
"digests": [
{
"algorithm": "sha256",
"value": "6700d789d2c38b0f7513058ddcea8f9a275e2206b4621a772eb065e12069956e"
}
]
}
}`),
expected: &Source{
ID: "foobar",
Name: "etcd",
Version: "3.4.36",
Type: "snap",
Metadata: source.SnapMetadata{
Summary: "Distributed reliable key-value store",
Base: "core18",
Grade: "stable",
Confinement: "strict",
Architectures: []string{
"amd64",
},
Digests: []file.Digest{
{
Algorithm: "sha256",
Value: "6700d789d2c38b0f7513058ddcea8f9a275e2206b4621a772eb065e12069956e",
},
},
},
},
},
{
name: "unknown source type",
input: []byte(`{
+3 -2
View File
@@ -41,7 +41,7 @@ func ToFormatModel(s sbom.SBOM, cfg EncoderConfig) model.Document {
ArtifactRelationships: toRelationshipModel(s.Relationships),
Files: toFile(s, coordinateSorter),
Source: toSourceModel(s.Source),
Distro: toLinuxReleaser(s.Artifacts.LinuxDistribution),
Distro: toLinuxRelease(s.Artifacts.LinuxDistribution),
Descriptor: toDescriptor(s.Descriptor),
Schema: model.Schema{
Version: internal.JSONSchemaVersion,
@@ -50,7 +50,7 @@ func ToFormatModel(s sbom.SBOM, cfg EncoderConfig) model.Document {
}
}
func toLinuxReleaser(d *linux.Release) model.LinuxRelease {
func toLinuxRelease(d *linux.Release) model.LinuxRelease {
if d == nil {
return model.LinuxRelease{}
}
@@ -73,6 +73,7 @@ func toLinuxReleaser(d *linux.Release) model.LinuxRelease {
PrivacyPolicyURL: d.PrivacyPolicyURL,
CPEName: d.CPEName,
SupportEnd: d.SupportEnd,
ExtendedSupport: d.ExtendedSupport,
}
}
@@ -127,6 +127,36 @@ func Test_toSourceModel(t *testing.T) {
},
},
},
{
name: "snap",
src: source.Description{
ID: "test-id",
Name: "some-name",
Version: "some-version",
Metadata: source.SnapMetadata{
Summary: "some summary",
Base: "some/base",
Grade: "some grade",
Confinement: "some confinement",
Architectures: []string{"x86_64", "arm64"},
Digests: []file.Digest{{Algorithm: "sha256", Value: "some-digest"}},
},
},
expected: model.Source{
ID: "test-id",
Name: "some-name",
Version: "some-version",
Type: "snap",
Metadata: source.SnapMetadata{
Summary: "some summary",
Base: "some/base",
Grade: "some grade",
Confinement: "some confinement",
Architectures: []string{"x86_64", "arm64"},
Digests: []file.Digest{{Algorithm: "sha256", Value: "some-digest"}},
},
},
},
// below are regression tests for when the name/version are not provided
// historically we've hoisted up the name/version from the metadata, now it is a simple pass-through
{
+6 -6
View File
@@ -40,7 +40,7 @@ func toSyftModel(doc model.Document) *sbom.SBOM {
FileLicenses: fileArtifacts.FileLicenses,
Executables: fileArtifacts.Executables,
Unknowns: fileArtifacts.Unknowns,
LinuxDistribution: toSyftLinuxRelease(doc.Distro),
LinuxDistribution: toInternalLinuxRelease(doc.Distro),
},
Source: *toSyftSourceData(doc.Source),
Descriptor: toSyftDescriptor(doc.Descriptor),
@@ -144,13 +144,12 @@ func toSyftFiles(files []model.File) sbom.Artifacts {
}
func safeFileModeConvert(val int) (fs.FileMode, error) {
if val < math.MinInt32 || val > math.MaxInt32 {
// Value is out of the range that int32 can represent
mode, err := strconv.ParseInt(strconv.Itoa(val), 8, 64)
if mode < 0 || mode > math.MaxUint32 {
// value is out of the range that int32 can represent
return 0, fmt.Errorf("value %d is out of the range that int32 can represent", val)
}
// Safe to convert to os.FileMode
mode, err := strconv.ParseInt(strconv.Itoa(val), 8, 64)
if err != nil {
return 0, err
}
@@ -196,7 +195,7 @@ func toSyftFileType(ty string) stereoscopeFile.Type {
}
}
func toSyftLinuxRelease(d model.LinuxRelease) *linux.Release {
func toInternalLinuxRelease(d model.LinuxRelease) *linux.Release {
if cmp.Equal(d, model.LinuxRelease{}) {
return nil
}
@@ -219,6 +218,7 @@ func toSyftLinuxRelease(d model.LinuxRelease) *linux.Release {
PrivacyPolicyURL: d.PrivacyPolicyURL,
CPEName: d.CPEName,
SupportEnd: d.SupportEnd,
ExtendedSupport: d.ExtendedSupport,
}
}
@@ -100,6 +100,36 @@ func Test_toSyftSourceData(t *testing.T) {
},
},
},
{
name: "snap",
src: model.Source{
ID: "the-id",
Name: "some-name",
Version: "some-version",
Type: "snap",
Metadata: source.SnapMetadata{
Summary: "something!",
Base: "base!",
Grade: "grade!",
Confinement: "confined!",
Architectures: []string{"arch!"},
Digests: []file.Digest{{Algorithm: "sha256", Value: "some-digest!"}},
},
},
expected: &source.Description{
ID: "the-id",
Name: "some-name",
Version: "some-version",
Metadata: source.SnapMetadata{
Summary: "something!",
Base: "base!",
Grade: "grade!",
Confinement: "confined!",
Architectures: []string{"arch!"},
Digests: []file.Digest{{Algorithm: "sha256", Value: "some-digest!"}},
},
},
},
// below are regression tests for when the name/version are not provided
// historically we've hoisted up the name/version from the metadata, now it is a simple pass-through
{
@@ -491,6 +521,12 @@ func Test_safeFileModeConvert(t *testing.T) {
want: os.FileMode(511), // 777 in octal equals 511 in decimal
wantErr: false,
},
{
name: "valid perm with symlink type",
val: 1000000777, // symlink + rwxrwxrwx
want: os.FileMode(0o1000000777), // 134218239
wantErr: false,
},
{
name: "outside int32 high",
val: int(math.MaxInt32) + 1,
+83 -16
View File
@@ -8,6 +8,8 @@ import (
"github.com/charmbracelet/lipgloss"
"github.com/olekukonko/tablewriter"
"github.com/olekukonko/tablewriter/renderer"
"github.com/olekukonko/tablewriter/tw"
"github.com/anchore/syft/syft/sbom"
)
@@ -66,25 +68,90 @@ func (e encoder) Encode(writer io.Writer, s sbom.SBOM) error {
columns = append(columns, "") // add a column for duplicate annotations
rows = markDuplicateRows(rows)
table := tablewriter.NewWriter(writer)
table := newTableWriter(writer, columns)
table.SetHeader(columns)
table.SetHeaderLine(false)
table.SetBorder(false)
table.SetAutoWrapText(false)
table.SetAutoFormatHeaders(true)
table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
table.SetAlignment(tablewriter.ALIGN_LEFT)
table.SetCenterSeparator("")
table.SetColumnSeparator("")
table.SetRowSeparator("")
table.SetTablePadding(" ")
table.SetNoWhiteSpace(true)
if err := table.Bulk(rows); err != nil {
return fmt.Errorf("failed to add table rows: %w", err)
}
table.AppendBulk(rows)
table.Render()
return table.Render()
}
return nil
func newTableWriter(writer io.Writer, columns []string) *tablewriter.Table {
// Here’s a simplified diagram of a table with a header, rows, and footer:
//
// [Borders.Top]
// | Header1 | Header2 | (Line below header: Lines.ShowTop)
// [Separators.BetweenRows]
// | Row1 | Row1 |
// [Separators.BetweenRows]
// | Row2 | Row2 |
// [Lines.ShowBottom]
// | Footer1 | Footer2 |
// [Borders.Bottom]
//
// So for example:
// ┌──────┬─────┐ <- Borders.Top
// │ NAME │ AGE │
// ├──────┼─────┤ <- Lines.ShowTop
// │ Alice│ 25 │
// ├──────┼─────┤ <- Separators.BetweenRows
// │ Bob │ 30 │
// ├──────┼─────┤ <- Lines.ShowBottom
// │ Total│ 2 │
// └──────┴─────┘ <- Borders.Bottom
return tablewriter.NewTable(writer,
tablewriter.WithHeader(columns),
tablewriter.WithHeaderAutoFormat(tw.On),
tablewriter.WithHeaderAutoWrap(tw.WrapNone),
tablewriter.WithHeaderAlignment(tw.AlignLeft),
tablewriter.WithRowAutoFormat(tw.Off),
tablewriter.WithRowAutoWrap(tw.WrapNone),
tablewriter.WithRowAlignment(tw.AlignLeft),
tablewriter.WithTrimSpace(tw.On),
tablewriter.WithAutoHide(tw.On),
tablewriter.WithRenderer(renderer.NewBlueprint()),
tablewriter.WithBehavior(
tw.Behavior{
TrimSpace: tw.On,
AutoHide: tw.On,
},
),
tablewriter.WithPadding(
tw.Padding{
Left: "",
Right: " ",
Top: "",
Bottom: "",
},
),
tablewriter.WithRendition(
tw.Rendition{
Symbols: tw.NewSymbols(tw.StyleNone),
Borders: tw.Border{
Left: tw.Off,
Top: tw.Off,
Right: tw.Off,
Bottom: tw.Off,
},
Settings: tw.Settings{
Separators: tw.Separators{
ShowHeader: tw.Off,
ShowFooter: tw.Off,
BetweenRows: tw.Off,
BetweenColumns: tw.Off,
},
Lines: tw.Lines{
ShowTop: tw.Off,
ShowBottom: tw.Off,
ShowHeaderLine: tw.Off,
ShowFooterLine: tw.Off,
},
},
},
),
)
}
func markDuplicateRows(items [][]string) [][]string {
@@ -1,3 +1,3 @@
NAME VERSION TYPE
package-1 1.0.1 python
package-2 2.0.1 deb
NAME VERSION TYPE
package-1 1.0.1 python
package-2 2.0.1 deb
+27 -9
View File
@@ -34,26 +34,44 @@ func GetSource(ctx context.Context, userInput string, cfg *GetSourceConfig) (sou
errs = append(errs, fmt.Errorf("%s: %w", p.Name(), err))
}
}
if err := validateSourcePlatform(src, cfg); err != nil {
return nil, err
}
if src != nil {
// if we have a non-image type and platform is specified, it's an error
if cfg.SourceProviderConfig.Platform != nil {
meta := src.Describe().Metadata
switch meta.(type) {
case *source.ImageMetadata, source.ImageMetadata:
default:
return src, fmt.Errorf("platform specified with non-image source")
}
}
return src, nil
}
}
if len(errs) == 0 {
return nil, fmt.Errorf("no source providers were able to resolve the input %q", userInput)
}
if len(fileNotFoundProviders) > 0 {
errs = append(errs, fmt.Errorf("additionally, the following providers failed with %w: %s", os.ErrNotExist, strings.Join(fileNotFoundProviders, ", ")))
}
return nil, sourceError(userInput, errs...)
}
func validateSourcePlatform(src source.Source, cfg *GetSourceConfig) error {
if src == nil {
return nil
}
if cfg == nil || cfg.SourceProviderConfig == nil || cfg.SourceProviderConfig.Platform == nil {
return nil
}
meta := src.Describe().Metadata
switch meta.(type) {
case *source.ImageMetadata, source.ImageMetadata:
return nil
case *source.SnapMetadata, source.SnapMetadata:
return nil
default:
return fmt.Errorf("platform is not supported for this source type")
}
}
func sourceError(userInput string, errs ...error) error {
switch len(errs) {
case 0:
+208
View File
@@ -0,0 +1,208 @@
package syft
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/anchore/stereoscope/pkg/image"
"github.com/anchore/syft/syft/internal/sourcemetadata"
"github.com/anchore/syft/syft/source"
"github.com/anchore/syft/syft/source/sourceproviders"
)
type mockSource struct {
source.Source
desc source.Description
}
func (s mockSource) Describe() source.Description {
return s.desc
}
func TestValidateSourcePlatform_NilSource(t *testing.T) {
cfg := &GetSourceConfig{
SourceProviderConfig: &sourceproviders.Config{
Platform: &image.Platform{
Architecture: "amd64",
OS: "linux",
},
},
}
err := validateSourcePlatform(nil, cfg)
if err != nil {
t.Errorf("Expected no error for nil source, got: %v", err)
}
}
func TestValidateSourcePlatform_NilPlatformConfig(t *testing.T) {
tests := []struct {
name string
cfg *GetSourceConfig
}{
{
name: "nil config",
cfg: nil,
},
{
name: "nil SourceProviderConfig",
cfg: &GetSourceConfig{
SourceProviderConfig: nil,
},
},
{
name: "nil Platform",
cfg: &GetSourceConfig{
SourceProviderConfig: &sourceproviders.Config{
Platform: nil,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
src := mockSource{
desc: source.Description{
Metadata: &source.ImageMetadata{},
},
}
err := validateSourcePlatform(src, tt.cfg)
if err != nil {
t.Errorf("Expected no error for nil platform, got: %v", err)
}
})
}
}
func TestValidateSourcePlatform_SupportedMetadataTypes(t *testing.T) {
tracker := sourcemetadata.NewCompletionTester(t)
cfg := &GetSourceConfig{
SourceProviderConfig: &sourceproviders.Config{
Platform: &image.Platform{
Architecture: "amd64",
OS: "linux",
},
},
}
tests := []struct {
name string
metadata any
wantErr require.ErrorAssertionFunc
}{
{
name: "image",
metadata: source.ImageMetadata{},
},
{
name: "snap",
metadata: source.SnapMetadata{},
},
{
name: "dir",
metadata: source.DirectoryMetadata{},
wantErr: require.Error,
},
{
name: "file",
metadata: source.FileMetadata{},
wantErr: require.Error,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.wantErr == nil {
tt.wantErr = require.NoError
}
tracker.Tested(t, tt.metadata)
src := mockSource{
desc: source.Description{
Metadata: tt.metadata,
},
}
err := validateSourcePlatform(src, cfg)
tt.wantErr(t, err, "Expected no error for %s, got: %v", tt.name, err)
})
}
}
func TestValidateSourcePlatform_UnsupportedMetadataTypes(t *testing.T) {
cfg := &GetSourceConfig{
SourceProviderConfig: &sourceproviders.Config{
Platform: &image.Platform{
Architecture: "amd64",
OS: "linux",
},
},
}
tests := []struct {
name string
metadata interface{}
}{
{
name: "string metadata",
metadata: "unsupported",
},
{
name: "int metadata",
metadata: 42,
},
{
name: "nil metadata",
metadata: nil,
},
{
name: "custom struct",
metadata: struct{ Name string }{Name: "test"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
src := mockSource{
desc: source.Description{
Metadata: tt.metadata,
},
}
err := validateSourcePlatform(src, cfg)
if err == nil {
t.Errorf("Expected error for %s, got nil", tt.name)
}
expectedMsg := "platform is not supported for this source type"
if err.Error() != expectedMsg {
t.Errorf("Expected error message %q, got %q", expectedMsg, err.Error())
}
})
}
}
func TestValidateSourcePlatform_ValidCombination(t *testing.T) {
cfg := &GetSourceConfig{
SourceProviderConfig: &sourceproviders.Config{
Platform: &image.Platform{
Architecture: "amd64",
OS: "linux",
},
},
}
src := mockSource{
desc: source.Description{
Metadata: &source.ImageMetadata{},
},
}
err := validateSourcePlatform(src, cfg)
if err != nil {
t.Errorf("Expected no error for valid combination, got: %v", err)
}
}
+9 -8
View File
@@ -14,7 +14,7 @@ var _ file.Resolver = (*Directory)(nil)
// Directory implements path and content access for the directory data source.
type Directory struct {
filetreeResolver
FiletreeResolver
path string
indexer *directoryIndexer
}
@@ -39,10 +39,11 @@ func newFromDirectoryWithoutIndex(root string, base string, pathFilters ...PathI
return &Directory{
path: cleanRoot,
filetreeResolver: filetreeResolver{
chroot: *chroot,
tree: filetree.New(),
index: filetree.NewIndex(),
FiletreeResolver: FiletreeResolver{
Chroot: *chroot,
Tree: filetree.New(),
Index: filetree.NewIndex(),
Opener: nativeOSFileOpener,
},
indexer: newDirectoryIndexer(cleanRoot, cleanBase, pathFilters...),
}, nil
@@ -57,9 +58,9 @@ func (r *Directory) buildIndex() error {
return err
}
r.tree = tree
r.index = index
r.searchContext = filetree.NewSearchContext(tree, index)
r.Tree = tree
r.Index = index
r.SearchContext = filetree.NewSearchContext(tree, index)
return nil
}
@@ -9,14 +9,12 @@ import (
"path/filepath"
"strings"
"github.com/wagoodman/go-partybus"
"github.com/wagoodman/go-progress"
"github.com/anchore/stereoscope/pkg/file"
"github.com/anchore/stereoscope/pkg/filetree"
"github.com/anchore/syft/internal/bus"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/event"
"github.com/anchore/syft/syft/internal/windows"
)
@@ -64,14 +62,14 @@ func (r *directoryIndexer) build() (filetree.Reader, filetree.IndexReader, error
return r.tree, r.index, indexAllRoots(r.path, r.indexTree)
}
func indexAllRoots(root string, indexer func(string, *progress.Stage) ([]string, error)) error {
func indexAllRoots(root string, indexer func(string, *progress.AtomicStage) ([]string, error)) error {
// why account for multiple roots? To cover cases when there is a symlink that references above the root path,
// in which case we need to additionally index where the link resolves to. it's for this reason why the filetree
// must be relative to the root of the filesystem (and not just relative to the given path).
pathsToIndex := []string{root}
fullPathsMap := map[string]struct{}{}
stager, prog := indexingProgress(root)
prog := bus.StartIndexingFiles(root)
defer prog.SetCompleted()
loop:
for {
@@ -85,7 +83,7 @@ loop:
currentPath, pathsToIndex = pathsToIndex[0], pathsToIndex[1:]
}
additionalRoots, err := indexer(currentPath, stager)
additionalRoots, err := indexer(currentPath, prog.AtomicStage)
if err != nil {
return fmt.Errorf("unable to index filesystem path=%q: %w", currentPath, err)
}
@@ -101,7 +99,7 @@ loop:
return nil
}
func (r *directoryIndexer) indexTree(root string, stager *progress.Stage) ([]string, error) {
func (r *directoryIndexer) indexTree(root string, stager *progress.AtomicStage) ([]string, error) {
log.WithFields("path", root).Trace("indexing filetree")
var roots []string
@@ -144,7 +142,7 @@ func (r *directoryIndexer) indexTree(root string, stager *progress.Stage) ([]str
err = filepath.Walk(root,
func(path string, info os.FileInfo, err error) error {
stager.Current = path
stager.Set(path)
newRoot, err := r.indexPath(path, info, err)
@@ -179,7 +177,7 @@ func isRealPath(root string) (bool, error) {
return rootParent == realRootParent, nil
}
func (r *directoryIndexer) indexBranch(root string, stager *progress.Stage) ([]string, error) {
func (r *directoryIndexer) indexBranch(root string, stager *progress.AtomicStage) ([]string, error) {
rootRealPath, err := filepath.EvalSymlinks(root)
if err != nil {
var pathErr *os.PathError
@@ -213,7 +211,7 @@ func (r *directoryIndexer) indexBranch(root string, stager *progress.Stage) ([]s
targetPath = p
}
stager.Current = targetPath
stager.Set(targetPath)
lstat, err := os.Lstat(targetPath)
newRoot, err := r.indexPath(targetPath, lstat, err)
@@ -492,22 +490,3 @@ func requireFileInfo(_, _ string, info os.FileInfo, _ error) error {
}
return nil
}
func indexingProgress(path string) (*progress.Stage, *progress.Manual) {
stage := &progress.Stage{}
prog := progress.NewManual(-1)
bus.Publish(partybus.Event{
Type: event.FileIndexingStarted,
Source: path,
Value: struct {
progress.Stager
progress.Progressable
}{
Stager: progress.Stager(stage),
Progressable: prog,
},
})
return stage, prog
}
@@ -25,7 +25,7 @@ type indexerMock struct {
additionalRoots map[string][]string
}
func (m *indexerMock) indexer(s string, _ *progress.Stage) ([]string, error) {
func (m *indexerMock) indexer(s string, _ *progress.AtomicStage) ([]string, error) {
m.observedRoots = append(m.observedRoots, s)
return m.additionalRoots[s], nil
}
+9 -8
View File
@@ -12,7 +12,7 @@ var _ file.Resolver = (*File)(nil)
// File implements path and content access for the file data source.
type File struct {
filetreeResolver
FiletreeResolver
path string
indexer *fileIndexer
}
@@ -29,10 +29,11 @@ func NewFromFile(parent, path string, pathFilters ...PathIndexVisitor) (*File, e
file := &File{
path: path,
filetreeResolver: filetreeResolver{
chroot: *chroot,
tree: filetree.New(),
index: filetree.NewIndex(),
FiletreeResolver: FiletreeResolver{
Chroot: *chroot,
Tree: filetree.New(),
Index: filetree.NewIndex(),
Opener: nativeOSFileOpener,
},
indexer: newFileIndexer(path, cleanBase, pathFilters...),
}
@@ -49,9 +50,9 @@ func (r *File) buildIndex() error {
return err
}
r.tree = tree
r.index = index
r.searchContext = filetree.NewSearchContext(tree, index)
r.Tree = tree
r.Index = index
r.SearchContext = filetree.NewSearchContext(tree, index)
return nil
}
+7 -6
View File
@@ -9,6 +9,7 @@ import (
"github.com/anchore/stereoscope/pkg/file"
"github.com/anchore/stereoscope/pkg/filetree"
"github.com/anchore/syft/internal/bus"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/internal/windows"
)
@@ -49,14 +50,14 @@ func (r *fileIndexer) build() (filetree.Reader, filetree.IndexReader, error) {
// Index file at the given path
// A file indexer simply indexes the file and its directory.
func index(path string, indexer func(string, *progress.Stage) error) error {
func index(path string, indexer func(string, *progress.AtomicStage) error) error {
// We want to index the file at the provided path and its parent directory.
// We need to probably check that we have file access
// We also need to determine what to do when the file itself is a symlink.
stager, prog := indexingProgress(path)
prog := bus.StartIndexingFiles(path)
defer prog.SetCompleted()
err := indexer(path, stager)
err := indexer(path, prog.AtomicStage)
if err != nil {
return fmt.Errorf("unable to index filesystem path=%q: %w", path, err)
}
@@ -70,7 +71,7 @@ func index(path string, indexer func(string, *progress.Stage) error) error {
// permissions errors on the file at path or its parent directory will return an error.
// Filter functions provided to the indexer are honoured, so if the path provided (or its parent
// directory) is filtered by a filter function, an error is returned.
func (r *fileIndexer) indexPath(path string, stager *progress.Stage) error {
func (r *fileIndexer) indexPath(path string, stager *progress.AtomicStage) error {
log.WithFields("path", path).Trace("indexing file path")
absPath, err := filepath.Abs(path)
@@ -105,14 +106,14 @@ func (r *fileIndexer) indexPath(path string, stager *progress.Stage) error {
return fmt.Errorf("unable to stat parent of file=%q: %w", absSymlinkFreeParent, err)
}
stager.Current = absSymlinkFreeParent
stager.Set(absSymlinkFreeParent)
indexParentErr := r.filterAndIndex(absSymlinkFreeParent, parentFi)
if indexParentErr != nil {
return indexParentErr
}
// We have indexed the parent successfully, now attempt to index the file.
stager.Current = absSymlinkFreeFilePath
stager.Set(absSymlinkFreeFilePath)
indexFileErr := r.filterAndIndex(absSymlinkFreeFilePath, fi)
if indexFileErr != nil {
return indexFileErr
+42 -36
View File
@@ -14,33 +14,46 @@ import (
"github.com/anchore/syft/syft/internal/windows"
)
type filetreeResolver struct {
chroot ChrootContext
tree filetree.Reader
index filetree.IndexReader
searchContext filetree.Searcher
// TODO: consider making a constructor for this
type FiletreeResolver struct {
Chroot ChrootContext
Tree filetree.Reader
Index filetree.IndexReader
SearchContext filetree.Searcher
Opener func(stereoscopeFile.Reference) (io.ReadCloser, error)
}
func (r *filetreeResolver) requestPath(userPath string) (string, error) {
return r.chroot.ToNativePath(userPath)
func nativeOSFileOpener(ref stereoscopeFile.Reference) (io.ReadCloser, error) {
// RealPath is posix so for windows file resolver we need to translate
// to its true on disk path.
filePath := string(ref.RealPath)
if windows.HostRunningOnWindows() {
filePath = windows.FromPosix(filePath)
}
return stereoscopeFile.NewLazyReadCloser(filePath), nil
}
func (r *FiletreeResolver) requestPath(userPath string) (string, error) {
return r.Chroot.ToNativePath(userPath)
}
// responsePath takes a path from the underlying fs domain and converts it to a path that is relative to the root of the file resolver.
func (r filetreeResolver) responsePath(path string) string {
return r.chroot.ToChrootPath(path)
func (r FiletreeResolver) responsePath(path string) string {
return r.Chroot.ToChrootPath(path)
}
// HasPath indicates if the given path exists in the underlying source.
func (r *filetreeResolver) HasPath(userPath string) bool {
func (r *FiletreeResolver) HasPath(userPath string) bool {
requestPath, err := r.requestPath(userPath)
if err != nil {
return false
}
return r.tree.HasPath(stereoscopeFile.Path(requestPath))
return r.Tree.HasPath(stereoscopeFile.Path(requestPath))
}
// FilesByPath returns all file.References that match the given paths from the file index.
func (r filetreeResolver) FilesByPath(userPaths ...string) ([]file.Location, error) {
func (r FiletreeResolver) FilesByPath(userPaths ...string) ([]file.Location, error) {
var references = make([]file.Location, 0)
for _, userPath := range userPaths {
@@ -51,7 +64,7 @@ func (r filetreeResolver) FilesByPath(userPaths ...string) ([]file.Location, err
}
// we should be resolving symlinks and preserving this information as a AccessPath to the real file
ref, err := r.searchContext.SearchByPath(userStrPath, filetree.FollowBasenameLinks)
ref, err := r.SearchContext.SearchByPath(userStrPath, filetree.FollowBasenameLinks)
if err != nil {
log.Tracef("unable to evaluate symlink for path=%q : %+v", userPath, err)
continue
@@ -61,7 +74,7 @@ func (r filetreeResolver) FilesByPath(userPaths ...string) ([]file.Location, err
continue
}
entry, err := r.index.Get(*ref.Reference)
entry, err := r.Index.Get(*ref.Reference)
if err != nil {
log.Warnf("unable to get file by path=%q : %+v", userPath, err)
continue
@@ -90,12 +103,12 @@ func (r filetreeResolver) FilesByPath(userPaths ...string) ([]file.Location, err
return references, nil
}
func (r filetreeResolver) requestGlob(pattern string) (string, error) {
return r.chroot.ToNativeGlob(pattern)
func (r FiletreeResolver) requestGlob(pattern string) (string, error) {
return r.Chroot.ToNativeGlob(pattern)
}
// FilesByGlob returns all file.References that match the given path glob pattern from any layer in the image.
func (r filetreeResolver) FilesByGlob(patterns ...string) ([]file.Location, error) {
func (r FiletreeResolver) FilesByGlob(patterns ...string) ([]file.Location, error) {
uniqueFileIDs := stereoscopeFile.NewFileReferenceSet()
uniqueLocations := make([]file.Location, 0)
@@ -104,7 +117,7 @@ func (r filetreeResolver) FilesByGlob(patterns ...string) ([]file.Location, erro
if err != nil {
return nil, err
}
refVias, err := r.searchContext.SearchByGlob(requestGlob, filetree.FollowBasenameLinks)
refVias, err := r.SearchContext.SearchByGlob(requestGlob, filetree.FollowBasenameLinks)
if err != nil {
return nil, err
}
@@ -112,7 +125,7 @@ func (r filetreeResolver) FilesByGlob(patterns ...string) ([]file.Location, erro
if !refVia.HasReference() || uniqueFileIDs.Contains(*refVia.Reference) {
continue
}
entry, err := r.index.Get(*refVia.Reference)
entry, err := r.Index.Get(*refVia.Reference)
if err != nil {
return nil, fmt.Errorf("unable to get file metadata for reference %s: %w", refVia.RealPath, err)
}
@@ -137,7 +150,7 @@ func (r filetreeResolver) FilesByGlob(patterns ...string) ([]file.Location, erro
// RelativeFileByPath fetches a single file at the given path relative to the layer squash of the given reference.
// This is helpful when attempting to find a file that is in the same layer or lower as another file.
func (r *filetreeResolver) RelativeFileByPath(_ file.Location, path string) *file.Location {
func (r *FiletreeResolver) RelativeFileByPath(_ file.Location, path string) *file.Location {
paths, err := r.FilesByPath(path)
if err != nil {
return nil
@@ -151,12 +164,12 @@ func (r *filetreeResolver) RelativeFileByPath(_ file.Location, path string) *fil
// FileContentsByLocation fetches file contents for a single file reference relative to a directory.
// If the path does not exist an error is returned.
func (r filetreeResolver) FileContentsByLocation(location file.Location) (io.ReadCloser, error) {
func (r FiletreeResolver) FileContentsByLocation(location file.Location) (io.ReadCloser, error) {
if location.RealPath == "" {
return nil, errors.New("empty path given")
}
entry, err := r.index.Get(location.Reference())
entry, err := r.Index.Get(location.Reference())
if err != nil {
return nil, err
}
@@ -166,21 +179,14 @@ func (r filetreeResolver) FileContentsByLocation(location file.Location) (io.Rea
return nil, fmt.Errorf("cannot read contents of non-file %q", location.Reference().RealPath)
}
// RealPath is posix so for windows file resolver we need to translate
// to its true on disk path.
filePath := string(location.Reference().RealPath)
if windows.HostRunningOnWindows() {
filePath = windows.FromPosix(filePath)
}
return stereoscopeFile.NewLazyReadCloser(filePath), nil
return r.Opener(location.Reference())
}
func (r *filetreeResolver) AllLocations(ctx context.Context) <-chan file.Location {
func (r *FiletreeResolver) AllLocations(ctx context.Context) <-chan file.Location {
results := make(chan file.Location)
go func() {
defer close(results)
for _, ref := range r.tree.AllFiles(stereoscopeFile.AllTypes()...) {
for _, ref := range r.Tree.AllFiles(stereoscopeFile.AllTypes()...) {
select {
case <-ctx.Done():
return
@@ -192,8 +198,8 @@ func (r *filetreeResolver) AllLocations(ctx context.Context) <-chan file.Locatio
return results
}
func (r *filetreeResolver) FileMetadataByLocation(location file.Location) (file.Metadata, error) {
entry, err := r.index.Get(location.Reference())
func (r *FiletreeResolver) FileMetadataByLocation(location file.Location) (file.Metadata, error) {
entry, err := r.Index.Get(location.Reference())
if err != nil {
return file.Metadata{}, fmt.Errorf("location: %+v : %w", location, os.ErrNotExist)
}
@@ -201,11 +207,11 @@ func (r *filetreeResolver) FileMetadataByLocation(location file.Location) (file.
return entry.Metadata, nil
}
func (r *filetreeResolver) FilesByMIMEType(types ...string) ([]file.Location, error) {
func (r *FiletreeResolver) FilesByMIMEType(types ...string) ([]file.Location, error) {
uniqueFileIDs := stereoscopeFile.NewFileReferenceSet()
uniqueLocations := make([]file.Location, 0)
refVias, err := r.searchContext.SearchByMIMEType(types...)
refVias, err := r.SearchContext.SearchByMIMEType(types...)
if err != nil {
return nil, err
}

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