Compare commits

...
40 Commits
Author SHA1 Message Date
Christopher Angelo Phillips dfcc07e512 feat: Add config option to allow user to select the default image source location
Signed-off-by: Christopher Phillips <christopher.phillips@anchore.com>
2023-03-31 10:04:10 -04:00
dependabot[bot] 2fa238af7c chore(deps): bump github.com/docker/docker (#1699)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 23.0.1+incompatible to 23.0.2+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v23.0.1...v23.0.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-29 10:00:37 -04:00
anchore-actions-token-generator[bot]andspiffcs 63bbd1e3ed chore(deps): update bootstrap tools to latest versions (#1697)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: spiffcs <spiffcs@users.noreply.github.com>
2023-03-27 09:17:34 -04:00
anchore-actions-token-generator[bot]andwagoodman 81b87dd108 chore(deps): update stereoscope to d7551b7f46f53179922d6229709d3d1602881080 (#1693)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: wagoodman <wagoodman@users.noreply.github.com>
2023-03-23 16:30:08 +00:00
Christopher Angelo Phillips f473bb75a8 1577 spdxlicense generate (#1691)
Update the license_list.go to have more permissible inputs for greater SPDXID matching.
EX:
GPL3 gpl3 gpl-3 and GPL-3 can all map to GPL-3.0-only

By moving all strings to lower and removing the "-" we're able to return valid SPDX license ID for a greater diversity of input strings.
---------

Signed-off-by: Christopher Phillips <christopher.phillips@anchore.com>
2023-03-23 11:48:24 -04:00
dependabot[bot] 539bc2afcb chore(deps): bump github.com/vbatts/go-mtree from 0.5.2 to 0.5.3 (#1692)
Bumps [github.com/vbatts/go-mtree](https://github.com/vbatts/go-mtree) from 0.5.2 to 0.5.3.
- [Release notes](https://github.com/vbatts/go-mtree/releases)
- [Changelog](https://github.com/vbatts/go-mtree/blob/main/releases.md)
- [Commits](https://github.com/vbatts/go-mtree/compare/v0.5.2...v0.5.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-23 11:09:32 -04:00
Avi DeitcherandKeith Zantow 9fd532246a feat: scan local go mod cache for licenses of golang packages (#1645)
Signed-off-by: Avi Deitcher <avi@deitcher.net>
Co-authored-by: Keith Zantow <kzantow@gmail.com>
2023-03-23 10:38:15 -04:00
Keith Zantow 11e926ab2f chore: fix flaky license sorting (#1690)
Signed-off-by: Keith Zantow <kzantow@gmail.com>
2023-03-22 14:41:49 -04:00
dependabot[bot] 168c5aed51 chore(deps): bump github.com/gookit/color from 1.5.2 to 1.5.3 (#1689) 2023-03-22 14:26:58 -04:00
Dan d02c56aa5f fix: shell completion by adding missing usage message required by spf13/cobra (#1688)
Signed-off-by: DanHam <DanHam@users.noreply.github.com>
2023-03-22 13:45:09 -04:00
anchore-actions-token-generator[bot] 829a71cd92 chore(deps): update bootstrap tools to latest versions (#1686) 2023-03-22 09:01:24 -04:00
Keith Zantow 34ace36a9e chore: tweak some workflow text (#1685)
Signed-off-by: Keith Zantow <kzantow@gmail.com>
2023-03-21 11:08:49 -04:00
Alex Goodman 100cf1003d Remove more side effects from application config testing (#1684)
* remove a few side effects from config testing

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

* fix xdg config app name prefix

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

* account for restoring and protecting xdg state throughout testing

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

---------

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2023-03-20 20:53:45 +00:00
Aidan DelaneyandChristopher Phillips f11a7b5e9f Deprecate config.yaml as valid config source; Add unit regression for correct config paths (#1640)
Warn user of future deprecation of ./config.yaml for v1.0.0 release

---------

Signed-off-by: Aidan Delaney <adelaney21@bloomberg.net>
Signed-off-by: Christopher Phillips <christopher.phillips@anchore.com>
Co-authored-by: Christopher Phillips <christopher.phillips@anchore.com>
2023-03-20 15:13:35 -04:00
anchore-actions-token-generator[bot] 434aa7fd46 chore: Update syft bootstrap tools to latest versions. (#1682)
Signed-off-by: Keith Zantow <kzantow@gmail.com>
2023-03-20 13:20:48 -04:00
Marc-Etienne Vargenau 5fb0423b72 Update documentation: (#1680)
- Syft is now outputing SPDX 2.3 by default
- Give syntax to get SPDX 2.2

Signed-off-by: Marc-Etienne Vargenau <marc-etienne.vargenau@nokia.com>
2023-03-20 10:10:35 -04:00
anchore-actions-token-generator[bot] 7998520848 chore: Update Stereoscope to 7928713c391e20abaede6a029f4ce37b628a4c8b (#1681) 2023-03-18 10:32:39 -04:00
Keith Zantow d05000ff21 fix: reduce logging for bad dpkg lines (#1675)
* fix: reduce logging for bad dpkg lines to Trace level
---------

Signed-off-by: Keith Zantow <kzantow@gmail.com>
2023-03-17 13:08:51 -04:00
witchcraze f66e77e2c6 fix ruby classifier (#1678)
Signed-off-by: witchcraze <witchcraze@gmail.com>
2023-03-17 09:42:20 -04:00
Christopher Angelo Phillips 928c4a55ff feat: add shared dir for easier cleanup (#1676)
Signed-off-by: Christopher Phillips <christopher.phillips@anchore.com>
2023-03-16 16:05:34 -04:00
dependabot[bot] 1899eb50d0 chore(deps): bump github.com/google/go-containerregistry (#1672)
Bumps [github.com/google/go-containerregistry](https://github.com/google/go-containerregistry) from 0.13.0 to 0.14.0.
- [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.13.0...v0.14.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-16 12:07:47 -04:00
dependabot[bot] b5ec4d4f08 chore(deps): bump actions/setup-go from 3 to 4 (#1671)
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 3 to 4.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v3...v4)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-16 12:02:07 -04:00
Christopher Angelo Phillips 61362c04fa fix: move defer after error to protect panic case (#1670)
Signed-off-by: Christopher Phillips <christopher.phillips@anchore.com>
2023-03-15 15:29:10 -04:00
Joye Lin e3140063d4 feat: add argocd, helm, kustomize and kubectl binary classifiers (#1663)
* add argocd, helm, kustomize and kubectl binary classifiers
* update golang PURL
* address PR faceback about binary/test-fixtures/Makefile
* remove the /v[n] suffix from the PURL in both argocd and helm

---------

Signed-off-by: y12studio <y12studio@gmail.com>
2023-03-15 14:53:22 -04:00
razzle 1d9ef34ec7 defer closing file (#1668)
Signed-off-by: razzle <harry@razzle.cloud>
2023-03-15 14:50:42 -04:00
Keith Zantow 302735097e fix: remove author contributing to javascript CPEs (#1669) 2023-03-14 14:10:24 +00:00
Keith Zantow cc0a376aba fix: more python matching support (#1667) 2023-03-13 13:26:43 -04:00
anchore-actions-token-generator[bot]andspiffcs b379dd9f27 Update syft bootstrap tools to latest versions. (#1666)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: spiffcs <spiffcs@users.noreply.github.com>
2023-03-13 10:40:13 -04:00
witchcraze a81e0c8008 feat: add ruby classifier (#1665)
Signed-off-by: witchcraze <witchcraze@gmail.com>
2023-03-10 08:29:40 -05:00
anchore-actions-token-generator[bot] 41cbbe09b2 Update syft bootstrap tools to latest versions. (#1658) 2023-03-07 12:54:32 -05:00
Keith Zantow 7714bc0521 fix: improved Python binary detection (#1648) 2023-03-07 10:52:29 -05:00
Weston Steimel 096d2b7bff fix: suppress some known incorrect vendor candidates for npm CPEs (#1659)
Signed-off-by: Weston Steimel <weston.steimel@anchore.com>
2023-03-07 10:18:44 -05:00
Keith Zantow 7cfdffab5f fix: sanitize SPDX LicenseRefs (#1657)
Signed-off-by: Keith Zantow <kzantow@gmail.com>
2023-03-06 10:55:23 -05:00
dependabot[bot] f43953d225 chore(deps): bump golang.org/x/mod from 0.8.0 to 0.9.0 (#1655) 2023-03-06 15:49:34 +00:00
dependabot[bot] eea1b48cbb chore(deps): bump golang.org/x/net from 0.7.0 to 0.8.0 (#1653) 2023-03-06 15:38:34 +00:00
dependabot[bot] a063cf300b chore(deps): bump github.com/spf13/afero from 1.9.4 to 1.9.5 (#1654) 2023-03-06 15:21:35 +00:00
dependabot[bot] b73903519c chore(deps): bump golang.org/x/term from 0.5.0 to 0.6.0 (#1656) 2023-03-06 15:20:43 +00:00
Keith Zantow 304be4a5a1 fix: dotnet PURL types are invalid (#1649)
Signed-off-by: Keith Zantow <kzantow@gmail.com>
2023-03-03 16:45:20 -05:00
Weston Steimel c4cbe211a3 feat: disable cpe vendor wildcards to reduce false positives (#1647)
* improved parsing of vendor from github url

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

* stop generating wildcard vendors

Add logic for parsing javascript and ruby package vendor candidates from
url and author fields and stop generating wildcard vendor candidates

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

---------

Signed-off-by: Weston Steimel <weston.steimel@anchore.com>
2023-03-03 17:26:46 +00:00
Avi Deitcher 01230aa766 read relative etc/apk/repositories for alpine version when no OS provided (#1615)
Signed-off-by: Avi Deitcher <avi@deitcher.net>
2023-03-02 13:04:56 -05:00
100 changed files with 235696 additions and 2748 deletions
+1 -1
View File
@@ -151,7 +151,7 @@ jobs:
with:
status: ${{ job.status }}
fields: repo,workflow,action,eventName
text: "A new Syft release has been published: https://github.com/anchore/syft/releases/tag/${{ github.ref_name }}"
text: "A new Syft release has been published: https://github.com/anchore/syft/releases/tag/${{ github.event.inputs.version }}"
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_TOOLBOX_WEBHOOK_URL }}
if: ${{ success() }}
+3 -3
View File
@@ -16,7 +16,7 @@ jobs:
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v3
- uses: actions/setup-go@v4
with:
go-version: ${{ env.GO_VERSION }}
stable: ${{ env.GO_STABLE_VERSION }}
@@ -64,8 +64,8 @@ jobs:
delete-branch: true
branch: auto/latest-bootstrap-tools
labels: dependencies
commit-message: 'Update syft bootstrap tools to latest versions.'
title: 'Update syft bootstrap tools to latest versions.'
commit-message: 'chore(deps): update bootstrap tools to latest versions'
title: 'chore(deps): update bootstrap tools to latest versions'
body: |
- [golangci-lint ${{ steps.latest-versions.outputs.GOLANGCILINT }}](https://github.com/golangci/golangci-lint/releases/tag/${{ steps.latest-versions.outputs.GOLANGCILINT }})
- [bouncer ${{ steps.latest-versions.outputs.BOUNCER }}](https://github.com/wagoodman/go-bouncer/releases/tag/${{ steps.latest-versions.outputs.BOUNCER }})
@@ -16,7 +16,7 @@ jobs:
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v3
- uses: actions/setup-go@v4
with:
go-version: ${{ env.GO_VERSION }}
stable: ${{ env.GO_STABLE_VERSION }}
@@ -42,10 +42,10 @@ jobs:
with:
signoff: true
delete-branch: true
branch: auto/latest
branch: auto/latest-stereoscope
labels: dependencies
commit-message: "Update Stereoscope to ${{ steps.latest-version.outputs.LATEST_VERSION }}"
title: "Update Stereoscope to ${{ steps.latest-version.outputs.LATEST_VERSION }}"
commit-message: "chore(deps): update stereoscope to ${{ steps.latest-version.outputs.LATEST_VERSION }}"
title: "chore(deps): update stereoscope to ${{ steps.latest-version.outputs.LATEST_VERSION }}"
body: |
Update Stereoscope to ${{ steps.latest-version.outputs.LATEST_VERSION }}
token: ${{ steps.generate-token.outputs.token }}
+7
View File
@@ -54,6 +54,13 @@ jobs:
path: syft/pkg/cataloger/golang/test-fixtures/archs/binaries
key: ${{ runner.os }}-unit-go-binaries-cache-${{ hashFiles( 'syft/pkg/cataloger/golang/test-fixtures/archs/binaries.fingerprint' ) }}
- name: Restore binary cataloger test-fixture cache
id: unit-binary-cataloger-cache
uses: actions/cache@v3
with:
path: syft/pkg/cataloger/binary/test-fixtures/classifiers/dynamic
key: ${{ runner.os }}-unit-binary-cataloger-cache-${{ hashFiles( 'syft/pkg/cataloger/binary/test-fixtures/cache.fingerprint' ) }}
- name: Run unit tests
run: make unit
+13 -3
View File
@@ -10,11 +10,11 @@ CHRONICLE_CMD = $(TEMP_DIR)/chronicle
GLOW_CMD = $(TEMP_DIR)/glow
# Tool versions #################################
GOLANGCILINT_VERSION := v1.51.2
GOSIMPORTS_VERSION := v0.3.7
GOLANGCILINT_VERSION := v1.52.2
GOSIMPORTS_VERSION := v0.3.8
BOUNCER_VERSION := v0.4.0
CHRONICLE_VERSION := v0.6.0
GORELEASER_VERSION := v1.15.2
GORELEASER_VERSION := v1.16.2
YAJSV_VERSION := v1.4.1
COSIGN_VERSION := v1.13.1
QUILL_VERSION := v0.2.0
@@ -189,6 +189,10 @@ fingerprints:
cd test/integration/test-fixtures && \
make cache.fingerprint
# for BINARY test fixtures
cd syft/pkg/cataloger/binary/test-fixtures && \
make cache.fingerprint
# for JAVA BUILD test fixtures
cd syft/pkg/cataloger/java/test-fixtures/java-builds && \
make packages.fingerprint
@@ -214,6 +218,7 @@ fixtures:
$(call title,Generating test fixtures)
cd syft/pkg/cataloger/java/test-fixtures/java-builds && make
cd syft/pkg/cataloger/rpm/test-fixtures && make
cd syft/pkg/cataloger/binary/test-fixtures && make
.PHONY: show-test-image-cache
show-test-image-cache: ## Show all docker and image tar cache
@@ -247,6 +252,11 @@ install-test-ci-mac: $(SNAPSHOT_DIR)
cd test/install && \
make ci-test-mac
.PHONY: generate-compare-file
generate-compare-file:
$(call title,Generating compare test file)
go run ./cmd/syft $(COMPARE_TEST_IMAGE) -o json > $(COMPARE_DIR)/test-fixtures/acceptance-centos-8.2.2004.json
# note: we cannot clean the snapshot directory since the pipeline builds the snapshot separately
.PHONY: compare-mac
compare-mac: $(TEMP_DIR) $(SNAPSHOT_DIR) ## Run compare tests on build snapshot binaries and packages (Mac)
+28 -8
View File
@@ -110,9 +110,7 @@ The above output includes only software that is visible in the container (i.e.,
syft <image> --scope all-layers
```
## Supported sources
### Supported sources
Syft can generate a SBOM from a variety of sources:
@@ -141,7 +139,13 @@ file:path/to/yourproject/file read directly from a path on disk (any
registry:yourrepo/yourimage:tag pull image directly from a registry (no container runtime required)
```
#### Default Cataloger Configuration by scan type
If an image source is not provided and cannot be detected from the given reference it is assumed the image should be pulled from the Docker daemon.
If docker is not present, then the Podman daemon is attempted next, followed by reaching out directly to the image registry last.
This default behavior can be overridden with the `default-image-pull-source` configuration option (See [Configuration](https://github.com/anchore/syft#configuration) for more details).
### Default Cataloger Configuration by scan type
##### Image Scanning:
- alpmdb
@@ -179,7 +183,7 @@ registry:yourrepo/yourimage:tag pull image directly from a registry (no
- conan
- hackage
#### Non Default:
##### Non Default:
- cargo-auditable-binary
### Excluding file paths
@@ -213,8 +217,10 @@ Where the `formats` available are:
- `text`: A row-oriented, human-and-machine-friendly output.
- `cyclonedx-xml`: A XML report conforming to the [CycloneDX 1.4 specification](https://cyclonedx.org/specification/overview/).
- `cyclonedx-json`: A JSON report conforming to the [CycloneDX 1.4 specification](https://cyclonedx.org/specification/overview/).
- `spdx-tag-value`: A tag-value formatted report conforming to the [SPDX 2.2 specification](https://spdx.github.io/spdx-spec/).
- `spdx-json`: A JSON report conforming to the [SPDX 2.2 JSON Schema](https://github.com/spdx/spdx-spec/blob/v2.2/schemas/spdx-schema.json).
- `spdx-tag-value`: A tag-value formatted report conforming to the [SPDX 2.3 specification](https://spdx.github.io/spdx-spec/v2.3/).
- `spdx-tag-value@2.2`: A tag-value formatted report conforming to the [SPDX 2.2 specification](https://spdx.github.io/spdx-spec/v2.2.2/).
- `spdx-json`: A JSON report conforming to the [SPDX 2.3 JSON Schema](https://github.com/spdx/spdx-spec/blob/v2.3/schemas/spdx-schema.json).
- `spdx-json@2.2`: A JSON report conforming to the [SPDX 2.2 JSON Schema](https://github.com/spdx/spdx-spec/blob/v2.2/schemas/spdx-schema.json).
- `github`: A JSON report conforming to GitHub's dependency snapshot format.
- `table`: A columnar summary (default).
- `template`: Lets the user specify the output format. See ["Using templates"](#using-templates) below.
@@ -391,7 +397,7 @@ Certificate subject: test.email@testdomain.com
Certificate issuer URL: https://accounts.google.com
```
#### Local private key support
### Local private key support
To generate an SBOM attestation for a container image using a local private key:
```
@@ -434,6 +440,10 @@ file: ""
# same as SYFT_CHECK_FOR_APP_UPDATE env var
check-for-app-update: true
# allows users to specify which image source should be used to generate the sbom
# valid values are: registry, docker, podman
default-image-pull-source: ""
# a list of globs to exclude from scanning. same as --exclude ; for example:
# exclude:
# - "/etc/**"
@@ -492,6 +502,16 @@ package:
# same as -s ; SYFT_PACKAGE_CATALOGER_SCOPE env var
scope: "squashed"
golang:
# search for go package licences in the GOPATH of the system running Syft, note that this is outside the
# container filesystem and potentially outside the root of a local directory scan
# SYFT_GOLANG_SEARCH_LOCAL_MOD_CACHE_LICENSES env var
search-local-mod-cache-licenses: false
# specify an explicit go mod cache directory, if unset this defaults to $GOPATH/pkg/mod or $HOME/go/pkg/mod
# SYFT_GOLANG_LOCAL_MOD_CACHE_DIR env var
local-mod-cache-dir: ""
# cataloging file contents is exposed through the power-user subcommand
file-contents:
cataloger:
+2 -2
View File
@@ -27,7 +27,7 @@ import (
"github.com/anchore/syft/syft/source"
)
func Run(ctx context.Context, app *config.Application, args []string) error {
func Run(_ context.Context, app *config.Application, args []string) error {
err := ValidateOutputOptions(app)
if err != nil {
return err
@@ -47,7 +47,7 @@ func Run(ctx context.Context, app *config.Application, args []string) error {
// could be an image or a directory, with or without a scheme
// TODO: validate that source is image
userInput := args[0]
si, err := source.ParseInputWithName(userInput, app.Platform, true, app.Name)
si, err := source.ParseInputWithName(userInput, app.Platform, app.Name, app.DefaultImagePullSource)
if err != nil {
return fmt.Errorf("could not generate source input for packages command: %w", err)
}
+1
View File
@@ -55,6 +55,7 @@ func New() (*cobra.Command, error) {
// rootCmd is currently an alias for the packages command
rootCmd := &cobra.Command{
Use: fmt.Sprintf("%s [SOURCE]", internal.ApplicationName),
Short: packagesCmd.Short,
Long: packagesCmd.Long,
Args: packagesCmd.Args,
+1
View File
@@ -17,6 +17,7 @@ func (o AttestOptions) AddFlags(cmd *cobra.Command, v *viper.Viper) error {
return bindAttestConfigOptions(cmd.Flags(), v)
}
//nolint:revive
func bindAttestConfigOptions(flags *pflag.FlagSet, v *viper.Viper) error {
if err := v.BindPFlag("attest.key", flags.Lookup("key")); err != nil {
return err
+1
View File
@@ -31,6 +31,7 @@ func (o *FulcioOptions) AddFlags(cmd *cobra.Command, v *viper.Viper) error {
return bindFulcioConfigOptions(cmd.Flags(), v)
}
//nolint:revive
func bindFulcioConfigOptions(flags *pflag.FlagSet, v *viper.Viper) error {
if err := v.BindPFlag("attest.fulcio-url", flags.Lookup("fulcio-url")); err != nil {
return err
+1
View File
@@ -31,6 +31,7 @@ func (o *OIDCOptions) AddFlags(cmd *cobra.Command, v *viper.Viper) error {
return bindOIDCConfigOptions(cmd.Flags(), v)
}
//nolint:revive
func bindOIDCConfigOptions(flags *pflag.FlagSet, v *viper.Viper) error {
if err := v.BindPFlag("attest.oidc-issuer", flags.Lookup("oidc-issuer")); err != nil {
return err
+1
View File
@@ -54,6 +54,7 @@ func (o *PackagesOptions) AddFlags(cmd *cobra.Command, v *viper.Viper) error {
return bindPackageConfigOptions(cmd.Flags(), v)
}
//nolint:revive
func bindPackageConfigOptions(flags *pflag.FlagSet, v *viper.Viper) error {
// Formatting & Input options //////////////////////////////////////////////
+1
View File
@@ -22,6 +22,7 @@ func (o *RekorOptions) AddFlags(cmd *cobra.Command, v *viper.Viper) error {
return bindRekorConfigOptions(cmd.Flags(), v)
}
//nolint:revive
func bindRekorConfigOptions(flags *pflag.FlagSet, v *viper.Viper) error {
// TODO: config re-design
if err := v.BindPFlag("attest.rekor-url", flags.Lookup("rekor-url")); err != nil {
+1
View File
@@ -22,6 +22,7 @@ func (o *RootOptions) AddFlags(cmd *cobra.Command, v *viper.Viper) error {
return bindRootConfigOptions(cmd.PersistentFlags(), v)
}
//nolint:revive
func bindRootConfigOptions(flags *pflag.FlagSet, v *viper.Viper) error {
if err := v.BindPFlag("config", flags.Lookup("config")); err != nil {
return err
+1 -1
View File
@@ -11,7 +11,7 @@ type VersionOptions struct {
var _ Interface = (*VersionOptions)(nil)
func (o *VersionOptions) AddFlags(cmd *cobra.Command, v *viper.Viper) error {
func (o *VersionOptions) AddFlags(cmd *cobra.Command, _ *viper.Viper) error {
cmd.Flags().StringVarP(&o.Output, "output", "o", "text", "format to show version information (available=[text, json])")
return nil
}
+4 -2
View File
@@ -18,8 +18,10 @@ const (
{{.appName}} {{.command}} alpine:latest -o json show all possible cataloging details
{{.appName}} {{.command}} alpine:latest -o cyclonedx show a CycloneDX formatted SBOM
{{.appName}} {{.command}} alpine:latest -o cyclonedx-json show a CycloneDX JSON formatted SBOM
{{.appName}} {{.command}} alpine:latest -o spdx show a SPDX 2.2 Tag-Value formatted SBOM
{{.appName}} {{.command}} alpine:latest -o spdx-json show a SPDX 2.2 JSON formatted SBOM
{{.appName}} {{.command}} alpine:latest -o spdx show a SPDX 2.3 Tag-Value formatted SBOM
{{.appName}} {{.command}} alpine:latest -o spdx@2.2 show a SPDX 2.2 Tag-Value formatted SBOM
{{.appName}} {{.command}} alpine:latest -o spdx-json show a SPDX 2.3 JSON formatted SBOM
{{.appName}} {{.command}} alpine:latest -o spdx-json@2.2 show a SPDX 2.2 JSON formatted SBOM
{{.appName}} {{.command}} alpine:latest -vv show verbose debug information
{{.appName}} {{.command}} alpine:latest -o template -t my_format.tmpl show a SBOM formatted according to given template file
+2 -2
View File
@@ -23,7 +23,7 @@ import (
"github.com/anchore/syft/syft/source"
)
func Run(ctx context.Context, app *config.Application, args []string) error {
func Run(_ context.Context, app *config.Application, args []string) error {
err := ValidateOutputOptions(app)
if err != nil {
return err
@@ -42,7 +42,7 @@ func Run(ctx context.Context, app *config.Application, args []string) error {
// could be an image or a directory, with or without a scheme
userInput := args[0]
si, err := source.ParseInputWithName(userInput, app.Platform, true, app.Name)
si, err := source.ParseInputWithName(userInput, app.Platform, app.Name, app.DefaultImagePullSource)
if err != nil {
return fmt.Errorf("could not generate source input for packages command: %w", err)
}
+1 -1
View File
@@ -47,7 +47,7 @@ func Run(_ context.Context, app *config.Application, args []string) error {
}()
userInput := args[0]
si, err := source.ParseInputWithName(userInput, app.Platform, true, app.Name)
si, err := source.ParseInputWithName(userInput, app.Platform, app.Name, app.DefaultImagePullSource)
if err != nil {
return fmt.Errorf("could not generate source input for packages command: %w", err)
}
+1 -1
View File
@@ -15,7 +15,7 @@ import (
"github.com/anchore/syft/internal/version"
)
func Version(v *viper.Viper, app *config.Application) *cobra.Command {
func Version(v *viper.Viper, _ *config.Application) *cobra.Command {
o := &options.VersionOptions{}
cmd := &cobra.Command{
Use: "version",
+19 -18
View File
@@ -16,7 +16,7 @@ require (
github.com/go-test/deep v1.1.0
github.com/google/go-cmp v0.5.9
github.com/google/uuid v1.3.0
github.com/gookit/color v1.5.2
github.com/gookit/color v1.5.3
github.com/hashicorp/go-multierror v1.1.1
github.com/jinzhu/copier v0.3.5
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
@@ -32,7 +32,7 @@ require (
github.com/sergi/go-diff v1.3.1
github.com/sirupsen/logrus v1.9.0
github.com/spdx/tools-golang v0.5.0-rc1
github.com/spf13/afero v1.9.4
github.com/spf13/afero v1.9.5
github.com/spf13/cobra v1.6.1
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.15.0
@@ -42,9 +42,9 @@ require (
github.com/wagoodman/go-progress v0.0.0-20230301185719-21920a456ad5
github.com/wagoodman/jotframe v0.0.0-20211129225309-56b0d0a4aebb
github.com/xeipuuv/gojsonschema v1.2.0
golang.org/x/mod v0.8.0
golang.org/x/net v0.7.0
golang.org/x/term v0.5.0
golang.org/x/mod v0.9.0
golang.org/x/net v0.8.0
golang.org/x/term v0.6.0
gopkg.in/yaml.v2 v2.4.0
)
@@ -52,14 +52,15 @@ require (
github.com/CycloneDX/cyclonedx-go v0.7.1-0.20221222100750-41a1ac565cce
github.com/Masterminds/sprig/v3 v3.2.3
github.com/anchore/go-logger v0.0.0-20220728155337-03b66a5207d8
github.com/anchore/stereoscope v0.0.0-20230301191755-abfb374a1122
github.com/docker/docker v23.0.1+incompatible
github.com/google/go-containerregistry v0.13.0
github.com/anchore/stereoscope v0.0.0-20230323161519-d7551b7f46f5
github.com/docker/docker v23.0.2+incompatible
github.com/google/go-containerregistry v0.14.0
github.com/google/licensecheck v0.3.1
github.com/invopop/jsonschema v0.7.0
github.com/knqyf263/go-rpmdb v0.0.0-20221030135625-4082a22221ce
github.com/opencontainers/go-digest v1.0.0
github.com/sassoftware/go-rpmutils v0.2.0
github.com/vbatts/go-mtree v0.5.2
github.com/vbatts/go-mtree v0.5.3
golang.org/x/exp v0.0.0-20230202163644-54bba9f4231b
gopkg.in/yaml.v3 v3.0.1
)
@@ -72,9 +73,9 @@ require (
github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 // indirect
github.com/becheran/wildmatch-go v1.0.0 // indirect
github.com/containerd/containerd v1.6.18 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.12.1 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/cli v20.10.20+incompatible // indirect
github.com/docker/cli v23.0.1+incompatible // indirect
github.com/docker/distribution v2.8.1+incompatible // indirect
github.com/docker/docker-credential-helpers v0.7.0 // indirect
github.com/docker/go-connections v0.4.0 // indirect
@@ -84,16 +85,16 @@ require (
github.com/gabriel-vasile/mimetype v1.4.0 // indirect
github.com/go-restruct/restruct v1.2.0-alpha // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/huandu/xstrings v1.3.3 // indirect
github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/klauspost/compress v1.15.11 // indirect
github.com/klauspost/compress v1.16.0 // indirect
github.com/klauspost/pgzip v1.2.5 // indirect
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381 // indirect
github.com/magiconair/properties v1.8.7 // indirect
@@ -126,13 +127,13 @@ require (
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/text v0.7.0 // indirect
golang.org/x/tools v0.2.0 // indirect
golang.org/x/sys v0.6.0 // indirect
golang.org/x/text v0.8.0 // indirect
golang.org/x/tools v0.7.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef // indirect
google.golang.org/grpc v1.52.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
google.golang.org/protobuf v1.29.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
lukechampine.com/uint128 v1.1.1 // indirect
modernc.org/cc/v3 v3.36.0 // indirect
+40 -35
View File
@@ -90,8 +90,8 @@ github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b h1:e1bmaoJfZV
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b/go.mod h1:Bkc+JYWjMCF8OyZ340IMSIi2Ebf3uwByOk6ho4wne1E=
github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501 h1:AV7qjwMcM4r8wFhJq3jLRztew3ywIyPTRapl2T1s9o8=
github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501/go.mod h1:Blo6OgJNiYF41ufcgHKkbCKF2MDOMlrqhXv/ij6ocR4=
github.com/anchore/stereoscope v0.0.0-20230301191755-abfb374a1122 h1:Oe2PE8zNbJH4nGZoCIC/VZBgpr62BInLnUqIMZICUOk=
github.com/anchore/stereoscope v0.0.0-20230301191755-abfb374a1122/go.mod h1:IihP/SUVHP94PBwIP2bepOB/c0MVadcII7lxo13Ijzs=
github.com/anchore/stereoscope v0.0.0-20230323161519-d7551b7f46f5 h1:2I4m/L4INVVyv0yBRuCadwxM+nuJoaKj9iBNPyXbSO4=
github.com/anchore/stereoscope v0.0.0-20230323161519-d7551b7f46f5/go.mod h1:IihP/SUVHP94PBwIP2bepOB/c0MVadcII7lxo13Ijzs=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
@@ -134,8 +134,8 @@ github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWH
github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/containerd/containerd v1.6.18 h1:qZbsLvmyu+Vlty0/Ex5xc0z2YtKpIsb5n45mAMI+2Ns=
github.com/containerd/containerd v1.6.18/go.mod h1:1RdCUu95+gc2v9t3IL+zIlpClSmew7/0YS8O5eQZrOw=
github.com/containerd/stargz-snapshotter/estargz v0.12.1 h1:+7nYmHJb0tEkcRaAW+MHqoKaJYZmkikupxCqVtmPuY0=
github.com/containerd/stargz-snapshotter/estargz v0.12.1/go.mod h1:12VUuCq3qPq4y8yUW+l5w3+oXV3cx2Po3KSe/SmPGqw=
github.com/containerd/stargz-snapshotter/estargz v0.14.3 h1:OqlDCK3ZVUO6C3B/5FSkDwbkEETK84kQgEeFwDC+62k=
github.com/containerd/stargz-snapshotter/estargz v0.14.3/go.mod h1:KY//uOCIkSuNAHhJogcZtrNHdKrA99/FCCRjE3HD36o=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
@@ -146,12 +146,12 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4=
github.com/docker/cli v20.10.20+incompatible h1:lWQbHSHUFs7KraSN2jOJK7zbMS2jNCHI4mt4xUFUVQ4=
github.com/docker/cli v20.10.20+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/cli v23.0.1+incompatible h1:LRyWITpGzl2C9e9uGxzisptnxAn1zfZKXy13Ul2Q5oM=
github.com/docker/cli v23.0.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68=
github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v23.0.1+incompatible h1:vjgvJZxprTTE1A37nm+CLNAdwu6xZekyoiVlUZEINcY=
github.com/docker/docker v23.0.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v23.0.2+incompatible h1:q81C2qQ/EhPm8COZMUGOQYh4qLv4Xu6CXELJ3WK/mlU=
github.com/docker/docker v23.0.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A=
github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
@@ -237,8 +237,9 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
@@ -260,9 +261,11 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-containerregistry v0.13.0 h1:y1C7Z3e149OJbOPDBxLYR8ITPz8dTKqQwjErKVHJC8k=
github.com/google/go-containerregistry v0.13.0/go.mod h1:J9FQ+eSS4a1aC2GNZxvNpbWhgp0487v+cgiilB4FqDo=
github.com/google/go-containerregistry v0.14.0 h1:z58vMqHxuwvAsVwvKEkmVBz2TlgBgH5k6koEXBtlYkw=
github.com/google/go-containerregistry v0.14.0/go.mod h1:aiJ2fp/SXvkWgmYHioXnbMdlgB8eXiiYOY55gfN91Wk=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/licensecheck v0.3.1 h1:QoxgoDkaeC4nFrtGN1jV7IPmDCHFNIVh54e5hSt6sPs=
github.com/google/licensecheck v0.3.1/go.mod h1:ORkR35t/JjW+emNKtfJDII0zlciG9JgbT7SmsohlHmY=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
@@ -293,8 +296,8 @@ github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pf
github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
github.com/gookit/color v1.2.5/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg=
github.com/gookit/color v1.5.2 h1:uLnfXcaFjlrDnQDT+NCBcfhrXqYTx/rcCa6xn01Y8yI=
github.com/gookit/color v1.5.2/go.mod h1:w8h4bGiHeeBpvQVePTutdbERIUf3oJE5lZ8HM0UgXyg=
github.com/gookit/color v1.5.3 h1:twfIhZs4QLCtimkP7MOxlF3A0U/5cDPseRT9M/+2SCE=
github.com/gookit/color v1.5.3/go.mod h1:NUzwzeehUfl7GIb36pqId+UGmRfQcU/WiiyTTeNjHtE=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M=
github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms=
@@ -342,8 +345,9 @@ github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/invopop/jsonschema v0.7.0 h1:2vgQcBz1n256N+FpX3Jq7Y17AjYt46Ig3zIWyy770So=
github.com/invopop/jsonschema v0.7.0/go.mod h1:O9uiLokuu0+MGFlyiaqtWxwqJm41/+8Nj0lD7A36YH0=
github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg=
@@ -363,8 +367,8 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.15.11 h1:Lcadnb3RKGin4FYM/orgq0qde+nc15E5Cbqg4B9Sx9c=
github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=
github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4=
github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE=
github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
@@ -519,8 +523,8 @@ github.com/spdx/tools-golang v0.5.0-rc1 h1:ooCSe48QatlidqEFd+nSI308tyeNTR6NJvauU
github.com/spdx/tools-golang v0.5.0-rc1/go.mod h1:LI6onw172PdO57Ob/hgnLDD4Y2PMnroeNT3wO/2WJJI=
github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
github.com/spf13/afero v1.9.4 h1:Sd43wM1IWz/s1aVXdOBkjJvuP8UdyqioeE4AmM0QsBs=
github.com/spf13/afero v1.9.4/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM=
github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
@@ -568,8 +572,8 @@ github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oW
github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8=
github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/vbatts/go-mtree v0.5.2 h1:d8SAbLJiR1cR3pe1J+FBaalRkCQw95gP12/P+a9PUcA=
github.com/vbatts/go-mtree v0.5.2/go.mod h1:e0NDJ+bT3jG7ZINeB9HR5AxTvjskCsOR54+9KoaXyDc=
github.com/vbatts/go-mtree v0.5.3 h1:S/jYlfG8rZ+a0bhZd+RANXejy7M4Js8fq9U+XoWTd5w=
github.com/vbatts/go-mtree v0.5.3/go.mod h1:eXsdoPMdL2jcJx6HweWi9lYQxBsTp4lNhqqAjgkZUg8=
github.com/vbatts/tar-split v0.11.2 h1:Via6XqJr0hceW4wff3QRzD5gAk/tatMw/4ZA7cTlIME=
github.com/vbatts/tar-split v0.11.2/go.mod h1:vV3ZuO2yWSVsz+pfFzDG/upWH1JhjOiEaWq6kXyQ3VI=
github.com/vifraa/gopom v0.2.1 h1:MYVMAMyiGzXPPy10EwojzKIL670kl5Zbae+o3fFvQEM=
@@ -627,7 +631,7 @@ golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWP
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A=
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -670,8 +674,8 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs=
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -715,10 +719,11 @@ golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -824,15 +829,15 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -843,8 +848,8 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -908,8 +913,8 @@ golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE=
golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -1065,8 +1070,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.29.0 h1:44S3JjaKmLEE4YIkjzexaP+NzZsudE3Zin5Njn/pYX0=
google.golang.org/protobuf v1.29.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+69 -27
View File
@@ -3,6 +3,7 @@ package config
import (
"errors"
"fmt"
"os"
"path"
"reflect"
"sort"
@@ -17,6 +18,7 @@ import (
"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/pkg/cataloger"
golangCataloger "github.com/anchore/syft/syft/pkg/cataloger/golang"
)
var (
@@ -38,25 +40,27 @@ type Application struct {
ConfigPath string `yaml:"configPath,omitempty" json:"configPath" mapstructure:"config"`
Verbosity uint `yaml:"verbosity,omitempty" json:"verbosity" mapstructure:"verbosity"`
// -q, indicates to not show any status output to stderr (ETUI or logging UI)
Quiet bool `yaml:"quiet" json:"quiet" mapstructure:"quiet"`
Outputs []string `yaml:"output" json:"output" mapstructure:"output"` // -o, the format to use for output
OutputTemplatePath string `yaml:"output-template-path" json:"output-template-path" mapstructure:"output-template-path"` // -t template file to use for output
File string `yaml:"file" json:"file" mapstructure:"file"` // --file, the file to write report output to
CheckForAppUpdate bool `yaml:"check-for-app-update" json:"check-for-app-update" mapstructure:"check-for-app-update"` // whether to check for an application update on start up or not
Dev development `yaml:"dev" json:"dev" mapstructure:"dev"`
Log logging `yaml:"log" json:"log" mapstructure:"log"` // all logging-related options
Catalogers []string `yaml:"catalogers" json:"catalogers" mapstructure:"catalogers"`
Package pkg `yaml:"package" json:"package" mapstructure:"package"`
Attest attest `yaml:"attest" json:"attest" mapstructure:"attest"`
FileMetadata FileMetadata `yaml:"file-metadata" json:"file-metadata" mapstructure:"file-metadata"`
FileClassification fileClassification `yaml:"file-classification" json:"file-classification" mapstructure:"file-classification"`
FileContents fileContents `yaml:"file-contents" json:"file-contents" mapstructure:"file-contents"`
Secrets secrets `yaml:"secrets" json:"secrets" mapstructure:"secrets"`
Registry registry `yaml:"registry" json:"registry" mapstructure:"registry"`
Exclusions []string `yaml:"exclude" json:"exclude" mapstructure:"exclude"`
Platform string `yaml:"platform" json:"platform" mapstructure:"platform"`
Name string `yaml:"name" json:"name" mapstructure:"name"`
Parallelism int `yaml:"parallelism" json:"parallelism" mapstructure:"parallelism"` // the number of catalog workers to run in parallel
Quiet bool `yaml:"quiet" json:"quiet" mapstructure:"quiet"`
Outputs []string `yaml:"output" json:"output" mapstructure:"output"` // -o, the format to use for output
OutputTemplatePath string `yaml:"output-template-path" json:"output-template-path" mapstructure:"output-template-path"` // -t template file to use for output
File string `yaml:"file" json:"file" mapstructure:"file"` // --file, the file to write report output to
CheckForAppUpdate bool `yaml:"check-for-app-update" json:"check-for-app-update" mapstructure:"check-for-app-update"` // whether to check for an application update on start up or not
Dev development `yaml:"dev" json:"dev" mapstructure:"dev"`
Log logging `yaml:"log" json:"log" mapstructure:"log"` // all logging-related options
Catalogers []string `yaml:"catalogers" json:"catalogers" mapstructure:"catalogers"`
Package pkg `yaml:"package" json:"package" mapstructure:"package"`
Golang golang `yaml:"golang" json:"golang" mapstructure:"golang"`
Attest attest `yaml:"attest" json:"attest" mapstructure:"attest"`
FileMetadata FileMetadata `yaml:"file-metadata" json:"file-metadata" mapstructure:"file-metadata"`
FileClassification fileClassification `yaml:"file-classification" json:"file-classification" mapstructure:"file-classification"`
FileContents fileContents `yaml:"file-contents" json:"file-contents" mapstructure:"file-contents"`
Secrets secrets `yaml:"secrets" json:"secrets" mapstructure:"secrets"`
Registry registry `yaml:"registry" json:"registry" mapstructure:"registry"`
Exclusions []string `yaml:"exclude" json:"exclude" mapstructure:"exclude"`
Platform string `yaml:"platform" json:"platform" mapstructure:"platform"`
Name string `yaml:"name" json:"name" mapstructure:"name"`
Parallelism int `yaml:"parallelism" json:"parallelism" mapstructure:"parallelism"` // the number of catalog workers to run in parallel
DefaultImagePullSource string `yaml:"default-image-pull-source" json:"default-image-pull-source" mapstructure:"default-image-pull-source"` // specify default image pull source
}
func (cfg Application) ToCatalogerConfig() cataloger.Config {
@@ -68,6 +72,10 @@ func (cfg Application) ToCatalogerConfig() cataloger.Config {
},
Catalogers: cfg.Catalogers,
Parallelism: cfg.Parallelism,
Golang: golangCataloger.GoCatalogerOpts{
SearchLocalModCacheLicenses: cfg.Golang.SearchLocalModCacheLicenses,
LocalModCacheDir: cfg.Golang.LocalModCacheDir,
},
}
}
@@ -123,6 +131,12 @@ func (cfg *Application) parseConfigValues() error {
return err
}
}
if err := checkDefaultSourceValues(cfg.DefaultImagePullSource); err != nil {
return err
}
// check for valid default source options
// parse nested config options
// for each field in the configuration struct, see if the field implements the parser interface
// note: the app config is a pointer, so we need to grab the elements explicitly (to traverse the address)
@@ -185,6 +199,7 @@ func loadDefaultValues(v *viper.Viper) {
v.SetDefault("check-for-app-update", true)
v.SetDefault("catalogers", nil)
v.SetDefault("parallelism", 1)
v.SetDefault("default-image-pull-source", "")
// for each field in the configuration struct, see if the field implements the defaultValueLoader interface and invoke it if it does
value := reflect.ValueOf(Application{})
@@ -208,6 +223,7 @@ func (cfg Application) String() string {
return string(appaStr)
}
// nolint:funlen
func loadConfig(v *viper.Viper, configPath string) error {
var err error
// use explicitly the given user config
@@ -223,13 +239,26 @@ func loadConfig(v *viper.Viper, configPath string) error {
// start searching for valid configs in order...
// 1. look for .<appname>.yaml (in the current directory)
confFilePath := "." + internal.ApplicationName
// TODO: Remove this before v1.0.0
// See syft #1634
v.AddConfigPath(".")
v.SetConfigName("." + internal.ApplicationName)
if err = v.ReadInConfig(); err == nil {
v.Set("config", v.ConfigFileUsed())
return nil
} else if !errors.As(err, &viper.ConfigFileNotFoundError{}) {
return fmt.Errorf("unable to parse config=%q: %w", v.ConfigFileUsed(), err)
v.SetConfigName(confFilePath)
// check if config.yaml exists in the current directory
// DEPRECATED: this will be removed in v1.0.0
if _, err := os.Stat("config.yaml"); err == nil {
log.Warn("DEPRECATED: ./config.yaml as a configuration file is deprecated and will be removed as an option in v1.0.0, please rename to .syft.yaml")
}
if _, err := os.Stat(confFilePath + ".yaml"); err == nil {
if err = v.ReadInConfig(); err == nil {
v.Set("config", v.ConfigFileUsed())
return nil
} else if !errors.As(err, &viper.ConfigFileNotFoundError{}) {
return fmt.Errorf("unable to parse config=%q: %w", v.ConfigFileUsed(), err)
}
}
// 2. look for .<appname>/config.yaml (in the current directory)
@@ -256,11 +285,12 @@ func loadConfig(v *viper.Viper, configPath string) error {
}
// 4. look for <appname>/config.yaml in xdg locations (starting with xdg home config dir, then moving upwards)
v.AddConfigPath(path.Join(xdg.ConfigHome, internal.ApplicationName))
v.SetConfigName("config")
configPath = path.Join(xdg.ConfigHome, internal.ApplicationName)
v.AddConfigPath(configPath)
for _, dir := range xdg.ConfigDirs {
v.AddConfigPath(path.Join(dir, internal.ApplicationName))
}
v.SetConfigName("config")
if err = v.ReadInConfig(); err == nil {
v.Set("config", v.ConfigFileUsed())
return nil
@@ -269,3 +299,15 @@ func loadConfig(v *viper.Viper, configPath string) error {
}
return nil
}
var validDefaultSourceValues = []string{"registry", "docker", "podman", ""}
func checkDefaultSourceValues(source string) error {
validValues := internal.NewStringSet(validDefaultSourceValues...)
if !validValues.Contains(source) {
validValuesString := strings.Join(validDefaultSourceValues, ", ")
return fmt.Errorf("%s is not a valid default source; please use one of the following: %s''", source, validValuesString)
}
return nil
}
+129
View File
@@ -0,0 +1,129 @@
package config
import (
"fmt"
"os"
"path"
"testing"
"github.com/adrg/xdg"
"github.com/mitchellh/go-homedir"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TODO: set negative case when config.yaml is no longer a valid option
func TestApplicationConfig(t *testing.T) {
// disable homedir package cache for testing
originalCacheOpt := homedir.DisableCache
homedir.DisableCache = true
t.Cleanup(func() {
homedir.DisableCache = originalCacheOpt
})
// ensure we have no side effects for xdg package for future tests
originalXDG := os.Getenv("XDG_CONFIG_HOME")
t.Cleanup(func() {
// note: we're not using t.Setenv since the effect we're trying to eliminate is within the xdg package
require.NoError(t, os.Setenv("XDG_CONFIG_HOME", originalXDG))
xdg.Reload()
})
// config is picked up at desired configuration paths
// VALID: .syft.yaml, .syft/config.yaml, ~/.syft.yaml, <XDG_CONFIG_HOME>/syft/config.yaml
// DEPRECATED: config.yaml is currently supported by
tests := []struct {
name string
setup func(t *testing.T) string
assertions func(t *testing.T, app *Application)
cleanup func()
}{
{
name: "explicit config",
setup: func(t *testing.T) string {
return "./test-fixtures/.syft.yaml"
}, // no-op for explicit config
assertions: func(t *testing.T, app *Application) {
assert.Equal(t, "test-explicit-config", app.File)
},
},
{
name: "current working directory named config",
setup: func(t *testing.T) string {
err := os.Chdir("./test-fixtures/config-wd-file") // change application cwd to test-fixtures
require.NoError(t, err)
return ""
},
assertions: func(t *testing.T, app *Application) {
assert.Equal(t, "test-wd-named-config", app.File)
},
},
{
name: "current working directory syft dir config",
setup: func(t *testing.T) string {
err := os.Chdir("./test-fixtures/config-dir-test") // change application cwd to test-fixtures
require.NoError(t, err)
return ""
},
assertions: func(t *testing.T, app *Application) {
assert.Equal(t, "test-dir-config", app.File)
},
},
{
name: "home directory file config",
setup: func(t *testing.T) string {
// Because Setenv affects the whole process, it cannot be used in parallel tests or
// tests with parallel ancestors: see separate XDG test for consequence of this
t.Setenv("HOME", "./test-fixtures/config-home-test/config-file")
return ""
},
assertions: func(t *testing.T, app *Application) {
assert.Equal(t, "test-home-config", app.File)
},
},
{
name: "XDG file config",
setup: func(t *testing.T) string {
wd, err := os.Getwd()
require.NoError(t, err)
configDir := path.Join(wd, "./test-fixtures/config-home-test") // set HOME to testdata
// note: this explicitly has multiple XDG paths, make certain we use the first VALID one (not the first one)
t.Setenv("XDG_CONFIG_DIRS", fmt.Sprintf("/another/foo/bar:%s", configDir))
xdg.Reload()
return ""
},
assertions: func(t *testing.T, app *Application) {
assert.Equal(t, "test-home-XDG-config", app.File)
},
cleanup: func() {
require.NoError(t, os.Unsetenv("XDG_CONFIG_DIRS"))
xdg.Reload()
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if test.cleanup != nil {
t.Cleanup(test.cleanup)
}
wd, err := os.Getwd()
require.NoError(t, err)
defer os.Chdir(wd) // reset working directory after test
application := &Application{}
viperInstance := viper.New()
// this will override home in case you are running this test locally and DO have a syft config
// in your home directory... now it will be ignored. Same for XDG_CONFIG_DIRS.
t.Setenv("HOME", "/foo/bar")
t.Setenv("XDG_CONFIG_DIRS", "/foo/bar")
xdg.Reload()
configPath := test.setup(t)
err = application.LoadAllValues(viperInstance, configPath)
require.NoError(t, err)
test.assertions(t, application)
})
}
}
+13
View File
@@ -0,0 +1,13 @@
package config
import "github.com/spf13/viper"
type golang struct {
SearchLocalModCacheLicenses bool `json:"search-local-mod-cache-licenses" yaml:"search-local-mod-cache-licenses" mapstructure:"search-local-mod-cache-licenses"`
LocalModCacheDir string `json:"local-mod-cache-dir" yaml:"local-mod-cache-dir" mapstructure:"local-mod-cache-dir"`
}
func (cfg golang) loadDefaultValues(v *viper.Viper) {
v.SetDefault("golang.search-local-mod-cache-licenses", false)
v.SetDefault("golang.local-mod-cache-dir", "")
}
+7
View File
@@ -0,0 +1,7 @@
# same as --file; write output report to a file (default is to write to stdout)
file: "test-explicit-config"
package:
cataloger:
scope: "squashed"
# same as --scope; limit the scope of the cataloger to only the specified types
@@ -0,0 +1,5 @@
# same as --file; write output report to a file (default is to write to stdout)
file: "test-dir-config"
package:
cataloger:
scope: "squashed"
@@ -0,0 +1,5 @@
# same as --file; write output report to a file (default is to write to stdout)
file: "test-home-config"
package:
cataloger:
scope: "squashed"
@@ -0,0 +1,5 @@
# same as --file; write output report to a file (default is to write to stdout)
file: "test-home-XDG-config"
package:
cataloger:
scope: "squashed"
@@ -0,0 +1,5 @@
# same as --file; write output report to a file (default is to write to stdout)
file: "test-wd-named-config"
package:
cataloger:
scope: "squashed"
+1 -4
View File
@@ -161,10 +161,7 @@ func UnzipToDir(archivePath, targetDir string) error {
return err
}
if err = extractSingleFile(file, joinedPath, archivePath); err != nil {
return err
}
return nil
return extractSingleFile(file, joinedPath, archivePath)
}
return TraverseFilesInZip(archivePath, visitor)
+53
View File
@@ -0,0 +1,53 @@
package licenses
import "github.com/anchore/syft/internal"
// all of these taken from https://github.com/golang/pkgsite/blob/8996ff632abee854aef1b764ca0501f262f8f523/internal/licenses/licenses.go#L338
// which unfortunately is not exported. But fortunately is under BSD-style license.
var (
FileNames = []string{
"COPYING",
"COPYING.md",
"COPYING.markdown",
"COPYING.txt",
"LICENCE",
"LICENCE.md",
"LICENCE.markdown",
"LICENCE.txt",
"LICENSE",
"LICENSE.md",
"LICENSE.markdown",
"LICENSE.txt",
"LICENSE-2.0.txt",
"LICENCE-2.0.txt",
"LICENSE-APACHE",
"LICENCE-APACHE",
"LICENSE-APACHE-2.0.txt",
"LICENCE-APACHE-2.0.txt",
"LICENSE-MIT",
"LICENCE-MIT",
"LICENSE.MIT",
"LICENCE.MIT",
"LICENSE.code",
"LICENCE.code",
"LICENSE.docs",
"LICENCE.docs",
"LICENSE.rst",
"LICENCE.rst",
"MIT-LICENSE",
"MIT-LICENCE",
"MIT-LICENSE.md",
"MIT-LICENCE.md",
"MIT-LICENSE.markdown",
"MIT-LICENCE.markdown",
"MIT-LICENSE.txt",
"MIT-LICENCE.txt",
"MIT_LICENSE",
"MIT_LICENCE",
"UNLICENSE",
"UNLICENCE",
}
FileNameSet = internal.NewStringSet(FileNames...)
)
+33
View File
@@ -0,0 +1,33 @@
package licenses
import (
"io"
"github.com/google/licensecheck"
"golang.org/x/exp/slices"
)
const (
coverageThreshold = 75
unknownLicenseType = "UNKNOWN"
)
// Parse scans the contents of a license file to attempt to determine the type of license it is
func Parse(reader io.Reader) (licenses []string, err error) {
contents, err := io.ReadAll(reader)
if err != nil {
return nil, err
}
cov := licensecheck.Scan(contents)
if cov.Percent < float64(coverageThreshold) {
licenses = append(licenses, unknownLicenseType)
}
for _, m := range cov.Match {
if slices.Contains(licenses, m.ID) {
continue
}
licenses = append(licenses, m.ID)
}
return
}
@@ -11,8 +11,6 @@ import (
"strings"
"text/template"
"time"
"github.com/scylladb/go-set/strset"
)
// This program generates license_list.go.
@@ -35,7 +33,7 @@ var licenseIDs = map[string]string{
}
`))
var versionMatch = regexp.MustCompile(`-([0-9]+)\.?([0-9]+)?\.?([0-9]+)?\.?`)
var versionMatch = regexp.MustCompile(`([0-9]+)\.?([0-9]+)?\.?([0-9]+)?\.?`)
func main() {
if err := run(); err != nil {
@@ -49,7 +47,6 @@ func run() error {
if err != nil {
return fmt.Errorf("unable to get licenses list: %w", err)
}
var result LicenseList
if err = json.NewDecoder(resp.Body).Decode(&result); err != nil {
return fmt.Errorf("unable to decode license list: %w", err)
@@ -103,58 +100,59 @@ func run() error {
// The third pass is for overwriting deprecated licenses with replacements, for example GPL-2.0+ is deprecated
// and now maps to GPL-2.0-or-later.
func processSPDXLicense(result LicenseList) map[string]string {
// first pass build map
var licenseIDs = make(map[string]string)
for _, l := range result.Licenses {
cleanID := strings.ToLower(l.ID)
if _, exists := licenseIDs[cleanID]; exists {
log.Fatalf("duplicate license ID found: %q", cleanID)
}
licenseIDs[cleanID] = l.ID
}
// The order of variations/permutations of a license ID matters because of we how shuffle its digits,
// that is because the permutation code can generate the same value for two difference licenses,
// for example: The licenses `ABC-1.0` and `ABC-1.1` can both map to `ABC-1`,
// so we need to guarantee the order they are created to avoid mapping them wrongly. So we use a sorted list.
// To overwrite deprecated licenses during the first pass we would later on rely on map order,
// [which in go is not consistent by design](https://stackoverflow.com/a/55925880).
// The order of variations/permutations of a license ID matter.
// The permutation code can generate the same value for two difference licenses,
// for example: The licenses `ABC-1.0` and `ABC-1.1` can both map to `ABC1`,
// we need to guarantee the order they are created to avoid mapping them incorrectly.
// To do this we use a sorted list.
sort.Slice(result.Licenses, func(i, j int) bool {
return result.Licenses[i].ID < result.Licenses[j].ID
})
// second pass to build exceptions and replacements
replaced := strset.New()
// keys are simplified by removing dashes and lowercasing ID
// this is so license declarations in the wild like: LGPL3 LGPL-3 lgpl3 and lgpl-3 can all match
licenseIDs := make(map[string]string)
for _, l := range result.Licenses {
var multipleID []string
cleanID := strings.ToLower(l.ID)
// licensePerms includes the cleanID in return slice
cleanID := cleanLicenseID(l.ID)
licensePerms := buildLicenseIDPermutations(cleanID)
var replacement *License
// if license is deprecated, find its replacement and add to licenseIDs
if l.Deprecated {
replacement = result.findReplacementLicense(l)
idToMap := l.ID
replacement := result.findReplacementLicense(l)
if replacement != nil {
licenseIDs[cleanID] = replacement.ID
idToMap = replacement.ID
}
// it's important to use the original licensePerms here so that the deprecated license
// can now point to the new correct license
for _, id := range licensePerms {
if _, exists := licenseIDs[id]; exists {
// can be used to debug duplicate license permutations and confirm that examples like GPL1
// do not point to GPL-1.1
// log.Println("duplicate license list permutation found when mapping deprecated license to replacement")
// log.Printf("already have key: %q for SPDX ID: %q; attempted to map replacement ID: %q for deprecated ID: %q\n", id, value, replacement.ID, l.ID)
continue
}
licenseIDs[id] = idToMap
}
}
multipleID = append(multipleID, buildLicensePermutations(cleanID)...)
for _, id := range multipleID {
// don't make replacements for IDs that have already been replaced. Since we have a sorted license list
// the earliest replacement is correct (any future replacements are not.
// e.g. replace lgpl-2 with LGPL-2.1-only is wrong, but with LGPL-2.0-only is correct)
if replacement == nil || replaced.Has(id) {
if _, exists := licenseIDs[id]; !exists {
licenseIDs[id] = l.ID
}
} else {
// a useful debugging line during builds
log.Printf("replacing %s with %s\n", id, replacement.ID)
licenseIDs[id] = replacement.ID
replaced.Add(id)
// if license is not deprecated, add all permutations to licenseIDs
for _, id := range licensePerms {
if _, exists := licenseIDs[id]; exists {
// log.Println("found duplicate license permutation key for non deprecated license")
// log.Printf("already have key: %q for SPDX ID: %q; tried to insert as SPDX ID:%q\n", id, value, l.ID)
continue
}
licenseIDs[id] = l.ID
}
}
return licenseIDs
}
func cleanLicenseID(id string) string {
cleanID := strings.ToLower(id)
return strings.ReplaceAll(cleanID, "-", "")
}
File diff suppressed because it is too large Load Diff
+55 -53
View File
@@ -20,51 +20,7 @@ type License struct {
SeeAlso []string `json:"seeAlso"`
}
func (l License) canReplace(other License) bool {
if l.Deprecated {
return false
}
// We want to replace deprecated licenses with non-deprecated counterparts
// For more information, see: https://github.com/spdx/license-list-XML/issues/1676
if other.Deprecated {
switch {
case strings.ReplaceAll(l.ID, "-only", "") == other.ID:
return true
case strings.ReplaceAll(l.ID, "-or-later", "+") == other.ID:
return true
case l.ID == "BSD-2-Clause" && other.ID == "BSD-2-Clause-NetBSD":
return true
case l.ID == "BSD-2-Clause-Views" && other.ID == "BSD-2-Clause-FreeBSD":
return true
case l.ID == "bzip2-1.0.6" && other.ID == "bzip2-1.0.5":
return true
case l.ID == "SMLNJ" && other.ID == "StandardML-NJ":
return true
}
}
if l.Name != other.Name {
return false
}
if l.OSIApproved != other.OSIApproved {
return false
}
if len(l.SeeAlso) != len(other.SeeAlso) {
return false
}
for i, sa := range l.SeeAlso {
if sa != other.SeeAlso[i] {
return false
}
}
return l.ID == other.ID
}
// findReplacementLicense returns a replacement license for a deprecated license
func (ll LicenseList) findReplacementLicense(deprecated License) *License {
for _, l := range ll.Licenses {
if l.canReplace(deprecated) {
@@ -75,16 +31,62 @@ func (ll LicenseList) findReplacementLicense(deprecated License) *License {
return nil
}
func buildLicensePermutations(license string) (perms []string) {
lv := findLicenseVersion(license)
vp := versionPermutations(lv)
version := strings.Join(lv, ".")
for _, p := range vp {
perms = append(perms, strings.Replace(license, version, p, 1))
func (l License) canReplace(deprecated License) bool {
// don't replace a license with a deprecated license
if l.Deprecated {
return false
}
return perms
// We want to replace deprecated licenses with non-deprecated counterparts
// For more information, see: https://github.com/spdx/license-list-XML/issues/1676
switch {
case strings.ReplaceAll(l.ID, "-only", "") == deprecated.ID:
return true
case strings.ReplaceAll(l.ID, "-or-later", "+") == deprecated.ID:
return true
case l.ID == "BSD-2-Clause" && deprecated.ID == "BSD-2-Clause-NetBSD":
return true
case l.ID == "BSD-2-Clause-Views" && deprecated.ID == "BSD-2-Clause-FreeBSD":
return true
case l.ID == "bzip2-1.0.6" && deprecated.ID == "bzip2-1.0.5":
return true
case l.ID == "SMLNJ" && deprecated.ID == "StandardML-NJ":
return true
}
if l.Name != deprecated.Name {
return false
}
if l.OSIApproved != deprecated.OSIApproved {
return false
}
if len(l.SeeAlso) != len(deprecated.SeeAlso) {
return false
}
for i, sa := range l.SeeAlso {
if sa != deprecated.SeeAlso[i] {
return false
}
}
return l.ID == deprecated.ID
}
func buildLicenseIDPermutations(cleanID string) (perms []string) {
lv := findLicenseVersion(cleanID)
vp := versionPermutations(lv)
permSet := strset.New()
version := strings.Join(lv, ".")
for _, p := range vp {
permSet.Add(strings.Replace(cleanID, version, p, 1))
}
permSet.Add(cleanID)
return permSet.List()
}
func findLicenseVersion(license string) (version []string) {
+20 -23
View File
@@ -67,56 +67,57 @@ func TestLicensePermutations(t *testing.T) {
{
"GPL-1-only",
[]string{
"GPL-1-only",
"GPL-1.0-only",
"GPL-1.0.0-only",
"gpl1only",
"gpl1.0only",
"gpl1.0.0only",
},
},
{
"GPL-2",
[]string{
"GPL-2",
"GPL-2.0",
"GPL-2.0.0",
"gpl2",
"gpl2.0",
"gpl2.0.0",
},
},
{
"GPL-2.0+",
[]string{
"GPL-2+",
"GPL-2.0+",
"GPL-2.0.0+",
"gpl2+",
"gpl2.0+",
"gpl2.0.0+",
},
},
{
"GPL-3.0.0-or-later",
[]string{
"GPL-3-or-later",
"GPL-3.0-or-later",
"GPL-3.0.0-or-later",
"gpl3orlater",
"gpl3.0orlater",
"gpl3.0.0orlater",
},
},
{
"abc-1.1",
[]string{
"abc-1",
"abc-1.1",
"abc-1.1.0",
"abc1",
"abc1.1",
"abc1.1.0",
},
},
{
"oldap-2.0",
[]string{
"oldap-2",
"oldap-2.0",
"oldap-2.0.0",
"oldap2",
"oldap2.0",
"oldap2.0.0",
},
},
}
for _, test := range tests {
t.Run(test.shortName, func(t *testing.T) {
perms := buildLicensePermutations(test.shortName)
cleanID := cleanLicenseID(test.shortName)
perms := buildLicenseIDPermutations(cleanID)
assert.ElementsMatch(t, test.permutations, perms)
})
}
@@ -183,10 +184,6 @@ func TestFindLicenseVersion(t *testing.T) {
"GPL-2",
[]string{"2"},
},
{
"bzip2-1",
[]string{"1"},
},
{
"php-3.01",
[]string{"3", "01"},
+13 -9
View File
@@ -18,16 +18,20 @@ const (
//go:generate go run ./generate
func ID(id string) (value, other string, exists bool) {
id = strings.TrimSpace(id)
// ignore blank strings or the joiner
if id == "" || id == "AND" {
return "", "", false
}
// ID returns the canonical license ID for the given license ID
// Note: this function is only concerned with returning a best match of an SPDX license ID
// SPDX Expressions will be handled by a parent package which will call this function
func ID(id string) (value string, exists bool) {
// first look for a canonical license
if value, exists := licenseIDs[strings.ToLower(id)]; exists {
return value, "", exists
if value, exists := licenseIDs[cleanLicenseID(id)]; exists {
return value, exists
}
// we did not find, so treat it as a separate license
return "", id, true
return "", false
}
func cleanLicenseID(id string) string {
id = strings.TrimSpace(id)
id = strings.ToLower(id)
return strings.ReplaceAll(id, "-", "")
}
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -9,8 +9,8 @@ import (
func TestLicenceListIDs(t *testing.T) {
// do a sanity check on the generated data
assert.Equal(t, "0BSD", licenseIDs["0bsd"])
assert.Equal(t, "ZPL-2.1", licenseIDs["zpl-2.1"])
assert.Equal(t, "GPL-2.0-only", licenseIDs["gpl-2"])
assert.Equal(t, "GPL-2.0-or-later", licenseIDs["gpl-2+"])
assert.Equal(t, "ZPL-2.1", licenseIDs["zpl2.1"])
assert.Equal(t, "GPL-2.0-only", licenseIDs["gpl2"])
assert.Equal(t, "GPL-2.0-or-later", licenseIDs["gpl2+"])
assert.NotEmpty(t, Version)
}
+13 -27
View File
@@ -6,83 +6,70 @@ import (
"github.com/stretchr/testify/assert"
)
func TestIDParse(t *testing.T) {
func TestSPDXIDRecognition(t *testing.T) {
var tests = []struct {
shortName string
id string
other string
found bool
}{
{
"GPL-1-only",
"GPL-1.0-only",
"",
true,
},
{
"gpl1",
"GPL-1.0-only",
true,
},
{
"gpl-1",
"GPL-1.0-only",
true,
},
{
"GPL-2",
"GPL-2.0-only",
"",
true,
},
{
"GPL-2+",
"GPL-2.0-or-later",
"",
true,
},
{
"GPL-3.0.0-or-later",
"GPL-3.0-or-later",
"",
true,
},
{
"GPL-3-with-autoconf-exception",
"GPL-3.0-with-autoconf-exception",
"",
true,
},
{
"CC-by-nc-3-de",
"CC-BY-NC-3.0-DE",
"",
true,
},
// the below few cases are NOT expected, however, seem unavoidable given the current approach
{
"w3c-20150513.0.0",
"W3C-20150513",
"",
true,
},
{
"spencer-86.0.0",
"Spencer-86",
"",
true,
},
{
"unicode-dfs-2015.0.0",
"Unicode-DFS-2015",
"",
true,
},
{
"Unknown",
"",
"Unknown",
true,
},
{
" ",
"",
"",
false,
},
{
"AND",
"",
" ",
"",
false,
},
@@ -90,10 +77,9 @@ func TestIDParse(t *testing.T) {
for _, test := range tests {
t.Run(test.shortName, func(t *testing.T) {
value, other, exists := ID(test.shortName)
value, exists := ID(test.shortName)
assert.Equal(t, test.found, exists)
assert.Equal(t, test.id, value)
assert.Equal(t, test.other, other)
})
}
}
+1 -2
View File
@@ -106,9 +106,8 @@ func stripSlashes(s string) string {
for i, c := range s {
if c == '\\' && i+1 < len(s) && strings.ContainsRune(allowedCPEPunctuation, rune(s[i+1])) {
continue
} else {
sb.WriteRune(c)
}
sb.WriteRune(c)
}
return sb.String()
}
@@ -10,14 +10,21 @@ import (
func encodeLicenses(p pkg.Package) *cyclonedx.Licenses {
lc := cyclonedx.Licenses{}
for _, licenseName := range p.Licenses {
if value, other, exists := spdxlicense.ID(licenseName); exists {
if value, exists := spdxlicense.ID(licenseName); exists {
lc = append(lc, cyclonedx.LicenseChoice{
License: &cyclonedx.License{
ID: value,
Name: other,
ID: value,
},
})
continue
}
// not found so append the licenseName as is
lc = append(lc, cyclonedx.LicenseChoice{
License: &cyclonedx.License{
Name: licenseName,
},
})
}
if len(lc) > 0 {
return &lc
+12 -6
View File
@@ -24,6 +24,12 @@ func License(p pkg.Package) string {
// take all licenses and assume an AND expression; for information about license expressions see https://spdx.github.io/spdx-spec/appendix-IV-SPDX-license-expressions/
parsedLicenses := parseLicenses(p.Licenses)
for i, v := range parsedLicenses {
if strings.HasPrefix(v, spdxlicense.LicenseRefPrefix) {
parsedLicenses[i] = SanitizeElementID(v)
}
}
if len(parsedLicenses) == 0 {
return NOASSERTION
}
@@ -33,12 +39,12 @@ func License(p pkg.Package) string {
func parseLicenses(raw []string) (parsedLicenses []string) {
for _, l := range raw {
if value, other, exists := spdxlicense.ID(l); exists {
parsed := value
if other != "" {
parsed = spdxlicense.LicenseRefPrefix + other
}
parsedLicenses = append(parsedLicenses, parsed)
if value, exists := spdxlicense.ID(l); exists {
parsedLicenses = append(parsedLicenses, value)
} else {
// we did not find a valid SPDX license ID so treat as separate license
otherLicense := spdxlicense.LicenseRefPrefix + l
parsedLicenses = append(parsedLicenses, otherLicense)
}
}
return
@@ -65,6 +65,17 @@ func Test_License(t *testing.T) {
},
expected: "GPL-2.0-only",
},
{
name: "includes valid LicenseRef-",
input: pkg.Package{
Licenses: []string{
"one thing first",
"two things/#$^second",
"MIT",
},
},
expected: "LicenseRef-one-thing-first AND LicenseRef-two-things----second AND MIT",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
@@ -9,6 +9,8 @@ import (
"time"
"github.com/spdx/tools-golang/spdx"
"golang.org/x/exp/maps"
"golang.org/x/exp/slices"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/log"
@@ -514,19 +516,23 @@ func toFileTypes(metadata *source.FileMetadata) (ty []string) {
func toOtherLicenses(catalog *pkg.Catalog) []*spdx.OtherLicense {
licenses := map[string]bool{}
for _, pkg := range catalog.Sorted() {
for _, license := range parseLicenses(pkg.Licenses) {
for _, p := range catalog.Sorted() {
for _, license := range parseLicenses(p.Licenses) {
if strings.HasPrefix(license, spdxlicense.LicenseRefPrefix) {
licenses[license] = true
}
}
}
var result []*spdx.OtherLicense
for license := range licenses {
sorted := maps.Keys(licenses)
slices.Sort(sorted)
for _, license := range sorted {
// separate the actual ID from the prefix
name := strings.TrimPrefix(license, spdxlicense.LicenseRefPrefix)
result = append(result, &spdx.OtherLicense{
LicenseIdentifier: license,
LicenseIdentifier: SanitizeElementID(license),
LicenseName: name,
ExtractedText: NONE, // we probably should have some extracted text here, but this is good enough for now
})
@@ -369,7 +369,7 @@ func Test_fileIDsForPackage(t *testing.T) {
}
func Test_H1Digest(t *testing.T) {
sbom := sbom.SBOM{}
s := sbom.SBOM{}
tests := []struct {
name string
pkg pkg.Package
@@ -416,7 +416,7 @@ func Test_H1Digest(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
catalog := pkg.NewCatalog(test.pkg)
pkgs := toPackages(catalog, sbom)
pkgs := toPackages(catalog, s)
require.Len(t, pkgs, 1)
for _, p := range pkgs {
if test.expectedDigest == "" {
@@ -431,3 +431,67 @@ func Test_H1Digest(t *testing.T) {
})
}
}
func Test_OtherLicenses(t *testing.T) {
tests := []struct {
name string
pkg pkg.Package
expected []*spdx.OtherLicense
}{
{
name: "no licenseRef",
pkg: pkg.Package{
Licenses: []string{
"MIT",
},
},
expected: nil,
},
{
name: "single licenseRef",
pkg: pkg.Package{
Licenses: []string{
"un known",
},
},
expected: []*spdx.OtherLicense{
{
LicenseIdentifier: "LicenseRef-un-known",
LicenseName: "un known",
ExtractedText: NONE,
},
},
},
{
name: "multiple licenseRef",
pkg: pkg.Package{
Licenses: []string{
"un known",
"not known %s",
"MIT",
},
},
expected: []*spdx.OtherLicense{
{
LicenseIdentifier: "LicenseRef-not-known--s",
LicenseName: "not known %s",
ExtractedText: NONE,
},
{
LicenseIdentifier: "LicenseRef-un-known",
LicenseName: "un known",
ExtractedText: NONE,
},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
catalog := pkg.NewCatalog(test.pkg)
otherLicenses := toOtherLicenses(catalog)
require.Len(t, otherLicenses, len(test.expected))
require.Equal(t, test.expected, otherLicenses)
})
}
}
@@ -24,6 +24,7 @@ func TestCataloger_Globs(t *testing.T) {
pkgtest.NewCatalogTester().
FromDirectory(t, test.fixture).
ExpectsResolverContentQueries(test.expected).
IgnoreUnfulfilledPathResponses("etc/apk/repositories").
TestCataloger(t, NewApkdbCataloger())
})
}
+73 -2
View File
@@ -3,7 +3,9 @@ package apkdb
import (
"bufio"
"fmt"
"io"
"path"
"regexp"
"strconv"
"strings"
@@ -20,11 +22,15 @@ import (
// integrity check
var _ generic.Parser = parseApkDB
var (
repoRegex = regexp.MustCompile(`(?m)^https://.*\.alpinelinux\.org/alpine/v([^/]+)/([a-zA-Z0-9_]+)$`)
)
// parseApkDB parses packages from a given APK installed DB file. For more
// information on specific fields, see https://wiki.alpinelinux.org/wiki/Apk_spec.
//
//nolint:funlen
func parseApkDB(_ source.FileResolver, env *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
//nolint:funlen,gocognit
func parseApkDB(resolver source.FileResolver, env *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
scanner := bufio.NewScanner(reader)
var apks []pkg.ApkMetadata
@@ -101,6 +107,19 @@ func parseApkDB(_ source.FileResolver, env *generic.Environment, reader source.L
if env != nil {
r = env.LinuxRelease
}
// this is somewhat ugly, but better than completely failing when we can't find the release,
// e.g. embedded deeper in the tree, like containers or chroots.
// but we now have no way of handling different repository sources. On the other hand,
// we never could before this. At least now, we can handle some.
// This should get fixed with https://gitlab.alpinelinux.org/alpine/apk-tools/-/issues/10875
if r == nil {
// find the repositories file from the relative directory of the DB file
releases := findReleases(resolver, reader.Location.RealPath)
if len(releases) > 0 {
r = &releases[0]
}
}
pkgs := make([]pkg.Package, 0, len(apks))
for _, apk := range apks {
@@ -110,6 +129,58 @@ func parseApkDB(_ source.FileResolver, env *generic.Environment, reader source.L
return pkgs, discoverPackageDependencies(pkgs), nil
}
func findReleases(resolver source.FileResolver, dbPath string) []linux.Release {
if resolver == nil {
return nil
}
reposLocation := path.Clean(path.Join(path.Dir(dbPath), "../../../etc/apk/repositories"))
locations, err := resolver.FilesByPath(reposLocation)
if err != nil {
log.Tracef("unable to find APK repositories file %q: %+v", reposLocation, err)
return nil
}
if len(locations) == 0 {
return nil
}
location := locations[0]
reposReader, err := resolver.FileContentsByLocation(location)
if err != nil {
log.Tracef("unable to fetch contents for APK repositories file %q: %+v", reposLocation, err)
return nil
}
return parseReleasesFromAPKRepository(source.LocationReadCloser{
Location: location,
ReadCloser: reposReader,
})
}
func parseReleasesFromAPKRepository(reader source.LocationReadCloser) []linux.Release {
var releases []linux.Release
reposB, err := io.ReadAll(reader)
if err != nil {
log.Tracef("unable to read APK repositories file %q: %+v", reader.Location.RealPath, err)
return nil
}
parts := repoRegex.FindAllStringSubmatch(string(reposB), -1)
for _, part := range parts {
if len(part) >= 3 {
releases = append(releases, linux.Release{
Name: "Alpine Linux",
ID: "alpine",
VersionID: part[1],
})
}
}
return releases
}
func parseApkField(line string) *apkField {
parts := strings.SplitN(line, ":", 2)
if len(parts) != 2 {
@@ -1,8 +1,10 @@
package apkdb
import (
"io"
"os"
"path/filepath"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
@@ -1186,3 +1188,49 @@ func Test_stripVersionSpecifier(t *testing.T) {
})
}
}
func TestParseReleasesFromAPKRepository(t *testing.T) {
tests := []struct {
repos string
want []linux.Release
desc string
}{
{
"https://foo.alpinelinux.org/alpine/v3.14/main",
[]linux.Release{
{Name: "Alpine Linux", ID: "alpine", VersionID: "3.14"},
},
"single repo",
},
{
`https://foo.alpinelinux.org/alpine/v3.14/main
https://foo.alpinelinux.org/alpine/v3.14/community`,
[]linux.Release{
{Name: "Alpine Linux", ID: "alpine", VersionID: "3.14"},
{Name: "Alpine Linux", ID: "alpine", VersionID: "3.14"},
},
"multiple repos",
},
{
``,
nil,
"empty",
},
{
`https://foo.bar.org/alpine/v3.14/main
https://foo.them.org/alpine/v3.14/community`,
nil,
"invalid repos",
},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
reposReader := io.NopCloser(strings.NewReader(tt.repos))
got := parseReleasesFromAPKRepository(source.LocationReadCloser{
Location: source.NewLocation("test"),
ReadCloser: reposReader,
})
assert.Equal(t, tt.want, got)
})
}
}
+1 -6
View File
@@ -74,12 +74,7 @@ func catalog(resolver source.FileResolver, cls classifier) (packages []pkg.Packa
return nil, err
}
for _, location := range locations {
reader, err := resolver.FileContentsByLocation(location)
if err != nil {
return nil, err
}
locationReader := source.NewLocationReadCloser(location, reader)
pkgs, err := cls.EvidenceMatcher(cls, locationReader)
pkgs, err := cls.EvidenceMatcher(resolver, cls, location)
if err != nil {
return nil, err
}
+301 -49
View File
@@ -4,8 +4,11 @@ import (
"errors"
"fmt"
"io"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -68,25 +71,6 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
Metadata: metadata("postgresql-binary"),
},
},
{
name: "positive-python-duplicates",
fixtureDir: "test-fixtures/classifiers/positive/python-duplicates",
expected: pkg.Package{
Name: "python",
Version: "3.8.16",
Type: "binary",
PURL: "pkg:generic/python@3.8.16",
Locations: locations("dir/python3.8", "python3.8", "libpython3.8.so", "patchlevel.h"),
Metadata: pkg.BinaryMetadata{
Matches: []pkg.ClassifierMatch{
match("python-binary", "dir/python3.8"),
match("python-binary", "python3.8"),
match("python-binary-lib", "libpython3.8.so"),
match("cpython-source", "patchlevel.h"),
},
},
},
},
{
name: "positive-traefik-2.9.6",
fixtureDir: "test-fixtures/classifiers/positive/traefik-2.9.6",
@@ -255,6 +239,114 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
Metadata: metadata("redis-binary"),
},
},
{
name: "positive-argocd-2.5.11",
fixtureDir: "test-fixtures/classifiers/dynamic/argocd-2.5.11",
expected: pkg.Package{
Name: "argocd",
Version: "2.5.11",
Type: "binary",
PURL: "pkg:golang/github.com/argoproj/argo-cd@2.5.11",
Locations: locations("argocd"),
Metadata: metadata("argocd"),
},
},
{
name: "positive-argocd-2.6.4",
fixtureDir: "test-fixtures/classifiers/dynamic/argocd-2.6.4",
expected: pkg.Package{
Name: "argocd",
Version: "2.6.4",
Type: "binary",
PURL: "pkg:golang/github.com/argoproj/argo-cd@2.6.4",
Locations: locations("argocd"),
Metadata: metadata("argocd"),
},
},
{
name: "positive-helm-3.11.1",
fixtureDir: "test-fixtures/classifiers/dynamic/helm-3.11.1",
expected: pkg.Package{
Name: "helm",
Version: "3.11.1",
Type: "binary",
PURL: "pkg:golang/helm.sh/helm@3.11.1",
Locations: locations("helm"),
Metadata: metadata("helm"),
},
},
{
name: "positive-helm-3.10.3",
fixtureDir: "test-fixtures/classifiers/dynamic/helm-3.10.3",
expected: pkg.Package{
Name: "helm",
Version: "3.10.3",
Type: "binary",
PURL: "pkg:golang/helm.sh/helm@3.10.3",
Locations: locations("helm"),
Metadata: metadata("helm"),
},
},
{
name: "positive-kubectl-1.24.11",
fixtureDir: "test-fixtures/classifiers/dynamic/kubectl-1.24.11",
expected: pkg.Package{
Name: "kubectl",
Version: "1.24.11",
Type: "binary",
PURL: "pkg:golang/k8s.io/kubectl@1.24.11",
Locations: locations("kubectl"),
Metadata: metadata("kubectl"),
},
},
{
name: "positive-kubectl-1.25.7",
fixtureDir: "test-fixtures/classifiers/dynamic/kubectl-1.25.7",
expected: pkg.Package{
Name: "kubectl",
Version: "1.25.7",
Type: "binary",
PURL: "pkg:golang/k8s.io/kubectl@1.25.7",
Locations: locations("kubectl"),
Metadata: metadata("kubectl"),
},
},
{
name: "positive-kubectl-1.26.2",
fixtureDir: "test-fixtures/classifiers/dynamic/kubectl-1.26.2",
expected: pkg.Package{
Name: "kubectl",
Version: "1.26.2",
Type: "binary",
PURL: "pkg:golang/k8s.io/kubectl@1.26.2",
Locations: locations("kubectl"),
Metadata: metadata("kubectl"),
},
},
{
name: "positive-kustomize-4.5.7",
fixtureDir: "test-fixtures/classifiers/dynamic/kustomize-4.5.7",
expected: pkg.Package{
Name: "kustomize",
Version: "4.5.7",
Type: "binary",
PURL: "pkg:golang/sigs.k8s.io/kustomize@4.5.7",
Locations: locations("kustomize"),
Metadata: metadata("kustomize"),
},
},
{
name: "positive-kustomize-5.0.0",
fixtureDir: "test-fixtures/classifiers/dynamic/kustomize-5.0.0",
expected: pkg.Package{
Name: "kustomize",
Version: "5.0.0",
Type: "binary",
PURL: "pkg:golang/sigs.k8s.io/kustomize@5.0.0",
Locations: locations("kustomize"),
Metadata: metadata("kustomize"),
},
},
{
name: "positive-redis-4.0.11",
fixtureDir: "test-fixtures/classifiers/positive/redis-server-4.0.11",
@@ -308,32 +400,116 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
fixtureDir: "test-fixtures/classifiers/positive/python-binary-lib-3.7",
expected: pkg.Package{
Name: "python",
Version: "3.7.4a-vZ9",
PURL: "pkg:generic/python@3.7.4a-vZ9",
Version: "3.7.4",
PURL: "pkg:generic/python@3.7.4",
Locations: locations("libpython3.7.so"),
Metadata: metadata("python-binary-lib"),
},
},
{
name: "positive-python-3.11.2-from-shared-lib",
fixtureDir: "test-fixtures/classifiers/dynamic/python-binary-shared-lib-3.11",
expected: pkg.Package{
Name: "python",
Version: "3.11.2",
PURL: "pkg:generic/python@3.11.2",
Locations: locations("python3", "libpython3.11.so.1.0"),
Metadata: pkg.BinaryMetadata{
Matches: []pkg.ClassifierMatch{
match("python-binary", "python3"),
match("python-binary", "libpython3.11.so.1.0"),
match("python-binary-lib", "libpython3.11.so.1.0"),
},
},
},
},
{
name: "positive-python-3.9-from-shared-redhat-lib",
fixtureDir: "test-fixtures/classifiers/dynamic/python-binary-shared-lib-redhat-3.9",
expected: pkg.Package{
Name: "python",
Version: "3.9.13",
PURL: "pkg:generic/python@3.9.13",
Locations: locations("python3.9", "libpython3.9.so.1.0"),
Metadata: pkg.BinaryMetadata{
Matches: []pkg.ClassifierMatch{
match("python-binary", "python3.9"),
match("python-binary", "libpython3.9.so.1.0"),
match("python-binary-lib", "libpython3.9.so.1.0"),
},
},
},
},
{
name: "positive-python-binary-with-version-3.9",
fixtureDir: "test-fixtures/classifiers/dynamic/python-binary-with-version-3.9",
expected: pkg.Package{
Name: "python",
Version: "3.9.2",
PURL: "pkg:generic/python@3.9.2",
Locations: locations("python3.9"),
Metadata: pkg.BinaryMetadata{
Matches: []pkg.ClassifierMatch{
match("python-binary", "python3.9"),
},
},
},
},
{
name: "positive-python-binary-3.4-alpine",
fixtureDir: "test-fixtures/classifiers/dynamic/python-binary-3.4-alpine",
expected: pkg.Package{
Name: "python",
Version: "3.4.10",
PURL: "pkg:generic/python@3.4.10",
Locations: locations("python3.4", "libpython3.4m.so.1.0"),
Metadata: pkg.BinaryMetadata{
Matches: []pkg.ClassifierMatch{
match("python-binary", "python3.4"),
match("python-binary", "libpython3.4m.so.1.0"),
match("python-binary-lib", "libpython3.4m.so.1.0"),
},
},
},
},
{
name: "positive-python-3.5-with-incorrect-match",
fixtureDir: "test-fixtures/classifiers/positive/python-3.5-with-incorrect-match",
expected: pkg.Package{
Name: "python",
Version: "3.5.3",
PURL: "pkg:generic/python@3.5.3",
Locations: locations("python3.5"),
Metadata: metadata("python-binary"),
},
},
{
name: "positive-python3.6",
fixtureDir: "test-fixtures/classifiers/positive/python-binary-3.6",
expected: pkg.Package{
Name: "python",
Version: "3.6.3a-vZ9",
PURL: "pkg:generic/python@3.6.3a-vZ9",
Version: "3.6.3",
PURL: "pkg:generic/python@3.6.3",
Locations: locations("python3.6"),
Metadata: metadata("python-binary"),
},
},
{
name: "positive-patchlevel.h",
fixtureDir: "test-fixtures/classifiers/positive/python-source-3.9",
name: "positive-python-duplicates",
fixtureDir: "test-fixtures/classifiers/positive/python-duplicates",
expected: pkg.Package{
Name: "python",
Version: "3.9-aZ5",
PURL: "pkg:generic/python@3.9-aZ5",
Locations: locations("patchlevel.h"),
Metadata: metadata("cpython-source"),
Version: "3.8.16",
Type: "binary",
PURL: "pkg:generic/python@3.8.16",
Locations: locations("dir/python3.8", "python3.8", "libpython3.8.so"),
Metadata: pkg.BinaryMetadata{
Matches: []pkg.ClassifierMatch{
match("python-binary", "dir/python3.8"),
match("python-binary", "python3.8"),
match("python-binary-lib", "libpython3.8.so"),
},
},
},
},
{
@@ -475,6 +651,69 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
Metadata: metadata("rust-standard-library-linux"),
},
},
{
name: "positive-ruby-3.2.1",
fixtureDir: "test-fixtures/classifiers/dynamic/ruby-library-3.2.1",
expected: pkg.Package{
Name: "ruby",
Version: "3.2.1",
Type: "binary",
PURL: "pkg:generic/ruby@3.2.1",
Locations: locations("ruby", "libruby.so.3.2.1"),
Metadata: pkg.BinaryMetadata{
Matches: []pkg.ClassifierMatch{
match("ruby-binary", "ruby"),
match("ruby-binary", "libruby.so.3.2.1"),
},
},
},
},
{
name: "positive-ruby-2.7.7",
fixtureDir: "test-fixtures/classifiers/dynamic/ruby-library-2.7.7",
expected: pkg.Package{
Name: "ruby",
Version: "2.7.7p221",
Type: "binary",
PURL: "pkg:generic/ruby@2.7.7p221",
Locations: locations("ruby", "libruby.so.2.7.7"),
Metadata: pkg.BinaryMetadata{
Matches: []pkg.ClassifierMatch{
match("ruby-binary", "ruby"),
match("ruby-binary", "libruby.so.2.7.7"),
},
},
},
},
{
name: "positive-ruby-2.6.10",
fixtureDir: "test-fixtures/classifiers/dynamic/ruby-library-2.6.10",
expected: pkg.Package{
Name: "ruby",
Version: "2.6.10p210",
Type: "binary",
PURL: "pkg:generic/ruby@2.6.10p210",
Locations: locations("ruby", "libruby.so.2.6.10"),
Metadata: pkg.BinaryMetadata{
Matches: []pkg.ClassifierMatch{
match("ruby-binary", "ruby"),
match("ruby-binary", "libruby.so.2.6.10"),
},
},
},
},
{
name: "positive-ruby-1.9.3p551",
fixtureDir: "test-fixtures/classifiers/positive/ruby-1.9.3p551",
expected: pkg.Package{
Name: "ruby",
Version: "1.9.3p551",
Type: "binary",
PURL: "pkg:generic/ruby@1.9.3p551",
Locations: locations("ruby"),
Metadata: metadata("ruby-binary"),
},
},
}
for _, test := range tests {
@@ -490,20 +729,9 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
packages, _, err := c.Catalog(resolver)
require.NoError(t, err)
for _, p := range packages {
expectedLocations := test.expected.Locations.ToSlice()
gotLocations := p.Locations.ToSlice()
require.Len(t, gotLocations, len(expectedLocations))
require.Len(t, packages, 1)
for i, expectedLocation := range expectedLocations {
gotLocation := gotLocations[i]
if expectedLocation.RealPath != gotLocation.RealPath {
t.Fatalf("locations do not match; expected: %v got: %v", expectedLocations, gotLocations)
}
}
assertPackagesAreEqual(t, test.expected, p)
}
assertPackagesAreEqual(t, test.expected, packages[0])
})
}
}
@@ -611,6 +839,21 @@ func match(classifier string, paths ...string) pkg.ClassifierMatch {
}
func assertPackagesAreEqual(t *testing.T, expected pkg.Package, p pkg.Package) {
var failMessages []string
expectedLocations := expected.Locations.ToSlice()
gotLocations := p.Locations.ToSlice()
if len(expectedLocations) != len(gotLocations) {
failMessages = append(failMessages, "locations are not equal length")
} else {
for i, expectedLocation := range expectedLocations {
gotLocation := gotLocations[i]
if expectedLocation.RealPath != gotLocation.RealPath {
failMessages = append(failMessages, fmt.Sprintf("locations do not match; expected: %v got: %v", expectedLocation.RealPath, gotLocation.RealPath))
}
}
}
m1 := expected.Metadata.(pkg.BinaryMetadata).Matches
m2 := p.Metadata.(pkg.BinaryMetadata).Matches
matches := true
@@ -633,17 +876,26 @@ func assertPackagesAreEqual(t *testing.T, expected pkg.Package, p pkg.Package) {
} else {
matches = false
}
if !matches {
failMessages = append(failMessages, "classifier matches not equal")
}
if expected.Name != p.Name ||
expected.Version != p.Version ||
expected.PURL != p.PURL ||
!matches {
assert.Failf(t, "packages not equal", "%v != %v", stringifyPkg(expected), stringifyPkg(p))
expected.PURL != p.PURL {
failMessages = append(failMessages, "packages do not match")
}
}
func stringifyPkg(p pkg.Package) string {
matches := p.Metadata.(pkg.BinaryMetadata).Matches
return fmt.Sprintf("(name=%s, version=%s, purl=%s, matches=%+v)", p.Name, p.Version, p.PURL, matches)
if len(failMessages) > 0 {
assert.Failf(t, strings.Join(failMessages, "; "), "diff: %s",
cmp.Diff(expected, p,
cmp.Transformer("Locations", func(l source.LocationSet) []source.Location {
return l.ToSlice()
}),
cmpopts.IgnoreUnexported(pkg.Package{}, source.Location{}),
cmpopts.IgnoreFields(pkg.Package{}, "CPEs", "FoundBy", "MetadataType", "Type"),
))
}
}
type panicyResolver struct {
+115 -16
View File
@@ -2,14 +2,19 @@ package binary
import (
"bytes"
"debug/elf"
"debug/macho"
"debug/pe"
"fmt"
"io"
"reflect"
"regexp"
"strings"
"text/template"
"github.com/anchore/packageurl-go"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/cpe"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/internal/unionreader"
@@ -49,12 +54,12 @@ type classifier struct {
}
// evidenceMatcher is a function called to catalog Packages that match some sort of evidence
type evidenceMatcher func(classifier classifier, reader source.LocationReadCloser) ([]pkg.Package, error)
type evidenceMatcher func(resolver source.FileResolver, classifier classifier, location source.Location) ([]pkg.Package, error)
func evidenceMatchers(matchers ...evidenceMatcher) evidenceMatcher {
return func(classifier classifier, reader source.LocationReadCloser) ([]pkg.Package, error) {
return func(resolver source.FileResolver, classifier classifier, location source.Location) ([]pkg.Package, error) {
for _, matcher := range matchers {
match, err := matcher(classifier, reader)
match, err := matcher(resolver, classifier, location)
if err != nil {
return nil, err
}
@@ -68,12 +73,17 @@ func evidenceMatchers(matchers ...evidenceMatcher) evidenceMatcher {
func fileNameTemplateVersionMatcher(fileNamePattern string, contentTemplate string) evidenceMatcher {
pat := regexp.MustCompile(fileNamePattern)
return func(classifier classifier, reader source.LocationReadCloser) ([]pkg.Package, error) {
if !pat.MatchString(reader.RealPath) {
return func(resolver source.FileResolver, classifier classifier, location source.Location) ([]pkg.Package, error) {
if !pat.MatchString(location.RealPath) {
return nil, nil
}
filepathNamedGroupValues := internal.MatchNamedCaptureGroups(pat, reader.RealPath)
filepathNamedGroupValues := internal.MatchNamedCaptureGroups(pat, location.RealPath)
// versions like 3.5 should not match any character, but explicit dot
for k, v := range filepathNamedGroupValues {
filepathNamedGroupValues[k] = strings.ReplaceAll(v, ".", "\\.")
}
tmpl, err := template.New("").Parse(contentTemplate)
if err != nil {
@@ -91,26 +101,70 @@ func fileNameTemplateVersionMatcher(fileNamePattern string, contentTemplate stri
return nil, fmt.Errorf("unable to compile rendered regex=%q: %w", patternBuf.String(), err)
}
contents, err := getContents(reader)
contents, err := getContents(resolver, location)
if err != nil {
return nil, fmt.Errorf("unable to get read contents for file: %w", err)
}
matchMetadata := internal.MatchNamedCaptureGroups(tmplPattern, string(contents))
return singlePackage(classifier, reader, matchMetadata), nil
return singlePackage(classifier, location, matchMetadata), nil
}
}
func fileContentsVersionMatcher(pattern string) evidenceMatcher {
pat := regexp.MustCompile(pattern)
return func(classifier classifier, reader source.LocationReadCloser) ([]pkg.Package, error) {
contents, err := getContents(reader)
return func(resolver source.FileResolver, classifier classifier, location source.Location) ([]pkg.Package, error) {
contents, err := getContents(resolver, location)
if err != nil {
return nil, fmt.Errorf("unable to get read contents for file: %w", err)
}
matchMetadata := internal.MatchNamedCaptureGroups(pat, string(contents))
return singlePackage(classifier, reader, matchMetadata), nil
return singlePackage(classifier, location, matchMetadata), nil
}
}
//nolint:gocognit
func sharedLibraryLookup(sharedLibraryPattern string, sharedLibraryMatcher evidenceMatcher) evidenceMatcher {
pat := regexp.MustCompile(sharedLibraryPattern)
return func(resolver source.FileResolver, classifier classifier, location source.Location) (packages []pkg.Package, _ error) {
libs, err := sharedLibraries(resolver, location)
if err != nil {
return nil, err
}
for _, lib := range libs {
if !pat.MatchString(lib) {
continue
}
locations, err := resolver.FilesByGlob("**/" + lib)
if err != nil {
return nil, err
}
for _, libraryLication := range locations {
pkgs, err := sharedLibraryMatcher(resolver, classifier, libraryLication)
if err != nil {
return nil, err
}
for _, p := range pkgs {
// set the source binary as the first location
locationSet := source.NewLocationSet(location)
locationSet.Add(p.Locations.ToSlice()...)
p.Locations = locationSet
meta, _ := p.Metadata.(pkg.BinaryMetadata)
p.Metadata = pkg.BinaryMetadata{
Matches: append([]pkg.ClassifierMatch{
{
Classifier: classifier.Class,
Location: location,
},
}, meta.Matches...),
}
packages = append(packages, p)
}
}
}
return packages, nil
}
}
@@ -122,7 +176,7 @@ func mustPURL(purl string) packageurl.PackageURL {
return p
}
func singlePackage(classifier classifier, reader source.LocationReadCloser, matchMetadata map[string]string) []pkg.Package {
func singlePackage(classifier classifier, location source.Location, matchMetadata map[string]string) []pkg.Package {
version, ok := matchMetadata["version"]
if !ok {
return nil
@@ -140,7 +194,7 @@ func singlePackage(classifier classifier, reader source.LocationReadCloser, matc
p := pkg.Package{
Name: classifier.Package,
Version: version,
Locations: source.NewLocationSet(reader.Location),
Locations: source.NewLocationSet(location),
Type: pkg.BinaryPkg,
CPEs: cpes,
FoundBy: catalogerName,
@@ -149,7 +203,7 @@ func singlePackage(classifier classifier, reader source.LocationReadCloser, matc
Matches: []pkg.ClassifierMatch{
{
Classifier: classifier.Class,
Location: reader.Location,
Location: location,
},
},
},
@@ -174,8 +228,13 @@ func singlePackage(classifier classifier, reader source.LocationReadCloser, matc
return []pkg.Package{p}
}
func getContents(reader source.LocationReadCloser) ([]byte, error) {
unionReader, err := unionreader.GetUnionReader(reader.ReadCloser)
func getContents(resolver source.FileResolver, location source.Location) ([]byte, error) {
reader, err := resolver.FileContentsByLocation(location)
if err != nil {
return nil, err
}
unionReader, err := unionreader.GetUnionReader(reader)
if err != nil {
return nil, fmt.Errorf("unable to get union reader for file: %w", err)
}
@@ -195,3 +254,43 @@ func singleCPE(cpeString string) []cpe.CPE {
cpe.Must(cpeString),
}
}
// sharedLibraries returns a list of all shared libraries found within a binary, currently
// supporting: elf, macho, and windows pe
func sharedLibraries(resolver source.FileResolver, location source.Location) ([]string, error) {
contents, err := getContents(resolver, location)
if err != nil {
return nil, err
}
r := bytes.NewReader(contents)
e, _ := elf.NewFile(r)
if e != nil {
symbols, err := e.ImportedLibraries()
if err != nil {
log.Debugf("unable to read elf binary at: %s -- %s", location.RealPath, err)
}
return symbols, nil
}
m, _ := macho.NewFile(r)
if m != nil {
symbols, err := m.ImportedLibraries()
if err != nil {
log.Debugf("unable to read macho binary at: %s -- %s", location.RealPath, err)
}
return symbols, nil
}
p, _ := pe.NewFile(r)
if p != nil {
symbols, err := p.ImportedLibraries()
if err != nil {
log.Debugf("unable to read pe binary at: %s -- %s", location.RealPath, err)
}
return symbols, nil
}
return nil, nil
}
+2 -4
View File
@@ -67,10 +67,8 @@ func Test_ClassifierCPEs(t *testing.T) {
locations, err := resolver.FilesByPath(test.fixture)
require.NoError(t, err)
require.Len(t, locations, 1)
location := locations[0]
readCloser, err := resolver.FileContentsByLocation(location)
require.NoError(t, err)
pkgs, err := test.classifier.EvidenceMatcher(test.classifier, source.NewLocationReadCloser(location, readCloser))
pkgs, err := test.classifier.EvidenceMatcher(resolver, test.classifier, locations[0])
require.NoError(t, err)
require.Len(t, pkgs, 1)
@@ -9,9 +9,16 @@ var defaultClassifiers = []classifier{
{
Class: "python-binary",
FileGlob: "**/python*",
EvidenceMatcher: fileNameTemplateVersionMatcher(
`(.*/|^)python(?P<version>[0-9]+\.[0-9]+)$`,
`(?m)(?P<version>{{ .version }}\.[0-9]+[-_a-zA-Z0-9]*)`),
EvidenceMatcher: evidenceMatchers(
// try to find version information from libpython shared libraries
sharedLibraryLookup(
`^libpython[0-9]+(?:\.[0-9]+)+[a-z]?\.so.*$`,
libpythonMatcher),
// check for version information in the binary
fileNameTemplateVersionMatcher(
`(?:.*/|^)python(?P<version>[0-9]+(?:\.[0-9]+)+)$`,
pythonVersionTemplate),
),
Package: "python",
PURL: mustPURL("pkg:generic/python@version"),
CPEs: []cpe.CPE{
@@ -20,25 +27,11 @@ var defaultClassifiers = []classifier{
},
},
{
Class: "python-binary-lib",
FileGlob: "**/libpython*.so*",
EvidenceMatcher: fileNameTemplateVersionMatcher(
`(.*/|^)libpython(?P<version>[0-9]+\.[0-9]+).so.*$`,
`(?m)(?P<version>{{ .version }}\.[0-9]+[-_a-zA-Z0-9]*)`),
Package: "python",
PURL: mustPURL("pkg:generic/python@version"),
CPEs: []cpe.CPE{
cpe.Must("cpe:2.3:a:python_software_foundation:python:*:*:*:*:*:*:*:*"),
cpe.Must("cpe:2.3:a:python:python:*:*:*:*:*:*:*:*"),
},
},
{
Class: "cpython-source",
FileGlob: "**/patchlevel.h",
EvidenceMatcher: fileContentsVersionMatcher(
`(?m)#define\s+PY_VERSION\s+"?(?P<version>[0-9\.\-_a-zA-Z]+)"?`),
Package: "python",
PURL: mustPURL("pkg:generic/python@version"),
Class: "python-binary-lib",
FileGlob: "**/libpython*.so*",
EvidenceMatcher: libpythonMatcher,
Package: "python",
PURL: mustPURL("pkg:generic/python@version"),
CPEs: []cpe.CPE{
cpe.Must("cpe:2.3:a:python_software_foundation:python:*:*:*:*:*:*:*:*"),
cpe.Must("cpe:2.3:a:python:python:*:*:*:*:*:*:*:*"),
@@ -53,6 +46,42 @@ var defaultClassifiers = []classifier{
PURL: mustPURL("pkg:generic/go@version"),
CPEs: singleCPE("cpe:2.3:a:golang:go:*:*:*:*:*:*:*:*"),
},
{
Class: "argocd",
FileGlob: "**/argocd",
EvidenceMatcher: fileContentsVersionMatcher(
`(?m)common\.version=(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
Package: "argocd",
PURL: mustPURL("pkg:golang/github.com/argoproj/argo-cd@version"),
CPEs: singleCPE("cpe:2.3:a:argoproj:argocd:*:*:*:*:*:*:*"),
},
{
Class: "helm",
FileGlob: "**/helm",
EvidenceMatcher: fileContentsVersionMatcher(
`(?m)\x00v(?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00`),
Package: "helm",
PURL: mustPURL("pkg:golang/helm.sh/helm@version"),
CPEs: singleCPE("cpe:2.3:a:helm:helm:*:*:*:*:*:*:*"),
},
{
Class: "kustomize",
FileGlob: "**/kustomize",
EvidenceMatcher: fileContentsVersionMatcher(
`(?m)version=kustomize/v(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
Package: "kustomize",
PURL: mustPURL("pkg:golang/sigs.k8s.io/kustomize@version"),
CPEs: singleCPE("cpe:2.3:a:kustomize:kustomize:*:*:*:*:*:*:*"),
},
{
Class: "kubectl",
FileGlob: "**/kubectl",
EvidenceMatcher: fileContentsVersionMatcher(
`(?m)\x00v(?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00`),
Package: "kubectl",
PURL: mustPURL("pkg:golang/k8s.io/kubectl@version"),
CPEs: singleCPE("cpe:2.3:a:kubectl:kubectl:*:*:*:*:*:*:*"),
},
{
Class: "redis-binary",
FileGlob: "**/redis-server",
@@ -227,4 +256,31 @@ var defaultClassifiers = []classifier{
PURL: mustPURL("pkg:generic/rust@version"),
CPEs: singleCPE("cpe:2.3:a:rust-lang:rust:*:*:*:*:*:*:*:*"),
},
{
Class: "ruby-binary",
FileGlob: "**/ruby",
EvidenceMatcher: evidenceMatchers(
rubyMatcher,
sharedLibraryLookup(
// try to find version information from libruby shared libraries
`^libruby\.so.*$`,
rubyMatcher),
),
Package: "ruby",
PURL: mustPURL("pkg:generic/ruby@version"),
CPEs: singleCPE("cpe:2.3:a:ruby-lang:ruby:*:*:*:*:*:*:*:*"),
},
}
// in both binaries and shared libraries, the version pattern is [NUL]3.11.2[NUL]
var pythonVersionTemplate = `(?m)\x00(?P<version>{{ .version }}[-._a-zA-Z0-9]*)\x00`
var libpythonMatcher = fileNameTemplateVersionMatcher(
`(?:.*/|^)libpython(?P<version>[0-9]+(?:\.[0-9]+)+)[a-z]?\.so.*$`,
pythonVersionTemplate,
)
var rubyMatcher = fileContentsVersionMatcher(
// ruby 3.2.1 (2023-02-08 revision 31819e82c8) [x86_64-linux]
// ruby 2.7.7p221 (2022-11-24 revision 168ec2b1e5) [x86_64-linux]
`(?m)ruby (?P<version>[0-9]+\.[0-9]+\.[0-9]+(p[0-9]+)?) `)
@@ -0,0 +1 @@
classifiers/dynamic
@@ -0,0 +1,151 @@
.PHONY: all
all: \
classifiers/dynamic/python-binary-shared-lib-3.11 \
classifiers/dynamic/python-binary-shared-lib-redhat-3.9 \
classifiers/dynamic/python-binary-with-version-3.9 \
classifiers/dynamic/python-binary-3.4-alpine \
classifiers/dynamic/ruby-library-3.2.1 \
classifiers/dynamic/ruby-library-2.7.7 \
classifiers/dynamic/ruby-library-2.6.10 \
classifiers/dynamic/argocd-2.5.11 \
classifiers/dynamic/argocd-2.6.4 \
classifiers/dynamic/helm-3.11.1 \
classifiers/dynamic/helm-3.10.3 \
classifiers/dynamic/kubectl-1.24.11 \
classifiers/dynamic/kubectl-1.25.7 \
classifiers/dynamic/kubectl-1.26.2 \
classifiers/dynamic/kustomize-4.5.7 \
classifiers/dynamic/kustomize-5.0.0
classifiers/dynamic/python-binary-shared-lib-3.11:
$(eval $@_image := "python:3.11-slim@sha256:0b106e1d2bf485c2a41474bc9cd5103e9eea4e179f40f10741b53b127059221e")
./get-image-file.sh $($@_image) \
/usr/local/bin/python3.11 \
$@/python3
./get-image-file.sh $($@_image) \
/usr/local/lib/libpython3.11.so.1.0 \
$@/libpython3.11.so.1.0
classifiers/dynamic/python-binary-shared-lib-redhat-3.9:
$(eval $@_image := "registry.access.redhat.com/ubi8/python-39@sha256:f3cf958b96ce016b63e3e163e488f52e42891304dafef5a0811563f22e3cbad0")
./get-image-file.sh $($@_image) \
/usr/bin/python3.9 \
$@/python3.9
./get-image-file.sh $($@_image) \
/usr/lib64/libpython3.9.so.1.0 \
$@/libpython3.9.so.1.0
classifiers/dynamic/python-binary-with-version-3.9:
$(eval $@_image := "python:3.9.16-bullseye@sha256:93fb93c461a2e47a2176706fad1f39eaacd5dd40e19c0b018699a28c03eb2e2a")
./get-image-file.sh $($@_image) \
/usr/bin/python3.9 \
$@/python3.9
classifiers/dynamic/python-binary-3.4-alpine:
$(eval $@_image := "python:3.4-alpine@sha256:c210b660e2ea553a7afa23b41a6ed112f85dbce25cbcb567c75dfe05342a4c4b")
./get-image-file.sh $($@_image) \
/usr/local/bin/python3.4 \
$@/python3.4
./get-image-file.sh $($@_image) \
/usr/local/lib/libpython3.4m.so.1.0 \
$@/libpython3.4m.so.1.0
classifiers/dynamic/ruby-library-3.2.1:
$(eval $@_image := "ruby:3.2.1-bullseye@sha256:b4a140656b0c5d26c0a80559b228b4d343f3fdbf56682fcbe88f6db1fa9afa6b")
./get-image-file.sh $($@_image) \
/usr/local/bin/ruby \
$@/ruby
./get-image-file.sh $($@_image) \
/usr/local/lib/libruby.so.3.2.1 \
$@/libruby.so.3.2.1
./get-image-file.sh $($@_image) \
/usr/local/lib/libruby.so.3.2 \
$@/libruby.so.3.2
classifiers/dynamic/ruby-library-2.7.7:
$(eval $@_image := "ruby:2.7.7-bullseye@sha256:055191740a063f33fef1f09423e5ed8f91143aae62a3772a90910118464c5120")
./get-image-file.sh $($@_image) \
/usr/local/bin/ruby \
$@/ruby
./get-image-file.sh $($@_image) \
/usr/local/lib/libruby.so.2.7.7 \
$@/libruby.so.2.7.7
./get-image-file.sh $($@_image) \
/usr/local/lib/libruby.so.2.7 \
$@/libruby.so.2.7
classifiers/dynamic/ruby-library-2.6.10:
$(eval $@_image := "ruby:2.6.10@sha256:771a810704167e55da8a19970c5dfa6eb795dfee32547adffa29ea72703f7243")
./get-image-file.sh $($@_image) \
/usr/local/bin/ruby \
$@/ruby
./get-image-file.sh $($@_image) \
/usr/local/lib/libruby.so.2.6.10 \
$@/libruby.so.2.6.10
./get-image-file.sh $($@_image) \
/usr/local/lib/libruby.so.2.6 \
$@/libruby.so.2.6
classifiers/dynamic/argocd-2.5.11:
$(eval $@_image := "argoproj/argocd:v2.5.11@sha256:d1062935b3256ec69422843ebcb50debb54fd389436961586000c8ce6ee7f249")
./get-image-file.sh $($@_image) \
/usr/local/bin/argocd \
$@/argocd
classifiers/dynamic/argocd-2.6.4:
$(eval $@_image := "argoproj/argocd:v2.6.4@sha256:61fcbba187ff53c00696cb580edf70cada59c45cf399d8477631acf43cf522ee")
./get-image-file.sh $($@_image) \
/usr/local/bin/argocd \
$@/argocd
classifiers/dynamic/helm-3.11.1:
$(eval $@_image := "alpine/helm:3.11.1@sha256:8628e3695fb743a8b9de89626f1b7a221280c2152c0e288c2504e59b68233e8b")
./get-image-file.sh $($@_image) \
/usr/bin/helm \
$@/helm
classifiers/dynamic/helm-3.10.3:
$(eval $@_image := "argoproj/argocd:v2.6.4@sha256:61fcbba187ff53c00696cb580edf70cada59c45cf399d8477631acf43cf522ee")
./get-image-file.sh $($@_image) \
/usr/local/bin/helm \
$@/helm
classifiers/dynamic/kubectl-1.24.11:
$(eval $@_image := "bitnami/kubectl:1.24.11@sha256:79d60c5ac8a1dc84e2c39f56d8e8cc0053159b5ed88f283bdf8fbda1ee86c8bc")
./get-image-file.sh $($@_image) \
/opt/bitnami/kubectl/bin/kubectl \
$@/kubectl
classifiers/dynamic/kubectl-1.25.7:
$(eval $@_image := "bitnami/kubectl:1.25.7@sha256:d7b00dbfdc6d8890aefe40edfb6c1d4c90cbb6c978794bb51a21744edc34ba7a")
./get-image-file.sh $($@_image) \
/opt/bitnami/kubectl/bin/kubectl \
$@/kubectl
classifiers/dynamic/kubectl-1.26.2:
$(eval $@_image := "line/kubectl-kustomize:1.26.2-5.0.0@sha256:9ee3b4a9a21f0777fc1d8c64208290f818a2e68c5e9e892e931621bda089bf06")
./get-image-file.sh $($@_image) \
/usr/local/bin/kubectl \
$@/kubectl
classifiers/dynamic/kustomize-4.5.7:
$(eval $@_image := "argoproj/argocd:v2.6.4@sha256:61fcbba187ff53c00696cb580edf70cada59c45cf399d8477631acf43cf522ee")
./get-image-file.sh $($@_image) \
/usr/local/bin/kustomize \
$@/kustomize
classifiers/dynamic/kustomize-5.0.0:
$(eval $@_image := "line/kubectl-kustomize:1.26.2-5.0.0@sha256:9ee3b4a9a21f0777fc1d8c64208290f818a2e68c5e9e892e931621bda089bf06")
./get-image-file.sh $($@_image) \
/usr/local/bin/kustomize \
$@/kustomize
.PHONY: clean
clean:
rm -rf classifiers/dynamic
.PHONY: cache.fingerprint
cache.fingerprint: # for CI
$(title,Install test fixture fingerprint)
@find ./classifiers/dynamic/* -type f -exec md5sum {} + | awk '{print $1}' | sort | tee /dev/stderr | md5sum | tee cache.fingerprint >> cache.fingerprint
@@ -1,7 +0,0 @@
# note: this SHOULD match as python 3.9
some source code...
#define PY_VERSION 3.9-aZ5
more source!
@@ -0,0 +1,9 @@
super from singleton method that is defined to multiple classes is not supported; this will be fixed in 1.9.3 or later
/usr/local/lib/ruby/site_ruby/1.9.1
/usr/local/lib/ruby/site_ruby/1.9.1/x86_64-linux
/usr/local/lib/ruby/vendor_ruby/1.9.1
/usr/local/lib/ruby/vendor_ruby/1.9.1/x86_64-linux
/usr/local/lib/ruby/1.9.1
/usr/local/lib/ruby/1.9.1/x86_64-linux
ruby 1.9.3p551 (2014-11-13 revision 48407) [x86_64-linux]
1.9.3
+15
View File
@@ -0,0 +1,15 @@
#!/usr/bin/env bash
set -uxe
CTRID=$(docker create $1)
function cleanup() {
docker rm "${CTRID}"
}
trap cleanup EXIT
set +e
mkdir -p $(dirname $3)
docker cp ${CTRID}:$2 $3
+1 -1
View File
@@ -105,7 +105,7 @@ func runCataloger(cataloger pkg.Cataloger, resolver source.FileResolver) (catalo
// request.
//
//nolint:funlen
func Catalog(resolver source.FileResolver, release *linux.Release, parallelism int, catalogers ...pkg.Cataloger) (*pkg.Catalog, []artifact.Relationship, error) {
func Catalog(resolver source.FileResolver, _ *linux.Release, parallelism int, catalogers ...pkg.Cataloger) (*pkg.Catalog, []artifact.Relationship, error) {
catalog := pkg.NewCatalog()
var allRelationships []artifact.Relationship
+5 -5
View File
@@ -48,7 +48,7 @@ func ImageCatalogers(cfg Config) []pkg.Cataloger {
java.NewJavaCataloger(cfg.Java()),
java.NewNativeImageCataloger(),
apkdb.NewApkdbCataloger(),
golang.NewGoModuleBinaryCataloger(),
golang.NewGoModuleBinaryCataloger(cfg.Go()),
dotnet.NewDotnetDepsCataloger(),
portage.NewPortageCataloger(),
sbom.NewSBOMCataloger(),
@@ -72,8 +72,8 @@ func DirectoryCatalogers(cfg Config) []pkg.Cataloger {
java.NewJavaPomCataloger(),
java.NewNativeImageCataloger(),
apkdb.NewApkdbCataloger(),
golang.NewGoModuleBinaryCataloger(),
golang.NewGoModFileCataloger(),
golang.NewGoModuleBinaryCataloger(cfg.Go()),
golang.NewGoModFileCataloger(cfg.Go()),
rust.NewCargoLockCataloger(),
dart.NewPubspecLockCataloger(),
dotnet.NewDotnetDepsCataloger(),
@@ -105,8 +105,8 @@ func AllCatalogers(cfg Config) []pkg.Cataloger {
java.NewJavaPomCataloger(),
java.NewNativeImageCataloger(),
apkdb.NewApkdbCataloger(),
golang.NewGoModuleBinaryCataloger(),
golang.NewGoModFileCataloger(),
golang.NewGoModuleBinaryCataloger(cfg.Go()),
golang.NewGoModFileCataloger(cfg.Go()),
rust.NewCargoLockCataloger(),
rust.NewAuditBinaryCataloger(),
dart.NewPubspecLockCataloger(),
@@ -350,6 +350,27 @@ var defaultCandidateRemovals = buildCandidateRemovalLookup(
candidateKey{PkgName: "redis"},
candidateRemovals{VendorsToRemove: []string{"redis"}},
},
// NPM packages
{
pkg.NpmPkg,
candidateKey{PkgName: "redis"},
candidateRemovals{VendorsToRemove: []string{"redis"}},
},
{
pkg.NpmPkg,
candidateKey{PkgName: "php"},
candidateRemovals{VendorsToRemove: []string{"php"}},
},
{
pkg.NpmPkg,
candidateKey{PkgName: "delegate"},
candidateRemovals{VendorsToRemove: []string{"delegate"}},
},
{
pkg.NpmPkg,
candidateKey{PkgName: "docker"},
candidateRemovals{VendorsToRemove: []string{"docker"}},
},
})
// buildCandidateLookup is a convenience function for creating the defaultCandidateAdditions set
+7 -7
View File
@@ -93,13 +93,6 @@ func candidateVendors(p pkg.Package) []string {
}
}
// some ecosystems do not have enough metadata to determine the vendor accurately, in which case we selectively
// allow * as a candidate. Note: do NOT allow Java packages to have * vendors.
switch p.Language {
case pkg.Ruby, pkg.JavaScript:
vendors.addValue(wfn.Any)
}
switch p.MetadataType {
case pkg.RpmMetadataType:
vendors.union(candidateVendorsForRPM(p))
@@ -111,8 +104,15 @@ func candidateVendors(p pkg.Package) []string {
vendors.union(candidateVendorsForJava(p))
case pkg.ApkMetadataType:
vendors.union(candidateVendorsForAPK(p))
case pkg.NpmPackageJSONMetadataType:
vendors.union(candidateVendorsForJavascript(p))
}
// We should no longer be generating vendor candidates with these values ["" and "*"]
// (since CPEs will match any other value)
vendors.removeByValue("")
vendors.removeByValue("*")
// try swapping hyphens for underscores, vice versa, and removing separators altogether
addDelimiterVariations(vendors)
+12 -8
View File
@@ -117,15 +117,19 @@ func TestGeneratePackageCPEs(t *testing.T) {
{
name: "javascript language",
p: pkg.Package{
Name: "name",
Version: "3.2",
FoundBy: "some-analyzer",
Language: pkg.JavaScript,
Type: pkg.DebPkg,
Name: "name",
Version: "3.2",
FoundBy: "some-analyzer",
Language: pkg.JavaScript,
MetadataType: pkg.NpmPackageJSONMetadataType,
Metadata: pkg.NpmPackageJSONMetadata{
Author: "jon",
URL: "https://github.com/bob/npm-name",
},
},
expected: []string{
"cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*",
"cpe:2.3:a:*:name:3.2:*:*:*:*:*:*:*",
"cpe:2.3:a:bob:name:3.2:*:*:*:*:*:*:*",
},
},
{
@@ -142,10 +146,10 @@ func TestGeneratePackageCPEs(t *testing.T) {
"someones name",
"someones.elses.name@gmail.com",
},
Homepage: "https://github.com/tom/ruby-name",
},
},
expected: []string{
"cpe:2.3:a:*:name:3.2:*:*:*:*:*:*:*",
"cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*",
"cpe:2.3:a:ruby-lang:name:3.2:*:*:*:*:*:*:*",
"cpe:2.3:a:ruby:name:3.2:*:*:*:*:*:*:*",
@@ -154,6 +158,7 @@ func TestGeneratePackageCPEs(t *testing.T) {
"cpe:2.3:a:someones-name:name:3.2:*:*:*:*:*:*:*",
"cpe:2.3:a:someones_elses_name:name:3.2:*:*:*:*:*:*:*",
"cpe:2.3:a:someones_name:name:3.2:*:*:*:*:*:*:*",
"cpe:2.3:a:tom:name:3.2:*:*:*:*:*:*:*",
},
},
{
@@ -641,7 +646,6 @@ func TestGeneratePackageCPEs(t *testing.T) {
},
},
expected: []string{
"cpe:2.3:a:*:bundler:2.1.4:*:*:*:*:*:*:*",
"cpe:2.3:a:bundler:bundler:2.1.4:*:*:*:*:*:*:*",
"cpe:2.3:a:ruby-lang:bundler:2.1.4:*:*:*:*:*:*:*",
"cpe:2.3:a:ruby:bundler:2.1.4:*:*:*:*:*:*:*",
@@ -0,0 +1,25 @@
package cpe
import "github.com/anchore/syft/syft/pkg"
func candidateVendorsForJavascript(p pkg.Package) fieldCandidateSet {
if p.MetadataType != pkg.NpmPackageJSONMetadataType {
return nil
}
vendors := newFieldCandidateSet()
metadata, ok := p.Metadata.(pkg.NpmPackageJSONMetadata)
if !ok {
return nil
}
if metadata.URL != "" {
vendors.union(candidateVendorsFromURL(metadata.URL))
}
if metadata.Homepage != "" {
vendors.union(candidateVendorsFromURL(metadata.Homepage))
}
return vendors
}
+5
View File
@@ -17,5 +17,10 @@ func candidateVendorsForRuby(p pkg.Package) fieldCandidateSet {
disallowSubSelections: true,
})
}
if metadata.Homepage != "" {
vendors.union(candidateVendorsFromURL(metadata.Homepage))
}
return vendors
}
@@ -21,7 +21,7 @@ var (
}
vendorExtractionPatterns = []*regexp.Regexp{
regexp.MustCompile(`^https://(?:github|gitlab)\.com/(?P<vendor>[\w\-]*?)/.*$`),
regexp.MustCompile(`^(?:https|http|git)://(?:github|gitlab)\.com/(?P<vendor>[\w\-]*?)/.*$`),
}
)
@@ -52,6 +52,16 @@ func Test_candidateVendorsFromURL(t *testing.T) {
url: "https://github.com/armadillo/abcxyz-12345/a/b/c/d/e/f/g",
expected: []string{"armadillo"},
},
{
name: "github username from git://",
url: "git://github.com/abc/xyz.git",
expected: []string{"abc"},
},
{
name: "github username from http://",
url: "http://github.com/abc/xyz.git",
expected: []string{"abc"},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
+6
View File
@@ -1,11 +1,13 @@
package cataloger
import (
"github.com/anchore/syft/syft/pkg/cataloger/golang"
"github.com/anchore/syft/syft/pkg/cataloger/java"
)
type Config struct {
Search SearchConfig
Golang golang.GoCatalogerOpts
Catalogers []string
Parallelism int
}
@@ -23,3 +25,7 @@ func (c Config) Java() java.Config {
SearchIndexedArchives: c.Search.IncludeIndexedArchives,
}
}
func (c Config) Go() golang.GoCatalogerOpts {
return c.Golang
}
+1 -1
View File
@@ -151,7 +151,7 @@ func extractAllFields(reader *bufio.Reader) (map[string]interface{}, error) {
var val interface{}
key, val, err = handleNewKeyValue(line)
if err != nil {
log.Warnf("parsing dpkg status: extracting key-value from line: %s err: %v", line, err)
log.Tracef("parsing dpkg status: extracting key-value from line: %s err: %v", line, err)
continue
}
+11 -1
View File
@@ -45,7 +45,17 @@ func packageURL(m pkg.DotnetDepsMetadata) string {
var qualifiers packageurl.Qualifiers
return packageurl.NewPackageURL(
packageurl.TypeDotnet,
// This originally was packageurl.TypeDotnet, but this isn't a valid PURL type, according to:
// https://github.com/package-url/purl-spec/blob/master/PURL-TYPES.rst
// Some history:
// https://github.com/anchore/packageurl-go/pull/8 added the type to Anchore's fork
// due to this PR: https://github.com/anchore/syft/pull/951
// There were questions about "dotnet" being the right purlType at the time, but it was
// acknowledged that scanning a dotnet file does not necessarily mean the packages found
// are nuget packages and so the alternate type was added. Since this is still an invalid
// PURL type, however, we will use TypeNuget and revisit at such time there is a better
// official PURL type available.
packageurl.TypeNuget,
"",
m.Name,
m.Version,
@@ -16,7 +16,7 @@ func TestParseDotnetDeps(t *testing.T) {
{
Name: "AWSSDK.Core",
Version: "3.7.10.6",
PURL: "pkg:dotnet/AWSSDK.Core@3.7.10.6",
PURL: "pkg:nuget/AWSSDK.Core@3.7.10.6",
Locations: fixtureLocationSet,
Language: pkg.Dotnet,
Type: pkg.DotnetPkg,
@@ -32,7 +32,7 @@ func TestParseDotnetDeps(t *testing.T) {
{
Name: "Microsoft.Extensions.DependencyInjection.Abstractions",
Version: "6.0.0",
PURL: "pkg:dotnet/Microsoft.Extensions.DependencyInjection.Abstractions@6.0.0",
PURL: "pkg:nuget/Microsoft.Extensions.DependencyInjection.Abstractions@6.0.0",
Locations: fixtureLocationSet,
Language: pkg.Dotnet,
Type: pkg.DotnetPkg,
@@ -48,7 +48,7 @@ func TestParseDotnetDeps(t *testing.T) {
{
Name: "Microsoft.Extensions.DependencyInjection",
Version: "6.0.0",
PURL: "pkg:dotnet/Microsoft.Extensions.DependencyInjection@6.0.0",
PURL: "pkg:nuget/Microsoft.Extensions.DependencyInjection@6.0.0",
Locations: fixtureLocationSet,
Language: pkg.Dotnet,
Type: pkg.DotnetPkg,
@@ -64,7 +64,7 @@ func TestParseDotnetDeps(t *testing.T) {
{
Name: "Microsoft.Extensions.Logging.Abstractions",
Version: "6.0.0",
PURL: "pkg:dotnet/Microsoft.Extensions.Logging.Abstractions@6.0.0",
PURL: "pkg:nuget/Microsoft.Extensions.Logging.Abstractions@6.0.0",
Locations: fixtureLocationSet,
Language: pkg.Dotnet,
Type: pkg.DotnetPkg,
@@ -80,7 +80,7 @@ func TestParseDotnetDeps(t *testing.T) {
{
Name: "Microsoft.Extensions.Logging",
Version: "6.0.0",
PURL: "pkg:dotnet/Microsoft.Extensions.Logging@6.0.0",
PURL: "pkg:nuget/Microsoft.Extensions.Logging@6.0.0",
Locations: fixtureLocationSet,
Language: pkg.Dotnet,
Type: pkg.DotnetPkg,
@@ -97,7 +97,7 @@ func TestParseDotnetDeps(t *testing.T) {
{
Name: "Microsoft.Extensions.Options",
Version: "6.0.0",
PURL: "pkg:dotnet/Microsoft.Extensions.Options@6.0.0",
PURL: "pkg:nuget/Microsoft.Extensions.Options@6.0.0",
Locations: fixtureLocationSet,
Language: pkg.Dotnet,
Type: pkg.DotnetPkg,
@@ -113,7 +113,7 @@ func TestParseDotnetDeps(t *testing.T) {
{
Name: "Microsoft.Extensions.Primitives",
Version: "6.0.0",
PURL: "pkg:dotnet/Microsoft.Extensions.Primitives@6.0.0",
PURL: "pkg:nuget/Microsoft.Extensions.Primitives@6.0.0",
Locations: fixtureLocationSet,
Language: pkg.Dotnet,
Type: pkg.DotnetPkg,
@@ -129,7 +129,7 @@ func TestParseDotnetDeps(t *testing.T) {
{
Name: "Newtonsoft.Json",
Version: "13.0.1",
PURL: "pkg:dotnet/Newtonsoft.Json@13.0.1",
PURL: "pkg:nuget/Newtonsoft.Json@13.0.1",
Locations: fixtureLocationSet,
Language: pkg.Dotnet,
Type: pkg.DotnetPkg,
@@ -145,7 +145,7 @@ func TestParseDotnetDeps(t *testing.T) {
{
Name: "Serilog.Sinks.Console",
Version: "4.0.1",
PURL: "pkg:dotnet/Serilog.Sinks.Console@4.0.1",
PURL: "pkg:nuget/Serilog.Sinks.Console@4.0.1",
Locations: fixtureLocationSet,
Language: pkg.Dotnet,
Type: pkg.DotnetPkg,
@@ -161,7 +161,7 @@ func TestParseDotnetDeps(t *testing.T) {
{
Name: "Serilog",
Version: "2.10.0",
PURL: "pkg:dotnet/Serilog@2.10.0",
PURL: "pkg:nuget/Serilog@2.10.0",
Locations: fixtureLocationSet,
Language: pkg.Dotnet,
Type: pkg.DotnetPkg,
@@ -177,7 +177,7 @@ func TestParseDotnetDeps(t *testing.T) {
{
Name: "System.Diagnostics.DiagnosticSource",
Version: "6.0.0",
PURL: "pkg:dotnet/System.Diagnostics.DiagnosticSource@6.0.0",
PURL: "pkg:nuget/System.Diagnostics.DiagnosticSource@6.0.0",
Locations: fixtureLocationSet,
Language: pkg.Dotnet,
Type: pkg.DotnetPkg,
@@ -193,7 +193,7 @@ func TestParseDotnetDeps(t *testing.T) {
{
Name: "System.Runtime.CompilerServices.Unsafe",
Version: "6.0.0",
PURL: "pkg:dotnet/System.Runtime.CompilerServices.Unsafe@6.0.0",
PURL: "pkg:nuget/System.Runtime.CompilerServices.Unsafe@6.0.0",
Locations: fixtureLocationSet,
Language: pkg.Dotnet,
Type: pkg.DotnetPkg,
+3 -2
View File
@@ -82,11 +82,12 @@ func printError(data []byte, i int) string {
if c == '\n' {
prev = append(prev, curr.String())
curr.Reset()
if idx >= i {
break
} else {
line++
}
line++
char = 1
continue
}
+15 -4
View File
@@ -8,14 +8,25 @@ import (
"github.com/anchore/syft/syft/pkg/cataloger/generic"
)
type GoCatalogerOpts struct {
SearchLocalModCacheLicenses bool
LocalModCacheDir string
}
// NewGoModFileCataloger returns a new Go module cataloger object.
func NewGoModFileCataloger() *generic.Cataloger {
func NewGoModFileCataloger(opts GoCatalogerOpts) *generic.Cataloger {
c := goModCataloger{
licenses: newGoLicenses(opts),
}
return generic.NewCataloger("go-mod-file-cataloger").
WithParserByGlobs(parseGoModFile, "**/go.mod")
WithParserByGlobs(c.parseGoModFile, "**/go.mod")
}
// NewGoModuleBinaryCataloger returns a new Golang cataloger object.
func NewGoModuleBinaryCataloger() *generic.Cataloger {
func NewGoModuleBinaryCataloger(opts GoCatalogerOpts) *generic.Cataloger {
c := goBinaryCataloger{
licenses: newGoLicenses(opts),
}
return generic.NewCataloger("go-module-binary-cataloger").
WithParserByMimeTypes(parseGoBinary, internal.ExecutableMIMETypeSet.List()...)
WithParserByMimeTypes(c.parseGoBinary, internal.ExecutableMIMETypeSet.List()...)
}
+2 -2
View File
@@ -27,7 +27,7 @@ func Test_Mod_Cataloger_Globs(t *testing.T) {
FromDirectory(t, test.fixture).
ExpectsResolverContentQueries(test.expected).
IgnoreUnfulfilledPathResponses("src/go.sum").
TestCataloger(t, NewGoModFileCataloger())
TestCataloger(t, NewGoModFileCataloger(GoCatalogerOpts{}))
})
}
}
@@ -52,7 +52,7 @@ func Test_Binary_Cataloger_Globs(t *testing.T) {
pkgtest.NewCatalogTester().
FromDirectory(t, test.fixture).
ExpectsResolverContentQueries(test.expected).
TestCataloger(t, NewGoModuleBinaryCataloger())
TestCataloger(t, NewGoModuleBinaryCataloger(GoCatalogerOpts{}))
})
}
}
+140
View File
@@ -0,0 +1,140 @@
package golang
import (
"fmt"
"os"
"path"
"regexp"
"strings"
"github.com/mitchellh/go-homedir"
"github.com/anchore/syft/internal/licenses"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/source"
)
type goLicenses struct {
searchLocalModCacheLicenses bool
localModCacheResolver source.FileResolver
}
func newGoLicenses(opts GoCatalogerOpts) goLicenses {
return goLicenses{
searchLocalModCacheLicenses: opts.SearchLocalModCacheLicenses,
localModCacheResolver: modCacheResolver(opts.LocalModCacheDir),
}
}
func defaultGoPath() string {
goPath := os.Getenv("GOPATH")
if goPath == "" {
homeDir, err := homedir.Dir()
if err != nil {
log.Debug("unable to determine user home dir: %v", err)
} else {
goPath = path.Join(homeDir, "go")
}
}
return goPath
}
// resolver needs to be shared between mod file & binary scanners so it's only scanned once
var modCacheResolvers = map[string]source.FileResolver{}
func modCacheResolver(modCacheDir string) source.FileResolver {
if modCacheDir == "" {
goPath := defaultGoPath()
if goPath != "" {
modCacheDir = path.Join(goPath, "pkg", "mod")
}
}
if r, ok := modCacheResolvers[modCacheDir]; ok {
return r
}
var r source.FileResolver
if modCacheDir == "" {
log.Trace("unable to determine mod cache directory, skipping mod cache resolver")
r = source.NewMockResolverForPaths()
} else {
stat, err := os.Stat(modCacheDir)
if os.IsNotExist(err) || stat == nil || !stat.IsDir() {
log.Tracef("unable to open mod cache directory: %s, skipping mod cache resolver", modCacheDir)
r = source.NewMockResolverForPaths()
} else {
r = source.NewDeferredResolverFromSource(func() (source.Source, error) {
return source.NewFromDirectory(modCacheDir)
})
}
}
modCacheResolvers[modCacheDir] = r
return r
}
func (c *goLicenses) getLicenses(resolver source.FileResolver, moduleName, moduleVersion string) (licenses []string, err error) {
moduleName = processCaps(moduleName)
licenses, err = findLicenses(resolver,
fmt.Sprintf(`**/go/pkg/mod/%s@%s/*`, moduleName, moduleVersion),
)
if c.searchLocalModCacheLicenses && err == nil && len(licenses) == 0 {
// if we're running against a directory on the filesystem, it may not include the
// user's homedir / GOPATH, so we defer to using the localModCacheResolver
licenses, err = findLicenses(c.localModCacheResolver,
fmt.Sprintf(`**/%s@%s/*`, moduleName, moduleVersion),
)
}
// always return a non-nil slice
if licenses == nil {
licenses = []string{}
}
return
}
func findLicenses(resolver source.FileResolver, globMatch string) (out []string, err error) {
if resolver == nil {
return
}
locations, err := resolver.FilesByGlob(globMatch)
if err != nil {
return nil, err
}
for _, l := range locations {
fileName := path.Base(l.RealPath)
if licenses.FileNameSet.Contains(fileName) {
contents, err := resolver.FileContentsByLocation(l)
if err != nil {
return nil, err
}
parsed, err := licenses.Parse(contents)
if err != nil {
return nil, err
}
out = append(out, parsed...)
}
}
return
}
var capReplacer = regexp.MustCompile("[A-Z]")
func processCaps(s string) string {
return capReplacer.ReplaceAllStringFunc(s, func(s string) string {
return "!" + strings.ToLower(s)
})
}
@@ -0,0 +1,76 @@
package golang
import (
"os"
"path"
"testing"
"github.com/stretchr/testify/require"
"github.com/anchore/syft/syft/source"
)
func Test_LicenseSearch(t *testing.T) {
tests := []struct {
name string
version string
expected string
}{
{
name: "github.com/someorg/somename",
version: "v0.3.2",
expected: "Apache-2.0",
},
{
name: "github.com/CapORG/CapProject",
version: "v4.111.5",
expected: "MIT",
},
}
wd, err := os.Getwd()
require.NoError(t, err)
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
l := newGoLicenses(GoCatalogerOpts{
SearchLocalModCacheLicenses: true,
LocalModCacheDir: path.Join(wd, "test-fixtures", "licenses"),
})
licenses, err := l.getLicenses(source.MockResolver{}, test.name, test.version)
require.NoError(t, err)
require.Len(t, licenses, 1)
require.Equal(t, test.expected, licenses[0])
})
}
}
func Test_processCaps(t *testing.T) {
tests := []struct {
name string
expected string
}{
{
name: "CycloneDX",
expected: "!cyclone!d!x",
},
{
name: "Azure",
expected: "!azure",
},
{
name: "xkcd",
expected: "xkcd",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
got := processCaps(test.name)
require.Equal(t, test.expected, got)
})
}
}
+8 -1
View File
@@ -6,18 +6,25 @@ import (
"strings"
"github.com/anchore/packageurl-go"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/source"
)
func newGoBinaryPackage(dep *debug.Module, mainModule, goVersion, architecture string, buildSettings map[string]string, locations ...source.Location) pkg.Package {
func (c *goBinaryCataloger) newGoBinaryPackage(resolver source.FileResolver, dep *debug.Module, mainModule, goVersion, architecture string, buildSettings map[string]string, locations ...source.Location) pkg.Package {
if dep.Replace != nil {
dep = dep.Replace
}
licenses, err := c.licenses.getLicenses(resolver, dep.Path, dep.Version)
if err != nil {
log.Tracef("error getting licenses for package: %s %v", dep.Path, err)
}
p := pkg.Package{
Name: dep.Path,
Version: dep.Version,
Licenses: licenses,
PURL: packageURL(dep.Path, dep.Version),
Language: pkg.Go,
Type: pkg.GoModulePkg,
+11 -7
View File
@@ -38,8 +38,12 @@ var (
const devel = "(devel)"
type goBinaryCataloger struct {
licenses goLicenses
}
// Catalog is given an object to resolve file references and content, this function returns any discovered Packages after analyzing rpm db installation.
func parseGoBinary(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
func (c *goBinaryCataloger) parseGoBinary(resolver source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
var pkgs []pkg.Package
unionReader, err := unionreader.GetUnionReader(reader.ReadCloser)
@@ -51,14 +55,14 @@ func parseGoBinary(_ source.FileResolver, _ *generic.Environment, reader source.
internal.CloseAndLogError(reader.ReadCloser, reader.RealPath)
for i, mod := range mods {
pkgs = append(pkgs, buildGoPkgInfo(reader.Location, mod, archs[i])...)
pkgs = append(pkgs, c.buildGoPkgInfo(resolver, reader.Location, mod, archs[i])...)
}
return pkgs, nil, nil
}
func makeGoMainPackage(mod *debug.BuildInfo, arch string, location source.Location) pkg.Package {
func (c *goBinaryCataloger) makeGoMainPackage(resolver source.FileResolver, mod *debug.BuildInfo, arch string, location source.Location) pkg.Package {
gbs := getBuildSettings(mod.Settings)
main := newGoBinaryPackage(&mod.Main, mod.Main.Path, mod.GoVersion, arch, gbs, location)
main := c.newGoBinaryPackage(resolver, &mod.Main, mod.Main.Path, mod.GoVersion, arch, gbs, location)
if main.Version == devel {
if version, ok := gbs["vcs.revision"]; ok {
if timestamp, ok := gbs["vcs.time"]; ok {
@@ -185,7 +189,7 @@ func createMainModuleFromPath(path string) (mod debug.Module) {
return
}
func buildGoPkgInfo(location source.Location, mod *debug.BuildInfo, arch string) []pkg.Package {
func (c *goBinaryCataloger) buildGoPkgInfo(resolver source.FileResolver, location source.Location, mod *debug.BuildInfo, arch string) []pkg.Package {
var pkgs []pkg.Package
if mod == nil {
return pkgs
@@ -200,7 +204,7 @@ func buildGoPkgInfo(location source.Location, mod *debug.BuildInfo, arch string)
if dep == nil {
continue
}
p := newGoBinaryPackage(dep, mod.Main.Path, mod.GoVersion, arch, nil, location)
p := c.newGoBinaryPackage(resolver, dep, mod.Main.Path, mod.GoVersion, arch, nil, location)
if pkg.IsValid(&p) {
pkgs = append(pkgs, p)
}
@@ -210,7 +214,7 @@ func buildGoPkgInfo(location source.Location, mod *debug.BuildInfo, arch string)
return pkgs
}
main := makeGoMainPackage(mod, arch, location)
main := c.makeGoMainPackage(resolver, mod, arch, location)
pkgs = append(pkgs, main)
return pkgs
@@ -497,6 +497,9 @@ func TestBuildGoPkgInfo(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
for i := range test.expected {
p := &test.expected[i]
if p.Licenses == nil {
p.Licenses = []string{}
}
p.SetID()
}
location := source.Location{
@@ -505,7 +508,9 @@ func TestBuildGoPkgInfo(t *testing.T) {
FileSystemID: "layer-id",
},
}
pkgs := buildGoPkgInfo(location, test.mod, test.arch)
c := goBinaryCataloger{}
pkgs := c.buildGoPkgInfo(source.NewMockResolverForPaths(), location, test.mod, test.arch)
assert.Equal(t, test.expected, pkgs)
})
}
+19 -1
View File
@@ -16,8 +16,14 @@ import (
"github.com/anchore/syft/syft/source"
)
type goModCataloger struct {
licenses goLicenses
}
// parseGoModFile takes a go.mod and lists all packages discovered.
func parseGoModFile(resolver source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
//
//nolint:funlen
func (c *goModCataloger) parseGoModFile(resolver source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
packages := make(map[string]pkg.Package)
contents, err := io.ReadAll(reader)
@@ -36,9 +42,15 @@ func parseGoModFile(resolver source.FileResolver, _ *generic.Environment, reader
}
for _, m := range file.Require {
licenses, err := c.licenses.getLicenses(resolver, m.Mod.Path, m.Mod.Version)
if err != nil {
log.Tracef("error getting licenses for package: %s %v", m.Mod.Path, err)
}
packages[m.Mod.Path] = pkg.Package{
Name: m.Mod.Path,
Version: m.Mod.Version,
Licenses: licenses,
Locations: source.NewLocationSet(reader.Location),
PURL: packageURL(m.Mod.Path, m.Mod.Version),
Language: pkg.Go,
@@ -52,9 +64,15 @@ func parseGoModFile(resolver source.FileResolver, _ *generic.Environment, reader
// remove any old packages and replace with new ones...
for _, m := range file.Replace {
licenses, err := c.licenses.getLicenses(resolver, m.New.Path, m.New.Version)
if err != nil {
log.Tracef("error getting licenses for package: %s %v", m.New.Path, err)
}
packages[m.New.Path] = pkg.Package{
Name: m.New.Path,
Version: m.New.Version,
Licenses: licenses,
Locations: source.NewLocationSet(reader.Location),
PURL: packageURL(m.New.Path, m.New.Version),
Language: pkg.Go,
+15 -2
View File
@@ -88,10 +88,17 @@ func TestParseGoMod(t *testing.T) {
for _, test := range tests {
t.Run(test.fixture, func(t *testing.T) {
for i := range test.expected {
p := &test.expected[i]
if p.Licenses == nil {
p.Licenses = []string{}
}
}
c := goModCataloger{}
pkgtest.NewCatalogTester().
FromFile(t, test.fixture).
Expects(test.expected, nil).
TestParser(t, parseGoModFile)
TestParser(t, c.parseGoModFile)
})
}
}
@@ -147,10 +154,16 @@ func Test_GoSumHashes(t *testing.T) {
for _, test := range tests {
t.Run(test.fixture, func(t *testing.T) {
for i := range test.expected {
p := &test.expected[i]
if p.Licenses == nil {
p.Licenses = []string{}
}
}
pkgtest.NewCatalogTester().
FromDirectory(t, test.fixture).
Expects(test.expected, nil).
TestCataloger(t, NewGoModFileCataloger())
TestCataloger(t, NewGoModFileCataloger(GoCatalogerOpts{}))
})
}
}
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014 Someone Cool <someone@cool.net.com.exe>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright (c) 2009-present, Alibaba Cloud All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -104,9 +104,8 @@ func parsePackageLock(resolver source.FileResolver, _ *generic.Environment, read
if name == "" {
if pkgMeta.Name == "" {
continue
} else {
name = pkgMeta.Name
}
name = pkgMeta.Name
}
// handles alias names
+108
View File
@@ -0,0 +1,108 @@
package source
import (
"io"
"github.com/anchore/syft/internal/log"
)
func NewDeferredResolverFromSource(creator func() (Source, error)) *DeferredResolver {
return NewDeferredResolver(func() (FileResolver, error) {
s, err := creator()
if err != nil {
return nil, err
}
return s.FileResolver(SquashedScope)
})
}
func NewDeferredResolver(creator func() (FileResolver, error)) *DeferredResolver {
return &DeferredResolver{
creator: creator,
}
}
type DeferredResolver struct {
creator func() (FileResolver, error)
resolver FileResolver
}
func (d *DeferredResolver) getResolver() (FileResolver, error) {
if d.resolver == nil {
resolver, err := d.creator()
if err != nil {
return nil, err
}
d.resolver = resolver
}
return d.resolver, nil
}
func (d *DeferredResolver) FileContentsByLocation(location Location) (io.ReadCloser, error) {
r, err := d.getResolver()
if err != nil {
return nil, err
}
return r.FileContentsByLocation(location)
}
func (d *DeferredResolver) HasPath(s string) bool {
r, err := d.getResolver()
if err != nil {
log.Debug("unable to get resolver: %v", err)
return false
}
return r.HasPath(s)
}
func (d *DeferredResolver) FilesByPath(paths ...string) ([]Location, error) {
r, err := d.getResolver()
if err != nil {
return nil, err
}
return r.FilesByPath(paths...)
}
func (d *DeferredResolver) FilesByGlob(patterns ...string) ([]Location, error) {
r, err := d.getResolver()
if err != nil {
return nil, err
}
return r.FilesByGlob(patterns...)
}
func (d *DeferredResolver) FilesByMIMEType(types ...string) ([]Location, error) {
r, err := d.getResolver()
if err != nil {
return nil, err
}
return r.FilesByMIMEType(types...)
}
func (d *DeferredResolver) RelativeFileByPath(location Location, path string) *Location {
r, err := d.getResolver()
if err != nil {
return nil
}
return r.RelativeFileByPath(location, path)
}
func (d *DeferredResolver) AllLocations() <-chan Location {
r, err := d.getResolver()
if err != nil {
log.Debug("unable to get resolver: %v", err)
return nil
}
return r.AllLocations()
}
func (d *DeferredResolver) FileMetadataByLocation(location Location) (FileMetadata, error) {
r, err := d.getResolver()
if err != nil {
return FileMetadata{}, err
}
return r.FileMetadataByLocation(location)
}
var _ FileResolver = (*DeferredResolver)(nil)
+24
View File
@@ -0,0 +1,24 @@
package source
import (
"testing"
"github.com/stretchr/testify/require"
)
func Test_NewDeferredResolver(t *testing.T) {
creatorCalled := false
deferredResolver := NewDeferredResolver(func() (FileResolver, error) {
creatorCalled = true
return NewMockResolverForPaths(), nil
})
require.False(t, creatorCalled)
require.Nil(t, deferredResolver.resolver)
_, _ = deferredResolver.FilesByGlob("**/*")
require.True(t, creatorCalled)
require.NotNil(t, deferredResolver.resolver)
}
+1 -1
View File
@@ -199,7 +199,7 @@ func (r MockResolver) FilesByBasename(filenames ...string) ([]Location, error) {
return results, nil
}
func (r MockResolver) FilesByBasenameGlob(globs ...string) ([]Location, error) {
func (r MockResolver) FilesByBasenameGlob(_ ...string) ([]Location, error) {
// TODO implement me
panic("implement me")
}
+37 -26
View File
@@ -40,24 +40,23 @@ type Source struct {
// Input is an object that captures the detected user input regarding source location, scheme, and provider type.
// It acts as a struct input for some source constructors.
type Input struct {
UserInput string
Scheme Scheme
ImageSource image.Source
Location string
Platform string
Name string
autoDetectAvailableImageSources bool
UserInput string
Scheme Scheme
ImageSource image.Source
Location string
Platform string
Name string
}
// ParseInput generates a source Input that can be used as an argument to generate a new source
// from specific providers including a registry.
func ParseInput(userInput string, platform string, detectAvailableImageSources bool) (*Input, error) {
return ParseInputWithName(userInput, platform, detectAvailableImageSources, "")
func ParseInput(userInput string, platform string) (*Input, error) {
return ParseInputWithName(userInput, platform, "", "")
}
// ParseInputWithName generates a source Input that can be used as an argument to generate a new source
// from specific providers including a registry, with an explicit name.
func ParseInputWithName(userInput string, platform string, detectAvailableImageSources bool, name string) (*Input, error) {
func ParseInputWithName(userInput string, platform, name, defaultImageSource string) (*Input, error) {
fs := afero.NewOsFs()
scheme, source, location, err := DetectScheme(fs, image.DetectSource, userInput)
if err != nil {
@@ -69,12 +68,13 @@ func ParseInputWithName(userInput string, platform string, detectAvailableImageS
// only check on packages command, attest we automatically try to pull from userInput
switch scheme {
case ImageScheme, UnknownScheme:
if detectAvailableImageSources {
if imagePullSource := image.DetermineDefaultImagePullSource(userInput); imagePullSource != image.UnknownSource {
scheme = ImageScheme
source = imagePullSource
location = userInput
}
scheme = ImageScheme
location = userInput
if defaultImageSource != "" {
source = parseDefaultImageSource(defaultImageSource)
} else {
imagePullSource := image.DetermineDefaultImagePullSource(userInput)
source = imagePullSource
}
if location == "" {
location = userInput
@@ -89,16 +89,28 @@ func ParseInputWithName(userInput string, platform string, detectAvailableImageS
// collect user input for downstream consumption
return &Input{
UserInput: userInput,
Scheme: scheme,
ImageSource: source,
Location: location,
Platform: platform,
Name: name,
autoDetectAvailableImageSources: detectAvailableImageSources,
UserInput: userInput,
Scheme: scheme,
ImageSource: source,
Location: location,
Platform: platform,
Name: name,
}, nil
}
func parseDefaultImageSource(defaultImageSource string) image.Source {
switch defaultImageSource {
case "registry":
return image.OciRegistrySource
case "docker":
return image.DockerDaemonSource
case "podman":
return image.PodmanDaemonSource
default:
return image.UnknownSource
}
}
type sourceDetector func(string) (image.Source, string, error)
func NewFromRegistry(in Input, registryOptions *image.RegistryOptions, exclusions []string) (*Source, func(), error) {
@@ -203,9 +215,7 @@ func getImageWithRetryStrategy(in Input, registryOptions *image.RegistryOptions)
// We need to determine the image source again, such that this determination
// doesn't take scheme parsing into account.
if in.autoDetectAvailableImageSources {
in.ImageSource = image.DetermineDefaultImagePullSource(in.UserInput)
}
in.ImageSource = image.DetermineDefaultImagePullSource(in.UserInput)
img, err = stereoscope.GetImageFromSource(ctx, in.UserInput, in.ImageSource, opts...)
cleanup = func() {
if err := img.Cleanup(); err != nil {
@@ -389,6 +399,7 @@ func (s *Source) SetID() {
d = digest.FromString(s.Metadata.Path).String()
break
}
defer file.Close()
di, err := digest.FromReader(file)
if err != nil {
d = digest.FromString(s.Metadata.Path).String()
+3 -3
View File
@@ -52,7 +52,7 @@ func TestParseInput(t *testing.T) {
if test.errFn == nil {
test.errFn = require.NoError
}
sourceInput, err := ParseInput(test.input, test.platform, true)
sourceInput, err := ParseInput(test.input, test.platform)
test.errFn(t, err)
if test.expected != "" {
require.NotNil(t, sourceInput)
@@ -596,7 +596,7 @@ func TestDirectoryExclusions(t *testing.T) {
registryOpts := &image.RegistryOptions{}
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
sourceInput, err := ParseInput("dir:"+test.input, "", false)
sourceInput, err := ParseInput("dir:"+test.input, "")
require.NoError(t, err)
src, fn, err := New(*sourceInput, registryOpts, test.exclusions)
defer fn()
@@ -696,7 +696,7 @@ func TestImageExclusions(t *testing.T) {
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
archiveLocation := imagetest.PrepareFixtureImage(t, "docker-archive", test.input)
sourceInput, err := ParseInput(archiveLocation, "", false)
sourceInput, err := ParseInput(archiveLocation, "")
require.NoError(t, err)
src, fn, err := New(*sourceInput, registryOpts, test.exclusions)
defer fn()
+3 -3
View File
@@ -73,7 +73,7 @@ func TestSpdxValidationTooling(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
// build the validation image
test.setup(t)
dir := t.TempDir()
for _, image := range test.images {
args := append(test.syftArgs, image)
cmd, stdout, stderr := runSyft(t, test.env, args...)
@@ -84,7 +84,7 @@ func TestSpdxValidationTooling(t *testing.T) {
cwd, err := os.Getwd()
require.NoError(t, err)
f, err := os.CreateTemp(t.TempDir(), "temp")
f, err := os.CreateTemp(dir, "temp")
require.NoError(t, err)
var suffix string
@@ -95,7 +95,7 @@ func TestSpdxValidationTooling(t *testing.T) {
}
// spdx tooling only takes a file with suffix spdx
rename := path.Join(path.Dir(f.Name()), fmt.Sprintf("%s.%s", path.Base(f.Name()), suffix))
rename := path.Join(path.Dir(f.Name()), fmt.Sprintf("%s%s", path.Base(f.Name()), suffix))
err = os.Rename(f.Name(), rename)
require.NoError(t, err)
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -25,7 +25,7 @@ func BenchmarkImagePackageCatalogers(b *testing.B) {
for _, c := range cataloger.ImageCatalogers(cataloger.DefaultConfig()) {
// in case of future alteration where state is persisted, assume no dependency is safe to reuse
userInput := "docker-archive:" + tarPath
sourceInput, err := source.ParseInput(userInput, "", false)
sourceInput, err := source.ParseInput(userInput, "")
require.NoError(b, err)
theSource, cleanupSource, err := source.New(*sourceInput, nil, nil)
b.Cleanup(cleanupSource)
+2 -2
View File
@@ -16,7 +16,7 @@ func catalogFixtureImage(t *testing.T, fixtureImageName string, scope source.Sco
imagetest.GetFixtureImage(t, "docker-archive", fixtureImageName)
tarPath := imagetest.GetFixtureImageTarPath(t, fixtureImageName)
userInput := "docker-archive:" + tarPath
sourceInput, err := source.ParseInput(userInput, "", false)
sourceInput, err := source.ParseInput(userInput, "")
require.NoError(t, err)
theSource, cleanupSource, err := source.New(*sourceInput, nil, nil)
t.Cleanup(cleanupSource)
@@ -52,7 +52,7 @@ func catalogFixtureImage(t *testing.T, fixtureImageName string, scope source.Sco
func catalogDirectory(t *testing.T, dir string) (sbom.SBOM, *source.Source) {
userInput := "dir:" + dir
sourceInput, err := source.ParseInput(userInput, "", false)
sourceInput, err := source.ParseInput(userInput, "")
require.NoError(t, err)
theSource, cleanupSource, err := source.New(*sourceInput, nil, nil)
t.Cleanup(cleanupSource)