mirror of
https://github.com/anchore/syft.git
synced 2026-10-12 22:27:22 +02:00
Compare commits
62
Commits
+6
-6
@@ -18,7 +18,7 @@ tools:
|
||||
# used to sign mac binaries at release
|
||||
- name: quill
|
||||
version:
|
||||
want: v0.5.0
|
||||
want: v0.5.1
|
||||
method: github-release
|
||||
with:
|
||||
repo: anchore/quill
|
||||
@@ -26,7 +26,7 @@ tools:
|
||||
# used for linting
|
||||
- name: golangci-lint
|
||||
version:
|
||||
want: v1.63.4
|
||||
want: v1.64.5
|
||||
method: github-release
|
||||
with:
|
||||
repo: golangci/golangci-lint
|
||||
@@ -42,7 +42,7 @@ tools:
|
||||
# used for signing the checksums file at release
|
||||
- name: cosign
|
||||
version:
|
||||
want: v2.4.1
|
||||
want: v2.4.3
|
||||
method: github-release
|
||||
with:
|
||||
repo: sigstore/cosign
|
||||
@@ -58,7 +58,7 @@ tools:
|
||||
# used to release all artifacts
|
||||
- name: goreleaser
|
||||
version:
|
||||
want: v2.5.1
|
||||
want: v2.7.0
|
||||
method: github-release
|
||||
with:
|
||||
repo: goreleaser/goreleaser
|
||||
@@ -103,7 +103,7 @@ tools:
|
||||
# used for running all local and CI tasks
|
||||
- name: task
|
||||
version:
|
||||
want: v3.40.1
|
||||
want: v3.41.0
|
||||
method: github-release
|
||||
with:
|
||||
repo: go-task/task
|
||||
@@ -111,7 +111,7 @@ tools:
|
||||
# used for triggering a release
|
||||
- name: gh
|
||||
version:
|
||||
want: v2.65.0
|
||||
want: v2.67.0
|
||||
method: github-release
|
||||
with:
|
||||
repo: cli/cli
|
||||
|
||||
@@ -5,7 +5,7 @@ inputs:
|
||||
go-version:
|
||||
description: "Go version to install"
|
||||
required: true
|
||||
default: "1.23.x"
|
||||
default: "1.24.x"
|
||||
go-dependencies:
|
||||
description: "Download go dependencies"
|
||||
required: true
|
||||
@@ -29,7 +29,7 @@ runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
# note: go mod and build is automatically cached on default with v4+
|
||||
- uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
|
||||
- uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
|
||||
if: inputs.go-version != ''
|
||||
with:
|
||||
go-version: ${{ inputs.go-version }}
|
||||
@@ -37,7 +37,7 @@ runs:
|
||||
- name: Restore tool cache
|
||||
if: inputs.tools == 'true'
|
||||
id: tool-cache
|
||||
uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2
|
||||
uses: actions/cache@0c907a75c2c80ebcb7f088228285e798b750cf8f # v4.2.1
|
||||
with:
|
||||
path: ${{ github.workspace }}/.tool
|
||||
key: ${{ inputs.cache-key-prefix }}-${{ runner.os }}-tool-${{ hashFiles('.binny.yaml') }}
|
||||
@@ -60,7 +60,7 @@ runs:
|
||||
|
||||
- name: Restore ORAS cache from github actions
|
||||
if: inputs.download-test-fixture-cache == 'true'
|
||||
uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2
|
||||
uses: actions/cache@0c907a75c2c80ebcb7f088228285e798b750cf8f # v4.2.1
|
||||
with:
|
||||
path: ${{ github.workspace }}/.tmp/oras-cache
|
||||
key: ${{ inputs.cache-key-prefix }}-oras-cache
|
||||
|
||||
@@ -19,7 +19,7 @@ updates:
|
||||
- "dependencies"
|
||||
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/.github/actions/boostrap"
|
||||
directory: "/.github/actions/bootstrap"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
open-pull-requests-limit: 10
|
||||
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
|
||||
Benchmark-Test:
|
||||
name: "Benchmark tests"
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-24.04
|
||||
# note: we want benchmarks to run on pull_request events in order to publish results to a sticky comment, and
|
||||
# we also want to run on push such that merges to main are recorded to the cache. For this reason we don't filter
|
||||
# the job by event.
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
uses: ./.github/actions/bootstrap
|
||||
|
||||
- name: Restore base benchmark result
|
||||
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 #v4.2.0
|
||||
uses: actions/cache@0c907a75c2c80ebcb7f088228285e798b750cf8f #v4.2.1
|
||||
with:
|
||||
path: test/results/benchmark-main.txt
|
||||
# use base sha for PR or new commit hash for main push in benchmark result key
|
||||
|
||||
@@ -39,13 +39,13 @@ jobs:
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a #v5.2.0
|
||||
uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 #v5.3.0
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c #v3.28.1
|
||||
uses: github/codeql-action/init@9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0 #v3.28.9
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c #v3.28.1
|
||||
uses: github/codeql-action/autobuild@9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0 #v3.28.9
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
@@ -70,4 +70,4 @@ jobs:
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c #v3.28.1
|
||||
uses: github/codeql-action/analyze@9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0 #v3.28.9
|
||||
|
||||
@@ -18,7 +18,7 @@ on:
|
||||
jobs:
|
||||
|
||||
release:
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ on:
|
||||
jobs:
|
||||
quality-gate:
|
||||
environment: release
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
|
||||
|
||||
@@ -98,7 +98,7 @@ jobs:
|
||||
|
||||
release:
|
||||
needs: [quality-gate]
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
@@ -148,7 +148,7 @@ jobs:
|
||||
# for updating brew formula in anchore/homebrew-syft
|
||||
GITHUB_BREW_TOKEN: ${{ secrets.ANCHOREOPS_GITHUB_OSS_WRITE_TOKEN }}
|
||||
|
||||
- uses: anchore/sbom-action@df80a981bc6edbc4e220a492d3cbe9f5547a6e75 #v0.17.9
|
||||
- uses: anchore/sbom-action@f325610c9f50a54015d37c8d16cb3b0e2c8f4de0 #v0.18.0
|
||||
continue-on-error: true
|
||||
with:
|
||||
file: go.mod
|
||||
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
Static-Analysis:
|
||||
# Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline
|
||||
name: "Static analysis"
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
|
||||
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
Unit-Test:
|
||||
# Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline
|
||||
name: "Unit tests"
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
|
||||
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
Integration-Test:
|
||||
# Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline
|
||||
name: "Integration tests"
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
|
||||
|
||||
@@ -63,7 +63,7 @@ jobs:
|
||||
|
||||
Build-Snapshot-Artifacts:
|
||||
name: "Build snapshot artifacts"
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
|
||||
|
||||
@@ -78,7 +78,7 @@ jobs:
|
||||
# why not use actions/upload-artifact? It is very slow (3 minutes to upload ~600MB of data, vs 10 seconds with this approach).
|
||||
# see https://github.com/actions/upload-artifact/issues/199 for more info
|
||||
- name: Upload snapshot artifacts
|
||||
uses: actions/cache/save@1bd1e32a3bdc45362d1e726936510720a7c30a57 #v4.2.0
|
||||
uses: actions/cache/save@0c907a75c2c80ebcb7f088228285e798b750cf8f #v4.2.1
|
||||
with:
|
||||
# we need to preserve the snapshot data itself as well as the task data that confirms if the
|
||||
# snapshot build is stale or not. Otherwise the downstream jobs will attempt to rebuild the snapshot
|
||||
@@ -93,7 +93,7 @@ jobs:
|
||||
# Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline
|
||||
name: "Acceptance tests (Linux)"
|
||||
needs: [Build-Snapshot-Artifacts]
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
|
||||
|
||||
@@ -104,7 +104,7 @@ jobs:
|
||||
|
||||
- name: Download snapshot build
|
||||
id: snapshot-cache
|
||||
uses: actions/cache/restore@1bd1e32a3bdc45362d1e726936510720a7c30a57 #v4.2.0
|
||||
uses: actions/cache/restore@0c907a75c2c80ebcb7f088228285e798b750cf8f #v4.2.1
|
||||
with:
|
||||
path: |
|
||||
snapshot
|
||||
@@ -139,7 +139,7 @@ jobs:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- name: Install Cosign
|
||||
uses: sigstore/cosign-installer@v3.7.0
|
||||
uses: sigstore/cosign-installer@v3.8.0
|
||||
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
|
||||
|
||||
@@ -152,7 +152,7 @@ jobs:
|
||||
|
||||
- name: Download snapshot build
|
||||
id: snapshot-cache
|
||||
uses: actions/cache/restore@1bd1e32a3bdc45362d1e726936510720a7c30a57 #v4.2.0
|
||||
uses: actions/cache/restore@0c907a75c2c80ebcb7f088228285e798b750cf8f #v4.2.1
|
||||
with:
|
||||
path: |
|
||||
snapshot
|
||||
@@ -176,7 +176,7 @@ jobs:
|
||||
# Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline
|
||||
name: "CLI tests (Linux)"
|
||||
needs: [Build-Snapshot-Artifacts]
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
|
||||
|
||||
@@ -187,7 +187,7 @@ jobs:
|
||||
|
||||
- name: Download snapshot build
|
||||
id: snapshot-cache
|
||||
uses: actions/cache/restore@1bd1e32a3bdc45362d1e726936510720a7c30a57 #v4.2.0
|
||||
uses: actions/cache/restore@0c907a75c2c80ebcb7f088228285e798b750cf8f #v4.2.1
|
||||
with:
|
||||
path: |
|
||||
snapshot
|
||||
@@ -207,7 +207,7 @@ jobs:
|
||||
Cleanup-Cache:
|
||||
name: "Cleanup snapshot cache"
|
||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
actions: write
|
||||
needs:
|
||||
|
||||
+5
-1
@@ -8,4 +8,8 @@ A cataloger contributed by Oracle Corporation that extracts packages given withi
|
||||
|
||||
## Swift Package Manager
|
||||
|
||||
A cataloger contributed by Axis Communications that catalogs packages resolved by Swift Package Manager.
|
||||
A cataloger contributed by Axis Communications that catalogs packages resolved by Swift Package Manager.
|
||||
|
||||
## Bitnami Packages
|
||||
|
||||
A cataloger contributed by Bitnami developer that catalogs packages described in Bitnami SBOMs.
|
||||
|
||||
@@ -123,6 +123,7 @@ Note that flags using the @<version> can be used for earlier versions of each sp
|
||||
### Supported Ecosystems
|
||||
|
||||
- Alpine (apk)
|
||||
- Bitnami packages
|
||||
- C (conan)
|
||||
- C++ (conan)
|
||||
- Dart (pubs)
|
||||
@@ -146,6 +147,7 @@ Note that flags using the @<version> can be used for earlier versions of each sp
|
||||
- Rust (cargo.lock)
|
||||
- Swift (cocoapods, swift-package-manager)
|
||||
- Wordpress plugins
|
||||
- Terraform providers (.terraform.lock.hcl)
|
||||
|
||||
## Documentation
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ type backgroundLineReader struct {
|
||||
func (m *Handler) handleAttestationStarted(e partybus.Event) []tea.Model {
|
||||
reader, prog, taskInfo, err := syftEventParsers.ParseAttestationStartedEvent(e)
|
||||
if err != nil {
|
||||
log.WithFields("error", err).Warn("unable to parse event")
|
||||
log.WithFields("error", err).Debug("unable to parse event")
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ func (cts catalogerTaskModel) View() string {
|
||||
func (m *Handler) handleCatalogerTaskStarted(e partybus.Event) ([]tea.Model, tea.Cmd) {
|
||||
mon, info, err := syftEventParsers.ParseCatalogerTaskStarted(e)
|
||||
if err != nil {
|
||||
log.WithFields("error", err).Warn("unable to parse event")
|
||||
log.WithFields("error", err).Debug("unable to parse event")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
func (m *Handler) handleFetchImage(e partybus.Event) []tea.Model {
|
||||
imgName, prog, err := stereoEventParsers.ParseFetchImage(e)
|
||||
if err != nil {
|
||||
log.WithFields("error", err).Warn("unable to parse event")
|
||||
log.WithFields("error", err).Debug("unable to parse event")
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
func (m *Handler) handleFileIndexingStarted(e partybus.Event) []tea.Model {
|
||||
path, prog, err := syftEventParsers.ParseFileIndexingStarted(e)
|
||||
if err != nil {
|
||||
log.WithFields("error", err).Warn("unable to parse event")
|
||||
log.WithFields("error", err).Debug("unable to parse event")
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ type containerdPullStatusFormatter struct {
|
||||
func (m *Handler) handlePullContainerdImage(e partybus.Event) []tea.Model {
|
||||
_, pullStatus, err := stereoscopeParsers.ParsePullContainerdImage(e)
|
||||
if err != nil {
|
||||
log.WithFields("error", err).Warn("unable to parse event")
|
||||
log.WithFields("error", err).Debug("unable to parse event")
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ type dockerPullStatusFormatter struct {
|
||||
func (m *Handler) handlePullDockerImage(e partybus.Event) []tea.Model {
|
||||
_, pullStatus, err := stereoscopeParsers.ParsePullDockerImage(e)
|
||||
if err != nil {
|
||||
log.WithFields("error", err).Warn("unable to parse event")
|
||||
log.WithFields("error", err).Debug("unable to parse event")
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
func (m *Handler) handleReadImage(e partybus.Event) []tea.Model {
|
||||
imgMetadata, prog, err := stereoEventParsers.ParseReadImage(e)
|
||||
if err != nil {
|
||||
log.WithFields("error", err).Warn("unable to parse event")
|
||||
log.WithFields("error", err).Debug("unable to parse event")
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
"github.com/anchore/clio"
|
||||
"github.com/anchore/stereoscope"
|
||||
ui2 "github.com/anchore/syft/cmd/syft/cli/ui"
|
||||
handler "github.com/anchore/syft/cmd/syft/cli/ui"
|
||||
"github.com/anchore/syft/cmd/syft/internal/ui"
|
||||
"github.com/anchore/syft/internal/bus"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
@@ -28,7 +28,7 @@ func AppClioSetupConfig(id clio.Identification, out io.Writer) *clio.SetupConfig
|
||||
|
||||
return clio.NewUICollection(
|
||||
ui.New(out, cfg.Log.Quiet,
|
||||
ui2.New(ui2.DefaultHandlerConfig()),
|
||||
handler.New(handler.DefaultHandlerConfig()),
|
||||
),
|
||||
noUI,
|
||||
), nil
|
||||
|
||||
@@ -3,6 +3,7 @@ package commands
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
@@ -14,7 +15,15 @@ import (
|
||||
"github.com/anchore/clio"
|
||||
"github.com/anchore/syft/internal/bus"
|
||||
"github.com/anchore/syft/internal/task"
|
||||
"github.com/anchore/syft/syft/cataloging/pkgcataloging"
|
||||
"github.com/anchore/syft/syft/cataloging"
|
||||
)
|
||||
|
||||
var (
|
||||
activelyAddedStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("10")) // hi green
|
||||
deselectedStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("8")) // dark grey
|
||||
activelyRemovedStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("9")) // high red
|
||||
defaultStyle = lipgloss.NewStyle().Underline(true)
|
||||
deselectedDefaultStyle = lipgloss.NewStyle().Inherit(deselectedStyle).Underline(true)
|
||||
)
|
||||
|
||||
type catalogerListOptions struct {
|
||||
@@ -44,8 +53,9 @@ func CatalogerList(app clio.Application) *cobra.Command {
|
||||
opts := defaultCatalogerListOptions()
|
||||
|
||||
return app.SetupCommand(&cobra.Command{
|
||||
Use: "list [OPTIONS]",
|
||||
Short: "List available catalogers",
|
||||
Use: "list [OPTIONS]",
|
||||
Short: "List available catalogers",
|
||||
PreRunE: disableUI(app, os.Stdout),
|
||||
RunE: func(_ *cobra.Command, _ []string) error {
|
||||
return runCatalogerList(opts)
|
||||
},
|
||||
@@ -53,13 +63,19 @@ func CatalogerList(app clio.Application) *cobra.Command {
|
||||
}
|
||||
|
||||
func runCatalogerList(opts *catalogerListOptions) error {
|
||||
factories := task.DefaultPackageTaskFactories()
|
||||
allTasks, err := factories.Tasks(task.DefaultCatalogingFactoryConfig())
|
||||
pkgTaskFactories := task.DefaultPackageTaskFactories()
|
||||
fileTaskFactories := task.DefaultFileTaskFactories()
|
||||
allPkgTasks, err := pkgTaskFactories.Tasks(task.DefaultCatalogingFactoryConfig())
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create cataloger tasks: %w", err)
|
||||
return fmt.Errorf("unable to create pkg cataloger tasks: %w", err)
|
||||
}
|
||||
|
||||
report, err := catalogerListReport(opts, allTasks)
|
||||
allFileTasks, err := fileTaskFactories.Tasks(task.DefaultCatalogingFactoryConfig())
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create file cataloger tasks: %w", err)
|
||||
}
|
||||
|
||||
report, err := catalogerListReport(opts, [][]task.Task{allPkgTasks, allFileTasks})
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to generate cataloger list report: %w", err)
|
||||
}
|
||||
@@ -69,9 +85,10 @@ func runCatalogerList(opts *catalogerListOptions) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func catalogerListReport(opts *catalogerListOptions, allTasks []task.Task) (string, error) {
|
||||
selectedTasks, selectionEvidence, err := task.Select(allTasks,
|
||||
pkgcataloging.NewSelectionRequest().
|
||||
func catalogerListReport(opts *catalogerListOptions, allTaskGroups [][]task.Task) (string, error) {
|
||||
selectedTaskGroups, selectionEvidence, err := task.SelectInGroups(
|
||||
allTaskGroups,
|
||||
cataloging.NewSelectionRequest().
|
||||
WithDefaults(opts.DefaultCatalogers...).
|
||||
WithExpression(opts.SelectCatalogers...),
|
||||
)
|
||||
@@ -82,12 +99,12 @@ func catalogerListReport(opts *catalogerListOptions, allTasks []task.Task) (stri
|
||||
|
||||
switch opts.Output {
|
||||
case "json":
|
||||
report, err = renderCatalogerListJSON(selectedTasks, selectionEvidence, opts.DefaultCatalogers, opts.SelectCatalogers)
|
||||
report, err = renderCatalogerListJSON(flattenTaskGroups(selectedTaskGroups), selectionEvidence, opts.DefaultCatalogers, opts.SelectCatalogers)
|
||||
case "table", "":
|
||||
if opts.ShowHidden {
|
||||
report = renderCatalogerListTable(allTasks, selectionEvidence, opts.DefaultCatalogers, opts.SelectCatalogers)
|
||||
report = renderCatalogerListTables(allTaskGroups, selectionEvidence)
|
||||
} else {
|
||||
report = renderCatalogerListTable(selectedTasks, selectionEvidence, opts.DefaultCatalogers, opts.SelectCatalogers)
|
||||
report = renderCatalogerListTables(selectedTaskGroups, selectionEvidence)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,6 +115,14 @@ func catalogerListReport(opts *catalogerListOptions, allTasks []task.Task) (stri
|
||||
return report, nil
|
||||
}
|
||||
|
||||
func flattenTaskGroups(taskGroups [][]task.Task) []task.Task {
|
||||
var allTasks []task.Task
|
||||
for _, tasks := range taskGroups {
|
||||
allTasks = append(allTasks, tasks...)
|
||||
}
|
||||
return allTasks
|
||||
}
|
||||
|
||||
func renderCatalogerListJSON(tasks []task.Task, selection task.Selection, defaultSelections, selections []string) (string, error) {
|
||||
type node struct {
|
||||
Name string `json:"name"`
|
||||
@@ -109,7 +134,12 @@ func renderCatalogerListJSON(tasks []task.Task, selection task.Selection, defaul
|
||||
nodesByName := make(map[string]node)
|
||||
|
||||
for name := range tagsByName {
|
||||
tagsSelected := selection.TokensByTask[name].SelectedOn.List()
|
||||
tokensByTask, ok := selection.TokensByTask[name]
|
||||
|
||||
var tagsSelected []string
|
||||
if ok {
|
||||
tagsSelected = tokensByTask.SelectedOn.List()
|
||||
}
|
||||
|
||||
if len(tagsSelected) == 1 && tagsSelected[0] == "all" {
|
||||
tagsSelected = tagsByName[name]
|
||||
@@ -153,10 +183,56 @@ func renderCatalogerListJSON(tasks []task.Task, selection task.Selection, defaul
|
||||
return string(by), err
|
||||
}
|
||||
|
||||
func renderCatalogerListTable(tasks []task.Task, selection task.Selection, defaultSelections, selections []string) string {
|
||||
func renderCatalogerListTables(taskGroups [][]task.Task, selection task.Selection) string {
|
||||
pkgCatalogerTable := renderCatalogerListTable(taskGroups[0], selection, "Package Cataloger")
|
||||
fileCatalogerTable := renderCatalogerListTable(taskGroups[1], selection, "File Cataloger")
|
||||
|
||||
report := fileCatalogerTable + "\n" + pkgCatalogerTable + "\n"
|
||||
|
||||
hasAdditions := len(selection.Request.AddNames) > 0
|
||||
hasDefaults := len(selection.Request.DefaultNamesOrTags) > 0
|
||||
hasRemovals := len(selection.Request.RemoveNamesOrTags) > 0
|
||||
hasSubSelections := len(selection.Request.SubSelectTags) > 0
|
||||
expressions := len(selection.Request.SubSelectTags) + len(selection.Request.AddNames) + len(selection.Request.RemoveNamesOrTags)
|
||||
|
||||
var header string
|
||||
|
||||
header += fmt.Sprintf("Default selections: %d\n", len(selection.Request.DefaultNamesOrTags))
|
||||
if hasDefaults {
|
||||
for _, expr := range selection.Request.DefaultNamesOrTags {
|
||||
header += fmt.Sprintf(" • '%s'\n", expr)
|
||||
}
|
||||
}
|
||||
|
||||
header += fmt.Sprintf("Selection expressions: %d\n", expressions)
|
||||
|
||||
if hasSubSelections {
|
||||
for _, n := range selection.Request.SubSelectTags {
|
||||
header += fmt.Sprintf(" • '%s' (intersect)\n", n)
|
||||
}
|
||||
}
|
||||
if hasRemovals {
|
||||
for _, n := range selection.Request.RemoveNamesOrTags {
|
||||
header += fmt.Sprintf(" • '-%s' (remove)\n", n)
|
||||
}
|
||||
}
|
||||
if hasAdditions {
|
||||
for _, n := range selection.Request.AddNames {
|
||||
header += fmt.Sprintf(" • '+%s' (add)\n", n)
|
||||
}
|
||||
}
|
||||
|
||||
return header + report
|
||||
}
|
||||
|
||||
func renderCatalogerListTable(tasks []task.Task, selection task.Selection, kindTitle string) string {
|
||||
if len(tasks) == 0 {
|
||||
return activelyRemovedStyle.Render(fmt.Sprintf("No %ss selected", strings.ToLower(kindTitle)))
|
||||
}
|
||||
|
||||
t := table.NewWriter()
|
||||
t.SetStyle(table.StyleLight)
|
||||
t.AppendHeader(table.Row{"Cataloger", "Tags"})
|
||||
t.AppendHeader(table.Row{kindTitle, "Tags"})
|
||||
|
||||
names, tagsByName := extractTaskInfo(tasks)
|
||||
|
||||
@@ -172,27 +248,12 @@ func renderCatalogerListTable(tasks []task.Task, selection task.Selection, defau
|
||||
|
||||
report := t.Render()
|
||||
|
||||
if len(selections) > 0 {
|
||||
header := "Selected by expressions:\n"
|
||||
for _, expr := range selections {
|
||||
header += fmt.Sprintf(" - %q\n", expr)
|
||||
}
|
||||
report = header + report
|
||||
}
|
||||
|
||||
if len(defaultSelections) > 0 {
|
||||
header := "Default selections:\n"
|
||||
for _, expr := range defaultSelections {
|
||||
header += fmt.Sprintf(" - %q\n", expr)
|
||||
}
|
||||
report = header + report
|
||||
}
|
||||
|
||||
return report
|
||||
}
|
||||
|
||||
func formatRow(name string, tags []string, selection task.Selection) table.Row {
|
||||
isIncluded := selection.Result.Has(name)
|
||||
defaults := strset.New(selection.Request.DefaultNamesOrTags...)
|
||||
var selections *task.TokenSelection
|
||||
if s, exists := selection.TokensByTask[name]; exists {
|
||||
selections = &s
|
||||
@@ -200,35 +261,32 @@ func formatRow(name string, tags []string, selection task.Selection) table.Row {
|
||||
|
||||
var formattedTags []string
|
||||
for _, tag := range tags {
|
||||
formattedTags = append(formattedTags, formatToken(tag, selections, isIncluded))
|
||||
formattedTags = append(formattedTags, formatToken(tag, selections, isIncluded, defaults))
|
||||
}
|
||||
|
||||
var tagStr string
|
||||
if isIncluded {
|
||||
tagStr = strings.Join(formattedTags, ", ")
|
||||
} else {
|
||||
tagStr = strings.Join(formattedTags, grey.Render(", "))
|
||||
tagStr = strings.Join(formattedTags, deselectedStyle.Render(", "))
|
||||
}
|
||||
|
||||
// TODO: selection should keep warnings (non-selections) in struct
|
||||
|
||||
return table.Row{
|
||||
formatToken(name, selections, isIncluded),
|
||||
formatToken(name, selections, isIncluded, defaults),
|
||||
tagStr,
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
green = lipgloss.NewStyle().Foreground(lipgloss.Color("10")) // hi green
|
||||
grey = lipgloss.NewStyle().Foreground(lipgloss.Color("8")) // dark grey
|
||||
red = lipgloss.NewStyle().Foreground(lipgloss.Color("9")) // high red
|
||||
)
|
||||
|
||||
func formatToken(token string, selection *task.TokenSelection, included bool) string {
|
||||
func formatToken(token string, selection *task.TokenSelection, included bool, defaults *strset.Set) string {
|
||||
if included && selection != nil {
|
||||
// format all tokens in selection in green
|
||||
if selection.SelectedOn.Has(token) {
|
||||
return green.Render(token)
|
||||
if defaults.Has(token) {
|
||||
return defaultStyle.Render(token)
|
||||
}
|
||||
return activelyAddedStyle.Render(token)
|
||||
}
|
||||
|
||||
return token
|
||||
@@ -236,10 +294,12 @@ func formatToken(token string, selection *task.TokenSelection, included bool) st
|
||||
|
||||
// format all tokens in selection in red, all others in grey
|
||||
if selection != nil && selection.DeselectedOn.Has(token) {
|
||||
return red.Render(token)
|
||||
return activelyRemovedStyle.Render(token)
|
||||
}
|
||||
|
||||
return grey.Render(token)
|
||||
if defaults.Has(token) {
|
||||
return deselectedDefaultStyle.Render(token)
|
||||
}
|
||||
return deselectedStyle.Render(token)
|
||||
}
|
||||
|
||||
func extractTaskInfo(tasks []task.Task) ([]string, map[string][]string) {
|
||||
|
||||
@@ -40,23 +40,35 @@ func (d dummyTask) Execute(_ context.Context, _ file.Resolver, _ sbomsync.Builde
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func testTasks() []task.Task {
|
||||
return []task.Task{
|
||||
dummyTask{
|
||||
name: "task1",
|
||||
selectors: []string{"image", "a", "b", "1"},
|
||||
func testTasks() [][]task.Task {
|
||||
return [][]task.Task{
|
||||
{
|
||||
dummyTask{
|
||||
name: "task1",
|
||||
selectors: []string{"image", "a", "b", "1"},
|
||||
},
|
||||
dummyTask{
|
||||
name: "task2",
|
||||
selectors: []string{"image", "b", "c", "2"},
|
||||
},
|
||||
dummyTask{
|
||||
name: "task3",
|
||||
selectors: []string{"directory", "c", "d", "3"},
|
||||
},
|
||||
dummyTask{
|
||||
name: "task4",
|
||||
selectors: []string{"directory", "d", "e", "4"},
|
||||
},
|
||||
},
|
||||
dummyTask{
|
||||
name: "task2",
|
||||
selectors: []string{"image", "b", "c", "2"},
|
||||
},
|
||||
dummyTask{
|
||||
name: "task3",
|
||||
selectors: []string{"directory", "c", "d", "3"},
|
||||
},
|
||||
dummyTask{
|
||||
name: "task4",
|
||||
selectors: []string{"directory", "d", "e", "4"},
|
||||
{
|
||||
dummyTask{
|
||||
name: "file-task1",
|
||||
selectors: []string{"file", "ft", "ft-1-b"},
|
||||
},
|
||||
dummyTask{
|
||||
name: "file-task2",
|
||||
selectors: []string{"file", "ft", "ft-2-b"},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -76,16 +88,23 @@ func Test_catalogerListReport(t *testing.T) {
|
||||
return c
|
||||
}(),
|
||||
want: `
|
||||
Default selections:
|
||||
- "all"
|
||||
┌───────────┬────────────────────┐
|
||||
│ CATALOGER │ TAGS │
|
||||
├───────────┼────────────────────┤
|
||||
│ task1 │ 1, a, b, image │
|
||||
│ task2 │ 2, b, c, image │
|
||||
│ task3 │ 3, c, d, directory │
|
||||
│ task4 │ 4, d, directory, e │
|
||||
└───────────┴────────────────────┘
|
||||
Default selections: 1
|
||||
• 'all'
|
||||
Selection expressions: 0
|
||||
┌────────────────┬──────────────────┐
|
||||
│ FILE CATALOGER │ TAGS │
|
||||
├────────────────┼──────────────────┤
|
||||
│ file-task1 │ file, ft, ft-1-b │
|
||||
│ file-task2 │ file, ft, ft-2-b │
|
||||
└────────────────┴──────────────────┘
|
||||
┌───────────────────┬────────────────────┐
|
||||
│ PACKAGE CATALOGER │ TAGS │
|
||||
├───────────────────┼────────────────────┤
|
||||
│ task1 │ 1, a, b, image │
|
||||
│ task2 │ 2, b, c, image │
|
||||
│ task3 │ 3, c, d, directory │
|
||||
│ task4 │ 4, d, directory, e │
|
||||
└───────────────────┴────────────────────┘
|
||||
`,
|
||||
},
|
||||
{
|
||||
@@ -96,7 +115,7 @@ Default selections:
|
||||
return c
|
||||
}(),
|
||||
want: `
|
||||
{"default":["all"],"selection":[],"catalogers":[{"name":"task1","tags":["1","a","b","image"]},{"name":"task2","tags":["2","b","c","image"]},{"name":"task3","tags":["3","c","d","directory"]},{"name":"task4","tags":["4","d","directory","e"]}]}
|
||||
{"default":["all"],"selection":[],"catalogers":[{"name":"file-task1","tags":["file","ft","ft-1-b"]},{"name":"file-task2","tags":["file","ft","ft-2-b"]},{"name":"task1","tags":["1","a","b","image"]},{"name":"task2","tags":["2","b","c","image"]},{"name":"task3","tags":["3","c","d","directory"]},{"name":"task4","tags":["4","d","directory","e"]}]}
|
||||
`,
|
||||
},
|
||||
{
|
||||
@@ -105,19 +124,27 @@ Default selections:
|
||||
c := defaultCatalogerListOptions()
|
||||
c.Output = "table"
|
||||
c.DefaultCatalogers = []string{
|
||||
"image",
|
||||
"image", // note: for backwards compatibility file will automatically be added
|
||||
}
|
||||
return c
|
||||
}(),
|
||||
want: `
|
||||
Default selections:
|
||||
- "image"
|
||||
┌───────────┬────────────────┐
|
||||
│ CATALOGER │ TAGS │
|
||||
├───────────┼────────────────┤
|
||||
│ task1 │ 1, a, b, image │
|
||||
│ task2 │ 2, b, c, image │
|
||||
└───────────┴────────────────┘
|
||||
Default selections: 2
|
||||
• 'image'
|
||||
• 'file'
|
||||
Selection expressions: 0
|
||||
┌────────────────┬──────────────────┐
|
||||
│ FILE CATALOGER │ TAGS │
|
||||
├────────────────┼──────────────────┤
|
||||
│ file-task1 │ file, ft, ft-1-b │
|
||||
│ file-task2 │ file, ft, ft-2-b │
|
||||
└────────────────┴──────────────────┘
|
||||
┌───────────────────┬────────────────┐
|
||||
│ PACKAGE CATALOGER │ TAGS │
|
||||
├───────────────────┼────────────────┤
|
||||
│ task1 │ 1, a, b, image │
|
||||
│ task2 │ 2, b, c, image │
|
||||
└───────────────────┴────────────────┘
|
||||
`,
|
||||
},
|
||||
{
|
||||
@@ -131,7 +158,7 @@ Default selections:
|
||||
return c
|
||||
}(),
|
||||
want: `
|
||||
{"default":["image"],"selection":[],"catalogers":[{"name":"task1","tags":["image"]},{"name":"task2","tags":["image"]}]}
|
||||
{"default":["image"],"selection":[],"catalogers":[{"name":"file-task1","tags":["file"]},{"name":"file-task2","tags":["file"]},{"name":"task1","tags":["image"]},{"name":"task2","tags":["image"]}]}
|
||||
`,
|
||||
},
|
||||
{
|
||||
@@ -147,23 +174,32 @@ Default selections:
|
||||
"+task3",
|
||||
"-c",
|
||||
"b",
|
||||
"-file",
|
||||
"+file-task1",
|
||||
}
|
||||
return c
|
||||
}(),
|
||||
want: `
|
||||
Default selections:
|
||||
- "image"
|
||||
Selected by expressions:
|
||||
- "-directory"
|
||||
- "+task3"
|
||||
- "-c"
|
||||
- "b"
|
||||
┌───────────┬────────────────────┐
|
||||
│ CATALOGER │ TAGS │
|
||||
├───────────┼────────────────────┤
|
||||
│ task1 │ 1, a, b, image │
|
||||
│ task3 │ 3, c, d, directory │
|
||||
└───────────┴────────────────────┘
|
||||
Default selections: 1
|
||||
• 'image'
|
||||
Selection expressions: 6
|
||||
• 'b' (intersect)
|
||||
• '-directory' (remove)
|
||||
• '-c' (remove)
|
||||
• '-file' (remove)
|
||||
• '+task3' (add)
|
||||
• '+file-task1' (add)
|
||||
┌────────────────┬──────────────────┐
|
||||
│ FILE CATALOGER │ TAGS │
|
||||
├────────────────┼──────────────────┤
|
||||
│ file-task1 │ file, ft, ft-1-b │
|
||||
└────────────────┴──────────────────┘
|
||||
┌───────────────────┬────────────────────┐
|
||||
│ PACKAGE CATALOGER │ TAGS │
|
||||
├───────────────────┼────────────────────┤
|
||||
│ task1 │ 1, a, b, image │
|
||||
│ task3 │ 3, c, d, directory │
|
||||
└───────────────────┴────────────────────┘
|
||||
`,
|
||||
},
|
||||
{
|
||||
@@ -183,7 +219,7 @@ Selected by expressions:
|
||||
return c
|
||||
}(),
|
||||
want: `
|
||||
{"default":["image"],"selection":["-directory","+task3","-c","b"],"catalogers":[{"name":"task1","tags":["b","image"]},{"name":"task3","tags":["task3"]}]}
|
||||
{"default":["image"],"selection":["-directory","+task3","-c","b"],"catalogers":[{"name":"file-task1","tags":["file"]},{"name":"file-task2","tags":["file"]},{"name":"task1","tags":["b","image"]},{"name":"task3","tags":["task3"]}]}
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/anchore/clio"
|
||||
"github.com/anchore/syft/cmd/syft/internal/ui"
|
||||
)
|
||||
|
||||
func disableUI(app clio.Application, out io.Writer) func(*cobra.Command, []string) error {
|
||||
return func(_ *cobra.Command, _ []string) error {
|
||||
type Stater interface {
|
||||
State() *clio.State
|
||||
}
|
||||
|
||||
state := app.(Stater).State()
|
||||
state.UI = clio.NewUICollection(ui.None(out, state.Config.Log.Quiet))
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"github.com/anchore/syft/syft/file/cataloger/executable"
|
||||
"github.com/anchore/syft/syft/file/cataloger/filecontent"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/binary"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/dotnet"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/golang"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/java"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/javascript"
|
||||
@@ -33,6 +34,7 @@ type Catalog struct {
|
||||
DefaultCatalogers []string `yaml:"default-catalogers" json:"default-catalogers" mapstructure:"default-catalogers"`
|
||||
SelectCatalogers []string `yaml:"select-catalogers" json:"select-catalogers" mapstructure:"select-catalogers"`
|
||||
Package packageConfig `yaml:"package" json:"package" mapstructure:"package"`
|
||||
License licenseConfig `yaml:"license" json:"license" mapstructure:"license"`
|
||||
File fileConfig `yaml:"file" json:"file" mapstructure:"file"`
|
||||
Scope string `yaml:"scope" json:"scope" mapstructure:"scope"`
|
||||
Parallelism int `yaml:"parallelism" json:"parallelism" mapstructure:"parallelism"` // the number of catalog workers to run in parallel
|
||||
@@ -41,6 +43,7 @@ type Catalog struct {
|
||||
Enrich []string `yaml:"enrich" json:"enrich" mapstructure:"enrich"`
|
||||
|
||||
// ecosystem-specific cataloger configuration
|
||||
Dotnet dotnetConfig `yaml:"dotnet" json:"dotnet" mapstructure:"dotnet"`
|
||||
Golang golangConfig `yaml:"golang" json:"golang" mapstructure:"golang"`
|
||||
Java javaConfig `yaml:"java" json:"java" mapstructure:"java"`
|
||||
JavaScript javaScriptConfig `yaml:"javascript" json:"javascript" mapstructure:"javascript"`
|
||||
@@ -69,7 +72,9 @@ func DefaultCatalog() Catalog {
|
||||
Compliance: defaultComplianceConfig(),
|
||||
Scope: source.SquashedScope.String(),
|
||||
Package: defaultPackageConfig(),
|
||||
License: defaultLicenseConfig(),
|
||||
LinuxKernel: defaultLinuxKernelConfig(),
|
||||
Dotnet: defaultDotnetConfig(),
|
||||
Golang: defaultGolangConfig(),
|
||||
Java: defaultJavaConfig(),
|
||||
File: defaultFileConfig(),
|
||||
@@ -89,9 +94,10 @@ func (cfg Catalog) ToSBOMConfig(id clio.Identification) *syft.CreateSBOMConfig {
|
||||
WithUnknownsConfig(cfg.ToUnknownsConfig()).
|
||||
WithSearchConfig(cfg.ToSearchConfig()).
|
||||
WithPackagesConfig(cfg.ToPackagesConfig()).
|
||||
WithLicenseConfig(cfg.ToLicenseConfig()).
|
||||
WithFilesConfig(cfg.ToFilesConfig()).
|
||||
WithCatalogerSelection(
|
||||
pkgcataloging.NewSelectionRequest().
|
||||
cataloging.NewSelectionRequest().
|
||||
WithDefaults(cfg.DefaultCatalogers...).
|
||||
WithExpression(cfg.SelectCatalogers...),
|
||||
)
|
||||
@@ -146,6 +152,13 @@ func (cfg Catalog) ToFilesConfig() filecataloging.Config {
|
||||
}
|
||||
}
|
||||
|
||||
func (cfg Catalog) ToLicenseConfig() cataloging.LicenseConfig {
|
||||
return cataloging.LicenseConfig{
|
||||
IncludeUnkownLicenseContent: cfg.License.IncludeUnknownLicenseContent,
|
||||
Coverage: cfg.License.LicenseCoverage,
|
||||
}
|
||||
}
|
||||
|
||||
func (cfg Catalog) ToPackagesConfig() pkgcataloging.Config {
|
||||
archiveSearch := cataloging.ArchiveSearchConfig{
|
||||
IncludeIndexedArchives: cfg.Package.SearchIndexedArchives,
|
||||
@@ -153,9 +166,13 @@ func (cfg Catalog) ToPackagesConfig() pkgcataloging.Config {
|
||||
}
|
||||
return pkgcataloging.Config{
|
||||
Binary: binary.DefaultClassifierCatalogerConfig(),
|
||||
Dotnet: dotnet.DefaultCatalogerConfig().
|
||||
WithCertificateValidation(cfg.Dotnet.EnableCertificateValidation),
|
||||
Golang: golang.DefaultCatalogerConfig().
|
||||
WithSearchLocalModCacheLicenses(*multiLevelOption(false, enrichmentEnabled(cfg.Enrich, task.Go, task.Golang), cfg.Golang.SearchLocalModCacheLicenses)).
|
||||
WithLocalModCacheDir(cfg.Golang.LocalModCacheDir).
|
||||
WithSearchLocalVendorLicenses(*multiLevelOption(false, enrichmentEnabled(cfg.Enrich, task.Go, task.Golang), cfg.Golang.SearchLocalVendorLicenses)).
|
||||
WithLocalVendorDir(cfg.Golang.LocalVendorDir).
|
||||
WithSearchRemoteLicenses(*multiLevelOption(false, enrichmentEnabled(cfg.Enrich, task.Go, task.Golang), cfg.Golang.SearchRemoteLicenses)).
|
||||
WithProxy(cfg.Golang.Proxy).
|
||||
WithNoProxy(cfg.Golang.NoProxy).
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package options
|
||||
|
||||
import (
|
||||
"github.com/anchore/clio"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/dotnet"
|
||||
)
|
||||
|
||||
type dotnetConfig struct {
|
||||
EnableCertificateValidation bool `json:"enable-certificate-validation" yaml:"enable-certificate-validation" mapstructure:"enable-certificate-validation"`
|
||||
}
|
||||
|
||||
var _ interface {
|
||||
clio.FieldDescriber
|
||||
} = (*dotnetConfig)(nil)
|
||||
|
||||
func (o *dotnetConfig) DescribeFields(descriptions clio.FieldDescriptionSet) {
|
||||
descriptions.Add(&o.EnableCertificateValidation, `enable certificate validation -- this requires an active internet connection to download certificates and CRLs`)
|
||||
}
|
||||
|
||||
func defaultDotnetConfig() dotnetConfig {
|
||||
def := dotnet.DefaultCatalogerConfig()
|
||||
return dotnetConfig{
|
||||
EnableCertificateValidation: def.EnableCertificateValidation,
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
type golangConfig 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"`
|
||||
SearchLocalVendorLicenses *bool `json:"search-local-vendor-licenses" yaml:"search-local-vendor-licenses" mapstructure:"search-local-vendor-licenses"`
|
||||
LocalVendorDir string `json:"local-vendor-dir" yaml:"local-vendor-dir" mapstructure:"local-vendor-dir"`
|
||||
SearchRemoteLicenses *bool `json:"search-remote-licenses" yaml:"search-remote-licenses" mapstructure:"search-remote-licenses"`
|
||||
Proxy string `json:"proxy" yaml:"proxy" mapstructure:"proxy"`
|
||||
NoProxy string `json:"no-proxy" yaml:"no-proxy" mapstructure:"no-proxy"`
|
||||
@@ -24,6 +26,9 @@ func (o *golangConfig) DescribeFields(descriptions clio.FieldDescriptionSet) {
|
||||
descriptions.Add(&o.SearchLocalModCacheLicenses, `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`)
|
||||
descriptions.Add(&o.LocalModCacheDir, `specify an explicit go mod cache directory, if unset this defaults to $GOPATH/pkg/mod or $HOME/go/pkg/mod`)
|
||||
descriptions.Add(&o.SearchLocalVendorLicenses, `search for go package licences in the vendor folder on the system running Syft, note that this is outside the
|
||||
container filesystem and potentially outside the root of a local directory scan`)
|
||||
descriptions.Add(&o.LocalVendorDir, `specify an explicit go vendor directory, if unset this defaults to ./vendor`)
|
||||
descriptions.Add(&o.SearchRemoteLicenses, `search for go package licences by retrieving the package from a network proxy`)
|
||||
descriptions.Add(&o.Proxy, `remote proxy to use when retrieving go packages from the network,
|
||||
if unset this defaults to $GOPROXY followed by https://proxy.golang.org`)
|
||||
@@ -49,6 +54,8 @@ func defaultGolangConfig() golangConfig {
|
||||
return golangConfig{
|
||||
SearchLocalModCacheLicenses: nil, // this defaults to false, which is the API default
|
||||
LocalModCacheDir: def.LocalModCacheDir,
|
||||
SearchLocalVendorLicenses: nil, // this defaults to false, which is the API default
|
||||
LocalVendorDir: def.LocalVendorDir,
|
||||
SearchRemoteLicenses: nil, // this defaults to false, which is the API default
|
||||
Proxy: strings.Join(def.Proxies, ","),
|
||||
NoProxy: strings.Join(def.NoProxy, ","),
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package options
|
||||
|
||||
import (
|
||||
"github.com/anchore/clio"
|
||||
)
|
||||
|
||||
type licenseConfig struct {
|
||||
IncludeUnknownLicenseContent bool `yaml:"include-unknown-license-content" json:"include-unknown-license-content" mapstructure:"include-unknown-license-content"`
|
||||
LicenseCoverage float64 `yaml:"license-coverage" json:"license-coverage" mapstructure:"license-coverage"`
|
||||
}
|
||||
|
||||
var _ interface {
|
||||
clio.FieldDescriber
|
||||
} = (*licenseConfig)(nil)
|
||||
|
||||
func (o *licenseConfig) DescribeFields(descriptions clio.FieldDescriptionSet) {
|
||||
descriptions.Add(&o.IncludeUnknownLicenseContent, `include the content of a license in the SBOM when syft
|
||||
cannot determine a valid SPDX ID for the given license`)
|
||||
descriptions.Add(&o.LicenseCoverage, `adjust the percent as a fraction of the total text, in normalized words, that
|
||||
matches any valid license for the given inputs, expressed as a percentage across all of the licenses matched.`)
|
||||
}
|
||||
|
||||
func defaultLicenseConfig() licenseConfig {
|
||||
return licenseConfig{
|
||||
IncludeUnknownLicenseContent: false,
|
||||
LicenseCoverage: 75,
|
||||
}
|
||||
}
|
||||
@@ -16,9 +16,9 @@ var _ interface {
|
||||
} = (*packageConfig)(nil)
|
||||
|
||||
func (o *packageConfig) DescribeFields(descriptions clio.FieldDescriptionSet) {
|
||||
descriptions.Add(&o.SearchUnindexedArchives, `search within archives that do contain a file index to search against (zip)
|
||||
descriptions.Add(&o.SearchIndexedArchives, `search within archives that do contain a file index to search against (zip)
|
||||
note: for now this only applies to the java package cataloger`)
|
||||
descriptions.Add(&o.SearchIndexedArchives, `search within archives that do not contain a file index to search against (tar, tar.gz, tar.bz2, etc)
|
||||
descriptions.Add(&o.SearchUnindexedArchives, `search within archives that do not contain a file index to search against (tar, tar.gz, tar.bz2, etc)
|
||||
note: enabling this may result in a performance impact since all discovered compressed tars will be decompressed
|
||||
note: for now this only applies to the java package cataloger`)
|
||||
descriptions.Add(&o.ExcludeBinaryOverlapByOwnership, `allows users to exclude synthetic binary packages from the sbom
|
||||
|
||||
@@ -418,6 +418,14 @@ var dirOnlyTestCases = []testCase{
|
||||
"ocaml-base-compiler": "4.14.0",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "find terraform packages",
|
||||
pkgType: pkg.TerraformPkg,
|
||||
pkgLanguage: pkg.Go,
|
||||
pkgInfo: map[string]string{
|
||||
"registry.terraform.io/hashicorp/aws": "5.72.1",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var commonTestCases = []testCase{
|
||||
|
||||
@@ -74,6 +74,8 @@ func TestPkgCoverageImage(t *testing.T) {
|
||||
definedPkgs.Remove(string(pkg.ConanPkg))
|
||||
definedPkgs.Remove(string(pkg.HackagePkg))
|
||||
definedPkgs.Remove(string(pkg.BinaryPkg))
|
||||
definedPkgs.Remove(string(pkg.BitnamiPkg))
|
||||
definedPkgs.Remove(string(pkg.GraalVMNativeImagePkg))
|
||||
definedPkgs.Remove(string(pkg.HexPkg))
|
||||
definedPkgs.Remove(string(pkg.LinuxKernelPkg))
|
||||
definedPkgs.Remove(string(pkg.LinuxKernelModulePkg))
|
||||
@@ -82,6 +84,7 @@ func TestPkgCoverageImage(t *testing.T) {
|
||||
definedPkgs.Remove(string(pkg.OpamPkg))
|
||||
definedPkgs.Remove(string(pkg.GithubActionPkg))
|
||||
definedPkgs.Remove(string(pkg.GithubActionWorkflowPkg))
|
||||
definedPkgs.Remove(string(pkg.TerraformPkg))
|
||||
|
||||
var cases []testCase
|
||||
cases = append(cases, commonTestCases...)
|
||||
@@ -218,6 +221,8 @@ func TestPkgCoverageDirectory(t *testing.T) {
|
||||
definedLanguages.Remove(pkg.R.String())
|
||||
observedPkgs.Remove(string(pkg.UnknownPkg))
|
||||
definedPkgs.Remove(string(pkg.BinaryPkg))
|
||||
definedPkgs.Remove(string(pkg.BitnamiPkg))
|
||||
definedPkgs.Remove(string(pkg.GraalVMNativeImagePkg))
|
||||
definedPkgs.Remove(string(pkg.LinuxKernelPkg))
|
||||
definedPkgs.Remove(string(pkg.LinuxKernelModulePkg))
|
||||
definedPkgs.Remove(string(pkg.Rpkg))
|
||||
|
||||
Generated
+25
@@ -0,0 +1,25 @@
|
||||
# This file is maintained automatically by "terraform init".
|
||||
# Manual edits may be lost in future updates.
|
||||
|
||||
provider "registry.terraform.io/hashicorp/aws" {
|
||||
version = "5.72.1"
|
||||
constraints = ">= 5.72.0"
|
||||
hashes = [
|
||||
"h1:jhd5O5o0CfZCNEwwN0EiDAzb7ApuFrtxJqa6HXW4EKE=",
|
||||
"zh:0dea6843836e926d33469b48b948744079023816d16a2ff7666bcfb6aa3522d4",
|
||||
"zh:195fa9513f75800a0d62797ebec75ee73e9b8c28d713fe9b63d3b1d1eec129b3",
|
||||
"zh:1ed92f3961715bf0e024bcde3c12dfbdc50b00c1f8a43cc00802cfc45a256208",
|
||||
"zh:2ac687e3a52606466cae4a6813e81d923042488df88d2424e28d3f8530f091bb",
|
||||
"zh:32e7ca75f9314557daada3c44628fe1f3bf964a4f833bfb4b2295d833fe64b6f",
|
||||
"zh:374ee0e6b4327cc6ef666908ce5d6450a3a56e90cd2b785e83c2bcfc100021d2",
|
||||
"zh:5500fd6fdac44f96411fcf9c6d01691159ec35455ed127eb4c3a498e1cc92a64",
|
||||
"zh:723a2dc4b064c12e7ee62ad4fbfd72fa5e025206ea47b735994ef53f3c373152",
|
||||
"zh:89d97b87605f1d734f27e642567cbecf785b521af8ea81dac55c77ccde876221",
|
||||
"zh:951ee1e5731e8d65d521d71b95927e55055b3c4656eef6d46fa580a63328befc",
|
||||
"zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425",
|
||||
"zh:9b2b362470b64ec227b2da64762ab8bc4111c6b80365fd9d82fc5e1e33f44038",
|
||||
"zh:aa6e57d0cb974ff0da5dee5d43ad2745cbbc4a2b507d4c799839b9fa96daf688",
|
||||
"zh:ba0d14c4a6b7aa844a830d47c0bf995b632e37f0795394b5b60c638b62b7fc03",
|
||||
"zh:c9764065a9c5d324db0b02bd201b9e3a2118e49c4960884acdeea377173302e9",
|
||||
]
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/anchore/stereoscope/pkg/imagetest"
|
||||
"github.com/anchore/syft/cmd/syft/internal/options"
|
||||
"github.com/anchore/syft/syft"
|
||||
"github.com/anchore/syft/syft/cataloging"
|
||||
"github.com/anchore/syft/syft/cataloging/pkgcataloging"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
@@ -20,7 +21,7 @@ func catalogFixtureImage(t *testing.T, fixtureImageName string, scope source.Sco
|
||||
Name: "syft-tester",
|
||||
Version: "v0.99.0",
|
||||
}).WithCatalogerSelection(
|
||||
pkgcataloging.NewSelectionRequest().
|
||||
cataloging.NewSelectionRequest().
|
||||
WithExpression(catalogerSelection...),
|
||||
)
|
||||
cfg.Search.Scope = scope
|
||||
@@ -55,7 +56,7 @@ func catalogDirectory(t *testing.T, dir string, catalogerSelection ...string) (s
|
||||
Name: "syft-tester",
|
||||
Version: "v0.99.0",
|
||||
}).WithCatalogerSelection(
|
||||
pkgcataloging.NewSelectionRequest().
|
||||
cataloging.NewSelectionRequest().
|
||||
WithExpression(catalogerSelection...),
|
||||
)
|
||||
|
||||
|
||||
@@ -150,7 +150,7 @@ func (m *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
}
|
||||
|
||||
case partybus.Event:
|
||||
log.WithFields("component", "ui").Tracef("event: %q", msg.Type)
|
||||
log.WithFields("component", "ui", "event", msg.Type).Trace("event")
|
||||
|
||||
switch msg.Type {
|
||||
case event.CLIReport, event.CLINotification, event.CLIAppUpdateAvailable:
|
||||
|
||||
@@ -74,7 +74,7 @@ func getSBOM(src source.Source) sbom.SBOM {
|
||||
// only use OS related catalogers that would have been used with the kind of
|
||||
// source type (container image or directory), but also add a specific python cataloger
|
||||
WithCatalogerSelection(
|
||||
pkgcataloging.NewSelectionRequest().
|
||||
cataloging.NewSelectionRequest().
|
||||
WithSubSelections("os").
|
||||
WithAdditions("python-package-cataloger"),
|
||||
).
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/anchore/syft/syft"
|
||||
"github.com/anchore/syft/syft/cataloging"
|
||||
"github.com/anchore/syft/syft/cataloging/pkgcataloging"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
@@ -55,7 +56,7 @@ func getSBOM(src source.Source, defaultTags ...string) sbom.SBOM {
|
||||
WithCatalogerSelection(
|
||||
// here you can sub-select, add, remove catalogers from the default selection...
|
||||
// or replace the default selection entirely!
|
||||
pkgcataloging.NewSelectionRequest().
|
||||
cataloging.NewSelectionRequest().
|
||||
WithDefaults(defaultTags...),
|
||||
)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module github.com/anchore/syft
|
||||
|
||||
go 1.23.2
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
github.com/CycloneDX/cyclonedx-go v0.9.2
|
||||
@@ -16,27 +16,27 @@ require (
|
||||
github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb
|
||||
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04
|
||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20241018175412-5c22e6360c4f
|
||||
github.com/anchore/stereoscope v0.0.12
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20250220190351-d62adb6e1115
|
||||
github.com/anchore/stereoscope v0.0.13
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be
|
||||
// we are hinting brotli to latest due to warning when installing archiver v3:
|
||||
// go: warning: github.com/andybalholm/brotli@v1.0.1: retracted by module author: occasional panics and data corruption
|
||||
github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46
|
||||
github.com/bmatcuk/doublestar/v4 v4.8.0
|
||||
github.com/aquasecurity/go-pep440-version v0.0.1
|
||||
github.com/bmatcuk/doublestar/v4 v4.8.1
|
||||
github.com/charmbracelet/bubbles v0.20.0
|
||||
github.com/charmbracelet/bubbletea v1.2.4
|
||||
github.com/charmbracelet/lipgloss v1.0.0
|
||||
github.com/dave/jennifer v1.7.1
|
||||
github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da
|
||||
github.com/distribution/reference v0.6.0
|
||||
github.com/docker/docker v27.5.0+incompatible
|
||||
github.com/docker/docker v28.0.0+incompatible
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
github.com/elliotchance/phpserialize v1.4.0
|
||||
github.com/facebookincubator/nvdtools v0.1.5
|
||||
github.com/github/go-spdx/v2 v2.3.2
|
||||
github.com/gkampitakis/go-snaps v0.5.8
|
||||
github.com/gkampitakis/go-snaps v0.5.11
|
||||
github.com/go-git/go-billy/v5 v5.6.2
|
||||
github.com/go-git/go-git/v5 v5.13.1
|
||||
github.com/go-git/go-git/v5 v5.13.2
|
||||
github.com/go-test/deep v1.1.1
|
||||
github.com/google/go-cmp v0.6.0
|
||||
github.com/google/go-containerregistry v0.20.3
|
||||
@@ -46,7 +46,7 @@ require (
|
||||
github.com/hashicorp/go-multierror v1.1.1
|
||||
github.com/iancoleman/strcase v0.3.0
|
||||
github.com/invopop/jsonschema v0.7.0
|
||||
github.com/jedib0t/go-pretty/v6 v6.6.5
|
||||
github.com/jedib0t/go-pretty/v6 v6.6.6
|
||||
github.com/jinzhu/copier v0.4.0
|
||||
github.com/kastenhq/goversion v0.0.0-20230811215019-93b2f8823953
|
||||
github.com/knqyf263/go-rpmdb v0.1.1
|
||||
@@ -61,14 +61,14 @@ require (
|
||||
github.com/quasilyte/go-ruleguard/dsl v0.3.22
|
||||
github.com/saferwall/pe v1.5.6
|
||||
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d
|
||||
github.com/sanity-io/litter v1.5.6
|
||||
github.com/sanity-io/litter v1.5.8
|
||||
github.com/sassoftware/go-rpmutils v0.4.0
|
||||
// pinned to pull in 386 arch fix: https://github.com/scylladb/go-set/commit/cc7b2070d91ebf40d233207b633e28f5bd8f03a5
|
||||
github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3
|
||||
github.com/spdx/tools-golang v0.5.5
|
||||
github.com/spf13/afero v1.12.0
|
||||
github.com/spf13/cobra v1.8.1
|
||||
github.com/spf13/cobra v1.9.1
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/vbatts/go-mtree v0.5.4
|
||||
github.com/vifraa/gopom v1.0.0
|
||||
@@ -77,10 +77,10 @@ require (
|
||||
github.com/xeipuuv/gojsonschema v1.2.0
|
||||
github.com/zyedidia/generic v1.2.2-0.20230320175451-4410d2372cb1
|
||||
go.uber.org/goleak v1.3.0
|
||||
golang.org/x/mod v0.22.0
|
||||
golang.org/x/net v0.34.0
|
||||
golang.org/x/mod v0.23.0
|
||||
golang.org/x/net v0.35.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
modernc.org/sqlite v1.34.5
|
||||
modernc.org/sqlite v1.35.0
|
||||
)
|
||||
|
||||
require google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect
|
||||
@@ -90,6 +90,8 @@ require (
|
||||
github.com/OneOfOne/xxhash v1.2.8
|
||||
github.com/adrg/xdg v0.5.3
|
||||
github.com/anchore/archiver/v3 v3.5.3-0.20241210171143-5b1d8d1c7c51
|
||||
github.com/bitnami/go-version v0.0.0-20250131085805-b1f57a8634ef
|
||||
github.com/hashicorp/hcl/v2 v2.23.0
|
||||
github.com/magiconair/properties v1.8.9
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
|
||||
)
|
||||
@@ -103,10 +105,13 @@ require (
|
||||
github.com/Masterminds/semver/v3 v3.3.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/Microsoft/hcsshim v0.11.7 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.1.3 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.1.5 // indirect
|
||||
github.com/agext/levenshtein v1.2.1 // indirect; indirectt
|
||||
github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 // indirect
|
||||
github.com/andybalholm/brotli v1.1.1 // indirect
|
||||
github.com/aquasecurity/go-version v0.0.0-20210121072130-637058cfe492 // indirect
|
||||
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
|
||||
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
|
||||
github.com/aquasecurity/go-version v0.0.1 // indirect
|
||||
github.com/atotto/clipboard v0.1.4 // indirect
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/becheran/wildmatch-go v1.0.0 // indirect
|
||||
@@ -137,7 +142,7 @@ require (
|
||||
github.com/edsrzf/mmap-go v1.1.0 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
||||
github.com/felixge/fgprof v0.9.3 // indirect
|
||||
github.com/felixge/fgprof v0.9.5 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||
@@ -172,6 +177,7 @@ require (
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/moby/locker v1.0.1 // indirect
|
||||
@@ -190,7 +196,7 @@ require (
|
||||
github.com/pborman/indent v1.2.1 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
||||
github.com/pjbgf/sha1cd v0.3.0 // indirect
|
||||
github.com/pjbgf/sha1cd v0.3.2 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pkg/profile v1.7.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
@@ -206,7 +212,7 @@ require (
|
||||
github.com/skeema/knownhosts v1.3.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/cast v1.7.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/spf13/pflag v1.0.6 // indirect
|
||||
github.com/spf13/viper v1.19.0 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/sylabs/sif/v2 v2.20.2 // indirect
|
||||
@@ -223,6 +229,7 @@ require (
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
github.com/zclconf/go-cty v1.13.0 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect
|
||||
@@ -231,20 +238,21 @@ require (
|
||||
go.opentelemetry.io/otel/trace v1.33.0 // indirect
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
go.uber.org/multierr v1.9.0 // indirect
|
||||
golang.org/x/crypto v0.32.0 // indirect
|
||||
golang.org/x/sync v0.10.0 // indirect
|
||||
golang.org/x/sys v0.29.0 // indirect
|
||||
golang.org/x/term v0.28.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||
golang.org/x/crypto v0.33.0 // indirect
|
||||
golang.org/x/sync v0.11.0 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
golang.org/x/term v0.29.0 // indirect
|
||||
golang.org/x/text v0.22.0 // indirect
|
||||
golang.org/x/tools v0.29.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 // indirect
|
||||
google.golang.org/grpc v1.67.3 // indirect
|
||||
google.golang.org/protobuf v1.36.3 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
modernc.org/libc v1.55.3 // indirect
|
||||
modernc.org/mathutil v1.6.0 // indirect
|
||||
modernc.org/memory v1.8.0 // indirect
|
||||
modernc.org/libc v1.61.13 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.8.2 // indirect
|
||||
)
|
||||
|
||||
retract (
|
||||
|
||||
@@ -80,14 +80,16 @@ github.com/Microsoft/hcsshim v0.11.7/go.mod h1:MV8xMfmECjl5HdO7U/3/hFVnkmSBjAjmA
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8=
|
||||
github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
|
||||
github.com/ProtonMail/go-crypto v1.1.3 h1:nRBOetoydLeUb4nHajyO2bKqMLfWQ/ZPwkXqXxPxCFk=
|
||||
github.com/ProtonMail/go-crypto v1.1.3/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
|
||||
github.com/ProtonMail/go-crypto v1.1.5 h1:eoAQfK2dwL+tFSFpr7TbOaPNUbPiJj4fLYwwGE1FQO4=
|
||||
github.com/ProtonMail/go-crypto v1.1.5/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
|
||||
github.com/acobaugh/osrelease v0.1.0 h1:Yb59HQDGGNhCj4suHaFQQfBps5wyoKLSSX/J/+UifRE=
|
||||
github.com/acobaugh/osrelease v0.1.0/go.mod h1:4bFEs0MtgHNHBrmHCt67gNisnabCRAlzdVasCEGHTWY=
|
||||
github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78=
|
||||
github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ=
|
||||
github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8=
|
||||
github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
@@ -112,20 +114,24 @@ github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04 h1:VzprUTpc0v
|
||||
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04/go.mod h1:6dK64g27Qi1qGQZ67gFmBFvEHScy0/C8qhQhNe5B5pQ=
|
||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b h1:e1bmaoJfZVsCYMrIZBpFxwV26CbsuoEh5muXD5I1Ods=
|
||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b/go.mod h1:Bkc+JYWjMCF8OyZ340IMSIi2Ebf3uwByOk6ho4wne1E=
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20241018175412-5c22e6360c4f h1:dAQPIrQ3a5PBqZeZ+B9NGZsGmodk4NO9OjDIsQmQyQM=
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20241018175412-5c22e6360c4f/go.mod h1:KoYIv7tdP5+CC9VGkeZV4/vGCKsY55VvoG+5dadg4YI=
|
||||
github.com/anchore/stereoscope v0.0.12 h1:ovUWeyeZGml6pTGiu/uha/rCbToANFPu+cnhLbeperY=
|
||||
github.com/anchore/stereoscope v0.0.12/go.mod h1:cmb/MGya7ccOd6fZZEREuhdSH2kFALBMrkY/66Sfv1o=
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20250220190351-d62adb6e1115 h1:ZyRCmiEjnoGJZ1+Ah0ZZ/mKKqNhGcUZBl0s7PTTDzvY=
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20250220190351-d62adb6e1115/go.mod h1:KoYIv7tdP5+CC9VGkeZV4/vGCKsY55VvoG+5dadg4YI=
|
||||
github.com/anchore/stereoscope v0.0.13 h1:9Ivkh7k+vOeG3JHrt44jOg/8UdZrCvMsSjLQ7trHBig=
|
||||
github.com/anchore/stereoscope v0.0.13/go.mod h1:QfhhFc2pezp5aX/dVJ5qnBFpBUv5+KUTphwaQLxMUig=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
||||
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46 h1:vmXNl+HDfqqXgr0uY1UgK1GAhps8nbAAtqHNBcgyf+4=
|
||||
github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46/go.mod h1:olhPNdiiAAMiSujemd1O/sc6GcyePr23f/6uGKtthNg=
|
||||
github.com/aquasecurity/go-version v0.0.0-20210121072130-637058cfe492 h1:rcEG5HI490FF0a7zuvxOxen52ddygCfNVjP0XOCMl+M=
|
||||
github.com/aquasecurity/go-version v0.0.0-20210121072130-637058cfe492/go.mod h1:9Beu8XsUNNfzml7WBf3QmyPToP1wm1Gj/Vc5UJKqTzU=
|
||||
github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw=
|
||||
github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo=
|
||||
github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
|
||||
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
|
||||
github.com/aquasecurity/go-pep440-version v0.0.1 h1:8VKKQtH2aV61+0hovZS3T//rUF+6GDn18paFTVS0h0M=
|
||||
github.com/aquasecurity/go-pep440-version v0.0.1/go.mod h1:3naPe+Bp6wi3n4l5iBFCZgS0JG8vY6FT0H4NGhFJ+i4=
|
||||
github.com/aquasecurity/go-version v0.0.1 h1:4cNl516agK0TCn5F7mmYN+xVs1E3S45LkgZk3cbaW2E=
|
||||
github.com/aquasecurity/go-version v0.0.1/go.mod h1:s1UU6/v2hctXcOa3OLwfj5d9yoXHa3ahf+ipSwEvGT0=
|
||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||
github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc=
|
||||
@@ -143,8 +149,10 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/bmatcuk/doublestar/v4 v4.8.0 h1:DSXtrypQddoug1459viM9X9D3dp1Z7993fw36I2kNcQ=
|
||||
github.com/bmatcuk/doublestar/v4 v4.8.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
|
||||
github.com/bitnami/go-version v0.0.0-20250131085805-b1f57a8634ef h1:TSFnfbbu2oAOuWbeDDTtwXWE6z+PmpgbSsMBeV7l0ww=
|
||||
github.com/bitnami/go-version v0.0.0-20250131085805-b1f57a8634ef/go.mod h1:9iglf1GG4oNRJ39bZ5AZrjgAFD2RwQbXw6Qf7Cs47wo=
|
||||
github.com/bmatcuk/doublestar/v4 v4.8.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR5wKP38=
|
||||
github.com/bmatcuk/doublestar/v4 v4.8.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
|
||||
github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M=
|
||||
github.com/bradleyjkemp/cupaloy/v2 v2.8.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
|
||||
@@ -166,9 +174,15 @@ github.com/charmbracelet/x/ansi v0.4.5 h1:LqK4vwBNaXw2AyGIICa5/29Sbdq58GbGdFngSe
|
||||
github.com/charmbracelet/x/ansi v0.4.5/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
|
||||
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
|
||||
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
|
||||
github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
|
||||
github.com/chromedp/chromedp v0.9.2/go.mod h1:LkSXJKONWTCHAfQasKFUZI+mxqS4tZqhmtGzzhLsnLs=
|
||||
github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
|
||||
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
|
||||
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
@@ -208,9 +222,8 @@ github.com/containerd/typeurl/v2 v2.1.1 h1:3Q4Pt7i8nYwy2KmQWIw2+1hTvwTE/6w9Fqctt
|
||||
github.com/containerd/typeurl/v2 v2.1.1/go.mod h1:IDp2JFvbwZ31H8dQbEIY7sDl2L3o3HZj1hsSQlywkQ0=
|
||||
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=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/cyphar/filepath-securejoin v0.3.6 h1:4d9N5ykBnSp5Xn2JkhocYDkOpURL/18CYMpo6xB9uWM=
|
||||
github.com/cyphar/filepath-securejoin v0.3.6/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
|
||||
@@ -230,8 +243,8 @@ github.com/docker/cli v27.5.0+incompatible h1:aMphQkcGtpHixwwhAXJT1rrK/detk2JIvD
|
||||
github.com/docker/cli v27.5.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
|
||||
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v27.5.0+incompatible h1:um++2NcQtGRTz5eEgO6aJimo6/JxrTXC941hd05JO6U=
|
||||
github.com/docker/docker v27.5.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v28.0.0+incompatible h1:Olh0KS820sJ7nPsBKChVhk5pzqcwDR15fumfAd/p9hM=
|
||||
github.com/docker/docker v28.0.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo=
|
||||
github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=
|
||||
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||
@@ -247,8 +260,8 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/edsrzf/mmap-go v1.1.0 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ=
|
||||
github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q=
|
||||
github.com/elazarl/goproxy v1.2.3 h1:xwIyKHbaP5yfT6O9KIeYJR5549MXRQkoQMRXGztz8YQ=
|
||||
github.com/elazarl/goproxy v1.2.3/go.mod h1:YfEbZtqP4AetfO6d40vWchF3znWX7C7Vd6ZMfdL8z64=
|
||||
github.com/elazarl/goproxy v1.4.0 h1:4GyuSbFa+s26+3rmYNSuUVsx+HgPrV1bk1jXI0l9wjM=
|
||||
github.com/elazarl/goproxy v1.4.0/go.mod h1:X/5W/t+gzDyLfHW4DrMdpjqYjpXsURlBt9lpBDxZZZQ=
|
||||
github.com/elliotchance/phpserialize v1.4.0 h1:cAp/9+KSnEbUC8oYCE32n2n84BeW8HOY3HMDI8hG2OY=
|
||||
github.com/elliotchance/phpserialize v1.4.0/go.mod h1:gt7XX9+ETUcLXbtTKEuyrqW3lcLUAeS/AnGZ2e49TZs=
|
||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||
@@ -274,8 +287,9 @@ github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL
|
||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||
github.com/fatih/set v0.2.1 h1:nn2CaJyknWE/6txyUDGwysr3G5QC6xWB/PtVjPBbeaA=
|
||||
github.com/fatih/set v0.2.1/go.mod h1:+RKtMCH+favT2+3YecHGxcc0b4KyVWA1QWWJUs4E0CI=
|
||||
github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
|
||||
github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
|
||||
github.com/felixge/fgprof v0.9.5 h1:8+vR6yu2vvSKn08urWyEuxx75NWPEvybbkBirEpsbVY=
|
||||
github.com/felixge/fgprof v0.9.5/go.mod h1:yKl+ERSa++RYOs32d8K6WEXCB4uXdLls4ZaZPpayhMM=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
@@ -292,8 +306,8 @@ github.com/gkampitakis/ciinfo v0.3.1 h1:lzjbemlGI4Q+XimPg64ss89x8Mf3xihJqy/0Mgag
|
||||
github.com/gkampitakis/ciinfo v0.3.1/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo=
|
||||
github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M=
|
||||
github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk=
|
||||
github.com/gkampitakis/go-snaps v0.5.8 h1:BB4ihcyXgJEVO/Pj/P+4bs7pFzsLcEjsfU2+mFdJh1c=
|
||||
github.com/gkampitakis/go-snaps v0.5.8/go.mod h1:PcKmy8q5Se7p48ywpogN5Td13reipz1Iivah4wrTIvY=
|
||||
github.com/gkampitakis/go-snaps v0.5.11 h1:LFG0ggUKR+KEiiaOvFCmLgJ5NO2zf93AxxddkBn3LdQ=
|
||||
github.com/gkampitakis/go-snaps v0.5.11/go.mod h1:PcKmy8q5Se7p48ywpogN5Td13reipz1Iivah4wrTIvY=
|
||||
github.com/glebarez/go-sqlite v1.20.3 h1:89BkqGOXR9oRmG58ZrzgoY/Fhy5x0M+/WV48U5zVrZ4=
|
||||
github.com/glebarez/go-sqlite v1.20.3/go.mod h1:u3N6D/wftiAzIOJtZl6BmedqxmmkDfH3q+ihjqxC9u0=
|
||||
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
||||
@@ -304,8 +318,8 @@ github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UN
|
||||
github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
||||
github.com/go-git/go-git/v5 v5.13.1 h1:DAQ9APonnlvSWpvolXWIuV6Q6zXy2wHbN4cVlNR5Q+M=
|
||||
github.com/go-git/go-git/v5 v5.13.1/go.mod h1:qryJB4cSBoq3FRoBRf5A77joojuBcmPJ0qu3XXXVixc=
|
||||
github.com/go-git/go-git/v5 v5.13.2 h1:7O7xvsK7K+rZPKW6AQR1YyNhfywkv7B8/FsP3ki6Zv0=
|
||||
github.com/go-git/go-git/v5 v5.13.2/go.mod h1:hWdW5P4YZRjmpGHwRH2v3zkWcNl6HeXaXQEMGb3NJ9A=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
@@ -324,6 +338,9 @@ github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
|
||||
github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
||||
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
|
||||
github.com/goccy/go-yaml v1.15.13 h1:Xd87Yddmr2rC1SLLTm2MNDcTjeO/GYo0JGiww6gSTDg=
|
||||
github.com/goccy/go-yaml v1.15.13/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
@@ -406,6 +423,7 @@ github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLe
|
||||
github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
|
||||
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
|
||||
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
@@ -451,6 +469,8 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ
|
||||
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hashicorp/hcl/v2 v2.23.0 h1:Fphj1/gCylPxHutVSEOf2fBOh1VE4AuLV7+kbJf3qos=
|
||||
github.com/hashicorp/hcl/v2 v2.23.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA=
|
||||
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
||||
github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY=
|
||||
github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
|
||||
@@ -468,6 +488,7 @@ github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
@@ -475,10 +496,11 @@ github.com/invopop/jsonschema v0.7.0 h1:2vgQcBz1n256N+FpX3Jq7Y17AjYt46Ig3zIWyy77
|
||||
github.com/invopop/jsonschema v0.7.0/go.mod h1:O9uiLokuu0+MGFlyiaqtWxwqJm41/+8Nj0lD7A36YH0=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||
github.com/jedib0t/go-pretty/v6 v6.6.5 h1:9PgMJOVBedpgYLI56jQRJYqngxYAAzfEUua+3NgSqAo=
|
||||
github.com/jedib0t/go-pretty/v6 v6.6.5/go.mod h1:Uq/HrbhuFty5WSVNfjpQQe47x16RwVGXIveNGEyGtHs=
|
||||
github.com/jedib0t/go-pretty/v6 v6.6.6 h1:LyezkL+1SuqH2z47e5IMQkYUIcs2BD+MnpdPRiRcN0c=
|
||||
github.com/jedib0t/go-pretty/v6 v6.6.6/go.mod h1:YwC5CE4fJ1HFUDeivSV1r//AmANFHyqczZk+U6BDALU=
|
||||
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
|
||||
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
@@ -513,6 +535,7 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
|
||||
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
||||
@@ -522,6 +545,7 @@ github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc8
|
||||
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||
github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM=
|
||||
github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo=
|
||||
github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
@@ -559,6 +583,8 @@ github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HK
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM=
|
||||
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
@@ -614,6 +640,7 @@ github.com/opencontainers/runtime-spec v1.1.0 h1:HHUyrt9mwHUjtasSbXSMvs4cyFxh+Bl
|
||||
github.com/opencontainers/runtime-spec v1.1.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/opencontainers/selinux v1.11.0 h1:+5Zbo97w3Lbmb3PeqQtpmTkMwsW5nRI3YaLpt7tQ7oU=
|
||||
github.com/opencontainers/selinux v1.11.0/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec=
|
||||
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pborman/indent v1.2.1 h1:lFiviAbISHv3Rf0jcuh489bi06hj98JsVMtIDZQb9yM=
|
||||
@@ -625,8 +652,8 @@ github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6
|
||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
|
||||
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
|
||||
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
|
||||
github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
|
||||
github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
@@ -667,7 +694,6 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/saferwall/pe v1.5.6 h1:DrRLnoQFxHWJ5lJUmrH7X2L0xeUu6SUS95Dc61eW2Yc=
|
||||
@@ -681,8 +707,8 @@ github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA=
|
||||
github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
|
||||
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA=
|
||||
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
|
||||
github.com/sanity-io/litter v1.5.6 h1:hCFycYzhRnW4niFbbmR7QKdmds69PbVa/sNmEN5euSU=
|
||||
github.com/sanity-io/litter v1.5.6/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U=
|
||||
github.com/sanity-io/litter v1.5.8 h1:uM/2lKrWdGbRXDrIq08Lh9XtVYoeGtcQxk9rtQ7+rYg=
|
||||
github.com/sanity-io/litter v1.5.8/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U=
|
||||
github.com/sassoftware/go-rpmutils v0.4.0 h1:ojND82NYBxgwrV+mX1CWsd5QJvvEZTKddtCdFLPWhpg=
|
||||
github.com/sassoftware/go-rpmutils v0.4.0/go.mod h1:3goNWi7PGAT3/dlql2lv3+MSN5jNYPjT5mVcQcIsYzI=
|
||||
github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e h1:7q6NSFZDeGfvvtIRwBrU/aegEYJYmvev0cHAwo17zZQ=
|
||||
@@ -698,7 +724,6 @@ github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
@@ -720,11 +745,12 @@ github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU
|
||||
github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
|
||||
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4=
|
||||
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
||||
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
||||
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
|
||||
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
|
||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM=
|
||||
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
|
||||
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
|
||||
@@ -774,7 +800,6 @@ github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqri
|
||||
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
|
||||
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||
github.com/vbatts/go-mtree v0.5.4 h1:OMAb8jaCyiFA7zXj0Zc/oARcxBDBoeu2LizjB8BVJl0=
|
||||
github.com/vbatts/go-mtree v0.5.4/go.mod h1:5GqJbVhm9BBiCc4K5uc/c42FPgXulHaQs4sFUEfIWMo=
|
||||
github.com/vbatts/tar-split v0.11.6 h1:4SjTW5+PU11n6fZenf2IPoV8/tz3AaYHMWjf23envGs=
|
||||
@@ -806,6 +831,10 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/zclconf/go-cty v1.13.0 h1:It5dfKTTZHe9aeppbNOda3mN7Ag7sg6QkBNm6TkyFa0=
|
||||
github.com/zclconf/go-cty v1.13.0/go.mod h1:YKQzy/7pZ7iq2jNFzy5go57xdxdWoLLpaEp4u238AE0=
|
||||
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo=
|
||||
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=
|
||||
github.com/zyedidia/generic v1.2.2-0.20230320175451-4410d2372cb1 h1:V+UsotZpAVvfj3X/LMoEytoLzSiP6Lg0F7wdVyu9gGg=
|
||||
github.com/zyedidia/generic v1.2.2-0.20230320175451-4410d2372cb1/go.mod h1:ly2RBz4mnz1yeuVbQA/VFwGjK3mnHGRj1JuoG336Bis=
|
||||
go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
|
||||
@@ -860,8 +889,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
|
||||
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -901,8 +930,8 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
|
||||
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
|
||||
golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -946,8 +975,8 @@ golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qx
|
||||
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
||||
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
||||
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -977,8 +1006,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
|
||||
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -1048,17 +1077,18 @@ golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
|
||||
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
|
||||
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
|
||||
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -1069,8 +1099,8 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@@ -1137,8 +1167,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
|
||||
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=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU=
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
@@ -1324,28 +1354,28 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ=
|
||||
modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
|
||||
modernc.org/ccgo/v4 v4.19.2 h1:lwQZgvboKD0jBwdaeVCTouxhxAyN6iawF3STraAal8Y=
|
||||
modernc.org/ccgo/v4 v4.19.2/go.mod h1:ysS3mxiMV38XGRTTcgo0DQTeTmAO4oCmJl1nX9VFI3s=
|
||||
modernc.org/cc/v4 v4.24.4 h1:TFkx1s6dCkQpd6dKurBNmpo+G8Zl4Sq/ztJ+2+DEsh0=
|
||||
modernc.org/cc/v4 v4.24.4/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||
modernc.org/ccgo/v4 v4.23.16 h1:Z2N+kk38b7SfySC1ZkpGLN2vthNJP1+ZzGZIlH7uBxo=
|
||||
modernc.org/ccgo/v4 v4.23.16/go.mod h1:nNma8goMTY7aQZQNTyN9AIoJfxav4nvTnvKThAeMDdo=
|
||||
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
|
||||
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
|
||||
modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=
|
||||
modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
|
||||
modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U=
|
||||
modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w=
|
||||
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
||||
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
||||
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
|
||||
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
|
||||
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
||||
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
|
||||
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
|
||||
modernc.org/sqlite v1.34.5 h1:Bb6SR13/fjp15jt70CL4f18JIN7p7dnMExd+UFnF15g=
|
||||
modernc.org/sqlite v1.34.5/go.mod h1:YLuNmX9NKs8wRNK2ko1LW1NGYcc9FkBO69JOt1AR9JE=
|
||||
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
|
||||
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
|
||||
modernc.org/gc/v2 v2.6.3 h1:aJVhcqAte49LF+mGveZ5KPlsp4tdGdAOT4sipJXADjw=
|
||||
modernc.org/gc/v2 v2.6.3/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||
modernc.org/libc v1.61.13 h1:3LRd6ZO1ezsFiX1y+bHd1ipyEHIJKvuprv0sLTBwLW8=
|
||||
modernc.org/libc v1.61.13/go.mod h1:8F/uJWL/3nNil0Lgt1Dpz+GgkApWh04N3el3hxJcA6E=
|
||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.8.2 h1:cL9L4bcoAObu4NkxOlKWBWtNHIsnnACGF/TbqQ6sbcI=
|
||||
modernc.org/memory v1.8.2/go.mod h1:ZbjSvMO5NQ1A2i3bWeDiVMxIorXwdClKE/0SZ+BMotU=
|
||||
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
||||
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||
modernc.org/sqlite v1.35.0 h1:yQps4fegMnZFdphtzlfQTCNBWtS0CZv48pRpW3RFHRw=
|
||||
modernc.org/sqlite v1.35.0/go.mod h1:9cr2sicr7jIaWTBKQmAxQLfBv9LL0su4ZTEV+utt3ic=
|
||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
|
||||
@@ -3,5 +3,5 @@ package internal
|
||||
const (
|
||||
// JSONSchemaVersion is the current schema version output by the JSON encoder
|
||||
// This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment.
|
||||
JSONSchemaVersion = "16.0.19"
|
||||
JSONSchemaVersion = "16.0.22"
|
||||
)
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
// CloseAndLogError closes the given io.Closer and reports any errors found as a warning in the log
|
||||
func CloseAndLogError(closer io.Closer, location string) {
|
||||
if err := closer.Close(); err != nil {
|
||||
log.Warnf("unable to close file for location=%q: %+v", location, err)
|
||||
log.Debugf("unable to close file for location=%q: %+v", location, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,9 +10,9 @@ func SetContextLicenseScanner(ctx context.Context, s Scanner) context.Context {
|
||||
return context.WithValue(ctx, licenseScannerKey{}, s)
|
||||
}
|
||||
|
||||
func ContextLicenseScanner(ctx context.Context) Scanner {
|
||||
func ContextLicenseScanner(ctx context.Context) (Scanner, error) {
|
||||
if s, ok := ctx.Value(licenseScannerKey{}).(Scanner); ok {
|
||||
return s
|
||||
return s, nil
|
||||
}
|
||||
return NewDefaultScanner()
|
||||
}
|
||||
|
||||
@@ -2,67 +2,84 @@ package licenses
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/google/licensecheck"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
const coverageThreshold = 75 // determined by experimentation
|
||||
const (
|
||||
DefaultCoverageThreshold = 75 // determined by experimentation
|
||||
DefaultIncludeLicenseContent = false
|
||||
)
|
||||
|
||||
type Scanner interface {
|
||||
IdentifyLicenseIDs(context.Context, io.Reader) ([]string, error)
|
||||
IdentifyLicenseIDs(context.Context, io.Reader) ([]string, []byte, error)
|
||||
FileSearch(context.Context, file.LocationReadCloser) ([]file.License, error)
|
||||
PkgSearch(context.Context, file.LocationReadCloser) ([]pkg.License, error)
|
||||
}
|
||||
|
||||
var _ Scanner = (*scanner)(nil)
|
||||
|
||||
type scanner struct {
|
||||
coverageThreshold float64 // between 0 and 100
|
||||
scanner func([]byte) licensecheck.Coverage
|
||||
coverageThreshold float64 // between 0 and 100
|
||||
includeLicenseContent bool
|
||||
scanner func([]byte) licensecheck.Coverage
|
||||
}
|
||||
|
||||
type ScannerConfig struct {
|
||||
CoverageThreshold float64
|
||||
IncludeLicenseContent bool
|
||||
Scanner func([]byte) licensecheck.Coverage
|
||||
}
|
||||
|
||||
type Option func(*scanner)
|
||||
|
||||
func WithCoverage(coverage float64) Option {
|
||||
return func(s *scanner) {
|
||||
s.coverageThreshold = coverage
|
||||
}
|
||||
}
|
||||
|
||||
func WithIncludeLicenseContent(includeLicenseContent bool) Option {
|
||||
return func(s *scanner) {
|
||||
s.includeLicenseContent = includeLicenseContent
|
||||
}
|
||||
}
|
||||
|
||||
// NewDefaultScanner returns a scanner that uses a new instance of the default licensecheck package scanner.
|
||||
func NewDefaultScanner() Scanner {
|
||||
func NewDefaultScanner(o ...Option) (Scanner, error) {
|
||||
s, err := licensecheck.NewScanner(licensecheck.BuiltinLicenses())
|
||||
if err != nil {
|
||||
log.WithFields("error", err).Trace("unable to create default license scanner")
|
||||
s = nil
|
||||
return nil, fmt.Errorf("unable to create default license scanner: %w", err)
|
||||
}
|
||||
newScanner := &scanner{
|
||||
coverageThreshold: DefaultCoverageThreshold,
|
||||
includeLicenseContent: DefaultIncludeLicenseContent,
|
||||
scanner: s.Scan,
|
||||
}
|
||||
|
||||
for _, opt := range o {
|
||||
opt(newScanner)
|
||||
}
|
||||
return newScanner, nil
|
||||
}
|
||||
|
||||
// NewScanner generates a license Scanner with the given ScannerConfig
|
||||
// if config is nil NewDefaultScanner is used
|
||||
func NewScanner(c *ScannerConfig) (Scanner, error) {
|
||||
if c == nil {
|
||||
return NewDefaultScanner()
|
||||
}
|
||||
|
||||
return &scanner{
|
||||
coverageThreshold: coverageThreshold,
|
||||
scanner: s.Scan,
|
||||
}
|
||||
}
|
||||
|
||||
// TestingOnlyScanner returns a scanner that uses the built-in license scanner from the licensecheck package.
|
||||
// THIS IS ONLY MEANT FOR TEST CODE, NOT PRODUCTION CODE.
|
||||
func TestingOnlyScanner() Scanner {
|
||||
return &scanner{
|
||||
coverageThreshold: coverageThreshold,
|
||||
scanner: licensecheck.Scan,
|
||||
}
|
||||
}
|
||||
|
||||
func (s scanner) IdentifyLicenseIDs(_ context.Context, reader io.Reader) ([]string, error) {
|
||||
if s.scanner == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
content, err := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cov := s.scanner(content)
|
||||
if cov.Percent < s.coverageThreshold {
|
||||
// unknown or no licenses here?
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var ids []string
|
||||
for _, m := range cov.Match {
|
||||
ids = append(ids, m.ID)
|
||||
}
|
||||
return ids, nil
|
||||
coverageThreshold: c.CoverageThreshold,
|
||||
includeLicenseContent: c.IncludeLicenseContent,
|
||||
scanner: c.Scanner,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
package licenses
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/google/licensecheck"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestIdentifyLicenseIDs(t *testing.T) {
|
||||
type expectation struct {
|
||||
yieldError bool
|
||||
ids []string
|
||||
content []byte
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
in string
|
||||
expected expectation
|
||||
}{
|
||||
{
|
||||
name: "apache license 2.0",
|
||||
in: `test-fixtures/apache-license-2.0`,
|
||||
expected: expectation{
|
||||
yieldError: false,
|
||||
ids: []string{"Apache-2.0"},
|
||||
content: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "custom license includes content for IdentifyLicenseIDs",
|
||||
in: "test-fixtures/nvidia-software-and-cuda-supplement",
|
||||
expected: expectation{
|
||||
yieldError: false,
|
||||
ids: []string{},
|
||||
content: mustOpen("test-fixtures/nvidia-software-and-cuda-supplement"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
content, err := os.ReadFile(test.in)
|
||||
require.NoError(t, err)
|
||||
ids, content, err := testScanner(false).IdentifyLicenseIDs(context.TODO(), bytes.NewReader(content))
|
||||
if test.expected.yieldError {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Len(t, ids, len(test.expected.ids))
|
||||
require.Len(t, content, len(test.expected.content))
|
||||
|
||||
if len(test.expected.ids) > 0 {
|
||||
require.Equal(t, ids, test.expected.ids)
|
||||
}
|
||||
|
||||
if len(test.expected.content) > 0 {
|
||||
require.Equal(t, content, test.expected.content)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testScanner(includeLicenseContent bool) Scanner {
|
||||
return &scanner{
|
||||
coverageThreshold: DefaultCoverageThreshold,
|
||||
includeLicenseContent: includeLicenseContent,
|
||||
scanner: licensecheck.Scan,
|
||||
}
|
||||
}
|
||||
|
||||
func mustOpen(fixture string) []byte {
|
||||
content, err := os.ReadFile(fixture)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return content
|
||||
}
|
||||
+101
-6
@@ -2,24 +2,119 @@ package licenses
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/license"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
// Search scans the contents of a license file to attempt to determine the type of license it is
|
||||
func Search(ctx context.Context, scanner Scanner, reader file.LocationReadCloser) (licenses []pkg.License, err error) {
|
||||
const (
|
||||
unknownLicenseType = "UNKNOWN"
|
||||
UnknownLicensePrefix = unknownLicenseType + "_"
|
||||
)
|
||||
|
||||
func getCustomLicenseContentHash(contents []byte) string {
|
||||
hash := sha256.Sum256(contents)
|
||||
return fmt.Sprintf("%x", hash[:])
|
||||
}
|
||||
|
||||
func (s *scanner) IdentifyLicenseIDs(_ context.Context, reader io.Reader) ([]string, []byte, error) {
|
||||
if s.scanner == nil {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
content, err := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
cov := s.scanner(content)
|
||||
if cov.Percent < s.coverageThreshold {
|
||||
// unknown or no licenses here
|
||||
// => check return content to Search to process
|
||||
return nil, content, nil
|
||||
}
|
||||
|
||||
var ids []string
|
||||
for _, m := range cov.Match {
|
||||
ids = append(ids, m.ID)
|
||||
}
|
||||
return ids, nil, nil
|
||||
}
|
||||
|
||||
// PkgSearch scans the contents of a license file to attempt to determine the type of license it is
|
||||
func (s *scanner) PkgSearch(ctx context.Context, reader file.LocationReadCloser) (licenses []pkg.License, err error) {
|
||||
licenses = make([]pkg.License, 0)
|
||||
|
||||
ids, err := scanner.IdentifyLicenseIDs(ctx, reader)
|
||||
ids, content, err := s.IdentifyLicenseIDs(ctx, reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, id := range ids {
|
||||
lic := pkg.NewLicenseFromLocations(id, reader.Location)
|
||||
lic.Type = license.Concluded
|
||||
// IdentifyLicenseIDs can only return a list of ID or content
|
||||
// These return values are mutually exclusive.
|
||||
// If the scanner threshold for matching scores < 75% then we return the license full content
|
||||
if len(ids) > 0 {
|
||||
for _, id := range ids {
|
||||
lic := pkg.NewLicenseFromLocations(id, reader.Location)
|
||||
lic.Type = license.Concluded
|
||||
|
||||
licenses = append(licenses, lic)
|
||||
}
|
||||
} else if len(content) > 0 {
|
||||
// harmonize line endings to unix compatible first:
|
||||
// 1. \r\n => \n (Windows => UNIX)
|
||||
// 2. \r => \n (Macintosh => UNIX)
|
||||
content = []byte(strings.ReplaceAll(strings.ReplaceAll(string(content), "\r\n", "\n"), "\r", "\n"))
|
||||
|
||||
lic := pkg.NewLicenseFromLocations(unknownLicenseType, reader.Location)
|
||||
lic.SPDXExpression = UnknownLicensePrefix + getCustomLicenseContentHash(content)
|
||||
if s.includeLicenseContent {
|
||||
lic.Contents = string(content)
|
||||
}
|
||||
lic.Type = license.Declared
|
||||
|
||||
licenses = append(licenses, lic)
|
||||
}
|
||||
|
||||
return licenses, nil
|
||||
}
|
||||
|
||||
// FileSearch scans the contents of a license file to attempt to determine the type of license it is
|
||||
func (s *scanner) FileSearch(ctx context.Context, reader file.LocationReadCloser) (licenses []file.License, err error) {
|
||||
licenses = make([]file.License, 0)
|
||||
|
||||
ids, content, err := s.IdentifyLicenseIDs(ctx, reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// IdentifyLicenseIDs can only return a list of ID or content
|
||||
// These return values are mutually exclusive.
|
||||
// If the scanner threshold for matching scores < 75% then we return the license full content
|
||||
if len(ids) > 0 {
|
||||
for _, id := range ids {
|
||||
lic := file.NewLicense(id)
|
||||
lic.Type = license.Concluded
|
||||
|
||||
licenses = append(licenses, lic)
|
||||
}
|
||||
} else if len(content) > 0 {
|
||||
// harmonize line endings to unix compatible first:
|
||||
// 1. \r\n => \n (Windows => UNIX)
|
||||
// 2. \r => \n (Macintosh => UNIX)
|
||||
content = []byte(strings.ReplaceAll(strings.ReplaceAll(string(content), "\r\n", "\n"), "\r", "\n"))
|
||||
|
||||
lic := file.NewLicense(unknownLicenseType)
|
||||
lic.SPDXExpression = UnknownLicensePrefix + getCustomLicenseContentHash(content)
|
||||
if s.includeLicenseContent {
|
||||
lic.Contents = string(content)
|
||||
}
|
||||
lic.Type = license.Declared
|
||||
|
||||
licenses = append(licenses, lic)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
package licenses
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
type bytesReadCloser struct {
|
||||
bytes.Buffer
|
||||
}
|
||||
|
||||
func (brc *bytesReadCloser) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func newBytesReadCloser(data []byte) *bytesReadCloser {
|
||||
return &bytesReadCloser{
|
||||
Buffer: *bytes.NewBuffer(data),
|
||||
}
|
||||
}
|
||||
|
||||
func TestSearchFileLicenses(t *testing.T) {
|
||||
type expectation struct {
|
||||
yieldError bool
|
||||
licenses []file.License
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
in string
|
||||
includeUnkownLicenseContent bool
|
||||
expected expectation
|
||||
}{
|
||||
{
|
||||
name: "apache license 2.0",
|
||||
in: "test-fixtures/apache-license-2.0",
|
||||
expected: expectation{
|
||||
yieldError: false,
|
||||
licenses: []file.License{
|
||||
{
|
||||
Value: "Apache-2.0",
|
||||
SPDXExpression: "Apache-2.0",
|
||||
Type: "concluded",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
content, err := os.ReadFile(test.in)
|
||||
require.NoError(t, err)
|
||||
s := testScanner(false)
|
||||
result, err := s.FileSearch(ctx, file.NewLocationReadCloser(file.NewLocation("LICENSE"), io.NopCloser(bytes.NewReader(content))))
|
||||
if test.expected.yieldError {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Len(t, result, len(test.expected.licenses))
|
||||
|
||||
if len(test.expected.licenses) > 0 {
|
||||
require.Equal(t, test.expected.licenses, result)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSearchPkgLicenses(t *testing.T) {
|
||||
type expectation struct {
|
||||
wantErr require.ErrorAssertionFunc
|
||||
licenses []pkg.License
|
||||
}
|
||||
|
||||
testLocation := file.NewLocation("LICENSE")
|
||||
tests := []struct {
|
||||
name string
|
||||
in string
|
||||
includeUnkownLicenseContent bool
|
||||
expected expectation
|
||||
}{
|
||||
{
|
||||
name: "apache license 2.0",
|
||||
in: "test-fixtures/apache-license-2.0",
|
||||
expected: expectation{
|
||||
licenses: []pkg.License{
|
||||
{
|
||||
Value: "Apache-2.0",
|
||||
SPDXExpression: "Apache-2.0",
|
||||
Type: "concluded",
|
||||
URLs: nil,
|
||||
Locations: file.NewLocationSet(testLocation),
|
||||
Contents: "",
|
||||
},
|
||||
},
|
||||
wantErr: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "custom license no content by default",
|
||||
in: "test-fixtures/nvidia-software-and-cuda-supplement",
|
||||
expected: expectation{
|
||||
licenses: []pkg.License{
|
||||
{
|
||||
Value: "UNKNOWN",
|
||||
SPDXExpression: "UNKNOWN_eebcea3ab1d1a28e671de90119ffcfb35fe86951e4af1b17af52b7a82fcf7d0a",
|
||||
Type: "declared",
|
||||
URLs: nil,
|
||||
Locations: file.NewLocationSet(testLocation),
|
||||
Contents: "",
|
||||
},
|
||||
},
|
||||
wantErr: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "custom license with content when scanner has content config",
|
||||
in: "test-fixtures/nvidia-software-and-cuda-supplement",
|
||||
includeUnkownLicenseContent: true,
|
||||
expected: expectation{
|
||||
licenses: []pkg.License{
|
||||
{
|
||||
Value: "UNKNOWN",
|
||||
SPDXExpression: "UNKNOWN_eebcea3ab1d1a28e671de90119ffcfb35fe86951e4af1b17af52b7a82fcf7d0a",
|
||||
Type: "declared",
|
||||
URLs: nil,
|
||||
Locations: file.NewLocationSet(testLocation),
|
||||
Contents: string(mustOpen("test-fixtures/nvidia-software-and-cuda-supplement")),
|
||||
},
|
||||
},
|
||||
wantErr: nil,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
content, err := os.ReadFile(test.in)
|
||||
require.NoError(t, err)
|
||||
s := testScanner(test.includeUnkownLicenseContent)
|
||||
result, err := s.PkgSearch(ctx, file.NewLocationReadCloser(file.NewLocation("LICENSE"), io.NopCloser(bytes.NewReader(content))))
|
||||
if test.expected.wantErr != nil {
|
||||
test.expected.wantErr(t, err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Len(t, result, len(test.expected.licenses))
|
||||
|
||||
if len(test.expected.licenses) > 0 {
|
||||
require.Equal(t, test.expected.licenses, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
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 [yyyy] [name of copyright owner]
|
||||
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.
|
||||
@@ -0,0 +1,5 @@
|
||||
End User License Agreement
|
||||
--------------------------
|
||||
|
||||
NVIDIA Software License Agreement and CUDA Supplement to
|
||||
Software License Agreement. Last updated: October 8, 2021
|
||||
@@ -58,11 +58,10 @@ func generateRelationships(resolver file.Resolver, accessor sbomsync.Accessor, i
|
||||
|
||||
// PackagesToRemove returns a list of binary packages (resolved by the ELF cataloger) that should be removed from the SBOM
|
||||
// These packages are removed because they are already represented by a higher order packages in the SBOM.
|
||||
func PackagesToRemove(resolver file.Resolver, accessor sbomsync.Accessor) []artifact.ID {
|
||||
func PackagesToRemove(accessor sbomsync.Accessor) []artifact.ID {
|
||||
pkgsToDelete := make([]artifact.ID, 0)
|
||||
accessor.ReadFromSBOM(func(s *sbom.SBOM) {
|
||||
// OTHER package type > ELF package type > Binary package type
|
||||
pkgsToDelete = append(pkgsToDelete, getBinaryPackagesToDelete(resolver, s)...)
|
||||
// ELF package type > Binary package type
|
||||
pkgsToDelete = append(pkgsToDelete, compareElfBinaryPackages(s)...)
|
||||
})
|
||||
return pkgsToDelete
|
||||
@@ -114,33 +113,6 @@ func isElfPackage(p pkg.Package) bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
func getBinaryPackagesToDelete(resolver file.Resolver, s *sbom.SBOM) []artifact.ID {
|
||||
pkgsToDelete := make([]artifact.ID, 0)
|
||||
for p := range s.Artifacts.Packages.Enumerate() {
|
||||
if p.Type == pkg.BinaryPkg {
|
||||
continue
|
||||
}
|
||||
fileOwner, ok := p.Metadata.(pkg.FileOwner)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
ownedFiles := fileOwner.OwnedFiles()
|
||||
locations, err := resolver.FilesByPath(ownedFiles...)
|
||||
if err != nil {
|
||||
log.WithFields("error", err).Trace("unable to find path for owned file")
|
||||
continue
|
||||
}
|
||||
for _, loc := range locations {
|
||||
for _, pathPkg := range s.Artifacts.Packages.PackagesByPath(loc.RealPath) {
|
||||
if pathPkg.Type == pkg.BinaryPkg {
|
||||
pkgsToDelete = append(pkgsToDelete, pathPkg.ID())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return pkgsToDelete
|
||||
}
|
||||
|
||||
func populateRelationships(exec file.Executable, parentPkg pkg.Package, resolver file.Resolver, addRelationship func(artifact.Relationship), index *sharedLibraryIndex) {
|
||||
for _, libReference := range exec.ImportedLibraries {
|
||||
// for each library reference, check s.Artifacts.Packages.Sorted(pkg.BinaryPkg) for a binary package that represents that library
|
||||
|
||||
@@ -21,7 +21,7 @@ func TestPackagesToRemove(t *testing.T) {
|
||||
Name: "glibc",
|
||||
Version: "2.28-236.el8_9.12",
|
||||
Locations: file.NewLocationSet(
|
||||
file.NewLocation(glibcCoordinate.RealPath),
|
||||
file.NewLocation("path/to/rpmdb"),
|
||||
),
|
||||
Type: pkg.RpmPkg,
|
||||
Metadata: pkg.RpmDBEntry{
|
||||
@@ -88,50 +88,45 @@ func TestPackagesToRemove(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
resolver file.Resolver
|
||||
accessor sbomsync.Accessor
|
||||
want []artifact.ID
|
||||
}{
|
||||
{
|
||||
name: "remove packages that are overlapping rpm --> binary",
|
||||
resolver: file.NewMockResolverForPaths(glibcCoordinate.RealPath),
|
||||
accessor: newAccessor([]pkg.Package{glibCPackage, glibCBinaryELFPackage}, map[file.Coordinates]file.Executable{}, nil),
|
||||
want: []artifact.ID{glibCBinaryELFPackage.ID()},
|
||||
// this is surprising, right? the calling function reasons about if any generic binary package (regardless of it being an ELF package or not)
|
||||
// should be deleted or kept based on the user configuration to do so.
|
||||
want: []artifact.ID{},
|
||||
},
|
||||
{
|
||||
name: "keep packages that are overlapping rpm --> binary when the binary self identifies as an RPM",
|
||||
resolver: file.NewMockResolverForPaths(glibcCoordinate.RealPath),
|
||||
accessor: newAccessor([]pkg.Package{glibCPackage, glibCBinaryELFPackageAsRPM}, map[file.Coordinates]file.Executable{}, nil),
|
||||
want: []artifact.ID{},
|
||||
},
|
||||
{
|
||||
name: "remove no packages when there is a single binary package (or self identifying RPM)",
|
||||
resolver: file.NewMockResolverForPaths(glibcCoordinate.RealPath),
|
||||
accessor: newAccessor([]pkg.Package{glibCBinaryELFPackage, glibCBinaryELFPackageAsRPM}, map[file.Coordinates]file.Executable{}, nil),
|
||||
want: []artifact.ID{},
|
||||
},
|
||||
{
|
||||
name: "remove packages when there is a single binary package and a classifier package",
|
||||
resolver: file.NewMockResolverForPaths(glibcCoordinate.RealPath),
|
||||
accessor: newAccessor([]pkg.Package{glibCBinaryELFPackage, glibCBinaryClassifierPackage}, map[file.Coordinates]file.Executable{}, nil),
|
||||
want: []artifact.ID{glibCBinaryClassifierPackage.ID()},
|
||||
},
|
||||
{
|
||||
name: "ensure we're considering ELF packages, not just binary packages (supporting evidence)",
|
||||
resolver: file.NewMockResolverForPaths(glibcCoordinate.RealPath),
|
||||
accessor: newAccessor([]pkg.Package{glibCBinaryClassifierPackage}, map[file.Coordinates]file.Executable{}, nil),
|
||||
want: []artifact.ID{},
|
||||
},
|
||||
{
|
||||
name: "ensure we're considering ELF packages, not just binary packages (primary evidence)",
|
||||
resolver: file.NewMockResolverForPaths(glibcCoordinate.RealPath),
|
||||
accessor: newAccessor([]pkg.Package{libCBinaryClassifierPackage}, map[file.Coordinates]file.Executable{}, nil),
|
||||
want: []artifact.ID{},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
pkgsToDelete := PackagesToRemove(tt.resolver, tt.accessor)
|
||||
pkgsToDelete := PackagesToRemove(tt.accessor)
|
||||
if diff := cmp.Diff(tt.want, pkgsToDelete); diff != "" {
|
||||
t.Errorf("unexpected packages to delete (-want, +got): %s", diff)
|
||||
}
|
||||
|
||||
@@ -22,6 +22,9 @@ var (
|
||||
binaryCatalogerTypes = []pkg.Type{
|
||||
pkg.BinaryPkg,
|
||||
}
|
||||
bitnamiCatalogerTypes = []pkg.Type{
|
||||
pkg.BitnamiPkg,
|
||||
}
|
||||
binaryMetadataTypes = []string{
|
||||
reflect.TypeOf(pkg.ELFBinaryPackageNoteJSONPayload{}).Name(),
|
||||
reflect.TypeOf(pkg.BinarySignature{}).Name(),
|
||||
@@ -65,6 +68,10 @@ func excludeByFileOwnershipOverlap(r artifact.Relationship, c *pkg.Collection) a
|
||||
return idToRemove
|
||||
}
|
||||
|
||||
if idToRemove := identifyOverlappingBitnamiRelationship(parent, child); idToRemove != "" {
|
||||
return idToRemove
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -124,3 +131,24 @@ func identifyOverlappingOSRelationship(parent *pkg.Package, child *pkg.Package)
|
||||
|
||||
return child.ID()
|
||||
}
|
||||
|
||||
// identifyOverlappingBitnamiRelationship indicates the package ID to remove if this is a Bitnami pkg -> bin pkg relationship.
|
||||
func identifyOverlappingBitnamiRelationship(parent *pkg.Package, child *pkg.Package) artifact.ID {
|
||||
if !slices.Contains(bitnamiCatalogerTypes, parent.Type) {
|
||||
return ""
|
||||
}
|
||||
|
||||
if slices.Contains(binaryCatalogerTypes, child.Type) {
|
||||
return child.ID()
|
||||
}
|
||||
|
||||
if child.Metadata == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
if !slices.Contains(binaryMetadataTypes, reflect.TypeOf(child.Metadata).Name()) {
|
||||
return ""
|
||||
}
|
||||
|
||||
return child.ID()
|
||||
}
|
||||
|
||||
@@ -13,7 +13,8 @@ func TestExcludeByFileOwnershipOverlap(t *testing.T) {
|
||||
packageA := pkg.Package{Name: "package-a", Type: pkg.ApkPkg}
|
||||
packageB := pkg.Package{Name: "package-b", Type: pkg.BinaryPkg, Metadata: pkg.JavaVMInstallation{}}
|
||||
packageC := pkg.Package{Name: "package-c", Type: pkg.BinaryPkg, Metadata: pkg.ELFBinaryPackageNoteJSONPayload{Type: "rpm"}}
|
||||
for _, p := range []*pkg.Package{&packageA, &packageB, &packageC} {
|
||||
packageD := pkg.Package{Name: "package-d", Type: pkg.BitnamiPkg}
|
||||
for _, p := range []*pkg.Package{&packageA, &packageB, &packageC, &packageD} {
|
||||
p := p
|
||||
p.SetID()
|
||||
}
|
||||
@@ -46,6 +47,17 @@ func TestExcludeByFileOwnershipOverlap(t *testing.T) {
|
||||
packages: pkg.NewCollection(packageC, packageB),
|
||||
shouldExclude: true,
|
||||
},
|
||||
{
|
||||
// prove that Bitnami -> bin exclusions are wired
|
||||
name: "exclusions from bitnami -> binary",
|
||||
relationship: artifact.Relationship{
|
||||
Type: artifact.OwnershipByFileOverlapRelationship,
|
||||
From: packageD, // Bitnami
|
||||
To: packageC, // ELF binary
|
||||
},
|
||||
packages: pkg.NewCollection(packageD, packageC),
|
||||
shouldExclude: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
@@ -117,6 +129,63 @@ func TestIdentifyOverlappingOSRelationship(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestIdentifyOverlappingBitnamiRelationship(t *testing.T) {
|
||||
packageA := pkg.Package{Name: "package-a", Type: pkg.BitnamiPkg} // Bitnami package
|
||||
packageB := pkg.Package{Name: "package-b", Type: pkg.BinaryPkg}
|
||||
packageC := pkg.Package{Name: "package-c", Type: pkg.BinaryPkg, Metadata: pkg.BinarySignature{}}
|
||||
packageD := pkg.Package{Name: "package-d", Type: pkg.PythonPkg} // Language package
|
||||
packageE := pkg.Package{Name: "package-e", Type: pkg.BinaryPkg, Metadata: pkg.ELFBinaryPackageNoteJSONPayload{}}
|
||||
|
||||
for _, p := range []*pkg.Package{&packageA, &packageB, &packageC, &packageD, &packageE} {
|
||||
p.SetID()
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
parent *pkg.Package
|
||||
child *pkg.Package
|
||||
expectedID artifact.ID
|
||||
}{
|
||||
{
|
||||
name: "Bitnami -> binary without metadata",
|
||||
parent: &packageA,
|
||||
child: &packageB,
|
||||
expectedID: packageB.ID(), // Bitnami package to binary package, should return child ID
|
||||
},
|
||||
{
|
||||
name: "Bitnami -> binary with binary metadata",
|
||||
parent: &packageA,
|
||||
child: &packageC,
|
||||
expectedID: packageC.ID(), // Bitnami package to binary package with binary metadata, should return child ID
|
||||
},
|
||||
{
|
||||
name: "Bitnami -> non-binary package",
|
||||
parent: &packageA,
|
||||
child: &packageD,
|
||||
expectedID: "", // Bitnami package to non-binary package, no exclusion
|
||||
},
|
||||
{
|
||||
name: "Bitnami -> binary with ELF metadata",
|
||||
parent: &packageA,
|
||||
child: &packageE,
|
||||
expectedID: packageE.ID(), // Bitnami package to binary package with ELF metadata, should return child ID
|
||||
},
|
||||
{
|
||||
name: "non-Bitnami parent",
|
||||
parent: &packageD, // non-Bitnami package
|
||||
child: &packageC,
|
||||
expectedID: "", // non-Bitnami parent, no exclusion
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
resultID := identifyOverlappingBitnamiRelationship(tt.parent, tt.child)
|
||||
assert.Equal(t, tt.expectedID, resultID)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIdentifyOverlappingJVMRelationship(t *testing.T) {
|
||||
|
||||
packageA := pkg.Package{Name: "package-a", Type: pkg.BinaryPkg}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Code generated by go generate; DO NOT EDIT.
|
||||
// This file was generated by robots at 2024-08-20 11:33:49.349625 -0400 EDT m=+0.383911876
|
||||
// This file was generated by robots at 2024-11-29 09:22:05.594787 +0100 CET m=+2.414400043
|
||||
// using data from https://spdx.org/licenses/licenses.json
|
||||
package spdxlicense
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/cataloging"
|
||||
"github.com/anchore/syft/syft/cataloging/filecataloging"
|
||||
"github.com/anchore/syft/syft/cataloging/pkgcataloging"
|
||||
)
|
||||
|
||||
type CatalogingFactoryConfig struct {
|
||||
ComplianceConfig cataloging.ComplianceConfig
|
||||
SearchConfig cataloging.SearchConfig
|
||||
RelationshipsConfig cataloging.RelationshipsConfig
|
||||
DataGenerationConfig cataloging.DataGenerationConfig
|
||||
LicenseConfig cataloging.LicenseConfig
|
||||
PackagesConfig pkgcataloging.Config
|
||||
FilesConfig filecataloging.Config
|
||||
}
|
||||
|
||||
func DefaultCatalogingFactoryConfig() CatalogingFactoryConfig {
|
||||
return CatalogingFactoryConfig{
|
||||
ComplianceConfig: cataloging.DefaultComplianceConfig(),
|
||||
SearchConfig: cataloging.DefaultSearchConfig(),
|
||||
RelationshipsConfig: cataloging.DefaultRelationshipsConfig(),
|
||||
DataGenerationConfig: cataloging.DefaultDataGenerationConfig(),
|
||||
LicenseConfig: cataloging.DefaultLicenseConfig(),
|
||||
PackagesConfig: pkgcataloging.DefaultConfig(),
|
||||
FilesConfig: filecataloging.DefaultConfig(),
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/scylladb/go-set/strset"
|
||||
|
||||
"github.com/anchore/syft/syft/cataloging/pkgcataloging"
|
||||
"github.com/anchore/syft/syft/cataloging"
|
||||
)
|
||||
|
||||
var expressionNodePattern = regexp.MustCompile(`^([a-zA-Z0-9][a-zA-Z0-9-+]*)+$`)
|
||||
@@ -142,7 +142,7 @@ func (ec expressionContext) newExpression(exp string, operation Operation, token
|
||||
}
|
||||
}
|
||||
|
||||
func newExpressionsFromSelectionRequest(nc *expressionContext, selectionRequest pkgcataloging.SelectionRequest) Expressions {
|
||||
func newExpressionsFromSelectionRequest(nc *expressionContext, selectionRequest cataloging.SelectionRequest) Expressions {
|
||||
var all Expressions
|
||||
|
||||
for _, exp := range selectionRequest.DefaultNamesOrTags {
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/syft/cataloging/pkgcataloging"
|
||||
"github.com/anchore/syft/syft/cataloging"
|
||||
)
|
||||
|
||||
func Test_newExpressionsFromSelectionRequest(t *testing.T) {
|
||||
@@ -135,7 +135,7 @@ func Test_newExpressionsFromSelectionRequest(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
req := pkgcataloging.NewSelectionRequest().WithDefaults(tt.basis...).WithExpression(tt.expressions...)
|
||||
req := cataloging.NewSelectionRequest().WithDefaults(tt.basis...).WithExpression(tt.expressions...)
|
||||
|
||||
result := newExpressionsFromSelectionRequest(nc, req)
|
||||
if tt.expectedErrors != nil {
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/scylladb/go-set/strset"
|
||||
)
|
||||
|
||||
type factory func(cfg CatalogingFactoryConfig) Task
|
||||
|
||||
type Factories []factory
|
||||
|
||||
func (f Factories) Tasks(cfg CatalogingFactoryConfig) ([]Task, error) {
|
||||
var allTasks []Task
|
||||
taskNames := strset.New()
|
||||
duplicateTaskNames := strset.New()
|
||||
var err error
|
||||
for _, fact := range f {
|
||||
tsk := fact(cfg)
|
||||
if tsk == nil {
|
||||
continue
|
||||
}
|
||||
tskName := tsk.Name()
|
||||
if taskNames.Has(tskName) {
|
||||
duplicateTaskNames.Add(tskName)
|
||||
}
|
||||
|
||||
allTasks = append(allTasks, tsk)
|
||||
taskNames.Add(tskName)
|
||||
}
|
||||
if duplicateTaskNames.Size() > 0 {
|
||||
names := duplicateTaskNames.List()
|
||||
sort.Strings(names)
|
||||
err = fmt.Errorf("duplicate cataloger task names: %v", strings.Join(names, ", "))
|
||||
}
|
||||
|
||||
return allTasks, err
|
||||
}
|
||||
+58
-27
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/anchore/syft/internal/sbomsync"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/cataloging/filecataloging"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/file/cataloger/executable"
|
||||
"github.com/anchore/syft/syft/file/cataloger/filecontent"
|
||||
@@ -15,14 +16,27 @@ import (
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
)
|
||||
|
||||
func NewFileDigestCatalogerTask(selection file.Selection, hashers ...crypto.Hash) Task {
|
||||
if selection == file.NoFilesSelection || len(hashers) == 0 {
|
||||
return nil
|
||||
func DefaultFileTaskFactories() Factories {
|
||||
return Factories{
|
||||
newFileDigestCatalogerTaskFactory("digest"),
|
||||
newFileMetadataCatalogerTaskFactory("file-metadata"),
|
||||
newFileContentCatalogerTaskFactory("content"),
|
||||
newExecutableCatalogerTaskFactory("binary-metadata"),
|
||||
}
|
||||
}
|
||||
|
||||
digestsCataloger := filedigest.NewCataloger(hashers)
|
||||
func newFileDigestCatalogerTaskFactory(tags ...string) factory {
|
||||
return func(cfg CatalogingFactoryConfig) Task {
|
||||
return newFileDigestCatalogerTask(cfg.FilesConfig.Selection, cfg.FilesConfig.Hashers, tags...)
|
||||
}
|
||||
}
|
||||
|
||||
func newFileDigestCatalogerTask(selection file.Selection, hashers []crypto.Hash, tags ...string) Task {
|
||||
fn := func(ctx context.Context, resolver file.Resolver, builder sbomsync.Builder) error {
|
||||
if selection == file.NoFilesSelection || len(hashers) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
accessor := builder.(sbomsync.Accessor)
|
||||
|
||||
coordinates, ok := coordinatesForSelection(selection, builder.(sbomsync.Accessor))
|
||||
@@ -30,7 +44,7 @@ func NewFileDigestCatalogerTask(selection file.Selection, hashers ...crypto.Hash
|
||||
return nil
|
||||
}
|
||||
|
||||
result, err := digestsCataloger.Catalog(ctx, resolver, coordinates...)
|
||||
result, err := filedigest.NewCataloger(hashers).Catalog(ctx, resolver, coordinates...)
|
||||
|
||||
accessor.WriteToSBOM(func(sbom *sbom.SBOM) {
|
||||
sbom.Artifacts.FileDigests = result
|
||||
@@ -39,17 +53,21 @@ func NewFileDigestCatalogerTask(selection file.Selection, hashers ...crypto.Hash
|
||||
return err
|
||||
}
|
||||
|
||||
return NewTask("file-digest-cataloger", fn)
|
||||
return NewTask("file-digest-cataloger", fn, commonFileTags(tags)...)
|
||||
}
|
||||
|
||||
func NewFileMetadataCatalogerTask(selection file.Selection) Task {
|
||||
if selection == file.NoFilesSelection {
|
||||
return nil
|
||||
func newFileMetadataCatalogerTaskFactory(tags ...string) factory {
|
||||
return func(cfg CatalogingFactoryConfig) Task {
|
||||
return newFileMetadataCatalogerTask(cfg.FilesConfig.Selection, tags...)
|
||||
}
|
||||
}
|
||||
|
||||
metadataCataloger := filemetadata.NewCataloger()
|
||||
|
||||
func newFileMetadataCatalogerTask(selection file.Selection, tags ...string) Task {
|
||||
fn := func(ctx context.Context, resolver file.Resolver, builder sbomsync.Builder) error {
|
||||
if selection == file.NoFilesSelection {
|
||||
return nil
|
||||
}
|
||||
|
||||
accessor := builder.(sbomsync.Accessor)
|
||||
|
||||
coordinates, ok := coordinatesForSelection(selection, builder.(sbomsync.Accessor))
|
||||
@@ -57,7 +75,7 @@ func NewFileMetadataCatalogerTask(selection file.Selection) Task {
|
||||
return nil
|
||||
}
|
||||
|
||||
result, err := metadataCataloger.Catalog(ctx, resolver, coordinates...)
|
||||
result, err := filemetadata.NewCataloger().Catalog(ctx, resolver, coordinates...)
|
||||
|
||||
accessor.WriteToSBOM(func(sbom *sbom.SBOM) {
|
||||
sbom.Artifacts.FileMetadata = result
|
||||
@@ -66,20 +84,24 @@ func NewFileMetadataCatalogerTask(selection file.Selection) Task {
|
||||
return err
|
||||
}
|
||||
|
||||
return NewTask("file-metadata-cataloger", fn)
|
||||
return NewTask("file-metadata-cataloger", fn, commonFileTags(tags)...)
|
||||
}
|
||||
|
||||
func NewFileContentCatalogerTask(cfg filecontent.Config) Task {
|
||||
if len(cfg.Globs) == 0 {
|
||||
return nil
|
||||
func newFileContentCatalogerTaskFactory(tags ...string) factory {
|
||||
return func(cfg CatalogingFactoryConfig) Task {
|
||||
return newFileContentCatalogerTask(cfg.FilesConfig.Content, tags...)
|
||||
}
|
||||
}
|
||||
|
||||
cat := filecontent.NewCataloger(cfg)
|
||||
|
||||
func newFileContentCatalogerTask(cfg filecontent.Config, tags ...string) Task {
|
||||
fn := func(ctx context.Context, resolver file.Resolver, builder sbomsync.Builder) error {
|
||||
if len(cfg.Globs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
accessor := builder.(sbomsync.Accessor)
|
||||
|
||||
result, err := cat.Catalog(ctx, resolver)
|
||||
result, err := filecontent.NewCataloger(cfg).Catalog(ctx, resolver)
|
||||
|
||||
accessor.WriteToSBOM(func(sbom *sbom.SBOM) {
|
||||
sbom.Artifacts.FileContents = result
|
||||
@@ -88,20 +110,24 @@ func NewFileContentCatalogerTask(cfg filecontent.Config) Task {
|
||||
return err
|
||||
}
|
||||
|
||||
return NewTask("file-content-cataloger", fn)
|
||||
return NewTask("file-content-cataloger", fn, commonFileTags(tags)...)
|
||||
}
|
||||
|
||||
func NewExecutableCatalogerTask(selection file.Selection, cfg executable.Config) Task {
|
||||
if selection == file.NoFilesSelection {
|
||||
return nil
|
||||
func newExecutableCatalogerTaskFactory(tags ...string) factory {
|
||||
return func(cfg CatalogingFactoryConfig) Task {
|
||||
return newExecutableCatalogerTask(cfg.FilesConfig.Selection, cfg.FilesConfig.Executable, tags...)
|
||||
}
|
||||
}
|
||||
|
||||
cat := executable.NewCataloger(cfg)
|
||||
|
||||
func newExecutableCatalogerTask(selection file.Selection, cfg executable.Config, tags ...string) Task {
|
||||
fn := func(_ context.Context, resolver file.Resolver, builder sbomsync.Builder) error {
|
||||
if selection == file.NoFilesSelection {
|
||||
return nil
|
||||
}
|
||||
|
||||
accessor := builder.(sbomsync.Accessor)
|
||||
|
||||
result, err := cat.Catalog(resolver)
|
||||
result, err := executable.NewCataloger(cfg).Catalog(resolver)
|
||||
|
||||
accessor.WriteToSBOM(func(sbom *sbom.SBOM) {
|
||||
sbom.Artifacts.Executables = result
|
||||
@@ -110,7 +136,7 @@ func NewExecutableCatalogerTask(selection file.Selection, cfg executable.Config)
|
||||
return err
|
||||
}
|
||||
|
||||
return NewTask("file-executable-cataloger", fn)
|
||||
return NewTask("file-executable-cataloger", fn, commonFileTags(tags)...)
|
||||
}
|
||||
|
||||
// TODO: this should be replaced with a fix that allows passing a coordinate or location iterator to the cataloger
|
||||
@@ -154,3 +180,8 @@ func coordinatesForSelection(selection file.Selection, accessor sbomsync.Accesso
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func commonFileTags(tags []string) []string {
|
||||
tags = append(tags, filecataloging.FileTag)
|
||||
return tags
|
||||
}
|
||||
|
||||
@@ -3,12 +3,9 @@ package task
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/scylladb/go-set/strset"
|
||||
|
||||
"github.com/anchore/syft/internal/bus"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/relationship"
|
||||
@@ -23,67 +20,18 @@ import (
|
||||
cpeutils "github.com/anchore/syft/syft/pkg/cataloger/common/cpe"
|
||||
)
|
||||
|
||||
type packageTaskFactory func(cfg CatalogingFactoryConfig) Task
|
||||
|
||||
type PackageTaskFactories []packageTaskFactory
|
||||
|
||||
type CatalogingFactoryConfig struct {
|
||||
ComplianceConfig cataloging.ComplianceConfig
|
||||
SearchConfig cataloging.SearchConfig
|
||||
RelationshipsConfig cataloging.RelationshipsConfig
|
||||
DataGenerationConfig cataloging.DataGenerationConfig
|
||||
PackagesConfig pkgcataloging.Config
|
||||
}
|
||||
|
||||
func DefaultCatalogingFactoryConfig() CatalogingFactoryConfig {
|
||||
return CatalogingFactoryConfig{
|
||||
ComplianceConfig: cataloging.DefaultComplianceConfig(),
|
||||
SearchConfig: cataloging.DefaultSearchConfig(),
|
||||
RelationshipsConfig: cataloging.DefaultRelationshipsConfig(),
|
||||
DataGenerationConfig: cataloging.DefaultDataGenerationConfig(),
|
||||
PackagesConfig: pkgcataloging.DefaultConfig(),
|
||||
}
|
||||
}
|
||||
|
||||
func newPackageTaskFactory(catalogerFactory func(CatalogingFactoryConfig) pkg.Cataloger, tags ...string) packageTaskFactory {
|
||||
func newPackageTaskFactory(catalogerFactory func(CatalogingFactoryConfig) pkg.Cataloger, tags ...string) factory {
|
||||
return func(cfg CatalogingFactoryConfig) Task {
|
||||
return NewPackageTask(cfg, catalogerFactory(cfg), tags...)
|
||||
}
|
||||
}
|
||||
|
||||
func newSimplePackageTaskFactory(catalogerFactory func() pkg.Cataloger, tags ...string) packageTaskFactory {
|
||||
func newSimplePackageTaskFactory(catalogerFactory func() pkg.Cataloger, tags ...string) factory {
|
||||
return func(cfg CatalogingFactoryConfig) Task {
|
||||
return NewPackageTask(cfg, catalogerFactory(), tags...)
|
||||
}
|
||||
}
|
||||
|
||||
func (f PackageTaskFactories) Tasks(cfg CatalogingFactoryConfig) ([]Task, error) {
|
||||
var allTasks []Task
|
||||
taskNames := strset.New()
|
||||
duplicateTaskNames := strset.New()
|
||||
var err error
|
||||
for _, factory := range f {
|
||||
tsk := factory(cfg)
|
||||
if tsk == nil {
|
||||
continue
|
||||
}
|
||||
tskName := tsk.Name()
|
||||
if taskNames.Has(tskName) {
|
||||
duplicateTaskNames.Add(tskName)
|
||||
}
|
||||
|
||||
allTasks = append(allTasks, tsk)
|
||||
taskNames.Add(tskName)
|
||||
}
|
||||
if duplicateTaskNames.Size() > 0 {
|
||||
names := duplicateTaskNames.List()
|
||||
sort.Strings(names)
|
||||
err = fmt.Errorf("duplicate cataloger task names: %v", strings.Join(names, ", "))
|
||||
}
|
||||
|
||||
return allTasks, err
|
||||
}
|
||||
|
||||
// NewPackageTask creates a Task function for a generic pkg.Cataloger, honoring the common configuration options.
|
||||
func NewPackageTask(cfg CatalogingFactoryConfig, c pkg.Cataloger, tags ...string) Task {
|
||||
fn := func(ctx context.Context, resolver file.Resolver, sbom sbomsync.Builder) error {
|
||||
@@ -153,7 +101,7 @@ func finalizePkgCatalogerResults(cfg CatalogingFactoryConfig, resolver file.Path
|
||||
// create file-to-package relationships for files owned by the package
|
||||
owningRelationships, err := packageFileOwnershipRelationships(p, resolver)
|
||||
if err != nil {
|
||||
log.Warnf("unable to create any package-file relationships for package name=%q type=%q: %v", p.Name, p.Type, err)
|
||||
log.Debugf("unable to create any package-file relationships for package name=%q type=%q: %v", p.Name, p.Type, err)
|
||||
} else {
|
||||
relationships = append(relationships, owningRelationships...)
|
||||
}
|
||||
@@ -226,7 +174,7 @@ func applyComplianceRules(p *pkg.Package, cfg cataloging.ComplianceConfig) (bool
|
||||
return true
|
||||
|
||||
case cataloging.ComplianceActionKeep:
|
||||
log.WithFields("pkg", p.String(), "location", loc).Tracef("package with missing %s, taking no action", fieldName)
|
||||
log.WithFields("pkg", p.String(), "location", loc, "field", fieldName).Trace("package with missing field, taking no action")
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/alpine"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/arch"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/binary"
|
||||
bitnamiSbomCataloger "github.com/anchore/syft/syft/pkg/cataloger/bitnami"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/cpp"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/dart"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/debian"
|
||||
@@ -31,6 +32,7 @@ import (
|
||||
sbomCataloger "github.com/anchore/syft/syft/pkg/cataloger/sbom"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/swift"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/swipl"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/terraform"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/wordpress"
|
||||
)
|
||||
|
||||
@@ -50,8 +52,8 @@ const (
|
||||
)
|
||||
|
||||
//nolint:funlen
|
||||
func DefaultPackageTaskFactories() PackageTaskFactories {
|
||||
return []packageTaskFactory{
|
||||
func DefaultPackageTaskFactories() Factories {
|
||||
return []factory{
|
||||
// OS package installed catalogers ///////////////////////////////////////////////////////////////////////////
|
||||
newSimplePackageTaskFactory(arch.NewDBCataloger, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, pkgcataloging.OSTag, "linux", "alpm", "archlinux"),
|
||||
newSimplePackageTaskFactory(alpine.NewDBCataloger, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, pkgcataloging.OSTag, "linux", "apk", "alpine"),
|
||||
@@ -115,7 +117,10 @@ func DefaultPackageTaskFactories() PackageTaskFactories {
|
||||
|
||||
// language-specific package for both image and directory scans (but not necessarily declared) ////////////////////////////////////////
|
||||
newSimplePackageTaskFactory(dotnet.NewDotnetPackagesLockCataloger, pkgcataloging.DeclaredTag, pkgcataloging.ImageTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "dotnet", "c#"),
|
||||
newSimplePackageTaskFactory(dotnet.NewDotnetPortableExecutableCataloger, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, pkgcataloging.LanguageTag, "dotnet", "c#", "binary"),
|
||||
newPackageTaskFactory(
|
||||
func(cfg CatalogingFactoryConfig) pkg.Cataloger {
|
||||
return dotnet.NewDotnetPortableExecutableCataloger(cfg.PackagesConfig.Dotnet)
|
||||
}, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, pkgcataloging.LanguageTag, "dotnet", "c#", "binary"),
|
||||
newSimplePackageTaskFactory(python.NewInstalledPackageCataloger, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, pkgcataloging.LanguageTag, "python"),
|
||||
newPackageTaskFactory(
|
||||
func(cfg CatalogingFactoryConfig) pkg.Cataloger {
|
||||
@@ -151,6 +156,8 @@ func DefaultPackageTaskFactories() PackageTaskFactories {
|
||||
pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, "linux", "kernel",
|
||||
),
|
||||
newSimplePackageTaskFactory(sbomCataloger.NewCataloger, "sbom"), // note: not evidence of installed packages
|
||||
newSimplePackageTaskFactory(bitnamiSbomCataloger.NewCataloger, "bitnami", pkgcataloging.InstalledTag, pkgcataloging.ImageTag),
|
||||
newSimplePackageTaskFactory(wordpress.NewWordpressPluginCataloger, pkgcataloging.DirectoryTag, pkgcataloging.ImageTag, "wordpress"),
|
||||
newSimplePackageTaskFactory(terraform.NewLockCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, "terraform"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ func finalizeRelationships(resolver file.Resolver, builder sbomsync.Builder, cfg
|
||||
|
||||
// remove ELF packages and Binary packages that are already
|
||||
// represented by a source package (e.g. a package that is evident by some package manager)
|
||||
builder.DeletePackages(binary.PackagesToRemove(resolver, accessor)...)
|
||||
builder.DeletePackages(binary.PackagesToRemove(accessor)...)
|
||||
|
||||
// add relationships showing packages that are evident by a file which is owned by another package (package-to-package)
|
||||
if cfg.PackageFileOwnershipOverlap {
|
||||
|
||||
+152
-3
@@ -3,17 +3,19 @@ package task
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/scylladb/go-set/strset"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/cataloging/pkgcataloging"
|
||||
"github.com/anchore/syft/syft/cataloging"
|
||||
"github.com/anchore/syft/syft/cataloging/filecataloging"
|
||||
)
|
||||
|
||||
// Selection represents the users request for a subset of tasks to run and the resulting set of task names that were
|
||||
// selected. Additionally, all tokens that were matched on to reach the returned conclusion are also provided.
|
||||
type Selection struct {
|
||||
Request pkgcataloging.SelectionRequest
|
||||
Request cataloging.SelectionRequest
|
||||
Result *strset.Set
|
||||
TokensByTask map[string]TokenSelection
|
||||
}
|
||||
@@ -52,7 +54,18 @@ func newSelection() Selection {
|
||||
// Select parses the given expressions as two sets: expressions that represent a "set" operation, and expressions that
|
||||
// represent all other operations. The parsed expressions are then evaluated against the given tasks to return
|
||||
// a subset (or the same) set of tasks.
|
||||
func Select(allTasks []Task, selectionRequest pkgcataloging.SelectionRequest) ([]Task, Selection, error) {
|
||||
func Select(allTasks []Task, selectionRequest cataloging.SelectionRequest) ([]Task, Selection, error) {
|
||||
ensureDefaultSelectionHasFiles(&selectionRequest, allTasks)
|
||||
|
||||
return _select(allTasks, selectionRequest)
|
||||
}
|
||||
|
||||
func _select(allTasks []Task, selectionRequest cataloging.SelectionRequest) ([]Task, Selection, error) {
|
||||
if selectionRequest.IsEmpty() {
|
||||
selection := newSelection()
|
||||
selection.Request = selectionRequest
|
||||
return nil, selection, nil
|
||||
}
|
||||
nodes := newExpressionsFromSelectionRequest(newExpressionContext(allTasks), selectionRequest)
|
||||
|
||||
finalTasks, selection := selectByExpressions(allTasks, nodes)
|
||||
@@ -62,6 +75,142 @@ func Select(allTasks []Task, selectionRequest pkgcataloging.SelectionRequest) ([
|
||||
return finalTasks, selection, nodes.Validate()
|
||||
}
|
||||
|
||||
// ensureDefaultSelectionHasFiles ensures that the default selection request has the "file" tag, as this is a required
|
||||
// for backwards compatibility (when catalogers were only for packages and not for separate groups of tasks).
|
||||
func ensureDefaultSelectionHasFiles(selectionRequest *cataloging.SelectionRequest, allTasks ...[]Task) {
|
||||
for _, ts := range allTasks {
|
||||
_, leftOver := tagsOrNamesThatTaskGroupRespondsTo(ts, strset.New(filecataloging.FileTag))
|
||||
if leftOver.Has(filecataloging.FileTag) {
|
||||
// the given set of tasks do not respond to file, so don't include it in the default selection
|
||||
continue
|
||||
}
|
||||
|
||||
defaultNamesOrTags := strset.New(selectionRequest.DefaultNamesOrTags...)
|
||||
removals := strset.New(selectionRequest.RemoveNamesOrTags...)
|
||||
missingFileIshTag := !defaultNamesOrTags.Has(filecataloging.FileTag) && !defaultNamesOrTags.Has("all") && !defaultNamesOrTags.Has("default")
|
||||
if missingFileIshTag && !removals.Has(filecataloging.FileTag) {
|
||||
log.Warnf("adding '%s' tag to the default cataloger selection, to override add '-%s' to the cataloger selection request", filecataloging.FileTag, filecataloging.FileTag)
|
||||
selectionRequest.DefaultNamesOrTags = append(selectionRequest.DefaultNamesOrTags, filecataloging.FileTag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SelectInGroups is a convenience function that allows for selecting tasks from multiple groups of tasks. The original
|
||||
// request is split into sub-requests, where only tokens that are relevant to the given group of tasks are considered.
|
||||
// If tokens are passed that are not relevant to any group of tasks, an error is returned.
|
||||
func SelectInGroups(taskGroups [][]Task, selectionRequest cataloging.SelectionRequest) ([][]Task, Selection, error) {
|
||||
ensureDefaultSelectionHasFiles(&selectionRequest, taskGroups...)
|
||||
|
||||
reqs, errs := splitCatalogerSelectionRequest(selectionRequest, taskGroups)
|
||||
if errs != nil {
|
||||
return nil, Selection{
|
||||
Request: selectionRequest,
|
||||
}, errs
|
||||
}
|
||||
|
||||
var finalTasks [][]Task
|
||||
var selections []Selection
|
||||
for idx, req := range reqs {
|
||||
tskGroup := taskGroups[idx]
|
||||
subFinalTasks, subSelection, err := _select(tskGroup, req)
|
||||
if err != nil {
|
||||
return nil, Selection{
|
||||
Request: selectionRequest,
|
||||
}, err
|
||||
}
|
||||
finalTasks = append(finalTasks, subFinalTasks)
|
||||
selections = append(selections, subSelection)
|
||||
}
|
||||
|
||||
return finalTasks, mergeSelections(selections, selectionRequest), nil
|
||||
}
|
||||
|
||||
func mergeSelections(selections []Selection, ogRequest cataloging.SelectionRequest) Selection {
|
||||
finalSelection := newSelection()
|
||||
for _, s := range selections {
|
||||
finalSelection.Result.Add(s.Result.List()...)
|
||||
for name, tokenSelection := range s.TokensByTask {
|
||||
if existing, exists := finalSelection.TokensByTask[name]; exists {
|
||||
existing.merge(tokenSelection)
|
||||
finalSelection.TokensByTask[name] = existing
|
||||
} else {
|
||||
finalSelection.TokensByTask[name] = tokenSelection
|
||||
}
|
||||
}
|
||||
}
|
||||
finalSelection.Request = ogRequest
|
||||
return finalSelection
|
||||
}
|
||||
|
||||
func splitCatalogerSelectionRequest(req cataloging.SelectionRequest, selectablePkgTaskGroups [][]Task) ([]cataloging.SelectionRequest, error) {
|
||||
requestTagsOrNames := allRequestReferences(req)
|
||||
leftoverTags := strset.New()
|
||||
usedTagsAndNames := strset.New()
|
||||
var usedTagGroups []*strset.Set
|
||||
for _, taskGroup := range selectablePkgTaskGroups {
|
||||
selectedTagOrNames, remainingTagsOrNames := tagsOrNamesThatTaskGroupRespondsTo(taskGroup, requestTagsOrNames)
|
||||
leftoverTags = strset.Union(leftoverTags, remainingTagsOrNames)
|
||||
usedTagGroups = append(usedTagGroups, selectedTagOrNames)
|
||||
usedTagsAndNames.Add(selectedTagOrNames.List()...)
|
||||
}
|
||||
|
||||
leftoverTags = strset.Difference(leftoverTags, usedTagsAndNames)
|
||||
leftoverTags.Remove("all")
|
||||
|
||||
if leftoverTags.Size() > 0 {
|
||||
l := leftoverTags.List()
|
||||
sort.Strings(l)
|
||||
return nil, fmt.Errorf("no cataloger tasks respond to the following selections: %v", strings.Join(l, ", "))
|
||||
}
|
||||
|
||||
var newSelections []cataloging.SelectionRequest
|
||||
for _, tags := range usedTagGroups {
|
||||
newSelections = append(newSelections, newSelectionWithTags(req, tags))
|
||||
}
|
||||
|
||||
return newSelections, nil
|
||||
}
|
||||
|
||||
func newSelectionWithTags(req cataloging.SelectionRequest, tags *strset.Set) cataloging.SelectionRequest {
|
||||
return cataloging.SelectionRequest{
|
||||
DefaultNamesOrTags: filterTags(req.DefaultNamesOrTags, tags),
|
||||
SubSelectTags: filterTags(req.SubSelectTags, tags),
|
||||
AddNames: filterTags(req.AddNames, tags),
|
||||
RemoveNamesOrTags: filterTags(req.RemoveNamesOrTags, tags),
|
||||
}
|
||||
}
|
||||
|
||||
func filterTags(reqTags []string, filterTags *strset.Set) []string {
|
||||
var filtered []string
|
||||
for _, tag := range reqTags {
|
||||
if filterTags.Has(tag) {
|
||||
filtered = append(filtered, tag)
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
func tagsOrNamesThatTaskGroupRespondsTo(tasks []Task, requestTagsOrNames *strset.Set) (*strset.Set, *strset.Set) {
|
||||
positiveRefs := strset.New()
|
||||
for _, t := range tasks {
|
||||
if sel, ok := t.(Selector); ok {
|
||||
positiveRefs.Add("all") // everything responds to "all"
|
||||
positiveRefs.Add(strset.Intersection(requestTagsOrNames, strset.New(sel.Selectors()...)).List()...)
|
||||
}
|
||||
positiveRefs.Add(t.Name())
|
||||
}
|
||||
return positiveRefs, strset.Difference(requestTagsOrNames, positiveRefs)
|
||||
}
|
||||
|
||||
func allRequestReferences(s cataloging.SelectionRequest) *strset.Set {
|
||||
st := strset.New()
|
||||
st.Add(s.DefaultNamesOrTags...)
|
||||
st.Add(s.SubSelectTags...)
|
||||
st.Add(s.AddNames...)
|
||||
st.Add(s.RemoveNamesOrTags...)
|
||||
return st
|
||||
}
|
||||
|
||||
// selectByExpressions the set of tasks to run based on the given expression(s).
|
||||
func selectByExpressions(ts tasks, nodes Expressions) (tasks, Selection) {
|
||||
if len(nodes) == 0 {
|
||||
|
||||
+459
-41
@@ -10,7 +10,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/internal/sbomsync"
|
||||
"github.com/anchore/syft/syft/cataloging/pkgcataloging"
|
||||
"github.com/anchore/syft/syft/cataloging"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
)
|
||||
|
||||
@@ -21,45 +21,54 @@ func dummyTask(name string, tags ...string) Task {
|
||||
}
|
||||
|
||||
// note: this test fixture does not need to be kept up to date here, but makes a great test subject
|
||||
func createDummyTasks() tasks {
|
||||
func createDummyPackageTasks() tasks {
|
||||
return []Task{
|
||||
// OS package installed catalogers
|
||||
dummyTask("alpm-db-cataloger", "directory", "installed", "image", "os", "alpm", "archlinux"),
|
||||
dummyTask("apk-db-cataloger", "directory", "installed", "image", "os", "apk", "alpine"),
|
||||
dummyTask("dpkg-db-cataloger", "directory", "installed", "image", "os", "dpkg", "debian"),
|
||||
dummyTask("portage-cataloger", "directory", "installed", "image", "os", "portage", "gentoo"),
|
||||
dummyTask("rpm-db-cataloger", "directory", "installed", "image", "os", "rpm", "redhat"),
|
||||
dummyTask("alpm-db-cataloger", "package", "directory", "installed", "image", "os", "alpm", "archlinux"),
|
||||
dummyTask("apk-db-cataloger", "package", "directory", "installed", "image", "os", "apk", "alpine"),
|
||||
dummyTask("dpkg-db-cataloger", "package", "directory", "installed", "image", "os", "dpkg", "debian"),
|
||||
dummyTask("portage-cataloger", "package", "directory", "installed", "image", "os", "portage", "gentoo"),
|
||||
dummyTask("rpm-db-cataloger", "package", "directory", "installed", "image", "os", "rpm", "redhat"),
|
||||
|
||||
// OS package declared catalogers
|
||||
dummyTask("rpm-archive-cataloger", "declared", "directory", "os", "rpm", "redhat"),
|
||||
dummyTask("rpm-archive-cataloger", "package", "declared", "directory", "os", "rpm", "redhat"),
|
||||
|
||||
// language-specific package installed catalogers
|
||||
dummyTask("conan-info-cataloger", "installed", "image", "language", "cpp", "conan"),
|
||||
dummyTask("javascript-package-cataloger", "installed", "image", "language", "javascript", "node"),
|
||||
dummyTask("php-composer-installed-cataloger", "installed", "image", "language", "php", "composer"),
|
||||
dummyTask("ruby-installed-gemspec-cataloger", "installed", "image", "language", "ruby", "gem", "gemspec"),
|
||||
dummyTask("rust-cargo-lock-cataloger", "installed", "image", "language", "rust", "binary"),
|
||||
dummyTask("conan-info-cataloger", "package", "installed", "image", "language", "cpp", "conan"),
|
||||
dummyTask("javascript-package-cataloger", "package", "installed", "image", "language", "javascript", "node"),
|
||||
dummyTask("php-composer-installed-cataloger", "package", "installed", "image", "language", "php", "composer"),
|
||||
dummyTask("ruby-installed-gemspec-cataloger", "package", "installed", "image", "language", "ruby", "gem", "gemspec"),
|
||||
dummyTask("rust-cargo-lock-cataloger", "package", "installed", "image", "language", "rust", "binary"),
|
||||
|
||||
// language-specific package declared catalogers
|
||||
dummyTask("conan-cataloger", "declared", "directory", "language", "cpp", "conan"),
|
||||
dummyTask("dart-pubspec-lock-cataloger", "declared", "directory", "language", "dart"),
|
||||
dummyTask("dotnet-deps-cataloger", "declared", "directory", "language", "dotnet", "c#"),
|
||||
dummyTask("elixir-mix-lock-cataloger", "declared", "directory", "language", "elixir"),
|
||||
dummyTask("erlang-rebar-lock-cataloger", "declared", "directory", "language", "erlang"),
|
||||
dummyTask("javascript-lock-cataloger", "declared", "directory", "language", "javascript", "node", "npm"),
|
||||
dummyTask("conan-cataloger", "package", "declared", "directory", "language", "cpp", "conan"),
|
||||
dummyTask("dart-pubspec-lock-cataloger", "package", "declared", "directory", "language", "dart"),
|
||||
dummyTask("dotnet-deps-cataloger", "package", "declared", "directory", "language", "dotnet", "c#"),
|
||||
dummyTask("elixir-mix-lock-cataloger", "package", "declared", "directory", "language", "elixir"),
|
||||
dummyTask("erlang-rebar-lock-cataloger", "package", "declared", "directory", "language", "erlang"),
|
||||
dummyTask("javascript-lock-cataloger", "package", "declared", "directory", "language", "javascript", "node", "npm"),
|
||||
|
||||
// language-specific package for both image and directory scans (but not necessarily declared)
|
||||
dummyTask("dotnet-portable-executable-cataloger", "directory", "installed", "image", "language", "dotnet", "c#"),
|
||||
dummyTask("python-installed-package-cataloger", "directory", "installed", "image", "language", "python"),
|
||||
dummyTask("go-module-binary-cataloger", "directory", "installed", "image", "language", "go", "golang", "gomod", "binary"),
|
||||
dummyTask("java-archive-cataloger", "directory", "installed", "image", "language", "java", "maven"),
|
||||
dummyTask("graalvm-native-image-cataloger", "directory", "installed", "image", "language", "java"),
|
||||
dummyTask("dotnet-portable-executable-cataloger", "package", "directory", "installed", "image", "language", "dotnet", "c#"),
|
||||
dummyTask("python-installed-package-cataloger", "package", "directory", "installed", "image", "language", "python"),
|
||||
dummyTask("go-module-binary-cataloger", "package", "directory", "installed", "image", "language", "go", "golang", "gomod", "binary"),
|
||||
dummyTask("java-archive-cataloger", "package", "directory", "installed", "image", "language", "java", "maven"),
|
||||
dummyTask("graalvm-native-image-cataloger", "package", "directory", "installed", "image", "language", "java"),
|
||||
|
||||
// other package catalogers
|
||||
dummyTask("binary-cataloger", "declared", "directory", "image", "binary"),
|
||||
dummyTask("github-actions-usage-cataloger", "declared", "directory", "github", "github-actions"),
|
||||
dummyTask("github-action-workflow-usage-cataloger", "declared", "directory", "github", "github-actions"),
|
||||
dummyTask("sbom-cataloger", "declared", "directory", "image", "sbom"),
|
||||
dummyTask("binary-cataloger", "package", "declared", "directory", "image", "binary"),
|
||||
dummyTask("github-actions-usage-cataloger", "package", "declared", "directory", "github", "github-actions"),
|
||||
dummyTask("github-action-workflow-usage-cataloger", "package", "declared", "directory", "github", "github-actions"),
|
||||
dummyTask("sbom-cataloger", "package", "declared", "directory", "image", "sbom"),
|
||||
}
|
||||
}
|
||||
|
||||
func createDummyFileTasks() tasks {
|
||||
return []Task{
|
||||
dummyTask("file-content-cataloger", "file", "content"),
|
||||
dummyTask("file-metadata-cataloger", "file", "metadata"),
|
||||
dummyTask("file-digest-cataloger", "file", "digest"),
|
||||
dummyTask("file-executable-cataloger", "file", "binary-metadata"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +81,7 @@ func TestSelect(t *testing.T) {
|
||||
expressions []string
|
||||
wantNames []string
|
||||
wantTokens map[string]TokenSelection
|
||||
wantRequest pkgcataloging.SelectionRequest
|
||||
wantRequest cataloging.SelectionRequest
|
||||
wantErr assert.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
@@ -82,11 +91,11 @@ func TestSelect(t *testing.T) {
|
||||
expressions: []string{},
|
||||
wantNames: []string{},
|
||||
wantTokens: map[string]TokenSelection{},
|
||||
wantRequest: pkgcataloging.SelectionRequest{},
|
||||
wantRequest: cataloging.SelectionRequest{},
|
||||
},
|
||||
{
|
||||
name: "use default tasks",
|
||||
allTasks: createDummyTasks(),
|
||||
allTasks: createDummyPackageTasks(),
|
||||
basis: []string{
|
||||
"image",
|
||||
},
|
||||
@@ -129,13 +138,13 @@ func TestSelect(t *testing.T) {
|
||||
"binary-cataloger": newTokenSelection([]string{"image"}, nil),
|
||||
"sbom-cataloger": newTokenSelection([]string{"image"}, nil),
|
||||
},
|
||||
wantRequest: pkgcataloging.SelectionRequest{
|
||||
wantRequest: cataloging.SelectionRequest{
|
||||
DefaultNamesOrTags: []string{"image"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "select, add, and remove tasks",
|
||||
allTasks: createDummyTasks(),
|
||||
allTasks: createDummyPackageTasks(),
|
||||
basis: []string{
|
||||
"image",
|
||||
},
|
||||
@@ -175,7 +184,7 @@ func TestSelect(t *testing.T) {
|
||||
"binary-cataloger": newTokenSelection([]string{"image"}, nil),
|
||||
"sbom-cataloger": newTokenSelection([]string{"image"}, nil),
|
||||
},
|
||||
wantRequest: pkgcataloging.SelectionRequest{
|
||||
wantRequest: cataloging.SelectionRequest{
|
||||
DefaultNamesOrTags: []string{"image"},
|
||||
SubSelectTags: []string{"os"},
|
||||
RemoveNamesOrTags: []string{"dpkg"},
|
||||
@@ -184,7 +193,7 @@ func TestSelect(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "allow for partial selections",
|
||||
allTasks: createDummyTasks(),
|
||||
allTasks: createDummyPackageTasks(),
|
||||
basis: []string{
|
||||
"image",
|
||||
},
|
||||
@@ -228,7 +237,7 @@ func TestSelect(t *testing.T) {
|
||||
"binary-cataloger": newTokenSelection([]string{"image"}, nil),
|
||||
"sbom-cataloger": newTokenSelection([]string{"image"}, nil),
|
||||
},
|
||||
wantRequest: pkgcataloging.SelectionRequest{
|
||||
wantRequest: cataloging.SelectionRequest{
|
||||
DefaultNamesOrTags: []string{"image"},
|
||||
SubSelectTags: []string{"os", "rust-cargo-lock-cataloger"},
|
||||
RemoveNamesOrTags: []string{"dpkg"},
|
||||
@@ -238,7 +247,7 @@ func TestSelect(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "select all tasks",
|
||||
allTasks: createDummyTasks(),
|
||||
allTasks: createDummyPackageTasks(),
|
||||
basis: []string{
|
||||
"all",
|
||||
},
|
||||
@@ -299,13 +308,13 @@ func TestSelect(t *testing.T) {
|
||||
"github-action-workflow-usage-cataloger": newTokenSelection([]string{"all"}, nil),
|
||||
"sbom-cataloger": newTokenSelection([]string{"all"}, nil),
|
||||
},
|
||||
wantRequest: pkgcataloging.SelectionRequest{
|
||||
wantRequest: cataloging.SelectionRequest{
|
||||
DefaultNamesOrTags: []string{"all"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "set default with multiple tags",
|
||||
allTasks: createDummyTasks(),
|
||||
allTasks: createDummyPackageTasks(),
|
||||
basis: []string{
|
||||
"gemspec",
|
||||
"python",
|
||||
@@ -319,10 +328,31 @@ func TestSelect(t *testing.T) {
|
||||
"ruby-installed-gemspec-cataloger": newTokenSelection([]string{"gemspec"}, nil),
|
||||
"python-installed-package-cataloger": newTokenSelection([]string{"python"}, nil),
|
||||
},
|
||||
wantRequest: pkgcataloging.SelectionRequest{
|
||||
wantRequest: cataloging.SelectionRequest{
|
||||
DefaultNamesOrTags: []string{"gemspec", "python"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "automatically add file to default tags",
|
||||
allTasks: createDummyFileTasks(),
|
||||
basis: []string{},
|
||||
expressions: []string{},
|
||||
wantNames: []string{
|
||||
"file-content-cataloger",
|
||||
"file-metadata-cataloger",
|
||||
"file-digest-cataloger",
|
||||
"file-executable-cataloger",
|
||||
},
|
||||
wantTokens: map[string]TokenSelection{
|
||||
"file-content-cataloger": newTokenSelection([]string{"file"}, nil),
|
||||
"file-metadata-cataloger": newTokenSelection([]string{"file"}, nil),
|
||||
"file-digest-cataloger": newTokenSelection([]string{"file"}, nil),
|
||||
"file-executable-cataloger": newTokenSelection([]string{"file"}, nil),
|
||||
},
|
||||
wantRequest: cataloging.SelectionRequest{
|
||||
DefaultNamesOrTags: []string{"file"},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
@@ -330,7 +360,7 @@ func TestSelect(t *testing.T) {
|
||||
tt.wantErr = assert.NoError
|
||||
}
|
||||
|
||||
req := pkgcataloging.NewSelectionRequest().WithDefaults(tt.basis...).WithExpression(tt.expressions...)
|
||||
req := cataloging.NewSelectionRequest().WithDefaults(tt.basis...).WithExpression(tt.expressions...)
|
||||
|
||||
got, gotEvidence, err := Select(tt.allTasks, req)
|
||||
tt.wantErr(t, err)
|
||||
@@ -367,3 +397,391 @@ func TestSelect(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectInGroups(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
taskGroups [][]Task
|
||||
selectionReq cataloging.SelectionRequest
|
||||
wantGroups [][]string
|
||||
wantTokens map[string]TokenSelection
|
||||
wantRequest cataloging.SelectionRequest
|
||||
wantErr assert.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "select only within the file tasks (leave package tasks alone)",
|
||||
taskGroups: [][]Task{
|
||||
createDummyPackageTasks(),
|
||||
createDummyFileTasks(),
|
||||
},
|
||||
selectionReq: cataloging.NewSelectionRequest().
|
||||
WithDefaults("image"). // note: file missing
|
||||
WithSubSelections("content", "digest"),
|
||||
wantGroups: [][]string{
|
||||
{
|
||||
// this is the original, untouched package task list
|
||||
"alpm-db-cataloger",
|
||||
"apk-db-cataloger",
|
||||
"dpkg-db-cataloger",
|
||||
"portage-cataloger",
|
||||
"rpm-db-cataloger",
|
||||
"conan-info-cataloger",
|
||||
"javascript-package-cataloger",
|
||||
"php-composer-installed-cataloger",
|
||||
"ruby-installed-gemspec-cataloger",
|
||||
"rust-cargo-lock-cataloger",
|
||||
"dotnet-portable-executable-cataloger",
|
||||
"python-installed-package-cataloger",
|
||||
"go-module-binary-cataloger",
|
||||
"java-archive-cataloger",
|
||||
"graalvm-native-image-cataloger",
|
||||
"binary-cataloger",
|
||||
"sbom-cataloger",
|
||||
},
|
||||
{
|
||||
// this has been filtered based on the request
|
||||
"file-content-cataloger",
|
||||
"file-digest-cataloger",
|
||||
},
|
||||
},
|
||||
wantTokens: map[string]TokenSelection{
|
||||
// packages
|
||||
"alpm-db-cataloger": newTokenSelection([]string{"image"}, nil),
|
||||
"apk-db-cataloger": newTokenSelection([]string{"image"}, nil),
|
||||
"binary-cataloger": newTokenSelection([]string{"image"}, nil),
|
||||
"conan-info-cataloger": newTokenSelection([]string{"image"}, nil),
|
||||
"dotnet-portable-executable-cataloger": newTokenSelection([]string{"image"}, nil),
|
||||
"dpkg-db-cataloger": newTokenSelection([]string{"image"}, nil),
|
||||
"go-module-binary-cataloger": newTokenSelection([]string{"image"}, nil),
|
||||
"graalvm-native-image-cataloger": newTokenSelection([]string{"image"}, nil),
|
||||
"java-archive-cataloger": newTokenSelection([]string{"image"}, nil),
|
||||
"javascript-package-cataloger": newTokenSelection([]string{"image"}, nil),
|
||||
"php-composer-installed-cataloger": newTokenSelection([]string{"image"}, nil),
|
||||
"portage-cataloger": newTokenSelection([]string{"image"}, nil),
|
||||
"python-installed-package-cataloger": newTokenSelection([]string{"image"}, nil),
|
||||
"rpm-db-cataloger": newTokenSelection([]string{"image"}, nil),
|
||||
"ruby-installed-gemspec-cataloger": newTokenSelection([]string{"image"}, nil),
|
||||
"rust-cargo-lock-cataloger": newTokenSelection([]string{"image"}, nil),
|
||||
"sbom-cataloger": newTokenSelection([]string{"image"}, nil),
|
||||
// files
|
||||
"file-content-cataloger": newTokenSelection([]string{"content", "file"}, nil),
|
||||
"file-digest-cataloger": newTokenSelection([]string{"digest", "file"}, nil),
|
||||
"file-executable-cataloger": newTokenSelection([]string{"file"}, nil),
|
||||
"file-metadata-cataloger": newTokenSelection([]string{"file"}, nil),
|
||||
},
|
||||
wantRequest: cataloging.SelectionRequest{
|
||||
DefaultNamesOrTags: []string{"image", "file"}, // note: file automatically added
|
||||
SubSelectTags: []string{"content", "digest"},
|
||||
},
|
||||
wantErr: assert.NoError,
|
||||
},
|
||||
{
|
||||
name: "select package tasks (leave file tasks alone)",
|
||||
taskGroups: [][]Task{
|
||||
createDummyPackageTasks(),
|
||||
createDummyFileTasks(),
|
||||
},
|
||||
selectionReq: cataloging.NewSelectionRequest().WithDefaults("image").WithSubSelections("os"),
|
||||
wantGroups: [][]string{
|
||||
{
|
||||
// filtered based on the request
|
||||
"alpm-db-cataloger",
|
||||
"apk-db-cataloger",
|
||||
"dpkg-db-cataloger",
|
||||
"portage-cataloger",
|
||||
"rpm-db-cataloger",
|
||||
},
|
||||
{
|
||||
// this is the original, untouched file task list
|
||||
"file-content-cataloger",
|
||||
"file-metadata-cataloger",
|
||||
"file-digest-cataloger",
|
||||
"file-executable-cataloger",
|
||||
},
|
||||
},
|
||||
wantTokens: map[string]TokenSelection{
|
||||
// packages - os
|
||||
"alpm-db-cataloger": newTokenSelection([]string{"os", "image"}, nil),
|
||||
"apk-db-cataloger": newTokenSelection([]string{"os", "image"}, nil),
|
||||
"rpm-archive-cataloger": newTokenSelection([]string{"os"}, nil),
|
||||
"rpm-db-cataloger": newTokenSelection([]string{"os", "image"}, nil),
|
||||
"portage-cataloger": newTokenSelection([]string{"os", "image"}, nil),
|
||||
"dpkg-db-cataloger": newTokenSelection([]string{"os", "image"}, nil),
|
||||
// packages - remaining
|
||||
"binary-cataloger": newTokenSelection([]string{"image"}, nil),
|
||||
"conan-info-cataloger": newTokenSelection([]string{"image"}, nil),
|
||||
"dotnet-portable-executable-cataloger": newTokenSelection([]string{"image"}, nil),
|
||||
"go-module-binary-cataloger": newTokenSelection([]string{"image"}, nil),
|
||||
"graalvm-native-image-cataloger": newTokenSelection([]string{"image"}, nil),
|
||||
"java-archive-cataloger": newTokenSelection([]string{"image"}, nil),
|
||||
"javascript-package-cataloger": newTokenSelection([]string{"image"}, nil),
|
||||
"php-composer-installed-cataloger": newTokenSelection([]string{"image"}, nil),
|
||||
"python-installed-package-cataloger": newTokenSelection([]string{"image"}, nil),
|
||||
"ruby-installed-gemspec-cataloger": newTokenSelection([]string{"image"}, nil),
|
||||
"rust-cargo-lock-cataloger": newTokenSelection([]string{"image"}, nil),
|
||||
"sbom-cataloger": newTokenSelection([]string{"image"}, nil),
|
||||
// files
|
||||
"file-content-cataloger": newTokenSelection([]string{"file"}, nil),
|
||||
"file-digest-cataloger": newTokenSelection([]string{"file"}, nil),
|
||||
"file-executable-cataloger": newTokenSelection([]string{"file"}, nil),
|
||||
"file-metadata-cataloger": newTokenSelection([]string{"file"}, nil),
|
||||
},
|
||||
wantRequest: cataloging.SelectionRequest{
|
||||
DefaultNamesOrTags: []string{"image", "file"},
|
||||
SubSelectTags: []string{"os"},
|
||||
},
|
||||
wantErr: assert.NoError,
|
||||
},
|
||||
{
|
||||
name: "select only file tasks (default)",
|
||||
taskGroups: [][]Task{
|
||||
createDummyPackageTasks(),
|
||||
createDummyFileTasks(),
|
||||
},
|
||||
selectionReq: cataloging.NewSelectionRequest().WithDefaults("file"),
|
||||
wantGroups: [][]string{
|
||||
// filtered based on the request
|
||||
nil,
|
||||
{
|
||||
// this is the original, untouched file task list
|
||||
"file-content-cataloger",
|
||||
"file-metadata-cataloger",
|
||||
"file-digest-cataloger",
|
||||
"file-executable-cataloger",
|
||||
},
|
||||
},
|
||||
wantTokens: map[string]TokenSelection{
|
||||
// files
|
||||
"file-content-cataloger": newTokenSelection([]string{"file"}, nil),
|
||||
"file-digest-cataloger": newTokenSelection([]string{"file"}, nil),
|
||||
"file-executable-cataloger": newTokenSelection([]string{"file"}, nil),
|
||||
"file-metadata-cataloger": newTokenSelection([]string{"file"}, nil),
|
||||
},
|
||||
wantRequest: cataloging.SelectionRequest{
|
||||
DefaultNamesOrTags: []string{"file"},
|
||||
},
|
||||
wantErr: assert.NoError,
|
||||
},
|
||||
{
|
||||
name: "select only file tasks (via removal of package)",
|
||||
taskGroups: [][]Task{
|
||||
createDummyPackageTasks(),
|
||||
createDummyFileTasks(),
|
||||
},
|
||||
selectionReq: cataloging.NewSelectionRequest().WithDefaults("file", "image").WithRemovals("package"),
|
||||
wantGroups: [][]string{
|
||||
// filtered based on the request
|
||||
nil,
|
||||
{
|
||||
// this is the original, untouched file task list
|
||||
"file-content-cataloger",
|
||||
"file-metadata-cataloger",
|
||||
"file-digest-cataloger",
|
||||
"file-executable-cataloger",
|
||||
},
|
||||
},
|
||||
wantTokens: map[string]TokenSelection{
|
||||
// packages
|
||||
"alpm-db-cataloger": newTokenSelection([]string{"image"}, []string{"package"}),
|
||||
"apk-db-cataloger": newTokenSelection([]string{"image"}, []string{"package"}),
|
||||
"binary-cataloger": newTokenSelection([]string{"image"}, []string{"package"}),
|
||||
"conan-info-cataloger": newTokenSelection([]string{"image"}, []string{"package"}),
|
||||
"dotnet-portable-executable-cataloger": newTokenSelection([]string{"image"}, []string{"package"}),
|
||||
"dpkg-db-cataloger": newTokenSelection([]string{"image"}, []string{"package"}),
|
||||
"go-module-binary-cataloger": newTokenSelection([]string{"image"}, []string{"package"}),
|
||||
"graalvm-native-image-cataloger": newTokenSelection([]string{"image"}, []string{"package"}),
|
||||
"java-archive-cataloger": newTokenSelection([]string{"image"}, []string{"package"}),
|
||||
"javascript-package-cataloger": newTokenSelection([]string{"image"}, []string{"package"}),
|
||||
"php-composer-installed-cataloger": newTokenSelection([]string{"image"}, []string{"package"}),
|
||||
"portage-cataloger": newTokenSelection([]string{"image"}, []string{"package"}),
|
||||
"python-installed-package-cataloger": newTokenSelection([]string{"image"}, []string{"package"}),
|
||||
"rpm-db-cataloger": newTokenSelection([]string{"image"}, []string{"package"}),
|
||||
"ruby-installed-gemspec-cataloger": newTokenSelection([]string{"image"}, []string{"package"}),
|
||||
"rust-cargo-lock-cataloger": newTokenSelection([]string{"image"}, []string{"package"}),
|
||||
"sbom-cataloger": newTokenSelection([]string{"image"}, []string{"package"}),
|
||||
"rpm-archive-cataloger": newTokenSelection(nil, []string{"package"}),
|
||||
"conan-cataloger": newTokenSelection(nil, []string{"package"}),
|
||||
"dart-pubspec-lock-cataloger": newTokenSelection(nil, []string{"package"}),
|
||||
"dotnet-deps-cataloger": newTokenSelection(nil, []string{"package"}),
|
||||
"elixir-mix-lock-cataloger": newTokenSelection(nil, []string{"package"}),
|
||||
"erlang-rebar-lock-cataloger": newTokenSelection(nil, []string{"package"}),
|
||||
"javascript-lock-cataloger": newTokenSelection(nil, []string{"package"}),
|
||||
"github-actions-usage-cataloger": newTokenSelection(nil, []string{"package"}),
|
||||
"github-action-workflow-usage-cataloger": newTokenSelection(nil, []string{"package"}),
|
||||
// files
|
||||
"file-content-cataloger": newTokenSelection([]string{"file"}, nil),
|
||||
"file-digest-cataloger": newTokenSelection([]string{"file"}, nil),
|
||||
"file-executable-cataloger": newTokenSelection([]string{"file"}, nil),
|
||||
"file-metadata-cataloger": newTokenSelection([]string{"file"}, nil),
|
||||
},
|
||||
wantRequest: cataloging.SelectionRequest{
|
||||
DefaultNamesOrTags: []string{"file", "image"},
|
||||
RemoveNamesOrTags: []string{"package"},
|
||||
},
|
||||
wantErr: assert.NoError,
|
||||
},
|
||||
{
|
||||
name: "select file and package tasks",
|
||||
taskGroups: [][]Task{
|
||||
createDummyPackageTasks(),
|
||||
createDummyFileTasks(),
|
||||
},
|
||||
selectionReq: cataloging.NewSelectionRequest().
|
||||
WithDefaults("image").
|
||||
WithSubSelections("os", "content", "digest"),
|
||||
wantGroups: [][]string{
|
||||
{
|
||||
// filtered based on the request
|
||||
"alpm-db-cataloger",
|
||||
"apk-db-cataloger",
|
||||
"dpkg-db-cataloger",
|
||||
"portage-cataloger",
|
||||
"rpm-db-cataloger",
|
||||
},
|
||||
{
|
||||
// filtered based on the request
|
||||
"file-content-cataloger",
|
||||
"file-digest-cataloger",
|
||||
},
|
||||
},
|
||||
wantTokens: map[string]TokenSelection{
|
||||
// packages - os
|
||||
"alpm-db-cataloger": newTokenSelection([]string{"os", "image"}, nil),
|
||||
"apk-db-cataloger": newTokenSelection([]string{"os", "image"}, nil),
|
||||
"rpm-archive-cataloger": newTokenSelection([]string{"os"}, nil),
|
||||
"rpm-db-cataloger": newTokenSelection([]string{"os", "image"}, nil),
|
||||
"portage-cataloger": newTokenSelection([]string{"os", "image"}, nil),
|
||||
"dpkg-db-cataloger": newTokenSelection([]string{"os", "image"}, nil),
|
||||
// packages - remaining
|
||||
"binary-cataloger": newTokenSelection([]string{"image"}, nil),
|
||||
"conan-info-cataloger": newTokenSelection([]string{"image"}, nil),
|
||||
"dotnet-portable-executable-cataloger": newTokenSelection([]string{"image"}, nil),
|
||||
"go-module-binary-cataloger": newTokenSelection([]string{"image"}, nil),
|
||||
"graalvm-native-image-cataloger": newTokenSelection([]string{"image"}, nil),
|
||||
"java-archive-cataloger": newTokenSelection([]string{"image"}, nil),
|
||||
"javascript-package-cataloger": newTokenSelection([]string{"image"}, nil),
|
||||
"php-composer-installed-cataloger": newTokenSelection([]string{"image"}, nil),
|
||||
"python-installed-package-cataloger": newTokenSelection([]string{"image"}, nil),
|
||||
"ruby-installed-gemspec-cataloger": newTokenSelection([]string{"image"}, nil),
|
||||
"rust-cargo-lock-cataloger": newTokenSelection([]string{"image"}, nil),
|
||||
"sbom-cataloger": newTokenSelection([]string{"image"}, nil),
|
||||
// files
|
||||
"file-content-cataloger": newTokenSelection([]string{"file", "content"}, nil), // note extra tags
|
||||
"file-digest-cataloger": newTokenSelection([]string{"file", "digest"}, nil), // note extra tags
|
||||
"file-executable-cataloger": newTokenSelection([]string{"file"}, nil),
|
||||
"file-metadata-cataloger": newTokenSelection([]string{"file"}, nil),
|
||||
},
|
||||
wantRequest: cataloging.SelectionRequest{
|
||||
DefaultNamesOrTags: []string{"image", "file"},
|
||||
SubSelectTags: []string{"os", "content", "digest"},
|
||||
},
|
||||
wantErr: assert.NoError,
|
||||
},
|
||||
{
|
||||
name: "complex selection with multiple operators across groups",
|
||||
taskGroups: [][]Task{
|
||||
createDummyPackageTasks(),
|
||||
createDummyFileTasks(),
|
||||
},
|
||||
selectionReq: cataloging.NewSelectionRequest().
|
||||
WithDefaults("os"). // note: no file tag present
|
||||
WithExpression("+github-actions-usage-cataloger", "-dpkg", "-digest", "content", "+file-metadata-cataloger", "-declared"),
|
||||
wantGroups: [][]string{
|
||||
{
|
||||
"alpm-db-cataloger",
|
||||
"apk-db-cataloger",
|
||||
"portage-cataloger",
|
||||
"rpm-db-cataloger",
|
||||
"github-actions-usage-cataloger",
|
||||
},
|
||||
{
|
||||
"file-content-cataloger",
|
||||
"file-metadata-cataloger",
|
||||
},
|
||||
},
|
||||
wantTokens: map[string]TokenSelection{
|
||||
// selected package tasks
|
||||
"alpm-db-cataloger": newTokenSelection([]string{"os"}, nil),
|
||||
"apk-db-cataloger": newTokenSelection([]string{"os"}, nil),
|
||||
"dpkg-db-cataloger": newTokenSelection([]string{"os"}, []string{"dpkg"}),
|
||||
"portage-cataloger": newTokenSelection([]string{"os"}, nil),
|
||||
"rpm-archive-cataloger": newTokenSelection([]string{"os"}, []string{"declared"}),
|
||||
"rpm-db-cataloger": newTokenSelection([]string{"os"}, nil),
|
||||
"github-actions-usage-cataloger": newTokenSelection([]string{"github-actions-usage-cataloger"}, []string{"declared"}),
|
||||
|
||||
// selected file tasks
|
||||
"file-content-cataloger": newTokenSelection([]string{"content", "file"}, nil),
|
||||
"file-metadata-cataloger": newTokenSelection([]string{"file-metadata-cataloger", "file"}, nil),
|
||||
|
||||
// removed package tasks
|
||||
"binary-cataloger": newTokenSelection(nil, []string{"declared"}),
|
||||
"conan-cataloger": newTokenSelection(nil, []string{"declared"}),
|
||||
"dart-pubspec-lock-cataloger": newTokenSelection(nil, []string{"declared"}),
|
||||
"dotnet-deps-cataloger": newTokenSelection(nil, []string{"declared"}),
|
||||
"elixir-mix-lock-cataloger": newTokenSelection(nil, []string{"declared"}),
|
||||
"erlang-rebar-lock-cataloger": newTokenSelection(nil, []string{"declared"}),
|
||||
"github-action-workflow-usage-cataloger": newTokenSelection(nil, []string{"declared"}),
|
||||
"javascript-lock-cataloger": newTokenSelection(nil, []string{"declared"}),
|
||||
"sbom-cataloger": newTokenSelection(nil, []string{"declared"}),
|
||||
|
||||
// removed file tasks
|
||||
"file-executable-cataloger": newTokenSelection([]string{"file"}, nil),
|
||||
"file-digest-cataloger": newTokenSelection([]string{"file"}, []string{"digest"}),
|
||||
},
|
||||
wantRequest: cataloging.SelectionRequest{
|
||||
DefaultNamesOrTags: []string{"os", "file"}, // note: file added automatically
|
||||
SubSelectTags: []string{"content"},
|
||||
RemoveNamesOrTags: []string{"dpkg", "digest", "declared"},
|
||||
AddNames: []string{"github-actions-usage-cataloger", "file-metadata-cataloger"},
|
||||
},
|
||||
wantErr: assert.NoError,
|
||||
},
|
||||
{
|
||||
name: "invalid tag",
|
||||
taskGroups: [][]Task{
|
||||
createDummyPackageTasks(),
|
||||
createDummyFileTasks(),
|
||||
},
|
||||
selectionReq: cataloging.NewSelectionRequest().WithDefaults("invalid"),
|
||||
wantGroups: nil,
|
||||
wantTokens: nil,
|
||||
wantRequest: cataloging.SelectionRequest{
|
||||
DefaultNamesOrTags: []string{"invalid", "file"},
|
||||
},
|
||||
wantErr: assert.Error,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.wantErr == nil {
|
||||
tt.wantErr = assert.NoError
|
||||
}
|
||||
|
||||
gotGroups, gotSelection, err := SelectInGroups(tt.taskGroups, tt.selectionReq)
|
||||
tt.wantErr(t, err)
|
||||
if err != nil {
|
||||
// dev note: this is useful for debugging when needed...
|
||||
//for _, e := range gotEvidence.Request.Expressions {
|
||||
// t.Logf("expression (errors %q): %#v", e.Errors, e)
|
||||
//}
|
||||
|
||||
// note: we DON'T bail early in validations... this is because we should always return the full set of
|
||||
// of selected tasks and surrounding evidence.
|
||||
}
|
||||
|
||||
var gotGroupNames [][]string
|
||||
for _, group := range gotGroups {
|
||||
var names []string
|
||||
for _, task := range group {
|
||||
names = append(names, task.Name())
|
||||
}
|
||||
gotGroupNames = append(gotGroupNames, names)
|
||||
}
|
||||
|
||||
assert.Equal(t, tt.wantGroups, gotGroupNames)
|
||||
assert.Equal(t, tt.wantTokens, gotSelection.TokensByTask)
|
||||
assert.Equal(t, tt.wantRequest, gotSelection.Request)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ func ProcessPathErrors(err error) error {
|
||||
}
|
||||
}
|
||||
unknowns, remainingErrors := ExtractCoordinateErrors(err)
|
||||
log.Warn(remainingErrors)
|
||||
log.Debug(remainingErrors)
|
||||
|
||||
var out []error
|
||||
for _, u := range unknowns {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "anchore.io/schema/syft/json/16.0.19/document",
|
||||
"$id": "anchore.io/schema/syft/json/16.0.22/document",
|
||||
"$ref": "#/$defs/Document",
|
||||
"$defs": {
|
||||
"AlpmDbEntry": {
|
||||
@@ -218,6 +218,44 @@
|
||||
"matches"
|
||||
]
|
||||
},
|
||||
"BitnamiSbomEntry": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"arch": {
|
||||
"type": "string"
|
||||
},
|
||||
"distro": {
|
||||
"type": "string"
|
||||
},
|
||||
"revision": {
|
||||
"type": "string"
|
||||
},
|
||||
"version": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"files": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name",
|
||||
"arch",
|
||||
"distro",
|
||||
"revision",
|
||||
"version",
|
||||
"path",
|
||||
"files"
|
||||
]
|
||||
},
|
||||
"CConanFileEntry": {
|
||||
"properties": {
|
||||
"ref": {
|
||||
@@ -1288,6 +1326,9 @@
|
||||
"$ref": "#/$defs/Location"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"contents": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
@@ -1650,6 +1691,9 @@
|
||||
{
|
||||
"$ref": "#/$defs/BinarySignature"
|
||||
},
|
||||
{
|
||||
"$ref": "#/$defs/BitnamiSbomEntry"
|
||||
},
|
||||
{
|
||||
"$ref": "#/$defs/CConanFileEntry"
|
||||
},
|
||||
@@ -1782,6 +1826,9 @@
|
||||
{
|
||||
"$ref": "#/$defs/SwiplpackPackage"
|
||||
},
|
||||
{
|
||||
"$ref": "#/$defs/TerraformLockProviderEntry"
|
||||
},
|
||||
{
|
||||
"$ref": "#/$defs/WordpressPluginEntry"
|
||||
}
|
||||
@@ -2720,6 +2767,32 @@
|
||||
"dependencies"
|
||||
]
|
||||
},
|
||||
"TerraformLockProviderEntry": {
|
||||
"properties": {
|
||||
"url": {
|
||||
"type": "string"
|
||||
},
|
||||
"constraints": {
|
||||
"type": "string"
|
||||
},
|
||||
"version": {
|
||||
"type": "string"
|
||||
},
|
||||
"hashes": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"url",
|
||||
"constraints",
|
||||
"version",
|
||||
"hashes"
|
||||
]
|
||||
},
|
||||
"WordpressPluginEntry": {
|
||||
"properties": {
|
||||
"pluginInstallDirectory": {
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package filecataloging
|
||||
|
||||
const (
|
||||
// FileTag should be used to identify catalogers that primarily discover information about files (as opposed to packages).
|
||||
FileTag = "file"
|
||||
)
|
||||
@@ -0,0 +1,15 @@
|
||||
package cataloging
|
||||
|
||||
import "github.com/anchore/syft/internal/licenses"
|
||||
|
||||
type LicenseConfig struct {
|
||||
IncludeUnkownLicenseContent bool `json:"include-unknown-license-content" yaml:"include-unknown-license-content" mapstructure:"include-unknown-license-content"`
|
||||
Coverage float64 `json:"coverage" yaml:"coverage" mapstructure:"coverage"`
|
||||
}
|
||||
|
||||
func DefaultLicenseConfig() LicenseConfig {
|
||||
return LicenseConfig{
|
||||
IncludeUnkownLicenseContent: licenses.DefaultIncludeLicenseContent,
|
||||
Coverage: licenses.DefaultCoverageThreshold,
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package pkgcataloging
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/binary"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/dotnet"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/golang"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/java"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/javascript"
|
||||
@@ -11,6 +12,7 @@ import (
|
||||
|
||||
type Config struct {
|
||||
Binary binary.ClassifierCatalogerConfig `yaml:"binary" json:"binary" mapstructure:"binary"`
|
||||
Dotnet dotnet.CatalogerConfig `yaml:"dotnet" json:"dotnet" mapstructure:"dotnet"`
|
||||
Golang golang.CatalogerConfig `yaml:"golang" json:"golang" mapstructure:"golang"`
|
||||
JavaArchive java.ArchiveCatalogerConfig `yaml:"java-archive" json:"java-archive" mapstructure:"java-archive"`
|
||||
JavaScript javascript.CatalogerConfig `yaml:"javascript" json:"javascript" mapstructure:"javascript"`
|
||||
@@ -21,6 +23,7 @@ type Config struct {
|
||||
func DefaultConfig() Config {
|
||||
return Config{
|
||||
Binary: binary.DefaultClassifierCatalogerConfig(),
|
||||
Dotnet: dotnet.DefaultCatalogerConfig(),
|
||||
Golang: golang.DefaultCatalogerConfig(),
|
||||
LinuxKernel: kernel.DefaultLinuxKernelCatalogerConfig(),
|
||||
Python: python.DefaultCatalogerConfig(),
|
||||
@@ -33,6 +36,11 @@ func (c Config) WithBinaryConfig(cfg binary.ClassifierCatalogerConfig) Config {
|
||||
return c
|
||||
}
|
||||
|
||||
func (c Config) WithDotnetConfig(cfg dotnet.CatalogerConfig) Config {
|
||||
c.Dotnet = cfg
|
||||
return c
|
||||
}
|
||||
|
||||
func (c Config) WithGolangConfig(cfg golang.CatalogerConfig) Config {
|
||||
c.Golang = cfg
|
||||
return c
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package pkgcataloging
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/cataloging"
|
||||
)
|
||||
|
||||
// SelectionRequest is deprecated: use cataloging.SelectionRequest instead
|
||||
type SelectionRequest = cataloging.SelectionRequest
|
||||
|
||||
// NewSelectionRequest is deprecated: use cataloging.NewSelectionRequest instead
|
||||
var NewSelectionRequest = cataloging.NewSelectionRequest
|
||||
@@ -1,8 +1,6 @@
|
||||
package pkgcataloging
|
||||
package cataloging
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
import "strings"
|
||||
|
||||
type SelectionRequest struct {
|
||||
DefaultNamesOrTags []string `json:"default,omitempty"`
|
||||
@@ -50,6 +48,10 @@ func (s SelectionRequest) WithRemovals(nameOrTags ...string) SelectionRequest {
|
||||
return s
|
||||
}
|
||||
|
||||
func (s SelectionRequest) IsEmpty() bool {
|
||||
return len(s.AddNames) == 0 && len(s.RemoveNamesOrTags) == 0 && len(s.SubSelectTags) == 0 && len(s.DefaultNamesOrTags) == 0
|
||||
}
|
||||
|
||||
func cleanSelection(tags []string) []string {
|
||||
var cleaned []string
|
||||
for _, tag := range tags {
|
||||
@@ -16,13 +16,14 @@ type configurationAuditTrail struct {
|
||||
DataGeneration cataloging.DataGenerationConfig `json:"data-generation" yaml:"data-generation" mapstructure:"data-generation"`
|
||||
Packages pkgcataloging.Config `json:"packages" yaml:"packages" mapstructure:"packages"`
|
||||
Files filecataloging.Config `json:"files" yaml:"files" mapstructure:"files"`
|
||||
Licenses cataloging.LicenseConfig `json:"licenses" yaml:"licenses" mapstructure:"licenses"`
|
||||
Catalogers catalogerManifest `json:"catalogers" yaml:"catalogers" mapstructure:"catalogers"`
|
||||
ExtraConfigs any `json:"extra,omitempty" yaml:"extra" mapstructure:"extra"`
|
||||
}
|
||||
|
||||
type catalogerManifest struct {
|
||||
Requested pkgcataloging.SelectionRequest `json:"requested" yaml:"requested" mapstructure:"requested"`
|
||||
Used []string `json:"used" yaml:"used" mapstructure:"used"`
|
||||
Requested cataloging.SelectionRequest `json:"requested" yaml:"requested" mapstructure:"requested"`
|
||||
Used []string `json:"used" yaml:"used" mapstructure:"used"`
|
||||
}
|
||||
|
||||
type marshalAPIConfiguration configurationAuditTrail
|
||||
|
||||
+13
-2
@@ -52,6 +52,7 @@ func CreateSBOM(ctx context.Context, src source.Source, cfg *CreateSBOMConfig) (
|
||||
DataGeneration: cfg.DataGeneration,
|
||||
Packages: cfg.Packages,
|
||||
Files: cfg.Files,
|
||||
Licenses: cfg.Licenses,
|
||||
Catalogers: *audit,
|
||||
ExtraConfigs: cfg.ToolConfiguration,
|
||||
},
|
||||
@@ -61,8 +62,15 @@ func CreateSBOM(ctx context.Context, src source.Source, cfg *CreateSBOMConfig) (
|
||||
},
|
||||
}
|
||||
|
||||
// inject a single license scanner for all package cataloging tasks into context
|
||||
ctx = licenses.SetContextLicenseScanner(ctx, licenses.NewDefaultScanner())
|
||||
// inject a single license scanner and content config for all package cataloging tasks into context
|
||||
licenseScanner, err := licenses.NewDefaultScanner(
|
||||
licenses.WithIncludeLicenseContent(cfg.Licenses.IncludeUnkownLicenseContent),
|
||||
licenses.WithCoverage(cfg.Licenses.Coverage),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not build licenseScanner for cataloging: %w", err)
|
||||
}
|
||||
ctx = licenses.SetContextLicenseScanner(ctx, licenseScanner)
|
||||
|
||||
catalogingProgress := monitorCatalogingTask(src.ID(), taskGroups)
|
||||
packageCatalogingProgress := monitorPackageCatalogingTask()
|
||||
@@ -125,6 +133,9 @@ func monitorCatalogingTask(srcID artifact.ID, tasks [][]task.Task) *monitor.Cata
|
||||
func formatTaskNames(tasks []task.Task) []string {
|
||||
set := strset.New()
|
||||
for _, td := range tasks {
|
||||
if td == nil {
|
||||
continue
|
||||
}
|
||||
set.Add(td.Name())
|
||||
}
|
||||
list := set.List()
|
||||
|
||||
+105
-48
@@ -26,16 +26,17 @@ type CreateSBOMConfig struct {
|
||||
Unknowns cataloging.UnknownsConfig
|
||||
DataGeneration cataloging.DataGenerationConfig
|
||||
Packages pkgcataloging.Config
|
||||
Licenses cataloging.LicenseConfig
|
||||
Files filecataloging.Config
|
||||
Parallelism int
|
||||
CatalogerSelection pkgcataloging.SelectionRequest
|
||||
CatalogerSelection cataloging.SelectionRequest
|
||||
|
||||
// audit what tool is being used to generate the SBOM
|
||||
ToolName string
|
||||
ToolVersion string
|
||||
ToolConfiguration interface{}
|
||||
|
||||
packageTaskFactories task.PackageTaskFactories
|
||||
packageTaskFactories task.Factories
|
||||
packageCatalogerReferences []pkgcataloging.CatalogerReference
|
||||
}
|
||||
|
||||
@@ -46,6 +47,7 @@ func DefaultCreateSBOMConfig() *CreateSBOMConfig {
|
||||
Relationships: cataloging.DefaultRelationshipsConfig(),
|
||||
DataGeneration: cataloging.DefaultDataGenerationConfig(),
|
||||
Packages: pkgcataloging.DefaultConfig(),
|
||||
Licenses: cataloging.DefaultLicenseConfig(),
|
||||
Files: filecataloging.DefaultConfig(),
|
||||
Parallelism: 1,
|
||||
packageTaskFactories: task.DefaultPackageTaskFactories(),
|
||||
@@ -134,6 +136,12 @@ func (c *CreateSBOMConfig) WithPackagesConfig(cfg pkgcataloging.Config) *CreateS
|
||||
return c
|
||||
}
|
||||
|
||||
// WithPackagesConfig allows for defining any specific behavior for syft-implemented catalogers.
|
||||
func (c *CreateSBOMConfig) WithLicenseConfig(cfg cataloging.LicenseConfig) *CreateSBOMConfig {
|
||||
c.Licenses = cfg
|
||||
return c
|
||||
}
|
||||
|
||||
// WithFilesConfig allows for defining file-based cataloging parameters.
|
||||
func (c *CreateSBOMConfig) WithFilesConfig(cfg filecataloging.Config) *CreateSBOMConfig {
|
||||
c.Files = cfg
|
||||
@@ -150,7 +158,7 @@ func (c *CreateSBOMConfig) WithoutFiles() *CreateSBOMConfig {
|
||||
}
|
||||
|
||||
// WithCatalogerSelection allows for adding to, removing from, or sub-selecting the final set of catalogers by name or tag.
|
||||
func (c *CreateSBOMConfig) WithCatalogerSelection(selection pkgcataloging.SelectionRequest) *CreateSBOMConfig {
|
||||
func (c *CreateSBOMConfig) WithCatalogerSelection(selection cataloging.SelectionRequest) *CreateSBOMConfig {
|
||||
c.CatalogerSelection = selection
|
||||
return c
|
||||
}
|
||||
@@ -166,6 +174,10 @@ func (c *CreateSBOMConfig) WithoutCatalogers() *CreateSBOMConfig {
|
||||
// WithCatalogers allows for adding user-provided catalogers to the final set of catalogers that will always be run
|
||||
// regardless of the source type or any cataloger selections provided.
|
||||
func (c *CreateSBOMConfig) WithCatalogers(catalogerRefs ...pkgcataloging.CatalogerReference) *CreateSBOMConfig {
|
||||
for i := range catalogerRefs {
|
||||
// ensure that all package catalogers have the package tag
|
||||
catalogerRefs[i].Tags = append(catalogerRefs[i].Tags, pkgcataloging.PackageTag)
|
||||
}
|
||||
c.packageCatalogerReferences = append(c.packageCatalogerReferences, catalogerRefs...)
|
||||
|
||||
return c
|
||||
@@ -182,8 +194,8 @@ func (c *CreateSBOMConfig) makeTaskGroups(src source.Description) ([][]task.Task
|
||||
environmentTasks := c.environmentTasks()
|
||||
relationshipsTasks := c.relationshipTasks(src)
|
||||
unknownTasks := c.unknownsTasks()
|
||||
fileTasks := c.fileTasks()
|
||||
pkgTasks, selectionEvidence, err := c.packageTasks(src)
|
||||
|
||||
pkgTasks, fileTasks, selectionEvidence, err := c.selectTasks(src)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -214,80 +226,117 @@ func (c *CreateSBOMConfig) makeTaskGroups(src source.Description) ([][]task.Task
|
||||
taskGroups...,
|
||||
)
|
||||
|
||||
var allTasks []task.Task
|
||||
allTasks = append(allTasks, pkgTasks...)
|
||||
allTasks = append(allTasks, fileTasks...)
|
||||
|
||||
return taskGroups, &catalogerManifest{
|
||||
Requested: selectionEvidence.Request,
|
||||
Used: formatTaskNames(pkgTasks),
|
||||
Used: formatTaskNames(allTasks),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// fileTasks returns the set of tasks that should be run to catalog files.
|
||||
func (c *CreateSBOMConfig) fileTasks() []task.Task {
|
||||
var tsks []task.Task
|
||||
|
||||
if t := task.NewFileDigestCatalogerTask(c.Files.Selection, c.Files.Hashers...); t != nil {
|
||||
tsks = append(tsks, t)
|
||||
}
|
||||
if t := task.NewFileMetadataCatalogerTask(c.Files.Selection); t != nil {
|
||||
tsks = append(tsks, t)
|
||||
}
|
||||
if t := task.NewFileContentCatalogerTask(c.Files.Content); t != nil {
|
||||
tsks = append(tsks, t)
|
||||
}
|
||||
if t := task.NewExecutableCatalogerTask(c.Files.Selection, c.Files.Executable); t != nil {
|
||||
tsks = append(tsks, t)
|
||||
func (c *CreateSBOMConfig) fileTasks(cfg task.CatalogingFactoryConfig) ([]task.Task, error) {
|
||||
tsks, err := task.DefaultFileTaskFactories().Tasks(cfg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to create file cataloger tasks: %w", err)
|
||||
}
|
||||
|
||||
return tsks
|
||||
return tsks, nil
|
||||
}
|
||||
|
||||
// packageTasks returns the set of tasks that should be run to catalog packages.
|
||||
func (c *CreateSBOMConfig) packageTasks(src source.Description) ([]task.Task, *task.Selection, error) {
|
||||
// selectTasks returns the set of tasks that should be run to catalog packages and files.
|
||||
func (c *CreateSBOMConfig) selectTasks(src source.Description) ([]task.Task, []task.Task, *task.Selection, error) {
|
||||
cfg := task.CatalogingFactoryConfig{
|
||||
SearchConfig: c.Search,
|
||||
RelationshipsConfig: c.Relationships,
|
||||
DataGenerationConfig: c.DataGeneration,
|
||||
PackagesConfig: c.Packages,
|
||||
ComplianceConfig: c.Compliance,
|
||||
FilesConfig: c.Files,
|
||||
}
|
||||
|
||||
persistentTasks, selectableTasks, err := c.allPackageTasks(cfg)
|
||||
persistentPkgTasks, selectablePkgTasks, err := c.allPackageTasks(cfg)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to create package cataloger tasks: %w", err)
|
||||
return nil, nil, nil, fmt.Errorf("unable to create package cataloger tasks: %w", err)
|
||||
}
|
||||
|
||||
req, err := finalSelectionRequest(c.CatalogerSelection, src)
|
||||
req, err := finalTaskSelectionRequest(c.CatalogerSelection, src)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
finalTasks, selection, err := task.Select(selectableTasks, *req)
|
||||
selectableFileTasks, err := c.fileTasks(cfg)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
finalTasks = append(finalTasks, persistentTasks...)
|
||||
|
||||
if len(finalTasks) == 0 {
|
||||
log.Warn("no catalogers selected")
|
||||
return finalTasks, &selection, nil
|
||||
taskGroups := [][]task.Task{
|
||||
selectablePkgTasks,
|
||||
selectableFileTasks,
|
||||
}
|
||||
|
||||
return finalTasks, &selection, nil
|
||||
finalTaskGroups, selection, err := task.SelectInGroups(taskGroups, *req)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
finalPkgTasks := finalTaskGroups[0]
|
||||
finalFileTasks := finalTaskGroups[1]
|
||||
|
||||
finalPkgTasks = append(finalPkgTasks, persistentPkgTasks...)
|
||||
|
||||
if len(finalPkgTasks) == 0 && len(finalFileTasks) == 0 {
|
||||
return nil, nil, nil, fmt.Errorf("no catalogers selected")
|
||||
}
|
||||
|
||||
logTaskNames(finalPkgTasks, "package cataloger")
|
||||
logTaskNames(finalFileTasks, "file cataloger")
|
||||
|
||||
if len(finalPkgTasks) == 0 && len(finalFileTasks) == 0 {
|
||||
return nil, nil, nil, fmt.Errorf("no catalogers selected")
|
||||
}
|
||||
|
||||
if len(finalPkgTasks) == 0 {
|
||||
log.Debug("no package catalogers selected")
|
||||
}
|
||||
|
||||
if len(finalFileTasks) == 0 {
|
||||
if c.Files.Selection != file.NoFilesSelection {
|
||||
log.Warnf("no file catalogers selected but file selection is configured as %q (this may be unintentional)", c.Files.Selection)
|
||||
} else {
|
||||
log.Debug("no file catalogers selected")
|
||||
}
|
||||
}
|
||||
|
||||
return finalPkgTasks, finalFileTasks, &selection, nil
|
||||
}
|
||||
|
||||
func finalSelectionRequest(req pkgcataloging.SelectionRequest, src source.Description) (*pkgcataloging.SelectionRequest, error) {
|
||||
func logTaskNames(tasks []task.Task, kind string) {
|
||||
// log as tree output (like tree command)
|
||||
log.Debugf("selected %d %s tasks", len(tasks), kind)
|
||||
names := formatTaskNames(tasks)
|
||||
for idx, t := range names {
|
||||
if idx == len(tasks)-1 {
|
||||
log.Tracef("└── %s", t)
|
||||
} else {
|
||||
log.Tracef("├── %s", t)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func finalTaskSelectionRequest(req cataloging.SelectionRequest, src source.Description) (*cataloging.SelectionRequest, error) {
|
||||
if len(req.DefaultNamesOrTags) == 0 {
|
||||
defaultTag, err := findDefaultTag(src)
|
||||
defaultTags, err := findDefaultTags(src)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to determine default cataloger tag: %w", err)
|
||||
}
|
||||
|
||||
if defaultTag != "" {
|
||||
req.DefaultNamesOrTags = append(req.DefaultNamesOrTags, defaultTag)
|
||||
}
|
||||
req.DefaultNamesOrTags = append(req.DefaultNamesOrTags, defaultTags...)
|
||||
|
||||
req.RemoveNamesOrTags = replaceDefaultTagReferences(defaultTag, req.RemoveNamesOrTags)
|
||||
req.SubSelectTags = replaceDefaultTagReferences(defaultTag, req.SubSelectTags)
|
||||
req.RemoveNamesOrTags = replaceDefaultTagReferences(defaultTags, req.RemoveNamesOrTags)
|
||||
req.SubSelectTags = replaceDefaultTagReferences(defaultTags, req.SubSelectTags)
|
||||
}
|
||||
|
||||
return &req, nil
|
||||
@@ -379,21 +428,29 @@ func (c *CreateSBOMConfig) Create(ctx context.Context, src source.Source) (*sbom
|
||||
return CreateSBOM(ctx, src, c)
|
||||
}
|
||||
|
||||
func findDefaultTag(src source.Description) (string, error) {
|
||||
func findDefaultTags(src source.Description) ([]string, error) {
|
||||
switch m := src.Metadata.(type) {
|
||||
case source.ImageMetadata:
|
||||
return pkgcataloging.ImageTag, nil
|
||||
return []string{pkgcataloging.ImageTag, filecataloging.FileTag}, nil
|
||||
case source.FileMetadata, source.DirectoryMetadata:
|
||||
return pkgcataloging.DirectoryTag, nil
|
||||
return []string{pkgcataloging.DirectoryTag, filecataloging.FileTag}, nil
|
||||
default:
|
||||
return "", fmt.Errorf("unable to determine default cataloger tag for source type=%T", m)
|
||||
return nil, fmt.Errorf("unable to determine default cataloger tag for source type=%T", m)
|
||||
}
|
||||
}
|
||||
|
||||
func replaceDefaultTagReferences(defaultTag string, lst []string) []string {
|
||||
func replaceDefaultTagReferences(defaultTags []string, lst []string) []string {
|
||||
for i, tag := range lst {
|
||||
if strings.ToLower(tag) == "default" {
|
||||
lst[i] = defaultTag
|
||||
switch len(defaultTags) {
|
||||
case 0:
|
||||
lst[i] = ""
|
||||
case 1:
|
||||
lst[i] = defaultTags[0]
|
||||
default:
|
||||
// remove the default tag and add the individual tags
|
||||
lst = append(lst[:i], append(defaultTags, lst[i+1:]...)...)
|
||||
}
|
||||
}
|
||||
}
|
||||
return lst
|
||||
|
||||
@@ -88,15 +88,15 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
||||
wantTaskNames: [][]string{
|
||||
environmentCatalogerNames(),
|
||||
pkgCatalogerNamesWithTagOrName(t, "image"),
|
||||
fileCatalogerNames(true, true, true),
|
||||
fileCatalogerNames(),
|
||||
relationshipCatalogerNames(),
|
||||
unknownsTaskNames(),
|
||||
},
|
||||
wantManifest: &catalogerManifest{
|
||||
Requested: pkgcataloging.SelectionRequest{
|
||||
DefaultNamesOrTags: []string{"image"},
|
||||
Requested: cataloging.SelectionRequest{
|
||||
DefaultNamesOrTags: []string{"image", "file"},
|
||||
},
|
||||
Used: pkgCatalogerNamesWithTagOrName(t, "image"),
|
||||
Used: flatten(pkgCatalogerNamesWithTagOrName(t, "image"), fileCatalogerNames()),
|
||||
},
|
||||
wantErr: require.NoError,
|
||||
},
|
||||
@@ -107,15 +107,15 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
||||
wantTaskNames: [][]string{
|
||||
environmentCatalogerNames(),
|
||||
pkgCatalogerNamesWithTagOrName(t, "directory"),
|
||||
fileCatalogerNames(true, true, true),
|
||||
fileCatalogerNames(),
|
||||
relationshipCatalogerNames(),
|
||||
unknownsTaskNames(),
|
||||
},
|
||||
wantManifest: &catalogerManifest{
|
||||
Requested: pkgcataloging.SelectionRequest{
|
||||
DefaultNamesOrTags: []string{"directory"},
|
||||
Requested: cataloging.SelectionRequest{
|
||||
DefaultNamesOrTags: []string{"directory", "file"},
|
||||
},
|
||||
Used: pkgCatalogerNamesWithTagOrName(t, "directory"),
|
||||
Used: flatten(pkgCatalogerNamesWithTagOrName(t, "directory"), fileCatalogerNames()),
|
||||
},
|
||||
wantErr: require.NoError,
|
||||
},
|
||||
@@ -127,51 +127,53 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
||||
wantTaskNames: [][]string{
|
||||
environmentCatalogerNames(),
|
||||
pkgCatalogerNamesWithTagOrName(t, "directory"),
|
||||
fileCatalogerNames(true, true, true),
|
||||
fileCatalogerNames(),
|
||||
relationshipCatalogerNames(),
|
||||
unknownsTaskNames(),
|
||||
},
|
||||
wantManifest: &catalogerManifest{
|
||||
Requested: pkgcataloging.SelectionRequest{
|
||||
DefaultNamesOrTags: []string{"directory"},
|
||||
Requested: cataloging.SelectionRequest{
|
||||
DefaultNamesOrTags: []string{"directory", "file"},
|
||||
},
|
||||
Used: pkgCatalogerNamesWithTagOrName(t, "directory"),
|
||||
Used: flatten(pkgCatalogerNamesWithTagOrName(t, "directory"), fileCatalogerNames()),
|
||||
},
|
||||
wantErr: require.NoError,
|
||||
},
|
||||
{
|
||||
name: "no file digest cataloger",
|
||||
src: imgSrc,
|
||||
cfg: DefaultCreateSBOMConfig().WithFilesConfig(filecataloging.DefaultConfig().WithHashers()),
|
||||
cfg: DefaultCreateSBOMConfig().WithCatalogerSelection(cataloging.NewSelectionRequest().WithRemovals("digest")),
|
||||
wantTaskNames: [][]string{
|
||||
environmentCatalogerNames(),
|
||||
pkgCatalogerNamesWithTagOrName(t, "image"),
|
||||
fileCatalogerNames(false, true, true), // note: the digest cataloger is not included
|
||||
fileCatalogerNames("file-metadata", "content", "binary-metadata"),
|
||||
relationshipCatalogerNames(),
|
||||
unknownsTaskNames(),
|
||||
},
|
||||
wantManifest: &catalogerManifest{
|
||||
Requested: pkgcataloging.SelectionRequest{
|
||||
DefaultNamesOrTags: []string{"image"},
|
||||
Requested: cataloging.SelectionRequest{
|
||||
DefaultNamesOrTags: []string{"image", "file"},
|
||||
RemoveNamesOrTags: []string{"digest"},
|
||||
},
|
||||
Used: pkgCatalogerNamesWithTagOrName(t, "image"),
|
||||
Used: flatten(pkgCatalogerNamesWithTagOrName(t, "image"), fileCatalogerNames("file-metadata", "content", "binary-metadata")),
|
||||
},
|
||||
wantErr: require.NoError,
|
||||
},
|
||||
{
|
||||
name: "select no file catalogers",
|
||||
src: imgSrc,
|
||||
cfg: DefaultCreateSBOMConfig().WithFilesConfig(filecataloging.DefaultConfig().WithSelection(file.NoFilesSelection)),
|
||||
cfg: DefaultCreateSBOMConfig().WithCatalogerSelection(cataloging.NewSelectionRequest().WithRemovals("file")),
|
||||
wantTaskNames: [][]string{
|
||||
environmentCatalogerNames(),
|
||||
pkgCatalogerNamesWithTagOrName(t, "image"),
|
||||
// note: there are no file catalogers in their own group
|
||||
nil, // note: there is a file cataloging group, with no items in it
|
||||
relationshipCatalogerNames(),
|
||||
unknownsTaskNames(),
|
||||
},
|
||||
wantManifest: &catalogerManifest{
|
||||
Requested: pkgcataloging.SelectionRequest{
|
||||
DefaultNamesOrTags: []string{"image"},
|
||||
Requested: cataloging.SelectionRequest{
|
||||
DefaultNamesOrTags: []string{"image", "file"},
|
||||
RemoveNamesOrTags: []string{"file"},
|
||||
},
|
||||
Used: pkgCatalogerNamesWithTagOrName(t, "image"),
|
||||
},
|
||||
@@ -186,16 +188,16 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
||||
// note: there is a single group of catalogers for pkgs and files
|
||||
append(
|
||||
pkgCatalogerNamesWithTagOrName(t, "image"),
|
||||
fileCatalogerNames(true, true, true)...,
|
||||
fileCatalogerNames()...,
|
||||
),
|
||||
relationshipCatalogerNames(),
|
||||
unknownsTaskNames(),
|
||||
},
|
||||
wantManifest: &catalogerManifest{
|
||||
Requested: pkgcataloging.SelectionRequest{
|
||||
DefaultNamesOrTags: []string{"image"},
|
||||
Requested: cataloging.SelectionRequest{
|
||||
DefaultNamesOrTags: []string{"image", "file"},
|
||||
},
|
||||
Used: pkgCatalogerNamesWithTagOrName(t, "image"),
|
||||
Used: flatten(pkgCatalogerNamesWithTagOrName(t, "image"), fileCatalogerNames()),
|
||||
},
|
||||
wantErr: require.NoError,
|
||||
},
|
||||
@@ -208,15 +210,15 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
||||
wantTaskNames: [][]string{
|
||||
environmentCatalogerNames(),
|
||||
addTo(pkgCatalogerNamesWithTagOrName(t, "image"), "persistent"),
|
||||
fileCatalogerNames(true, true, true),
|
||||
fileCatalogerNames(),
|
||||
relationshipCatalogerNames(),
|
||||
unknownsTaskNames(),
|
||||
},
|
||||
wantManifest: &catalogerManifest{
|
||||
Requested: pkgcataloging.SelectionRequest{
|
||||
DefaultNamesOrTags: []string{"image"},
|
||||
Requested: cataloging.SelectionRequest{
|
||||
DefaultNamesOrTags: []string{"image", "file"},
|
||||
},
|
||||
Used: addTo(pkgCatalogerNamesWithTagOrName(t, "image"), "persistent"),
|
||||
Used: flatten(addTo(pkgCatalogerNamesWithTagOrName(t, "image"), "persistent"), fileCatalogerNames()),
|
||||
},
|
||||
wantErr: require.NoError,
|
||||
},
|
||||
@@ -229,15 +231,15 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
||||
wantTaskNames: [][]string{
|
||||
environmentCatalogerNames(),
|
||||
addTo(pkgCatalogerNamesWithTagOrName(t, "directory"), "persistent"),
|
||||
fileCatalogerNames(true, true, true),
|
||||
fileCatalogerNames(),
|
||||
relationshipCatalogerNames(),
|
||||
unknownsTaskNames(),
|
||||
},
|
||||
wantManifest: &catalogerManifest{
|
||||
Requested: pkgcataloging.SelectionRequest{
|
||||
DefaultNamesOrTags: []string{"directory"},
|
||||
Requested: cataloging.SelectionRequest{
|
||||
DefaultNamesOrTags: []string{"directory", "file"},
|
||||
},
|
||||
Used: addTo(pkgCatalogerNamesWithTagOrName(t, "directory"), "persistent"),
|
||||
Used: flatten(addTo(pkgCatalogerNamesWithTagOrName(t, "directory"), "persistent"), fileCatalogerNames()),
|
||||
},
|
||||
wantErr: require.NoError,
|
||||
},
|
||||
@@ -246,20 +248,20 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
||||
src: imgSrc,
|
||||
cfg: DefaultCreateSBOMConfig().WithCatalogers(
|
||||
pkgcataloging.NewAlwaysEnabledCatalogerReference(newDummyCataloger("persistent")),
|
||||
).WithCatalogerSelection(pkgcataloging.NewSelectionRequest().WithSubSelections("javascript")),
|
||||
).WithCatalogerSelection(cataloging.NewSelectionRequest().WithSubSelections("javascript")),
|
||||
wantTaskNames: [][]string{
|
||||
environmentCatalogerNames(),
|
||||
addTo(pkgIntersect("image", "javascript"), "persistent"),
|
||||
fileCatalogerNames(true, true, true),
|
||||
fileCatalogerNames(),
|
||||
relationshipCatalogerNames(),
|
||||
unknownsTaskNames(),
|
||||
},
|
||||
wantManifest: &catalogerManifest{
|
||||
Requested: pkgcataloging.SelectionRequest{
|
||||
DefaultNamesOrTags: []string{"image"},
|
||||
Requested: cataloging.SelectionRequest{
|
||||
DefaultNamesOrTags: []string{"image", "file"},
|
||||
SubSelectTags: []string{"javascript"},
|
||||
},
|
||||
Used: addTo(pkgIntersect("image", "javascript"), "persistent"),
|
||||
Used: flatten(addTo(pkgIntersect("image", "javascript"), "persistent"), fileCatalogerNames()),
|
||||
},
|
||||
wantErr: require.NoError,
|
||||
},
|
||||
@@ -272,15 +274,15 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
||||
wantTaskNames: [][]string{
|
||||
environmentCatalogerNames(),
|
||||
addTo(pkgCatalogerNamesWithTagOrName(t, "image"), "user-provided"),
|
||||
fileCatalogerNames(true, true, true),
|
||||
fileCatalogerNames(),
|
||||
relationshipCatalogerNames(),
|
||||
unknownsTaskNames(),
|
||||
},
|
||||
wantManifest: &catalogerManifest{
|
||||
Requested: pkgcataloging.SelectionRequest{
|
||||
DefaultNamesOrTags: []string{"image"},
|
||||
Requested: cataloging.SelectionRequest{
|
||||
DefaultNamesOrTags: []string{"image", "file"},
|
||||
},
|
||||
Used: addTo(pkgCatalogerNamesWithTagOrName(t, "image"), "user-provided"),
|
||||
Used: flatten(addTo(pkgCatalogerNamesWithTagOrName(t, "image"), "user-provided"), fileCatalogerNames()),
|
||||
},
|
||||
wantErr: require.NoError,
|
||||
},
|
||||
@@ -293,15 +295,15 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
||||
wantTaskNames: [][]string{
|
||||
environmentCatalogerNames(),
|
||||
pkgCatalogerNamesWithTagOrName(t, "image"),
|
||||
fileCatalogerNames(true, true, true),
|
||||
fileCatalogerNames(),
|
||||
relationshipCatalogerNames(),
|
||||
unknownsTaskNames(),
|
||||
},
|
||||
wantManifest: &catalogerManifest{
|
||||
Requested: pkgcataloging.SelectionRequest{
|
||||
DefaultNamesOrTags: []string{"image"},
|
||||
Requested: cataloging.SelectionRequest{
|
||||
DefaultNamesOrTags: []string{"image", "file"},
|
||||
},
|
||||
Used: pkgCatalogerNamesWithTagOrName(t, "image"),
|
||||
Used: flatten(pkgCatalogerNamesWithTagOrName(t, "image"), fileCatalogerNames()),
|
||||
},
|
||||
wantErr: require.NoError,
|
||||
},
|
||||
@@ -314,9 +316,6 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
||||
|
||||
// sanity check
|
||||
require.NotEmpty(t, tt.wantTaskNames)
|
||||
for _, group := range tt.wantTaskNames {
|
||||
require.NotEmpty(t, group)
|
||||
}
|
||||
|
||||
// test the subject
|
||||
gotTasks, gotManifest, err := tt.cfg.makeTaskGroups(tt.src)
|
||||
@@ -378,20 +377,51 @@ func pkgCatalogerNamesWithTagOrName(t *testing.T, token string) []string {
|
||||
return names
|
||||
}
|
||||
|
||||
func fileCatalogerNames(digest, metadata, executable bool) []string {
|
||||
func fileCatalogerNames(tokens ...string) []string {
|
||||
var names []string
|
||||
if digest {
|
||||
names = append(names, "file-digest-cataloger")
|
||||
}
|
||||
if executable {
|
||||
names = append(names, "file-executable-cataloger")
|
||||
}
|
||||
if metadata {
|
||||
names = append(names, "file-metadata-cataloger")
|
||||
cfg := task.DefaultCatalogingFactoryConfig()
|
||||
topLoop:
|
||||
for _, factory := range task.DefaultFileTaskFactories() {
|
||||
cat := factory(cfg)
|
||||
|
||||
if cat == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
name := cat.Name()
|
||||
|
||||
if len(tokens) == 0 {
|
||||
names = append(names, name)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, token := range tokens {
|
||||
if selector, ok := cat.(task.Selector); ok {
|
||||
if selector.HasAllSelectors(token) {
|
||||
names = append(names, name)
|
||||
continue topLoop
|
||||
}
|
||||
|
||||
}
|
||||
if name == token {
|
||||
names = append(names, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sort.Strings(names)
|
||||
return names
|
||||
}
|
||||
|
||||
func flatten(lists ...[]string) []string {
|
||||
var final []string
|
||||
for _, lst := range lists {
|
||||
final = append(final, lst...)
|
||||
}
|
||||
sort.Strings(final)
|
||||
return final
|
||||
}
|
||||
|
||||
func relationshipCatalogerNames() []string {
|
||||
return []string{"relationships-cataloger"}
|
||||
}
|
||||
@@ -436,7 +466,7 @@ func Test_replaceDefaultTagReferences(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equal(t, tt.want, replaceDefaultTagReferences("replacement", tt.lst))
|
||||
assert.Equal(t, tt.want, replaceDefaultTagReferences([]string{"replacement"}, tt.lst))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -446,7 +476,7 @@ func Test_findDefaultTag(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
src source.Description
|
||||
want string
|
||||
want []string
|
||||
wantErr require.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
@@ -454,21 +484,21 @@ func Test_findDefaultTag(t *testing.T) {
|
||||
src: source.Description{
|
||||
Metadata: source.ImageMetadata{},
|
||||
},
|
||||
want: pkgcataloging.ImageTag,
|
||||
want: []string{pkgcataloging.ImageTag, filecataloging.FileTag},
|
||||
},
|
||||
{
|
||||
name: "directory",
|
||||
src: source.Description{
|
||||
Metadata: source.DirectoryMetadata{},
|
||||
},
|
||||
want: pkgcataloging.DirectoryTag,
|
||||
want: []string{pkgcataloging.DirectoryTag, filecataloging.FileTag},
|
||||
},
|
||||
{
|
||||
name: "file",
|
||||
src: source.Description{
|
||||
Metadata: source.FileMetadata{},
|
||||
},
|
||||
want: pkgcataloging.DirectoryTag, // not a mistake...
|
||||
want: []string{pkgcataloging.DirectoryTag, filecataloging.FileTag}, // not a mistake...
|
||||
},
|
||||
{
|
||||
name: "unknown",
|
||||
@@ -483,7 +513,7 @@ func Test_findDefaultTag(t *testing.T) {
|
||||
if tt.wantErr == nil {
|
||||
tt.wantErr = require.NoError
|
||||
}
|
||||
got, err := findDefaultTag(tt.src)
|
||||
got, err := findDefaultTags(tt.src)
|
||||
tt.wantErr(t, err)
|
||||
if err != nil {
|
||||
return
|
||||
|
||||
@@ -86,14 +86,14 @@ func (i *Cataloger) Catalog(resolver file.Resolver) (map[file.Coordinates]file.E
|
||||
func processExecutableLocation(loc file.Location, resolver file.Resolver) (*file.Executable, error) {
|
||||
reader, err := resolver.FileContentsByLocation(loc)
|
||||
if err != nil {
|
||||
log.WithFields("error", err).Warnf("unable to get file contents for %q", loc.RealPath)
|
||||
log.WithFields("error", err, "path", loc.RealPath).Debug("unable to get file contents")
|
||||
return nil, fmt.Errorf("unable to get file contents: %w", err)
|
||||
}
|
||||
defer internal.CloseAndLogError(reader, loc.RealPath)
|
||||
|
||||
uReader, err := unionreader.GetUnionReader(reader)
|
||||
if err != nil {
|
||||
log.WithFields("error", err).Warnf("unable to get union reader for %q", loc.RealPath)
|
||||
log.WithFields("error", err, "path", loc.RealPath).Debug("unable to get union reader")
|
||||
return nil, fmt.Errorf("unable to get union reader: %w", err)
|
||||
}
|
||||
|
||||
@@ -168,17 +168,17 @@ func processExecutable(loc file.Location, reader unionreader.UnionReader) (*file
|
||||
switch format {
|
||||
case file.ELF:
|
||||
if err = findELFFeatures(&data, reader); err != nil {
|
||||
log.WithFields("error", err).Tracef("unable to determine ELF features for %q", loc.RealPath)
|
||||
log.WithFields("error", err, "path", loc.RealPath).Trace("unable to determine ELF features")
|
||||
err = fmt.Errorf("unable to determine ELF features: %w", err)
|
||||
}
|
||||
case file.PE:
|
||||
if err = findPEFeatures(&data, reader); err != nil {
|
||||
log.WithFields("error", err).Tracef("unable to determine PE features for %q", loc.RealPath)
|
||||
log.WithFields("error", err, "path", loc.RealPath).Trace("unable to determine PE features")
|
||||
err = fmt.Errorf("unable to determine PE features: %w", err)
|
||||
}
|
||||
case file.MachO:
|
||||
if err = findMachoFeatures(&data, reader); err != nil {
|
||||
log.WithFields("error", err).Tracef("unable to determine Macho features for %q", loc.RealPath)
|
||||
log.WithFields("error", err, "path", loc.RealPath).Trace("unable to determine Macho features")
|
||||
err = fmt.Errorf("unable to determine Macho features: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,14 +14,14 @@ func AllRegularFiles(ctx context.Context, resolver file.Resolver) (locations []f
|
||||
for location := range resolver.AllLocations(ctx) {
|
||||
resolvedLocations, err := resolver.FilesByPath(location.RealPath)
|
||||
if err != nil {
|
||||
log.Warnf("unable to resolve %+v: %+v", location, err)
|
||||
log.Debugf("unable to resolve %+v: %+v", location, err)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, resolvedLocation := range resolvedLocations {
|
||||
metadata, err := resolver.FileMetadataByLocation(resolvedLocation)
|
||||
if err != nil {
|
||||
log.Warnf("unable to get metadata for %+v: %+v", location, err)
|
||||
log.Debugf("unable to get metadata for %+v: %+v", location, err)
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ func (c Coordinates) ID() artifact.ID {
|
||||
f, err := artifact.IDByHash(c)
|
||||
if err != nil {
|
||||
// TODO: what to do in this case?
|
||||
log.Warnf("unable to get fingerprint of location coordinate=%+v: %+v", c, err)
|
||||
log.Debugf("unable to get fingerprint of location coordinate=%+v: %+v", c, err)
|
||||
return ""
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ type License struct {
|
||||
SPDXExpression string
|
||||
Type license.Type
|
||||
LicenseEvidence *LicenseEvidence // evidence from license classifier
|
||||
Contents string `hash:"ignore"`
|
||||
}
|
||||
|
||||
type LicenseEvidence struct {
|
||||
|
||||
@@ -6,12 +6,14 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/CycloneDX/cyclonedx-go"
|
||||
cyclonedx "github.com/CycloneDX/cyclonedx-go"
|
||||
"github.com/google/uuid"
|
||||
|
||||
stfile "github.com/anchore/stereoscope/pkg/file"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/cpe"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/format/internal/cyclonedxutil/helpers"
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
@@ -19,6 +21,18 @@ import (
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
var cycloneDXValidHash = map[string]cyclonedx.HashAlgorithm{
|
||||
"sha1": cyclonedx.HashAlgoSHA1,
|
||||
"md5": cyclonedx.HashAlgoMD5,
|
||||
"sha256": cyclonedx.HashAlgoSHA256,
|
||||
"sha384": cyclonedx.HashAlgoSHA384,
|
||||
"sha512": cyclonedx.HashAlgoSHA512,
|
||||
"blake2b256": cyclonedx.HashAlgoBlake2b_256,
|
||||
"blake2b384": cyclonedx.HashAlgoBlake2b_384,
|
||||
"blake2b512": cyclonedx.HashAlgoBlake2b_512,
|
||||
"blake3": cyclonedx.HashAlgoBlake3,
|
||||
}
|
||||
|
||||
func ToFormatModel(s sbom.SBOM) *cyclonedx.BOM {
|
||||
cdxBOM := cyclonedx.NewBOM()
|
||||
|
||||
@@ -28,12 +42,49 @@ func ToFormatModel(s sbom.SBOM) *cyclonedx.BOM {
|
||||
cdxBOM.SerialNumber = uuid.New().URN()
|
||||
cdxBOM.Metadata = toBomDescriptor(s.Descriptor.Name, s.Descriptor.Version, s.Source)
|
||||
|
||||
// Packages
|
||||
packages := s.Artifacts.Packages.Sorted()
|
||||
components := make([]cyclonedx.Component, len(packages))
|
||||
for i, p := range packages {
|
||||
components[i] = helpers.EncodeComponent(p)
|
||||
}
|
||||
components = append(components, toOSComponent(s.Artifacts.LinuxDistribution)...)
|
||||
|
||||
// Files
|
||||
artifacts := s.Artifacts
|
||||
coordinates := s.AllCoordinates()
|
||||
for _, coordinate := range coordinates {
|
||||
var metadata *file.Metadata
|
||||
// File Info
|
||||
fileMetadata, exists := artifacts.FileMetadata[coordinate]
|
||||
// no file metadata then don't include in SBOM
|
||||
// the syft config allows for sometimes only capturing files owned by packages
|
||||
// so there can be a map miss here where we have less metadata than all coordinates
|
||||
if !exists {
|
||||
continue
|
||||
}
|
||||
if fileMetadata.Type == stfile.TypeDirectory ||
|
||||
fileMetadata.Type == stfile.TypeSocket ||
|
||||
fileMetadata.Type == stfile.TypeSymLink {
|
||||
// skip dir, symlinks and sockets for the final bom
|
||||
continue
|
||||
}
|
||||
metadata = &fileMetadata
|
||||
|
||||
// Digests
|
||||
var digests []file.Digest
|
||||
if digestsForLocation, exists := artifacts.FileDigests[coordinate]; exists {
|
||||
digests = digestsForLocation
|
||||
}
|
||||
|
||||
cdxHashes := digestsToHashes(digests)
|
||||
components = append(components, cyclonedx.Component{
|
||||
BOMRef: string(coordinate.ID()),
|
||||
Type: cyclonedx.ComponentTypeFile,
|
||||
Name: metadata.Path,
|
||||
Hashes: &cdxHashes,
|
||||
})
|
||||
}
|
||||
cdxBOM.Components = &components
|
||||
|
||||
dependencies := toDependencies(s.Relationships)
|
||||
@@ -44,6 +95,22 @@ func ToFormatModel(s sbom.SBOM) *cyclonedx.BOM {
|
||||
return cdxBOM
|
||||
}
|
||||
|
||||
func digestsToHashes(digests []file.Digest) []cyclonedx.Hash {
|
||||
var hashes []cyclonedx.Hash
|
||||
for _, digest := range digests {
|
||||
lookup := strings.ToLower(digest.Algorithm)
|
||||
cdxAlgo, exists := cycloneDXValidHash[lookup]
|
||||
if !exists {
|
||||
continue
|
||||
}
|
||||
hashes = append(hashes, cyclonedx.Hash{
|
||||
Algorithm: cdxAlgo,
|
||||
Value: digest.Value,
|
||||
})
|
||||
}
|
||||
return hashes
|
||||
}
|
||||
|
||||
func toOSComponent(distro *linux.Release) []cyclonedx.Component {
|
||||
if distro == nil {
|
||||
return []cyclonedx.Component{}
|
||||
@@ -235,7 +302,7 @@ func toBomDescriptorComponent(srcMetadata source.Description) *cyclonedx.Compone
|
||||
}
|
||||
bomRef, err := artifact.IDByHash(metadata.ID)
|
||||
if err != nil {
|
||||
log.Warnf("unable to get fingerprint of source image metadata=%s: %+v", metadata.ID, err)
|
||||
log.Debugf("unable to get fingerprint of source image metadata=%s: %+v", metadata.ID, err)
|
||||
}
|
||||
return &cyclonedx.Component{
|
||||
BOMRef: string(bomRef),
|
||||
@@ -249,7 +316,7 @@ func toBomDescriptorComponent(srcMetadata source.Description) *cyclonedx.Compone
|
||||
}
|
||||
bomRef, err := artifact.IDByHash(metadata.Path)
|
||||
if err != nil {
|
||||
log.Warnf("unable to get fingerprint of source directory metadata path=%s: %+v", metadata.Path, err)
|
||||
log.Debugf("unable to get fingerprint of source directory metadata path=%s: %+v", metadata.Path, err)
|
||||
}
|
||||
return &cyclonedx.Component{
|
||||
BOMRef: string(bomRef),
|
||||
@@ -264,7 +331,7 @@ func toBomDescriptorComponent(srcMetadata source.Description) *cyclonedx.Compone
|
||||
}
|
||||
bomRef, err := artifact.IDByHash(metadata.Path)
|
||||
if err != nil {
|
||||
log.Warnf("unable to get fingerprint of source file metadata path=%s: %+v", metadata.Path, err)
|
||||
log.Debugf("unable to get fingerprint of source file metadata path=%s: %+v", metadata.Path, err)
|
||||
}
|
||||
return &cyclonedx.Component{
|
||||
BOMRef: string(bomRef),
|
||||
|
||||
@@ -9,7 +9,9 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
stfile "github.com/anchore/stereoscope/pkg/file"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/format/internal/cyclonedxutil/helpers"
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
@@ -143,6 +145,178 @@ func Test_relationships(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_FileComponents(t *testing.T) {
|
||||
p1 := pkg.Package{
|
||||
Name: "p1",
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
sbom sbom.SBOM
|
||||
want []cyclonedx.Component
|
||||
}{
|
||||
{
|
||||
name: "sbom coordinates with file metadata are serialized to cdx along with packages",
|
||||
sbom: sbom.SBOM{
|
||||
Artifacts: sbom.Artifacts{
|
||||
Packages: pkg.NewCollection(p1),
|
||||
FileMetadata: map[file.Coordinates]file.Metadata{
|
||||
{RealPath: "/test"}: {Path: "/test", Type: stfile.TypeRegular},
|
||||
},
|
||||
FileDigests: map[file.Coordinates][]file.Digest{
|
||||
{RealPath: "/test"}: {
|
||||
{
|
||||
Algorithm: "sha256",
|
||||
Value: "xyz12345",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []cyclonedx.Component{
|
||||
{
|
||||
BOMRef: "2a1fc74ade23e357",
|
||||
Type: cyclonedx.ComponentTypeLibrary,
|
||||
Name: "p1",
|
||||
},
|
||||
{
|
||||
BOMRef: "3f31cb2d98be6c1e",
|
||||
Name: "/test",
|
||||
Type: cyclonedx.ComponentTypeFile,
|
||||
Hashes: &[]cyclonedx.Hash{
|
||||
{Algorithm: "SHA-256", Value: "xyz12345"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sbom coordinates that don't contain metadata are not added to the final output",
|
||||
sbom: sbom.SBOM{
|
||||
Artifacts: sbom.Artifacts{
|
||||
FileMetadata: map[file.Coordinates]file.Metadata{
|
||||
{RealPath: "/test"}: {Path: "/test", Type: stfile.TypeRegular},
|
||||
},
|
||||
FileDigests: map[file.Coordinates][]file.Digest{
|
||||
{RealPath: "/test"}: {
|
||||
{
|
||||
Algorithm: "sha256",
|
||||
Value: "xyz12345",
|
||||
},
|
||||
},
|
||||
{RealPath: "/test-2"}: {
|
||||
{
|
||||
Algorithm: "sha256",
|
||||
Value: "xyz678910",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []cyclonedx.Component{
|
||||
{
|
||||
BOMRef: "3f31cb2d98be6c1e",
|
||||
Name: "/test",
|
||||
Type: cyclonedx.ComponentTypeFile,
|
||||
Hashes: &[]cyclonedx.Hash{
|
||||
{Algorithm: "SHA-256", Value: "xyz12345"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sbom coordinates that return hashes not covered by cdx only include valid digests",
|
||||
sbom: sbom.SBOM{
|
||||
Artifacts: sbom.Artifacts{
|
||||
FileMetadata: map[file.Coordinates]file.Metadata{
|
||||
{RealPath: "/test"}: {Path: "/test", Type: stfile.TypeRegular},
|
||||
},
|
||||
FileDigests: map[file.Coordinates][]file.Digest{
|
||||
{RealPath: "/test"}: {
|
||||
{
|
||||
Algorithm: "xxh64",
|
||||
Value: "xyz12345",
|
||||
},
|
||||
{
|
||||
Algorithm: "sha256",
|
||||
Value: "xyz678910",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []cyclonedx.Component{
|
||||
{
|
||||
BOMRef: "3f31cb2d98be6c1e",
|
||||
Name: "/test",
|
||||
Type: cyclonedx.ComponentTypeFile,
|
||||
Hashes: &[]cyclonedx.Hash{
|
||||
{Algorithm: "SHA-256", Value: "xyz678910"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sbom coordinates who's metadata is directory or symlink are skipped",
|
||||
sbom: sbom.SBOM{
|
||||
Artifacts: sbom.Artifacts{
|
||||
FileMetadata: map[file.Coordinates]file.Metadata{
|
||||
{RealPath: "/testdir"}: {
|
||||
Path: "/testdir",
|
||||
Type: stfile.TypeDirectory,
|
||||
},
|
||||
{RealPath: "/testsym"}: {
|
||||
Path: "/testsym",
|
||||
Type: stfile.TypeSymLink,
|
||||
},
|
||||
{RealPath: "/test"}: {Path: "/test", Type: stfile.TypeRegular},
|
||||
},
|
||||
FileDigests: map[file.Coordinates][]file.Digest{
|
||||
{RealPath: "/test"}: {
|
||||
{
|
||||
Algorithm: "sha256",
|
||||
Value: "xyz12345",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []cyclonedx.Component{
|
||||
{
|
||||
BOMRef: "3f31cb2d98be6c1e",
|
||||
Name: "/test",
|
||||
Type: cyclonedx.ComponentTypeFile,
|
||||
Hashes: &[]cyclonedx.Hash{
|
||||
{Algorithm: "SHA-256", Value: "xyz12345"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sbom with no files serialized correctly",
|
||||
sbom: sbom.SBOM{
|
||||
Artifacts: sbom.Artifacts{
|
||||
Packages: pkg.NewCollection(p1),
|
||||
},
|
||||
},
|
||||
want: []cyclonedx.Component{
|
||||
{
|
||||
BOMRef: "2a1fc74ade23e357",
|
||||
Type: cyclonedx.ComponentTypeLibrary,
|
||||
Name: "p1",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
cdx := ToFormatModel(test.sbom)
|
||||
got := *cdx.Components
|
||||
if diff := cmp.Diff(test.want, got); diff != "" {
|
||||
t.Errorf("cdx file components mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_toBomDescriptor(t *testing.T) {
|
||||
type args struct {
|
||||
name string
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/spdx/tools-golang/spdx"
|
||||
|
||||
"github.com/anchore/packageurl-go"
|
||||
internallicenses "github.com/anchore/syft/internal/licenses"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/mimetype"
|
||||
"github.com/anchore/syft/internal/relationship"
|
||||
@@ -762,10 +763,16 @@ func toOtherLicenses(catalog *pkg.Collection) []*spdx.OtherLicense {
|
||||
if license.Value == "" {
|
||||
value, _ = strings.CutPrefix(license.ID, "LicenseRef-")
|
||||
}
|
||||
result = append(result, &spdx.OtherLicense{
|
||||
other := &spdx.OtherLicense{
|
||||
LicenseIdentifier: license.ID,
|
||||
ExtractedText: value,
|
||||
})
|
||||
}
|
||||
customPrefix := spdxlicense.LicenseRefPrefix + helpers.SanitizeElementID(internallicenses.UnknownLicensePrefix)
|
||||
if strings.HasPrefix(license.ID, customPrefix) {
|
||||
other.LicenseName = strings.TrimPrefix(license.ID, customPrefix)
|
||||
other.LicenseComment = strings.Trim(internallicenses.UnknownLicensePrefix, "-_")
|
||||
}
|
||||
result = append(result, other)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ func Test_toFormatModel(t *testing.T) {
|
||||
{
|
||||
Category: "PACKAGE-MANAGER",
|
||||
RefType: "purl",
|
||||
Locator: "pkg:oci/alpine@sha256:d34db33f?arch=&tag=latest",
|
||||
Locator: "pkg:oci/alpine@sha256%3Ad34db33f?arch=&tag=latest",
|
||||
},
|
||||
},
|
||||
PackageSupplier: &spdx.Supplier{
|
||||
|
||||
@@ -164,7 +164,7 @@ func toDependencies(s *sbom.SBOM, p pkg.Package) (out []string) {
|
||||
func dependencyName(p pkg.Package) string {
|
||||
purl, err := packageurl.FromString(p.PURL)
|
||||
if err != nil {
|
||||
log.Warnf("Invalid PURL for package: '%s' PURL: '%s' (%w)", p.Name, p.PURL, err)
|
||||
log.Debugf("Invalid PURL for package: '%s' PURL: '%s' (%w)", p.Name, p.PURL, err)
|
||||
return ""
|
||||
}
|
||||
// don't use qualifiers for this
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"version": "v0.42.0-bogus"
|
||||
},
|
||||
"metadata": {
|
||||
"syft:distro": "pkg:generic/debian@1.2.3?like=like!"
|
||||
"syft:distro": "pkg:generic/debian@1.2.3?like=like%21"
|
||||
},
|
||||
"manifests": {
|
||||
"redacted/some/path/some/path/pkg1": {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"version": "v0.42.0-bogus"
|
||||
},
|
||||
"metadata": {
|
||||
"syft:distro": "pkg:generic/debian@1.2.3?like=like!"
|
||||
"syft:distro": "pkg:generic/debian@1.2.3?like=like%21"
|
||||
},
|
||||
"manifests": {
|
||||
"user-image-input:/somefile-1.txt": {
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"strings"
|
||||
|
||||
"github.com/CycloneDX/cyclonedx-go"
|
||||
|
||||
"github.com/anchore/syft/internal/licenses"
|
||||
"github.com/anchore/syft/internal/spdxlicense"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
@@ -110,7 +112,7 @@ func separateLicenses(p pkg.Package) (spdx, other cyclonedx.Licenses, expression
|
||||
continue
|
||||
}
|
||||
|
||||
if l.SPDXExpression != "" {
|
||||
if l.SPDXExpression != "" && !strings.HasPrefix(l.SPDXExpression, licenses.UnknownLicensePrefix) {
|
||||
// COMPLEX EXPRESSION CASE
|
||||
ex = append(ex, l.SPDXExpression)
|
||||
continue
|
||||
@@ -118,17 +120,43 @@ func separateLicenses(p pkg.Package) (spdx, other cyclonedx.Licenses, expression
|
||||
|
||||
// license string that are not valid spdx expressions or ids
|
||||
// we only use license Name here since we cannot guarantee that the license is a valid SPDX expression
|
||||
if len(l.URLs) > 0 {
|
||||
if len(l.URLs) > 0 && !strings.HasPrefix(l.SPDXExpression, licenses.UnknownLicensePrefix) {
|
||||
processLicenseURLs(l, "", &otherc)
|
||||
continue
|
||||
}
|
||||
otherc = append(otherc, cyclonedx.LicenseChoice{
|
||||
|
||||
otherc = append(otherc, processCustomLicense(l)...)
|
||||
}
|
||||
return spdxc, otherc, ex
|
||||
}
|
||||
|
||||
func processCustomLicense(l pkg.License) cyclonedx.Licenses {
|
||||
result := cyclonedx.Licenses{}
|
||||
if strings.HasPrefix(l.SPDXExpression, licenses.UnknownLicensePrefix) {
|
||||
cyclonedxLicense := &cyclonedx.License{
|
||||
Name: l.SPDXExpression,
|
||||
}
|
||||
if len(l.URLs) > 0 {
|
||||
cyclonedxLicense.URL = l.URLs[0]
|
||||
}
|
||||
if len(l.Contents) > 0 {
|
||||
cyclonedxLicense.Text = &cyclonedx.AttachedText{
|
||||
Content: base64.StdEncoding.EncodeToString([]byte(l.Contents)),
|
||||
}
|
||||
cyclonedxLicense.Text.ContentType = "text/plain"
|
||||
cyclonedxLicense.Text.Encoding = "base64"
|
||||
}
|
||||
result = append(result, cyclonedx.LicenseChoice{
|
||||
License: cyclonedxLicense,
|
||||
})
|
||||
} else {
|
||||
result = append(result, cyclonedx.LicenseChoice{
|
||||
License: &cyclonedx.License{
|
||||
Name: l.Value,
|
||||
},
|
||||
})
|
||||
}
|
||||
return spdxc, otherc, ex
|
||||
return result
|
||||
}
|
||||
|
||||
func processLicenseURLs(l pkg.License, spdxID string, populate *cyclonedx.Licenses) {
|
||||
|
||||
@@ -136,7 +136,7 @@ func encode(out map[string]string, value reflect.Value, prefix string, fn FieldN
|
||||
encode(out, value.MapIndex(key), fmt.Sprintf("%s:%v", prefix, key.Interface()), fn)
|
||||
}
|
||||
default:
|
||||
log.Warnf("skipping encoding of unsupported property: %s", prefix)
|
||||
log.Debugf("skipping encoding of unsupported property: %s", prefix)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,7 +213,7 @@ func decode(vals map[string]string, value reflect.Value, prefix string, fn Field
|
||||
}
|
||||
if decode(vals, v.Elem(), prefix, fn) && value.CanSet() {
|
||||
o := v.Interface()
|
||||
log.Infof("%v", o)
|
||||
log.Tracef("%v", o)
|
||||
value.Set(v)
|
||||
} else {
|
||||
return false
|
||||
@@ -355,7 +355,7 @@ func decode(vals map[string]string, value reflect.Value, prefix string, fn Field
|
||||
}
|
||||
return values
|
||||
default:
|
||||
log.Warnf("unable to set field: %s", prefix)
|
||||
log.Debugf("unable to set field: %s", prefix)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/internal/licenses"
|
||||
"github.com/anchore/syft/internal/spdxlicense"
|
||||
"github.com/anchore/syft/syft/license"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
@@ -69,19 +70,26 @@ func ParseLicenses(raw []pkg.License) (concluded, declared []SPDXLicense) {
|
||||
}
|
||||
|
||||
candidate := SPDXLicense{}
|
||||
if l.SPDXExpression != "" {
|
||||
if l.SPDXExpression != "" && !strings.HasPrefix(l.SPDXExpression, licenses.UnknownLicensePrefix) {
|
||||
candidate.ID = l.SPDXExpression
|
||||
} else {
|
||||
// we did not find a valid SPDX license ID so treat as separate license
|
||||
if len(l.Value) <= 64 {
|
||||
// if the license text is less than the size of the hash,
|
||||
// just use it directly so the id is more readable
|
||||
candidate.ID = spdxlicense.LicenseRefPrefix + SanitizeElementID(l.Value)
|
||||
} else {
|
||||
hash := sha256.Sum256([]byte(l.Value))
|
||||
candidate.ID = fmt.Sprintf("%s%x", spdxlicense.LicenseRefPrefix, hash)
|
||||
}
|
||||
candidate.Value = l.Value
|
||||
// we did not find a valid SPDX license ID so treat as separate license
|
||||
if strings.HasPrefix(l.SPDXExpression, licenses.UnknownLicensePrefix) {
|
||||
candidate.ID = spdxlicense.LicenseRefPrefix + SanitizeElementID(l.SPDXExpression)
|
||||
if len(l.Contents) > 0 {
|
||||
candidate.Value = l.Contents
|
||||
}
|
||||
} else {
|
||||
if len(l.Value) <= 64 {
|
||||
// if the license text is less than the size of the hash,
|
||||
// just use it directly so the id is more readable
|
||||
candidate.ID = spdxlicense.LicenseRefPrefix + SanitizeElementID(l.Value)
|
||||
} else {
|
||||
hash := sha256.Sum256([]byte(l.Value))
|
||||
candidate.ID = fmt.Sprintf("%s%x", spdxlicense.LicenseRefPrefix, hash)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch l.Type {
|
||||
|
||||
@@ -43,6 +43,10 @@ func Originator(p pkg.Package) (typ string, author string) { //nolint: funlen
|
||||
case pkg.ApkDBEntry:
|
||||
author = metadata.Maintainer
|
||||
|
||||
case pkg.BitnamiSBOMEntry:
|
||||
typ = orgType
|
||||
author = "Bitnami"
|
||||
|
||||
case pkg.DotnetPortableExecutableEntry:
|
||||
typ = orgType
|
||||
author = metadata.CompanyName
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
func Test_OriginatorSupplier(t *testing.T) {
|
||||
completionTester := packagemetadata.NewCompletionTester(t,
|
||||
pkg.BinarySignature{},
|
||||
pkg.BitnamiSBOMEntry{},
|
||||
pkg.CocoaPodfileLockEntry{},
|
||||
pkg.ConanV1LockEntry{},
|
||||
pkg.ConanV2LockEntry{}, // the field Username might be the username of either the package originator or the supplier (unclear currently)
|
||||
@@ -89,6 +90,14 @@ func Test_OriginatorSupplier(t *testing.T) {
|
||||
originator: "",
|
||||
supplier: "Person: someone",
|
||||
},
|
||||
{
|
||||
name: "from bitnami",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.BitnamiSBOMEntry{},
|
||||
},
|
||||
originator: "Organization: Bitnami",
|
||||
supplier: "Organization: Bitnami",
|
||||
},
|
||||
{
|
||||
name: "from dotnet -- PE binary",
|
||||
input: pkg.Package{
|
||||
@@ -372,6 +381,14 @@ func Test_OriginatorSupplier(t *testing.T) {
|
||||
originator: "",
|
||||
supplier: "",
|
||||
},
|
||||
{
|
||||
name: "from terraform lock",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.TerraformLockProviderEntry{},
|
||||
},
|
||||
originator: "",
|
||||
supplier: "",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
|
||||
@@ -16,6 +16,8 @@ func SourceInfo(p pkg.Package) string {
|
||||
answer = "acquired package info from RPM DB"
|
||||
case pkg.ApkPkg:
|
||||
answer = "acquired package info from APK DB"
|
||||
case pkg.BitnamiPkg:
|
||||
answer = "acquired package info from a Bitnami SBOM"
|
||||
case pkg.DartPubPkg:
|
||||
answer = "acquired package info from pubspec manifest"
|
||||
case pkg.DebPkg:
|
||||
@@ -32,6 +34,8 @@ func SourceInfo(p pkg.Package) string {
|
||||
answer = "acquired package info from installed gem metadata file"
|
||||
case pkg.GoModulePkg:
|
||||
answer = "acquired package info from go module information"
|
||||
case pkg.GraalVMNativeImagePkg:
|
||||
answer = "acquired package info from GraalVM native image"
|
||||
case pkg.RustPkg:
|
||||
answer = "acquired package info from rust cargo manifest"
|
||||
case pkg.PhpComposerPkg:
|
||||
@@ -70,6 +74,8 @@ func SourceInfo(p pkg.Package) string {
|
||||
answer = "acquired package info from GitHub Actions workflow file or composite action file"
|
||||
case pkg.WordpressPluginPkg:
|
||||
answer = "acquired package info from found wordpress plugin PHP source files"
|
||||
case pkg.TerraformPkg:
|
||||
answer = "acquired package info from Terraform dependency lock file"
|
||||
default:
|
||||
answer = "acquired package info from the following paths"
|
||||
}
|
||||
|
||||
@@ -111,6 +111,14 @@ func Test_SourceInfo(t *testing.T) {
|
||||
"from go module information",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: pkg.Package{
|
||||
Type: pkg.GraalVMNativeImagePkg,
|
||||
},
|
||||
expected: []string{
|
||||
"from GraalVM native image",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: pkg.Package{
|
||||
Type: pkg.RustPkg,
|
||||
@@ -199,6 +207,14 @@ func Test_SourceInfo(t *testing.T) {
|
||||
"acquired package info from the following paths",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: pkg.Package{
|
||||
Type: pkg.BitnamiPkg,
|
||||
},
|
||||
expected: []string{
|
||||
"acquired package info from a Bitnami SBOM",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: pkg.Package{
|
||||
Type: pkg.HexPkg,
|
||||
@@ -303,6 +319,14 @@ func Test_SourceInfo(t *testing.T) {
|
||||
"acquired package info from found wordpress plugin PHP source files",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: pkg.Package{
|
||||
Type: pkg.TerraformPkg,
|
||||
},
|
||||
expected: []string{
|
||||
"acquired package info from Terraform dependency lock file",
|
||||
},
|
||||
},
|
||||
}
|
||||
var pkgTypes []pkg.Type
|
||||
for _, test := range tests {
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
{
|
||||
"referenceCategory": "PACKAGE-MANAGER",
|
||||
"referenceType": "purl",
|
||||
"referenceLocator": "pkg:oci/user-image-input@sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368?arch="
|
||||
"referenceLocator": "pkg:oci/user-image-input@sha256%3A2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368?arch="
|
||||
}
|
||||
],
|
||||
"primaryPackagePurpose": "CONTAINER"
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
{
|
||||
"referenceCategory": "PACKAGE-MANAGER",
|
||||
"referenceType": "purl",
|
||||
"referenceLocator": "pkg:oci/user-image-input@sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368?arch="
|
||||
"referenceLocator": "pkg:oci/user-image-input@sha256%3A2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368?arch="
|
||||
}
|
||||
],
|
||||
"primaryPackagePurpose": "CONTAINER"
|
||||
|
||||
@@ -71,7 +71,7 @@ PackageChecksum: SHA256: 2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf4
|
||||
PackageLicenseConcluded: NOASSERTION
|
||||
PackageLicenseDeclared: NOASSERTION
|
||||
PackageCopyrightText: NOASSERTION
|
||||
ExternalRef: PACKAGE-MANAGER purl pkg:oci/user-image-input@sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368?arch=
|
||||
ExternalRef: PACKAGE-MANAGER purl pkg:oci/user-image-input@sha256%3A2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368?arch=
|
||||
|
||||
##### Package: package-2
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ PackageChecksum: SHA256: 2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf4
|
||||
PackageLicenseConcluded: NOASSERTION
|
||||
PackageLicenseDeclared: NOASSERTION
|
||||
PackageCopyrightText: NOASSERTION
|
||||
ExternalRef: PACKAGE-MANAGER purl pkg:oci/user-image-input@sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368?arch=
|
||||
ExternalRef: PACKAGE-MANAGER purl pkg:oci/user-image-input@sha256%3A2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368?arch=
|
||||
|
||||
##### Package: package-2
|
||||
|
||||
|
||||
@@ -51,6 +51,7 @@ type License struct {
|
||||
Type license.Type `json:"type"`
|
||||
URLs []string `json:"urls"`
|
||||
Locations []file.Location `json:"locations"`
|
||||
Contents string `json:"contents,omitempty"`
|
||||
}
|
||||
|
||||
func newModelLicensesFromValues(licenses []string) (ml []License) {
|
||||
|
||||
@@ -160,7 +160,7 @@ func toFileMetadataEntry(coordinates file.Coordinates, metadata *file.Metadata)
|
||||
|
||||
mode, err = strconv.Atoi(fmt.Sprintf("%o", metadata.Mode()))
|
||||
if err != nil {
|
||||
log.Warnf("invalid mode found in file catalog @ location=%+v mode=%q: %+v", coordinates, metadata.Mode, err)
|
||||
log.Debugf("invalid mode found in file catalog @ location=%+v mode=%q: %+v", coordinates, metadata.Mode, err)
|
||||
mode = 0
|
||||
}
|
||||
|
||||
@@ -234,6 +234,7 @@ func toLicenseModel(pkgLicenses []pkg.License) (modelLicenses []model.License) {
|
||||
Type: l.Type,
|
||||
URLs: urls,
|
||||
Locations: locations,
|
||||
Contents: l.Contents,
|
||||
})
|
||||
}
|
||||
return
|
||||
|
||||
@@ -164,6 +164,7 @@ func toSyftLicenses(m []model.License) (p []pkg.License) {
|
||||
Type: l.Type,
|
||||
URLs: l.URLs,
|
||||
Locations: file.NewLocationSet(l.Locations...),
|
||||
Contents: l.Contents,
|
||||
})
|
||||
}
|
||||
return
|
||||
|
||||
@@ -34,7 +34,7 @@ type pathSkipper struct {
|
||||
func skipPathsByMountTypeAndName(root string) PathIndexVisitor {
|
||||
infos, err := mountinfo.GetMounts(nil)
|
||||
if err != nil {
|
||||
log.WithFields("error", err).Warnf("unable to get system mounts")
|
||||
log.WithFields("error", err).Debug("unable to get system mounts")
|
||||
return func(_ string, _ string, _ os.FileInfo, _ error) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ func AllTypes() []any {
|
||||
pkg.AlpmDBEntry{},
|
||||
pkg.ApkDBEntry{},
|
||||
pkg.BinarySignature{},
|
||||
pkg.BitnamiSBOMEntry{},
|
||||
pkg.CocoaPodfileLockEntry{},
|
||||
pkg.ConanV1LockEntry{},
|
||||
pkg.ConanV2LockEntry{},
|
||||
@@ -53,6 +54,7 @@ func AllTypes() []any {
|
||||
pkg.RustCargoLockEntry{},
|
||||
pkg.SwiftPackageManagerResolvedEntry{},
|
||||
pkg.SwiplPackEntry{},
|
||||
pkg.TerraformLockProviderEntry{},
|
||||
pkg.WordpressPluginEntry{},
|
||||
pkg.YarnLockEntry{},
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user