mirror of
https://github.com/anchore/syft.git
synced 2026-10-17 08:37:19 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7845381331 | ||
|
|
7464079a09 | ||
|
|
8a574c9ed9 | ||
|
|
681d250fdc | ||
|
|
2022ffa0e5 | ||
|
|
dfcc07e512 | ||
|
|
2fa238af7c | ||
|
|
63bbd1e3ed | ||
|
|
81b87dd108 | ||
|
|
f473bb75a8 | ||
|
|
539bc2afcb | ||
|
|
9fd532246a | ||
|
|
11e926ab2f | ||
|
|
168c5aed51 | ||
|
|
d02c56aa5f | ||
|
|
829a71cd92 | ||
|
|
34ace36a9e | ||
|
|
100cf1003d | ||
|
|
f11a7b5e9f | ||
|
|
434aa7fd46 | ||
|
|
5fb0423b72 | ||
|
|
7998520848 | ||
|
|
d05000ff21 | ||
|
|
f66e77e2c6 | ||
|
|
928c4a55ff | ||
|
|
1899eb50d0 | ||
|
|
b5ec4d4f08 | ||
|
|
61362c04fa | ||
|
|
e3140063d4 | ||
|
|
1d9ef34ec7 | ||
|
|
302735097e | ||
|
|
cc0a376aba | ||
|
|
b379dd9f27 | ||
|
|
a81e0c8008 |
@@ -33,7 +33,7 @@ jobs:
|
||||
OUTPUT="${OUTPUT//'%'/'%25'}" # URL encode all '%' characters
|
||||
OUTPUT="${OUTPUT//$'\n'/'%0A'}" # URL encode all '\n' characters
|
||||
OUTPUT="${OUTPUT//$'\r'/'%0D'}" # URL encode all '\r' characters
|
||||
echo "::set-output name=result::$OUTPUT"
|
||||
echo "result=$OUTPUT" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
|
||||
@@ -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() }}
|
||||
|
||||
@@ -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 }}
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
go mod tidy
|
||||
|
||||
# export the version for use with create-pull-request
|
||||
echo "::set-output name=LATEST_VERSION::$LATEST_VERSION"
|
||||
echo "LATEST_VERSION=$LATEST_VERSION" >> $GITHUB_OUTPUT
|
||||
id: latest-version
|
||||
|
||||
- uses: tibdex/github-app-token@v1
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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.16.0
|
||||
GORELEASER_VERSION := v1.16.2
|
||||
YAJSV_VERSION := v1.4.1
|
||||
COSIGN_VERSION := v1.13.1
|
||||
QUILL_VERSION := v0.2.0
|
||||
@@ -252,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)
|
||||
|
||||
@@ -45,6 +45,7 @@ For commercial support options with Syft or Grype, please [contact Anchore](http
|
||||
- Java (jar, ear, war, par, sar, native-image)
|
||||
- JavaScript (npm, yarn)
|
||||
- Jenkins Plugins (jpi, hpi)
|
||||
- Nix (outputs in /nix/store)
|
||||
- PHP (composer)
|
||||
- Python (wheel, egg, poetry, requirements.txt)
|
||||
- Red Hat (rpm)
|
||||
@@ -110,9 +111,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 +140,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 +184,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 +218,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 +398,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 +441,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 +503,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:
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 //////////////////////////////////////////////
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
@@ -31,7 +31,7 @@ require (
|
||||
github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e
|
||||
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/spdx/tools-golang v0.5.0
|
||||
github.com/spf13/afero v1.9.5
|
||||
github.com/spf13/cobra v1.6.1
|
||||
github.com/spf13/pflag v1.0.5
|
||||
@@ -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
|
||||
@@ -128,11 +129,11 @@ require (
|
||||
golang.org/x/sync v0.1.0 // indirect
|
||||
golang.org/x/sys v0.6.0 // indirect
|
||||
golang.org/x/text v0.8.0 // indirect
|
||||
golang.org/x/tools v0.6.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
|
||||
|
||||
@@ -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=
|
||||
@@ -257,12 +258,13 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
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 +295,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 +344,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 +366,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=
|
||||
@@ -515,8 +518,8 @@ github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0
|
||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spdx/gordf v0.0.0-20201111095634-7098f93598fb/go.mod h1:uKWaldnbMnjsSAXRurWqqrdyZen1R7kxl8TkmWk2OyM=
|
||||
github.com/spdx/tools-golang v0.5.0-rc1 h1:ooCSe48QatlidqEFd+nSI308tyeNTR6NJvauUj3ApX8=
|
||||
github.com/spdx/tools-golang v0.5.0-rc1/go.mod h1:LI6onw172PdO57Ob/hgnLDD4Y2PMnroeNT3wO/2WJJI=
|
||||
github.com/spdx/tools-golang v0.5.0 h1:/fqihV2Jna7fmow65dHpgKNsilgLK7ICpd2tkCnPEyY=
|
||||
github.com/spdx/tools-golang v0.5.0/go.mod h1:kkGlrSXXfHwuSzHQZJRV3aKu9ZXCq/MSf2+xyiJH1lM=
|
||||
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.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM=
|
||||
@@ -568,8 +571,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=
|
||||
@@ -909,8 +912,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.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
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=
|
||||
@@ -1066,8 +1069,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=
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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", "")
|
||||
}
|
||||
@@ -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"
|
||||
@@ -6,5 +6,5 @@ const (
|
||||
|
||||
// JSONSchemaVersion is the current schema version output by the JSON encoder
|
||||
// This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment.
|
||||
JSONSchemaVersion = "7.0.1"
|
||||
JSONSchemaVersion = "7.1.1"
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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...)
|
||||
)
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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"},
|
||||
|
||||
@@ -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, "-", "")
|
||||
}
|
||||
|
||||
+1140
-1122
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ type artifactMetadataContainer struct {
|
||||
Hackage pkg.HackageMetadata
|
||||
Java pkg.JavaMetadata
|
||||
KbPackage pkg.KbPackageMetadata
|
||||
Nix pkg.NixStoreMetadata
|
||||
NpmPackage pkg.NpmPackageJSONMetadata
|
||||
NpmPackageLock pkg.NpmPackageLockJSONMetadata
|
||||
MixLock pkg.MixLockMetadata
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+1
-2
@@ -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
|
||||
|
||||
@@ -39,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
|
||||
|
||||
@@ -45,6 +45,8 @@ func SourceInfo(p pkg.Package) string {
|
||||
answer = "acquired package info from cabal or stack manifest files"
|
||||
case pkg.HexPkg:
|
||||
answer = "acquired package info from rebar3 or mix manifest file"
|
||||
case pkg.NixPkg:
|
||||
answer = "acquired package info from nix store path"
|
||||
default:
|
||||
answer = "acquired package info from the following paths"
|
||||
}
|
||||
|
||||
@@ -199,6 +199,14 @@ func Test_SourceInfo(t *testing.T) {
|
||||
"from rebar3 or mix manifest file",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: pkg.Package{
|
||||
Type: pkg.NixPkg,
|
||||
},
|
||||
expected: []string{
|
||||
"from nix store path",
|
||||
},
|
||||
},
|
||||
}
|
||||
var pkgTypes []pkg.Type
|
||||
for _, test := range tests {
|
||||
|
||||
@@ -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"
|
||||
@@ -521,8 +523,12 @@ func toOtherLicenses(catalog *pkg.Catalog) []*spdx.OtherLicense {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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{
|
||||
|
||||
@@ -473,13 +473,13 @@ func Test_OtherLicenses(t *testing.T) {
|
||||
},
|
||||
expected: []*spdx.OtherLicense{
|
||||
{
|
||||
LicenseIdentifier: "LicenseRef-un-known",
|
||||
LicenseName: "un known",
|
||||
LicenseIdentifier: "LicenseRef-not-known--s",
|
||||
LicenseName: "not known %s",
|
||||
ExtractedText: NONE,
|
||||
},
|
||||
{
|
||||
LicenseIdentifier: "LicenseRef-not-known--s",
|
||||
LicenseName: "not known %s",
|
||||
LicenseIdentifier: "LicenseRef-un-known",
|
||||
LicenseName: "un known",
|
||||
ExtractedText: NONE,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
}
|
||||
},
|
||||
"schema": {
|
||||
"version": "6.2.0",
|
||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-6.2.0.json"
|
||||
"version": "7.1.1",
|
||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-7.1.1.json"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,7 +185,7 @@
|
||||
}
|
||||
},
|
||||
"schema": {
|
||||
"version": "6.2.0",
|
||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-6.2.0.json"
|
||||
"version": "7.1.1",
|
||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-7.1.1.json"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@
|
||||
}
|
||||
},
|
||||
"schema": {
|
||||
"version": "6.2.0",
|
||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-6.2.0.json"
|
||||
"version": "7.1.1",
|
||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-7.1.1.json"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,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",
|
||||
@@ -292,8 +400,8 @@ 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"),
|
||||
},
|
||||
@@ -347,6 +455,34 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
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",
|
||||
@@ -515,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 {
|
||||
@@ -530,9 +729,9 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
|
||||
packages, _, err := c.Catalog(resolver)
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, p := range packages {
|
||||
assertPackagesAreEqual(t, test.expected, p)
|
||||
}
|
||||
require.Len(t, packages, 1)
|
||||
|
||||
assertPackagesAreEqual(t, test.expected, packages[0])
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"io"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/anchore/packageurl-go"
|
||||
@@ -79,6 +80,11 @@ func fileNameTemplateVersionMatcher(fileNamePattern string, contentTemplate stri
|
||||
|
||||
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 {
|
||||
return nil, fmt.Errorf("unable to parse classifier template=%q : %w", contentTemplate, err)
|
||||
|
||||
@@ -12,7 +12,7 @@ var defaultClassifiers = []classifier{
|
||||
EvidenceMatcher: evidenceMatchers(
|
||||
// try to find version information from libpython shared libraries
|
||||
sharedLibraryLookup(
|
||||
`^libpython[0-9]+(?:\.[0-9]+)+\.so.*$`,
|
||||
`^libpython[0-9]+(?:\.[0-9]+)+[a-z]?\.so.*$`,
|
||||
libpythonMatcher),
|
||||
// check for version information in the binary
|
||||
fileNameTemplateVersionMatcher(
|
||||
@@ -46,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",
|
||||
@@ -220,12 +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]+)+)\.so.*$`,
|
||||
`(?:.*/|^)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]+)?) `)
|
||||
|
||||
@@ -2,7 +2,21 @@
|
||||
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-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")
|
||||
@@ -28,6 +42,105 @@ classifiers/dynamic/python-binary-with-version-3.9:
|
||||
/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
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/haskell"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/java"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/javascript"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/nix"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/php"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/portage"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/python"
|
||||
@@ -48,9 +49,10 @@ func ImageCatalogers(cfg Config) []pkg.Cataloger {
|
||||
java.NewJavaCataloger(cfg.Java()),
|
||||
java.NewNativeImageCataloger(),
|
||||
apkdb.NewApkdbCataloger(),
|
||||
golang.NewGoModuleBinaryCataloger(),
|
||||
golang.NewGoModuleBinaryCataloger(cfg.Go()),
|
||||
dotnet.NewDotnetDepsCataloger(),
|
||||
portage.NewPortageCataloger(),
|
||||
nix.NewStoreCataloger(),
|
||||
sbom.NewSBOMCataloger(),
|
||||
binary.NewCataloger(),
|
||||
}, cfg.Catalogers)
|
||||
@@ -72,8 +74,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(),
|
||||
@@ -85,6 +87,7 @@ func DirectoryCatalogers(cfg Config) []pkg.Cataloger {
|
||||
binary.NewCataloger(),
|
||||
elixir.NewMixLockCataloger(),
|
||||
erlang.NewRebarLockCataloger(),
|
||||
nix.NewStoreCataloger(),
|
||||
}, cfg.Catalogers)
|
||||
}
|
||||
|
||||
@@ -105,8 +108,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(),
|
||||
@@ -121,6 +124,7 @@ func AllCatalogers(cfg Config) []pkg.Cataloger {
|
||||
binary.NewCataloger(),
|
||||
elixir.NewMixLockCataloger(),
|
||||
erlang.NewRebarLockCataloger(),
|
||||
nix.NewStoreCataloger(),
|
||||
}, cfg.Catalogers)
|
||||
}
|
||||
|
||||
|
||||
@@ -129,7 +129,6 @@ func TestGeneratePackageCPEs(t *testing.T) {
|
||||
},
|
||||
expected: []string{
|
||||
"cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:jon:name:3.2:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:bob:name:3.2:*:*:*:*:*:*:*",
|
||||
},
|
||||
},
|
||||
|
||||
@@ -13,13 +13,6 @@ func candidateVendorsForJavascript(p pkg.Package) fieldCandidateSet {
|
||||
return nil
|
||||
}
|
||||
|
||||
if metadata.Author != "" {
|
||||
vendors.add(fieldCandidate{
|
||||
value: normalizePersonName(stripEmailSuffix(metadata.Author)),
|
||||
disallowSubSelections: true,
|
||||
})
|
||||
}
|
||||
|
||||
if metadata.URL != "" {
|
||||
vendors.union(candidateVendorsFromURL(metadata.URL))
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()...)
|
||||
}
|
||||
|
||||
@@ -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{}))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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{}))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+21
@@ -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.
|
||||
+201
@@ -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.
|
||||
@@ -28,13 +28,14 @@ func newPackageJSONPackage(u packageJSON, locations ...source.Location) pkg.Pack
|
||||
Type: pkg.NpmPkg,
|
||||
MetadataType: pkg.NpmPackageJSONMetadataType,
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Name: u.Name,
|
||||
Version: u.Version,
|
||||
Author: u.Author.AuthorString(),
|
||||
Homepage: u.Homepage,
|
||||
URL: u.Repository.URL,
|
||||
Licenses: licenses,
|
||||
Private: u.Private,
|
||||
Name: u.Name,
|
||||
Version: u.Version,
|
||||
Author: u.Author.AuthorString(),
|
||||
Homepage: u.Homepage,
|
||||
URL: u.Repository.URL,
|
||||
Licenses: licenses,
|
||||
Private: u.Private,
|
||||
Description: u.Description,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -26,12 +26,13 @@ func TestParsePackageJSON(t *testing.T) {
|
||||
Language: pkg.JavaScript,
|
||||
MetadataType: pkg.NpmPackageJSONMetadataType,
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
Author: "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)",
|
||||
Homepage: "https://docs.npmjs.com/",
|
||||
URL: "https://github.com/npm/cli",
|
||||
Licenses: []string{"Artistic-2.0"},
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
Author: "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)",
|
||||
Homepage: "https://docs.npmjs.com/",
|
||||
URL: "https://github.com/npm/cli",
|
||||
Licenses: []string{"Artistic-2.0"},
|
||||
Description: "a package manager for JavaScript",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -46,12 +47,13 @@ func TestParsePackageJSON(t *testing.T) {
|
||||
Language: pkg.JavaScript,
|
||||
MetadataType: pkg.NpmPackageJSONMetadataType,
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
Author: "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)",
|
||||
Homepage: "https://docs.npmjs.com/",
|
||||
URL: "https://github.com/npm/cli",
|
||||
Licenses: []string{"ISC"},
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
Author: "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)",
|
||||
Homepage: "https://docs.npmjs.com/",
|
||||
URL: "https://github.com/npm/cli",
|
||||
Licenses: []string{"ISC"},
|
||||
Description: "a package manager for JavaScript",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -66,12 +68,13 @@ func TestParsePackageJSON(t *testing.T) {
|
||||
Language: pkg.JavaScript,
|
||||
MetadataType: pkg.NpmPackageJSONMetadataType,
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
Author: "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)",
|
||||
Homepage: "https://docs.npmjs.com/",
|
||||
URL: "https://github.com/npm/cli",
|
||||
Licenses: []string{"MIT", "Apache-2.0"},
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
Author: "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)",
|
||||
Homepage: "https://docs.npmjs.com/",
|
||||
URL: "https://github.com/npm/cli",
|
||||
Licenses: []string{"MIT", "Apache-2.0"},
|
||||
Description: "a package manager for JavaScript",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -86,12 +89,13 @@ func TestParsePackageJSON(t *testing.T) {
|
||||
Language: pkg.JavaScript,
|
||||
MetadataType: pkg.NpmPackageJSONMetadataType,
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
Author: "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)",
|
||||
Homepage: "https://docs.npmjs.com/",
|
||||
URL: "https://github.com/npm/cli",
|
||||
Licenses: nil,
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
Author: "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)",
|
||||
Homepage: "https://docs.npmjs.com/",
|
||||
URL: "https://github.com/npm/cli",
|
||||
Licenses: nil,
|
||||
Description: "a package manager for JavaScript",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -106,12 +110,13 @@ func TestParsePackageJSON(t *testing.T) {
|
||||
Language: pkg.JavaScript,
|
||||
MetadataType: pkg.NpmPackageJSONMetadataType,
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
Author: "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)",
|
||||
Homepage: "https://docs.npmjs.com/",
|
||||
URL: "https://github.com/npm/cli",
|
||||
Licenses: []string{},
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
Author: "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)",
|
||||
Homepage: "https://docs.npmjs.com/",
|
||||
URL: "https://github.com/npm/cli",
|
||||
Licenses: []string{},
|
||||
Description: "a package manager for JavaScript",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -126,12 +131,13 @@ func TestParsePackageJSON(t *testing.T) {
|
||||
Language: pkg.JavaScript,
|
||||
MetadataType: pkg.NpmPackageJSONMetadataType,
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
Author: "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)",
|
||||
Homepage: "https://docs.npmjs.com/",
|
||||
URL: "https://github.com/npm/cli",
|
||||
Licenses: []string{"Artistic-2.0"},
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
Author: "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)",
|
||||
Homepage: "https://docs.npmjs.com/",
|
||||
URL: "https://github.com/npm/cli",
|
||||
Licenses: []string{"Artistic-2.0"},
|
||||
Description: "a package manager for JavaScript",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -146,12 +152,13 @@ func TestParsePackageJSON(t *testing.T) {
|
||||
Language: pkg.JavaScript,
|
||||
MetadataType: pkg.NpmPackageJSONMetadataType,
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Name: "function-bind",
|
||||
Version: "1.1.1",
|
||||
Author: "Raynos <raynos2@gmail.com>",
|
||||
Homepage: "https://github.com/Raynos/function-bind",
|
||||
URL: "git://github.com/Raynos/function-bind.git",
|
||||
Licenses: []string{"MIT"},
|
||||
Name: "function-bind",
|
||||
Version: "1.1.1",
|
||||
Author: "Raynos <raynos2@gmail.com>",
|
||||
Homepage: "https://github.com/Raynos/function-bind",
|
||||
URL: "git://github.com/Raynos/function-bind.git",
|
||||
Licenses: []string{"MIT"},
|
||||
Description: "Implementation of Function.prototype.bind",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -166,13 +173,14 @@ func TestParsePackageJSON(t *testing.T) {
|
||||
Language: pkg.JavaScript,
|
||||
MetadataType: pkg.NpmPackageJSONMetadataType,
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
Author: "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)",
|
||||
Homepage: "https://docs.npmjs.com/",
|
||||
URL: "https://github.com/npm/cli",
|
||||
Licenses: []string{"Artistic-2.0"},
|
||||
Private: true,
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
Author: "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)",
|
||||
Homepage: "https://docs.npmjs.com/",
|
||||
URL: "https://github.com/npm/cli",
|
||||
Licenses: []string{"Artistic-2.0"},
|
||||
Private: true,
|
||||
Description: "a package manager for JavaScript",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
package nix
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/bmatcuk/doublestar/v4"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
const (
|
||||
catalogerName = "nix-store-cataloger"
|
||||
nixStoreGlob = "**/nix/store/*"
|
||||
)
|
||||
|
||||
// StoreCataloger finds package outputs installed in the Nix store location (/nix/store/*).
|
||||
type StoreCataloger struct{}
|
||||
|
||||
func NewStoreCataloger() *StoreCataloger {
|
||||
return &StoreCataloger{}
|
||||
}
|
||||
|
||||
func (c *StoreCataloger) Name() string {
|
||||
return catalogerName
|
||||
}
|
||||
|
||||
func (c *StoreCataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
// we want to search for only directories, which isn't possible via the stereoscope API, so we need to apply the glob manually on all returned paths
|
||||
var pkgs []pkg.Package
|
||||
var filesByPath = make(map[string]*source.LocationSet)
|
||||
for location := range resolver.AllLocations() {
|
||||
matchesStorePath, err := doublestar.Match(nixStoreGlob, location.RealPath)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to match nix store path: %w", err)
|
||||
}
|
||||
|
||||
parentStorePath := findParentNixStorePath(location.RealPath)
|
||||
if parentStorePath != "" {
|
||||
if _, ok := filesByPath[parentStorePath]; !ok {
|
||||
s := source.NewLocationSet()
|
||||
filesByPath[parentStorePath] = &s
|
||||
}
|
||||
filesByPath[parentStorePath].Add(location)
|
||||
}
|
||||
|
||||
if !matchesStorePath {
|
||||
continue
|
||||
}
|
||||
|
||||
storePath := parseNixStorePath(location.RealPath)
|
||||
|
||||
if storePath == nil || !storePath.isValidPackage() {
|
||||
continue
|
||||
}
|
||||
|
||||
p := newNixStorePackage(*storePath, location)
|
||||
pkgs = append(pkgs, p)
|
||||
}
|
||||
|
||||
// add file sets to packages
|
||||
for i := range pkgs {
|
||||
p := &pkgs[i]
|
||||
locations := p.Locations.ToSlice()
|
||||
if len(locations) == 0 {
|
||||
log.WithFields("package", p.Name).Warn("nix package has no evidence locations associated")
|
||||
continue
|
||||
}
|
||||
parentStorePath := locations[0].RealPath
|
||||
files, ok := filesByPath[parentStorePath]
|
||||
if !ok {
|
||||
log.WithFields("path", parentStorePath, "nix-store-path", parentStorePath).Warn("found a nix store file for a non-existent package")
|
||||
continue
|
||||
}
|
||||
appendFiles(p, files.ToSlice()...)
|
||||
}
|
||||
|
||||
return pkgs, nil, nil
|
||||
}
|
||||
|
||||
func appendFiles(p *pkg.Package, location ...source.Location) {
|
||||
metadata, ok := p.Metadata.(pkg.NixStoreMetadata)
|
||||
if !ok {
|
||||
log.WithFields("package", p.Name).Warn("nix package metadata missing")
|
||||
return
|
||||
}
|
||||
|
||||
for _, l := range location {
|
||||
metadata.Files = append(metadata.Files, l.RealPath)
|
||||
}
|
||||
|
||||
if metadata.Files == nil {
|
||||
// note: we always have an allocated collection for output
|
||||
metadata.Files = []string{}
|
||||
}
|
||||
|
||||
p.Metadata = metadata
|
||||
p.SetID()
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package nix
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func TestCataloger_Catalog(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
fixture string
|
||||
wantPkgs []pkg.Package
|
||||
wantRel []artifact.Relationship
|
||||
}{
|
||||
{
|
||||
fixture: "test-fixtures/fixture-1",
|
||||
wantPkgs: []pkg.Package{
|
||||
{
|
||||
Name: "glibc",
|
||||
Version: "2.34-210",
|
||||
PURL: "pkg:nix/glibc@2.34-210?output=bin&outputhash=h0cnbmfcn93xm5dg2x27ixhag1cwndga",
|
||||
Locations: source.NewLocationSet(source.NewLocation("nix/store/h0cnbmfcn93xm5dg2x27ixhag1cwndga-glibc-2.34-210-bin")),
|
||||
FoundBy: catalogerName,
|
||||
Type: pkg.NixPkg,
|
||||
MetadataType: pkg.NixStoreMetadataType,
|
||||
Metadata: pkg.NixStoreMetadata{
|
||||
OutputHash: "h0cnbmfcn93xm5dg2x27ixhag1cwndga",
|
||||
Output: "bin",
|
||||
Files: []string{
|
||||
"nix/store/h0cnbmfcn93xm5dg2x27ixhag1cwndga-glibc-2.34-210-bin/lib",
|
||||
"nix/store/h0cnbmfcn93xm5dg2x27ixhag1cwndga-glibc-2.34-210-bin/lib/glibc.so",
|
||||
"nix/store/h0cnbmfcn93xm5dg2x27ixhag1cwndga-glibc-2.34-210-bin/share",
|
||||
"nix/store/h0cnbmfcn93xm5dg2x27ixhag1cwndga-glibc-2.34-210-bin/share/man",
|
||||
"nix/store/h0cnbmfcn93xm5dg2x27ixhag1cwndga-glibc-2.34-210-bin/share/man/glibc.1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.fixture, func(t *testing.T) {
|
||||
c := NewStoreCataloger()
|
||||
|
||||
pkgtest.NewCatalogTester().
|
||||
FromDirectory(t, tt.fixture).
|
||||
Expects(tt.wantPkgs, tt.wantRel).
|
||||
TestCataloger(t, c)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package nix
|
||||
|
||||
import (
|
||||
"github.com/anchore/packageurl-go"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func newNixStorePackage(storePath nixStorePath, locations ...source.Location) pkg.Package {
|
||||
p := pkg.Package{
|
||||
Name: storePath.name,
|
||||
Version: storePath.version,
|
||||
FoundBy: catalogerName,
|
||||
Locations: source.NewLocationSet(locations...),
|
||||
Type: pkg.NixPkg,
|
||||
PURL: packageURL(storePath),
|
||||
MetadataType: pkg.NixStoreMetadataType,
|
||||
Metadata: pkg.NixStoreMetadata{
|
||||
OutputHash: storePath.outputHash,
|
||||
Output: storePath.output,
|
||||
},
|
||||
}
|
||||
|
||||
p.SetID()
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func packageURL(storePath nixStorePath) string {
|
||||
var qualifiers packageurl.Qualifiers
|
||||
if storePath.output != "" {
|
||||
// since there is no nix pURL type yet, this is a guess, however, it is reasonable to assume that
|
||||
// if only a single output is installed the pURL should be able to express this.
|
||||
qualifiers = append(qualifiers,
|
||||
packageurl.Qualifier{
|
||||
Key: "output",
|
||||
Value: storePath.output,
|
||||
},
|
||||
)
|
||||
}
|
||||
if storePath.outputHash != "" {
|
||||
// it's not immediately clear if the hash found in the store path should be encoded in the pURL
|
||||
qualifiers = append(qualifiers,
|
||||
packageurl.Qualifier{
|
||||
Key: "outputhash",
|
||||
Value: storePath.outputHash,
|
||||
},
|
||||
)
|
||||
}
|
||||
pURL := packageurl.NewPackageURL(
|
||||
// TODO: nix pURL type has not been accepted yet (only proposed at this time)
|
||||
"nix",
|
||||
"",
|
||||
storePath.name,
|
||||
storePath.version,
|
||||
qualifiers,
|
||||
"")
|
||||
return pURL.ToString()
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package nix
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_packageURL(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
storePath nixStorePath
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "name + version",
|
||||
storePath: nixStorePath{
|
||||
name: "glibc",
|
||||
version: "2.34",
|
||||
},
|
||||
want: "pkg:nix/glibc@2.34",
|
||||
},
|
||||
{
|
||||
name: "hash qualifier",
|
||||
storePath: nixStorePath{
|
||||
name: "glibc",
|
||||
version: "2.34",
|
||||
outputHash: "h0cnbmfcn93xm5dg2x27ixhag1cwndga",
|
||||
},
|
||||
want: "pkg:nix/glibc@2.34?outputhash=h0cnbmfcn93xm5dg2x27ixhag1cwndga",
|
||||
},
|
||||
{
|
||||
name: "output qualifier",
|
||||
storePath: nixStorePath{
|
||||
name: "glibc",
|
||||
version: "2.34",
|
||||
outputHash: "h0cnbmfcn93xm5dg2x27ixhag1cwndga",
|
||||
output: "bin",
|
||||
},
|
||||
want: "pkg:nix/glibc@2.34?output=bin&outputhash=h0cnbmfcn93xm5dg2x27ixhag1cwndga",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equal(t, tt.want, packageURL(tt.storePath))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
package nix
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
numericPattern = regexp.MustCompile(`\d`)
|
||||
|
||||
// attempts to find the right-most example of something that appears to be a version (semver or otherwise)
|
||||
// example input: h0cnbmfcn93xm5dg2x27ixhag1cwndga-glibc-2.34-210-bin
|
||||
// example output:
|
||||
// version: "2.34-210"
|
||||
// major: "2"
|
||||
// minor: "34"
|
||||
// patch: "210"
|
||||
// (there are other capture groups, but they can be ignored)
|
||||
rightMostVersionIshPattern = regexp.MustCompile(`-(?P<version>(?P<major>[0-9][a-zA-Z0-9]*)(\.(?P<minor>[0-9][a-zA-Z0-9]*))?(\.(?P<patch>0|[1-9][a-zA-Z0-9]*)){0,3}(?:-(?P<prerelease>\d*[.a-zA-Z-][.0-9a-zA-Z-]*)*)?(?:\+(?P<metadata>[.0-9a-zA-Z-]+(?:\.[.0-9a-zA-Z-]+)*))?)`)
|
||||
|
||||
unstableVersion = regexp.MustCompile(`-(?P<version>unstable-\d{4}-\d{2}-\d{2})$`)
|
||||
)
|
||||
|
||||
// checkout the package naming conventions here: https://nixos.org/manual/nixpkgs/stable/#sec-package-naming
|
||||
|
||||
type nixStorePath struct {
|
||||
outputHash string
|
||||
name string
|
||||
version string
|
||||
output string
|
||||
}
|
||||
|
||||
func (p nixStorePath) isValidPackage() bool {
|
||||
return p.name != "" && p.version != ""
|
||||
}
|
||||
|
||||
func findParentNixStorePath(source string) string {
|
||||
source = strings.TrimRight(source, "/")
|
||||
indicator := "nix/store/"
|
||||
start := strings.Index(source, indicator)
|
||||
if start == -1 {
|
||||
return ""
|
||||
}
|
||||
|
||||
startOfHash := start + len(indicator)
|
||||
nextField := strings.Index(source[startOfHash:], "/")
|
||||
if nextField == -1 {
|
||||
return ""
|
||||
}
|
||||
startOfSubPath := startOfHash + nextField
|
||||
|
||||
return source[0:startOfSubPath]
|
||||
}
|
||||
|
||||
func parseNixStorePath(source string) *nixStorePath {
|
||||
if strings.HasSuffix(source, ".drv") {
|
||||
// ignore derivations
|
||||
return nil
|
||||
}
|
||||
|
||||
source = path.Base(source)
|
||||
|
||||
versionStartIdx, versionIsh, prerelease := findVersionIsh(source)
|
||||
if versionStartIdx == -1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
hashName := strings.TrimSuffix(source[0:versionStartIdx], "-")
|
||||
hashNameFields := strings.Split(hashName, "-")
|
||||
if len(hashNameFields) < 2 {
|
||||
return nil
|
||||
}
|
||||
hash, name := hashNameFields[0], strings.Join(hashNameFields[1:], "-")
|
||||
|
||||
prereleaseFields := strings.Split(prerelease, "-")
|
||||
lastPrereleaseField := prereleaseFields[len(prereleaseFields)-1]
|
||||
|
||||
var version = versionIsh
|
||||
var output string
|
||||
if !hasNumeric(lastPrereleaseField) {
|
||||
// this last prerelease field is probably a nix output
|
||||
version = strings.TrimSuffix(versionIsh, fmt.Sprintf("-%s", lastPrereleaseField))
|
||||
output = lastPrereleaseField
|
||||
}
|
||||
|
||||
return &nixStorePath{
|
||||
outputHash: hash,
|
||||
name: name,
|
||||
version: version,
|
||||
output: output,
|
||||
}
|
||||
}
|
||||
|
||||
func hasNumeric(s string) bool {
|
||||
return numericPattern.MatchString(s)
|
||||
}
|
||||
|
||||
func findVersionIsh(input string) (int, string, string) {
|
||||
// we want to return the index of the start of the "version" group (the first capture group).
|
||||
// note that the match indices are in the form of [start, end, start, end, ...]. Also note that the
|
||||
// capture group for version in both regexes are the same index, but if the regexes are changed
|
||||
// this code will start to fail.
|
||||
versionGroup := 1
|
||||
|
||||
match := unstableVersion.FindAllStringSubmatchIndex(input, -1)
|
||||
if len(match) > 0 && len(match[0]) > 0 {
|
||||
return match[0][versionGroup*2], input[match[0][versionGroup*2]:match[0][(versionGroup*2)+1]], ""
|
||||
}
|
||||
|
||||
match = rightMostVersionIshPattern.FindAllStringSubmatchIndex(input, -1)
|
||||
if len(match) == 0 || len(match[0]) == 0 {
|
||||
return -1, "", ""
|
||||
}
|
||||
|
||||
var version string
|
||||
versionStart, versionStop := match[0][versionGroup*2], match[0][(versionGroup*2)+1]
|
||||
if versionStart != -1 || versionStop != -1 {
|
||||
version = input[versionStart:versionStop]
|
||||
}
|
||||
|
||||
prereleaseGroup := 7
|
||||
|
||||
var prerelease string
|
||||
prereleaseStart, prereleaseStop := match[0][prereleaseGroup*2], match[0][(prereleaseGroup*2)+1]
|
||||
if prereleaseStart != -1 && prereleaseStop != -1 {
|
||||
prerelease = input[prereleaseStart:prereleaseStop]
|
||||
}
|
||||
|
||||
return versionStart,
|
||||
version,
|
||||
prerelease
|
||||
}
|
||||
@@ -0,0 +1,304 @@
|
||||
package nix
|
||||
|
||||
import (
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_findVersionIsh(t *testing.T) {
|
||||
// note: only the package version fields are tested here, the name is tested in parseNixStorePath below.
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
wantIdx int
|
||||
wantVersion string
|
||||
wantPreRelease string
|
||||
}{
|
||||
{
|
||||
name: "no version",
|
||||
input: "5q7vxm9lc4b9hifc3br4sr8dy7f2h0qa-source",
|
||||
wantIdx: -1,
|
||||
wantVersion: "",
|
||||
wantPreRelease: "",
|
||||
},
|
||||
{
|
||||
name: "semver with overbite into output",
|
||||
input: "/nix/store/h0cnbmfcn93xm5dg2x27ixhag1cwndga-glibc-2.34-210-bin",
|
||||
wantIdx: 50,
|
||||
wantVersion: "2.34-210-bin",
|
||||
wantPreRelease: "210-bin",
|
||||
},
|
||||
{
|
||||
name: "multiple versions",
|
||||
input: "5zzrvdmlkc5rh3k5862krd3wfb3pqhyf-perl5.34.1-TimeDate-2.33",
|
||||
wantIdx: 53,
|
||||
wantVersion: "2.33",
|
||||
wantPreRelease: "",
|
||||
},
|
||||
{
|
||||
name: "name ends with number",
|
||||
input: "55nswyz8335lk954y1ccx6as2jbq1z8f-libfido2-1.10.0",
|
||||
wantIdx: 42,
|
||||
wantVersion: "1.10.0",
|
||||
wantPreRelease: "",
|
||||
},
|
||||
{
|
||||
name: "major-minor only",
|
||||
input: "q8gnp7r8475p52k9gmdzsrcddw5hirbn-gdbm-1.23",
|
||||
wantIdx: 38,
|
||||
wantVersion: "1.23",
|
||||
wantPreRelease: "",
|
||||
},
|
||||
{
|
||||
name: "0-prefixed version field",
|
||||
input: "r705jm2icczpnmfccby3fzfrckfjakx3-perl5.34.1-URI-5.05",
|
||||
wantIdx: 48,
|
||||
wantVersion: "5.05",
|
||||
wantPreRelease: "",
|
||||
},
|
||||
{
|
||||
name: "prerelease with alpha prefix",
|
||||
input: "v48s6iddb518j9lc1pk3rcn3x8c2ff0j-bash-interactive-5.1-p16",
|
||||
wantIdx: 50,
|
||||
wantVersion: "5.1-p16",
|
||||
wantPreRelease: "p16",
|
||||
},
|
||||
{
|
||||
|
||||
name: "0-major version",
|
||||
input: "x2f9x5q6qrs6cssx09ylxqyg9q2isi1z-aws-c-http-0.6.15",
|
||||
wantIdx: 44,
|
||||
wantVersion: "0.6.15",
|
||||
wantPreRelease: "",
|
||||
},
|
||||
{
|
||||
|
||||
name: "several version fields",
|
||||
// note: this package version is fictitious
|
||||
input: "z24qs6f5d1mmwdp73n1jfc3swj4v2c5s-krb5-1.19.3.9.10",
|
||||
wantIdx: 38,
|
||||
wantVersion: "1.19.3.9.10",
|
||||
wantPreRelease: "",
|
||||
},
|
||||
{
|
||||
|
||||
name: "skip drv + major only version",
|
||||
input: "z0fqylhisz47krxv8fd0izm1i2qbswfr-readline63-006.drv",
|
||||
wantIdx: 44,
|
||||
wantVersion: "006",
|
||||
wantPreRelease: "",
|
||||
},
|
||||
{
|
||||
|
||||
name: "prerelease with multiple dashes",
|
||||
input: "zkgyp2vra0bgqm0dv1qi514l5fd0aksx-bash-interactive-5.1-p16-man",
|
||||
wantIdx: 50,
|
||||
wantVersion: "5.1-p16-man",
|
||||
wantPreRelease: "p16-man",
|
||||
},
|
||||
{
|
||||
|
||||
name: "date as major version",
|
||||
input: "0amf0d1dymv9gqcyhhjb9j0l8sn00c56-libedit-20210910-3.1",
|
||||
wantIdx: 41,
|
||||
wantVersion: "20210910-3.1",
|
||||
wantPreRelease: "3.1",
|
||||
},
|
||||
{
|
||||
|
||||
name: "long name",
|
||||
input: "0296qxvn30z9b2ah1g5p97k5wr9k8y78-busybox-static-x86_64-unknown-linux-musl-1.35.0",
|
||||
wantIdx: 74,
|
||||
wantVersion: "1.35.0",
|
||||
wantPreRelease: "",
|
||||
},
|
||||
{
|
||||
// this accounts for https://nixos.org/manual/nixpkgs/stable/#sec-package-naming
|
||||
// > If a package is not a release but a commit from a repository, then the version attribute must
|
||||
// > be the date of that (fetched) commit. The date must be in "unstable-YYYY-MM-DD" format.
|
||||
// example: https://github.com/NixOS/nixpkgs/blob/798e23beab9b5cba4d6f05e8b243e1d4535770f3/pkgs/servers/webdav-server-rs/default.nix#L14
|
||||
name: "unstable version",
|
||||
input: "q5dhwzcn82by5ndc7g0q83wsnn13qkqw-webdav-server-rs-unstable-2021-08-16",
|
||||
wantIdx: 50,
|
||||
wantVersion: "unstable-2021-08-16",
|
||||
wantPreRelease: "",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gotIdx, gotVersion, gotPreRelease := findVersionIsh(tt.input)
|
||||
assert.Equal(t, tt.wantIdx, gotIdx)
|
||||
assert.Equal(t, tt.wantVersion, gotVersion)
|
||||
assert.Equal(t, tt.wantPreRelease, gotPreRelease)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_parseNixStorePath(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
source string
|
||||
want *nixStorePath
|
||||
}{
|
||||
{
|
||||
source: "/nix/store/h0cnbmfcn93xm5dg2x27ixhag1cwndga-glibc-2.34-210-bin",
|
||||
want: &nixStorePath{
|
||||
outputHash: "h0cnbmfcn93xm5dg2x27ixhag1cwndga",
|
||||
name: "glibc",
|
||||
version: "2.34-210",
|
||||
output: "bin",
|
||||
},
|
||||
},
|
||||
{
|
||||
source: "/nix/store/0296qxvn30z9b2ah1g5p97k5wr9k8y78-busybox-static-x86_64-unknown-linux-musl-1.35.0",
|
||||
want: &nixStorePath{
|
||||
outputHash: "0296qxvn30z9b2ah1g5p97k5wr9k8y78",
|
||||
name: "busybox-static-x86_64-unknown-linux-musl",
|
||||
version: "1.35.0",
|
||||
},
|
||||
},
|
||||
{
|
||||
source: "/nix/store/5zzrvdmlkc5rh3k5862krd3wfb3pqhyf-perl5.34.1-TimeDate-2.33",
|
||||
want: &nixStorePath{
|
||||
outputHash: "5zzrvdmlkc5rh3k5862krd3wfb3pqhyf",
|
||||
name: "perl5.34.1-TimeDate",
|
||||
version: "2.33",
|
||||
},
|
||||
},
|
||||
{
|
||||
source: "/nix/store/q38q8ng57zwjg1h15ry5zx0lb0xyax4b-libcap-2.63-lib",
|
||||
want: &nixStorePath{
|
||||
outputHash: "q38q8ng57zwjg1h15ry5zx0lb0xyax4b",
|
||||
name: "libcap",
|
||||
version: "2.63",
|
||||
output: "lib",
|
||||
},
|
||||
},
|
||||
{
|
||||
source: "/nix/store/p0y8fbpbqr2jm5zfrdll0rgyg2lvp5g2-util-linux-minimal-2.37.4-bin",
|
||||
want: &nixStorePath{
|
||||
outputHash: "p0y8fbpbqr2jm5zfrdll0rgyg2lvp5g2",
|
||||
name: "util-linux-minimal",
|
||||
version: "2.37.4",
|
||||
output: "bin",
|
||||
},
|
||||
},
|
||||
{
|
||||
source: "/nix/store/z24qs6f5d1mmwdp73n1jfc3swj4v2c5s-krb5-1.19.3.9.10",
|
||||
want: &nixStorePath{
|
||||
outputHash: "z24qs6f5d1mmwdp73n1jfc3swj4v2c5s",
|
||||
name: "krb5",
|
||||
version: "1.19.3.9.10",
|
||||
},
|
||||
},
|
||||
{
|
||||
source: "/nix/store/zkgyp2vra0bgqm0dv1qi514l5fd0aksx-bash-interactive-5.1-p16-man",
|
||||
want: &nixStorePath{
|
||||
outputHash: "zkgyp2vra0bgqm0dv1qi514l5fd0aksx",
|
||||
name: "bash-interactive",
|
||||
version: "5.1-p16",
|
||||
output: "man",
|
||||
},
|
||||
},
|
||||
{
|
||||
source: "/nix/store/nwf2y0nc48ybim56308cr5ccvwkabcqc-openssl-1.1.1q",
|
||||
want: &nixStorePath{
|
||||
outputHash: "nwf2y0nc48ybim56308cr5ccvwkabcqc",
|
||||
name: "openssl",
|
||||
version: "1.1.1q",
|
||||
},
|
||||
},
|
||||
{
|
||||
source: "/nix/store/nwv742f1bxv6g78hy9yc6slxdbxlmqhb-kmod-29",
|
||||
want: &nixStorePath{
|
||||
outputHash: "nwv742f1bxv6g78hy9yc6slxdbxlmqhb",
|
||||
name: "kmod",
|
||||
version: "29",
|
||||
},
|
||||
},
|
||||
{
|
||||
source: "/nix/store/n83qx7m848kg51lcjchwbkmlgdaxfckf-tzdata-2022a",
|
||||
want: &nixStorePath{
|
||||
outputHash: "n83qx7m848kg51lcjchwbkmlgdaxfckf",
|
||||
name: "tzdata",
|
||||
version: "2022a",
|
||||
},
|
||||
},
|
||||
{
|
||||
source: "'/nix/store/q5dhwzcn82by5ndc7g0q83wsnn13qkqw-webdav-server-rs-unstable-2021-08-16",
|
||||
want: &nixStorePath{
|
||||
outputHash: "q5dhwzcn82by5ndc7g0q83wsnn13qkqw",
|
||||
name: "webdav-server-rs",
|
||||
version: "unstable-2021-08-16",
|
||||
},
|
||||
},
|
||||
// negative cases...
|
||||
{
|
||||
source: "'z33yk02rsr6b4rb56lgb80bnvxx6yw39-?id=21ee35dde73aec5eba35290587d479218c6dd824.drv'",
|
||||
},
|
||||
{
|
||||
source: "/nix/store/yzahni8aig6mdrvcsccgwm2515lcpi5q-git-minimal-2.36.0.drv",
|
||||
},
|
||||
{
|
||||
source: "/nix/store/z9yvxs0s3xdkp5jgmzis4g50bfq3dgvm-0018-pkg-config-derive-prefix-from-prefix.patch",
|
||||
},
|
||||
{
|
||||
source: "/nix/store/w3hl7zrmc9qvzadc0k7cp9ysxiyz88j6-base-system",
|
||||
},
|
||||
{
|
||||
source: "/nix/store/zz1lc28x25fcx6al6xwk3dk8kp7wx47y-Test-RequiresInternet-0.05.tar.gz.drv",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(path.Base(tt.source), func(t *testing.T) {
|
||||
assert.Equal(t, tt.want, parseNixStorePath(tt.source))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_parentNixStorePath(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
source string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "exact path from absolute root",
|
||||
source: "/nix/store/5zzrvdmlkc5rh3k5862krd3wfb3pqhyf-perl5.34.1-TimeDate-2.33",
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "exact path from relative root",
|
||||
source: "nix/store/5zzrvdmlkc5rh3k5862krd3wfb3pqhyf-perl5.34.1-TimeDate-2.33",
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "clean paths",
|
||||
source: "//nix/store/5zzrvdmlkc5rh3k5862krd3wfb3pqhyf-perl5.34.1-TimeDate-2.33///",
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "relative root with subdir file",
|
||||
source: "nix/store/5zzrvdmlkc5rh3k5862krd3wfb3pqhyf-perl5.34.1-TimeDate-2.33/bin/perl-timedate",
|
||||
want: "nix/store/5zzrvdmlkc5rh3k5862krd3wfb3pqhyf-perl5.34.1-TimeDate-2.33",
|
||||
},
|
||||
{
|
||||
name: "absolute root with with subdir file",
|
||||
source: "/nix/store/5zzrvdmlkc5rh3k5862krd3wfb3pqhyf-perl5.34.1-TimeDate-2.33/bin/perl-timedate",
|
||||
want: "/nix/store/5zzrvdmlkc5rh3k5862krd3wfb3pqhyf-perl5.34.1-TimeDate-2.33",
|
||||
},
|
||||
{
|
||||
name: "nexted root with with subdir file",
|
||||
source: "/somewhere/nix/store/5zzrvdmlkc5rh3k5862krd3wfb3pqhyf-perl5.34.1-TimeDate-2.33/bin/perl-timedate",
|
||||
want: "/somewhere/nix/store/5zzrvdmlkc5rh3k5862krd3wfb3pqhyf-perl5.34.1-TimeDate-2.33",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equal(t, tt.want, findParentNixStorePath(tt.source))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
# this is not a real binary, just a small text file
|
||||
!nix/store/h0cnbmfcn93xm5dg2x27ixhag1cwndga-glibc-2.34-210-bin/lib/glibc.so
|
||||
+1
@@ -0,0 +1 @@
|
||||
the binary
|
||||
+1
@@ -0,0 +1 @@
|
||||
the man pages
|
||||
@@ -2,8 +2,23 @@ package pkg
|
||||
|
||||
// GemMetadata represents all metadata parsed from the gemspec file
|
||||
type GemMetadata struct {
|
||||
Name string `mapstructure:"name" json:"name"`
|
||||
Version string `mapstructure:"version" json:"version"`
|
||||
Name string `mapstructure:"name" json:"name"`
|
||||
Version string `mapstructure:"version" json:"version"`
|
||||
// note regarding if Files can contribute to GemMetadata being able to implement FileOwner: this list is a
|
||||
// "logical" list of files, not a list of paths that can be used to find the files without additional processing.
|
||||
//
|
||||
// For example: The "bundler" gem has a file entry of:
|
||||
// "lib/bundler/vendor/uri/lib/uri/ldap.rb"
|
||||
// but the actual file is located at:
|
||||
// "/usr/local/lib/ruby/3.2.0/bundler/vendor/uri/lib/uri/ldap.rb"
|
||||
// which do not match (the "lib" prefix is missing even for relative processing).
|
||||
//
|
||||
// without additional information about:
|
||||
// - the gem installation path
|
||||
// - the ruby installation path
|
||||
// - the ruby version
|
||||
// - environment variables (e.g. GEM_HOME) that may affect the gem installation path
|
||||
// ... we can't reliably determine the full path to the file on disk, thus cannot implement FileOwner (...yet...).
|
||||
Files []string `mapstructure:"files" json:"files,omitempty"`
|
||||
Authors []string `mapstructure:"authors" json:"authors,omitempty"`
|
||||
Licenses []string `mapstructure:"licenses" json:"licenses,omitempty"`
|
||||
|
||||
@@ -9,6 +9,7 @@ type MetadataType string
|
||||
|
||||
const (
|
||||
// this is the full set of data shapes that can be represented within the pkg.Package.Metadata field
|
||||
|
||||
UnknownMetadataType MetadataType = "UnknownMetadata"
|
||||
AlpmMetadataType MetadataType = "AlpmMetadata"
|
||||
ApkMetadataType MetadataType = "ApkMetadata"
|
||||
@@ -26,6 +27,7 @@ const (
|
||||
JavaMetadataType MetadataType = "JavaMetadata"
|
||||
KbPackageMetadataType MetadataType = "KbPackageMetadata"
|
||||
MixLockMetadataType MetadataType = "MixLockMetadataType"
|
||||
NixStoreMetadataType MetadataType = "NixStoreMetadata"
|
||||
NpmPackageJSONMetadataType MetadataType = "NpmPackageJsonMetadata"
|
||||
NpmPackageLockJSONMetadataType MetadataType = "NpmPackageLockJsonMetadata"
|
||||
PhpComposerJSONMetadataType MetadataType = "PhpComposerJsonMetadata"
|
||||
@@ -54,6 +56,7 @@ var AllMetadataTypes = []MetadataType{
|
||||
JavaMetadataType,
|
||||
KbPackageMetadataType,
|
||||
MixLockMetadataType,
|
||||
NixStoreMetadataType,
|
||||
NpmPackageJSONMetadataType,
|
||||
NpmPackageLockJSONMetadataType,
|
||||
PhpComposerJSONMetadataType,
|
||||
@@ -82,6 +85,7 @@ var MetadataTypeByName = map[MetadataType]reflect.Type{
|
||||
JavaMetadataType: reflect.TypeOf(JavaMetadata{}),
|
||||
KbPackageMetadataType: reflect.TypeOf(KbPackageMetadata{}),
|
||||
MixLockMetadataType: reflect.TypeOf(MixLockMetadata{}),
|
||||
NixStoreMetadataType: reflect.TypeOf(NixStoreMetadata{}),
|
||||
NpmPackageJSONMetadataType: reflect.TypeOf(NpmPackageJSONMetadata{}),
|
||||
NpmPackageLockJSONMetadataType: reflect.TypeOf(NpmPackageLockJSONMetadata{}),
|
||||
PhpComposerJSONMetadataType: reflect.TypeOf(PhpComposerJSONMetadata{}),
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package pkg
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/scylladb/go-set/strset"
|
||||
)
|
||||
|
||||
type NixStoreMetadata struct {
|
||||
// OutputHash is the prefix of the nix store basename path
|
||||
OutputHash string `mapstructure:"outputHash" json:"outputHash"`
|
||||
|
||||
// Output allows for optionally specifying the specific nix package output this package represents (for packages that support multiple outputs).
|
||||
// Note: the default output for a package is an empty string, so will not be present in the output.
|
||||
Output string `mapstructure:"output" json:"output,omitempty"`
|
||||
|
||||
// Files is a listing a files that are under the nix/store path for this package
|
||||
Files []string `mapstructure:"files" json:"files"`
|
||||
}
|
||||
|
||||
func (m NixStoreMetadata) OwnedFiles() (result []string) {
|
||||
result = strset.New(m.Files...).List()
|
||||
sort.Strings(result)
|
||||
return
|
||||
}
|
||||
@@ -4,7 +4,6 @@ package pkg
|
||||
type NpmPackageJSONMetadata struct {
|
||||
Name string `mapstructure:"name" json:"name"`
|
||||
Version string `mapstructure:"version" json:"version"`
|
||||
Files []string `mapstructure:"files" json:"files,omitempty"`
|
||||
Author string `mapstructure:"author" json:"author"`
|
||||
Licenses []string `mapstructure:"licenses" json:"licenses"`
|
||||
Homepage string `mapstructure:"homepage" json:"homepage"`
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
package pkg
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/scylladb/go-set/strset"
|
||||
|
||||
"github.com/anchore/syft/syft/file"
|
||||
)
|
||||
|
||||
var _ FileOwner = (*PortageMetadata)(nil)
|
||||
|
||||
// PortageMetadata represents all captured data for a Package package DB entry.
|
||||
type PortageMetadata struct {
|
||||
InstalledSize int `mapstructure:"InstalledSize" json:"installedSize" cyclonedx:"installedSize"`
|
||||
@@ -15,3 +21,15 @@ type PortageFileRecord struct {
|
||||
Path string `json:"path"`
|
||||
Digest *file.Digest `json:"digest,omitempty"`
|
||||
}
|
||||
|
||||
func (m PortageMetadata) OwnedFiles() (result []string) {
|
||||
s := strset.New()
|
||||
for _, f := range m.Files {
|
||||
if f.Path != "" {
|
||||
s.Add(f.Path)
|
||||
}
|
||||
}
|
||||
result = s.List()
|
||||
sort.Strings(result)
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ const (
|
||||
JavaPkg Type = "java-archive"
|
||||
JenkinsPluginPkg Type = "jenkins-plugin"
|
||||
KbPkg Type = "msrc-kb"
|
||||
NixPkg Type = "nix"
|
||||
NpmPkg Type = "npm"
|
||||
PhpComposerPkg Type = "php-composer"
|
||||
PortagePkg Type = "portage"
|
||||
@@ -51,6 +52,7 @@ var AllPkgs = []Type{
|
||||
JavaPkg,
|
||||
JenkinsPluginPkg,
|
||||
KbPkg,
|
||||
NixPkg,
|
||||
NpmPkg,
|
||||
PhpComposerPkg,
|
||||
PortagePkg,
|
||||
@@ -92,6 +94,8 @@ func (t Type) PackageURLType() string {
|
||||
return packageurl.TypePyPi
|
||||
case PortagePkg:
|
||||
return "portage"
|
||||
case NixPkg:
|
||||
return "nix"
|
||||
case NpmPkg:
|
||||
return packageurl.TypeNPM
|
||||
case RpmPkg:
|
||||
@@ -151,6 +155,8 @@ func TypeByName(name string) Type {
|
||||
return PortagePkg
|
||||
case packageurl.TypeHex:
|
||||
return HexPkg
|
||||
case "nix":
|
||||
return NixPkg
|
||||
default:
|
||||
return UnknownPkg
|
||||
}
|
||||
|
||||
@@ -83,6 +83,10 @@ func TestTypeFromPURL(t *testing.T) {
|
||||
purl: "pkg:hex/hpax/hpax@0.1.1",
|
||||
expected: HexPkg,
|
||||
},
|
||||
{
|
||||
purl: "pkg:nix/glibc@2.34?hash=h0cnbmfcn93xm5dg2x27ixhag1cwndga",
|
||||
expected: NixPkg,
|
||||
},
|
||||
}
|
||||
|
||||
var pkgTypes []string
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user