mirror of
https://github.com/anchore/syft.git
synced 2026-10-12 06:07:20 +02:00
Compare commits
20
Commits
+6
-6
@@ -2,7 +2,7 @@ tools:
|
||||
# we want to use a pinned version of binny to manage the toolchain (so binny manages itself!)
|
||||
- name: binny
|
||||
version:
|
||||
want: v0.11.3
|
||||
want: v0.12.0
|
||||
method: github-release
|
||||
with:
|
||||
repo: anchore/binny
|
||||
@@ -18,7 +18,7 @@ tools:
|
||||
# used to sign mac binaries at release
|
||||
- name: quill
|
||||
version:
|
||||
want: v0.5.1
|
||||
want: v0.7.1
|
||||
method: github-release
|
||||
with:
|
||||
repo: anchore/quill
|
||||
@@ -26,7 +26,7 @@ tools:
|
||||
# used for linting
|
||||
- name: golangci-lint
|
||||
version:
|
||||
want: v2.11.2
|
||||
want: v2.11.4
|
||||
method: github-release
|
||||
with:
|
||||
repo: golangci/golangci-lint
|
||||
@@ -58,7 +58,7 @@ tools:
|
||||
# used to release all artifacts
|
||||
- name: goreleaser
|
||||
version:
|
||||
want: v2.14.2
|
||||
want: v2.14.3
|
||||
method: github-release
|
||||
with:
|
||||
repo: goreleaser/goreleaser
|
||||
@@ -98,7 +98,7 @@ tools:
|
||||
# used for triggering a release
|
||||
- name: gh
|
||||
version:
|
||||
want: v2.87.3
|
||||
want: v2.88.1
|
||||
method: github-release
|
||||
with:
|
||||
repo: cli/cli
|
||||
@@ -106,7 +106,7 @@ tools:
|
||||
# used to upload test fixture cache
|
||||
- name: oras
|
||||
version:
|
||||
want: v1.3.0
|
||||
want: v1.3.1
|
||||
method: github-release
|
||||
with:
|
||||
repo: oras-project/oras
|
||||
|
||||
@@ -23,6 +23,11 @@ updates:
|
||||
open-pull-requests-limit: 10
|
||||
labels:
|
||||
- "dependencies"
|
||||
ignore:
|
||||
- dependency-name: "github.com/aquasecurity/go-pep440-version"
|
||||
- dependency-name: "github.com/aquasecurity/go-version"
|
||||
- dependency-name: "github.com/knqyf263/go-apk-version"
|
||||
- dependency-name: "github.com/knqyf263/go-deb-version"
|
||||
groups:
|
||||
go-minor-patch:
|
||||
applies-to: version-updates # security updates get individual PRs
|
||||
|
||||
@@ -48,7 +48,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3
|
||||
uses: github/codeql-action/init@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@@ -59,7 +59,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@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3
|
||||
uses: github/codeql-action/autobuild@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
@@ -73,4 +73,4 @@ jobs:
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3
|
||||
uses: github/codeql-action/analyze@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6
|
||||
|
||||
@@ -37,6 +37,8 @@ jobs:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
repository: anchore/syft # IMPORTANT! An additional protection that this is checking out code from the expected repository
|
||||
ref: main # IMPORTANT! It is CRITICAL that this only ever considers the code from main and NEVER EVER from a fork.
|
||||
|
||||
- run: python .github/scripts/labeler.py
|
||||
env:
|
||||
|
||||
@@ -150,13 +150,13 @@ jobs:
|
||||
uses: ./.github/actions/bootstrap
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 #v3.7.0
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 #v4.0.0
|
||||
with:
|
||||
username: ${{ secrets.ANCHOREOSSWRITE_DH_USERNAME }}
|
||||
password: ${{ secrets.ANCHOREOSSWRITE_DH_PAT }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 #v3.7.0
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 #v4.0.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
name: PR to update Anchore dependencies
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
repos:
|
||||
description: "List of dependencies to update"
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
update:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository_owner == 'anchore' # only run for main repo (not forks)
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
with:
|
||||
tools: false
|
||||
bootstrap-apt-packages: ""
|
||||
|
||||
- name: Update dependencies
|
||||
id: update
|
||||
uses: anchore/workflows/.github/actions/update-go-dependencies@main
|
||||
with:
|
||||
repos: ${{ github.event.inputs.repos }}
|
||||
|
||||
- uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf #v2.2.1
|
||||
id: generate-token
|
||||
with:
|
||||
app-id: ${{ secrets.TOKEN_APP_ID }}
|
||||
private-key: ${{ secrets.TOKEN_APP_PRIVATE_KEY }}
|
||||
|
||||
- uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 #v8.1.0
|
||||
with:
|
||||
signoff: true
|
||||
delete-branch: true
|
||||
draft: ${{ steps.update.outputs.draft }}
|
||||
# do not change this branch, as other workflows depend on it
|
||||
branch: auto/integration
|
||||
labels: dependencies,pre-release
|
||||
commit-message: "chore(deps): update anchore dependencies"
|
||||
title: "chore(deps): update anchore dependencies"
|
||||
body: ${{ steps.update.outputs.summary }}
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
@@ -1,67 +0,0 @@
|
||||
name: PR for latest versions of tools
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 8 * * *" # 3 AM EST
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
update-bootstrap-tools:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'anchore/syft' # only run for main repo
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
with:
|
||||
bootstrap-apt-packages: ""
|
||||
go-dependencies: false
|
||||
|
||||
- name: "Update tool versions"
|
||||
id: latest-versions
|
||||
run: |
|
||||
make update-tools
|
||||
make list-tools
|
||||
|
||||
export NO_COLOR=1
|
||||
delimiter="$(openssl rand -hex 8)"
|
||||
|
||||
{
|
||||
echo "status<<${delimiter}"
|
||||
make list-tool-updates
|
||||
echo "${delimiter}"
|
||||
} >> $GITHUB_OUTPUT
|
||||
|
||||
{
|
||||
echo "### Tool version status"
|
||||
echo "\`\`\`"
|
||||
make list-tool-updates
|
||||
echo "\`\`\`"
|
||||
} >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf #v2.2.1
|
||||
id: generate-token
|
||||
with:
|
||||
app-id: ${{ secrets.TOKEN_APP_ID }}
|
||||
private-key: ${{ secrets.TOKEN_APP_PRIVATE_KEY }}
|
||||
|
||||
- uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 #v8.1.0
|
||||
with:
|
||||
signoff: true
|
||||
delete-branch: true
|
||||
branch: auto/latest-tools
|
||||
labels: dependencies
|
||||
commit-message: 'chore(deps): update tools to latest versions'
|
||||
title: 'chore(deps): update tools to latest versions'
|
||||
body: |
|
||||
```
|
||||
${{ steps.latest-versions.outputs.status }}
|
||||
```
|
||||
This is an auto-generated pull request to update all of the tools to the latest versions.
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
@@ -1,84 +0,0 @@
|
||||
name: PR to update CPE dictionary index
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 1 * * 1" # every monday at 1 AM
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
SLACK_NOTIFICATIONS: true
|
||||
|
||||
jobs:
|
||||
upgrade-cpe-dictionary-index:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
if: github.repository == 'anchore/syft' # only run for main repo
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
id: bootstrap
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
run: echo "${{ secrets.GITHUB_TOKEN }}" | .tool/oras login ghcr.io -u "$ACTOR" --password-stdin
|
||||
env:
|
||||
ACTOR: ${{ github.actor }}
|
||||
|
||||
- name: Pull CPE cache from registry
|
||||
run: make generate:cpe-index:cache:pull
|
||||
|
||||
- name: Update CPE cache from NVD API
|
||||
run: make generate:cpe-index:cache:update
|
||||
env:
|
||||
NVD_API_KEY: ${{ secrets.NVD_API_KEY }}
|
||||
|
||||
- name: Generate CPE dictionary index
|
||||
run: make generate:cpe-index:build
|
||||
|
||||
- name: Push updated CPE cache to registry
|
||||
run: make generate:cpe-index:cache:push
|
||||
|
||||
- uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf #v2.2.1
|
||||
id: generate-token
|
||||
with:
|
||||
app-id: ${{ secrets.TOKEN_APP_ID }}
|
||||
private-key: ${{ secrets.TOKEN_APP_PRIVATE_KEY }}
|
||||
|
||||
- uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 #v8.1.0
|
||||
with:
|
||||
signoff: true
|
||||
delete-branch: true
|
||||
branch: auto/latest-cpe-dictionary-index
|
||||
labels: dependencies
|
||||
commit-message: "chore(deps): update CPE dictionary index"
|
||||
title: "chore(deps): update CPE dictionary index"
|
||||
body: |
|
||||
Update CPE dictionary index based on the latest available CPE dictionary
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
|
||||
- name: Notify Slack on failure
|
||||
uses: slackapi/slack-github-action@91efab103c0de0a537f72a35f6b8cda0ee76bf0a #v2.1.1
|
||||
with:
|
||||
webhook: ${{ secrets.SLACK_TOOLBOX_WEBHOOK_URL }}
|
||||
webhook-type: incoming-webhook
|
||||
payload: |
|
||||
text: "Syft CPE dictionary index update failed"
|
||||
blocks:
|
||||
- type: section
|
||||
text:
|
||||
type: mrkdwn
|
||||
text: |
|
||||
*Syft CPE dictionary index update failed*
|
||||
• Workflow: `${{ github.workflow }}`
|
||||
• Event: `${{ github.event_name }}`
|
||||
• Job Status: `${{ job.status }}`
|
||||
• <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View Run>
|
||||
if: ${{ failure() && env.SLACK_NOTIFICATIONS == 'true' }}
|
||||
@@ -1,64 +0,0 @@
|
||||
name: PR to update SPDX license list
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 6 * * 1" # every monday at 6 AM UTC
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
SLACK_NOTIFICATIONS: true
|
||||
|
||||
jobs:
|
||||
upgrade-spdx-license-list:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'anchore/syft' # only run for main repo
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
|
||||
- run: |
|
||||
make generate-license-list
|
||||
|
||||
- uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a #v2.1.0
|
||||
id: generate-token
|
||||
with:
|
||||
app_id: ${{ secrets.TOKEN_APP_ID }}
|
||||
private_key: ${{ secrets.TOKEN_APP_PRIVATE_KEY }}
|
||||
|
||||
- uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 #v8.1.0
|
||||
with:
|
||||
signoff: true
|
||||
delete-branch: true
|
||||
branch: auto/latest-spdx-license-list
|
||||
labels: dependencies
|
||||
commit-message: "chore(deps): update SPDX license list"
|
||||
title: "chore(deps): update SPDX license list"
|
||||
body: |
|
||||
Update SPDX license list based on the latest available list from spdx.org
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
|
||||
- name: Notify Slack on failure
|
||||
uses: slackapi/slack-github-action@91efab103c0de0a537f72a35f6b8cda0ee76bf0a #v2.1.1
|
||||
with:
|
||||
webhook: ${{ secrets.SLACK_TOOLBOX_WEBHOOK_URL }}
|
||||
webhook-type: incoming-webhook
|
||||
payload: |
|
||||
text: "Syft SPDX license list update failed"
|
||||
blocks:
|
||||
- type: section
|
||||
text:
|
||||
type: mrkdwn
|
||||
text: |
|
||||
*Syft SPDX license list update failed*
|
||||
• Workflow: `${{ github.workflow }}`
|
||||
• Event: `${{ github.event_name }}`
|
||||
• Job Status: `${{ job.status }}`
|
||||
• <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View Run>
|
||||
if: ${{ failure() && env.SLACK_NOTIFICATIONS == 'true' }}
|
||||
@@ -48,7 +48,6 @@ func create(id clio.Identification, out io.Writer) (clio.Application, *cobra.Com
|
||||
commands.Cataloger(app),
|
||||
commands.Attest(app),
|
||||
commands.Convert(app),
|
||||
commands.Split(app),
|
||||
clio.VersionCommand(id, schemaVersion),
|
||||
clio.ConfigCommand(app, nil),
|
||||
cranecmd.NewCmdAuthLogin(id.Name), // syft login uses the same command as crane
|
||||
|
||||
@@ -1,264 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/anchore/fangs"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/anchore/clio"
|
||||
"github.com/anchore/syft/cmd/syft/internal/options"
|
||||
"github.com/anchore/syft/cmd/syft/internal/split"
|
||||
"github.com/anchore/syft/cmd/syft/internal/ui"
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/format"
|
||||
"github.com/anchore/syft/syft/format/syftjson"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
)
|
||||
|
||||
const (
|
||||
splitExample = ` {{.appName}} {{.command}} image.sbom.json split SBOM into one file per package
|
||||
{{.appName}} {{.command}} image.sbom.json --pkg musl split only the musl package
|
||||
{{.appName}} {{.command}} image.sbom.json --pkg musl --dir /tmp write output to /tmp directory
|
||||
{{.appName}} {{.command}} image.sbom.json --drop source --drop location:fsid drop source and filesystem IDs
|
||||
`
|
||||
)
|
||||
|
||||
// SplitOptions defines the options for the split command
|
||||
type SplitOptions struct {
|
||||
options.Config `yaml:",inline" mapstructure:",squash"`
|
||||
options.UpdateCheck `yaml:",inline" mapstructure:",squash"`
|
||||
Packages []string `yaml:"packages" json:"packages" mapstructure:"packages"`
|
||||
OutputDir string `yaml:"output-dir" json:"output-dir" mapstructure:"output-dir"`
|
||||
Drop []string `yaml:"drop" json:"drop" mapstructure:"drop"`
|
||||
}
|
||||
|
||||
var _ interface {
|
||||
clio.FlagAdder
|
||||
fangs.FieldDescriber
|
||||
} = (*SplitOptions)(nil)
|
||||
|
||||
func (o *SplitOptions) AddFlags(flags clio.FlagSet) {
|
||||
flags.StringArrayVarP(&o.Packages, "pkg", "p", "package(s) to split (can be specified multiple times)")
|
||||
flags.StringVarP(&o.OutputDir, "dir", "d", "output directory for split SBOMs (default: current directory)")
|
||||
flags.StringArrayVarP(&o.Drop, "drop", "", "drop options to apply (source, descriptor, distro, pkg:*, file:*, location:*, all)")
|
||||
}
|
||||
|
||||
func (o *SplitOptions) DescribeFields(descriptions fangs.FieldDescriptionSet) {
|
||||
descriptions.Add(&o.Packages, "package queries to match for splitting (ID, PURL, name, or name@version)")
|
||||
descriptions.Add(&o.OutputDir, "output directory for split SBOM files")
|
||||
descriptions.Add(&o.Drop, "options for dropping SBOM sections (source, descriptor, distro, pkg:licenses, pkg:metadata.files, file:metadata, file:digests, file:executable, file:unknowns, file:licenses, file:contents, location:fsid, location:non-primary-evidence, all)")
|
||||
}
|
||||
|
||||
// Split creates the split command
|
||||
func Split(app clio.Application) *cobra.Command {
|
||||
id := app.ID()
|
||||
|
||||
opts := &SplitOptions{
|
||||
UpdateCheck: options.DefaultUpdateCheck(),
|
||||
OutputDir: ".",
|
||||
}
|
||||
|
||||
return app.SetupCommand(&cobra.Command{
|
||||
Use: "split [SOURCE-SBOM] [flags]",
|
||||
Short: "Split an SBOM into separate SBOMs per package",
|
||||
Long: `[Experimental] Split a syft-format SBOM into one or more SBOMs, one per package.
|
||||
Each output SBOM contains only the target package, its related packages (connected via
|
||||
ownership-by-file-overlap and evident-by relationships), and their associated files.
|
||||
|
||||
If no --pkg flags are specified, creates one SBOM file per package in the source SBOM.
|
||||
If --pkg flags are specified, only creates SBOM files for the matching packages.
|
||||
|
||||
Package matching (in order of precedence):
|
||||
1. Exact package ID
|
||||
2. Exact PURL or PURL prefix
|
||||
3. Case-insensitive package name
|
||||
4. name@version format
|
||||
|
||||
Drop options:
|
||||
source Drop the source object entirely
|
||||
descriptor Drop the descriptor object
|
||||
distro Drop the distro (Linux distribution) object
|
||||
pkg:licenses Drop package licenses
|
||||
pkg:metadata.files Drop files from package metadata (for FileOwner types)
|
||||
file:metadata Drop file metadata (size, permissions, etc.)
|
||||
file:digests Drop file digests
|
||||
file:executable Drop executable metadata
|
||||
file:unknowns Drop unknown file entries
|
||||
file:licenses Drop file-level licenses
|
||||
file:contents Drop file contents
|
||||
location:fsid Clear FileSystemID from all coordinates
|
||||
location:non-primary-evidence Drop locations without "evidence": "primary" annotation
|
||||
all Apply all drop options above`,
|
||||
Example: internal.Tprintf(splitExample, map[string]interface{}{
|
||||
"appName": id.Name,
|
||||
"command": "split",
|
||||
}),
|
||||
Args: validateSplitArgs,
|
||||
PreRunE: applicationUpdateCheck(id, &opts.UpdateCheck),
|
||||
RunE: func(_ *cobra.Command, args []string) error {
|
||||
restoreStdout := ui.CaptureStdoutToTraceLog()
|
||||
defer restoreStdout()
|
||||
|
||||
return RunSplit(opts, args[0])
|
||||
},
|
||||
}, opts)
|
||||
}
|
||||
|
||||
func validateSplitArgs(cmd *cobra.Command, args []string) error {
|
||||
return validateArgs(cmd, args, "an SBOM argument is required")
|
||||
}
|
||||
|
||||
// RunSplit executes the split operation
|
||||
func RunSplit(opts *SplitOptions, userInput string) error {
|
||||
log.Warn("split is an experimental feature, run `syft split -h` for help")
|
||||
|
||||
// validate drop options
|
||||
for _, d := range opts.Drop {
|
||||
if !split.ValidDropOption(d) {
|
||||
return fmt.Errorf("invalid drop option: %q", d)
|
||||
}
|
||||
}
|
||||
|
||||
// parse drop options
|
||||
dropOpts := split.ParseDropOptions(opts.Drop)
|
||||
dropLocationFSID := split.HasDropLocationFSID(dropOpts)
|
||||
dropNonPrimaryEvidence := split.HasDropLocationNonPrimaryEvidence(dropOpts)
|
||||
|
||||
// read SBOM
|
||||
var reader io.ReadSeekCloser
|
||||
if userInput == "-" {
|
||||
reader = internal.NewBufferedSeeker(os.Stdin)
|
||||
} else {
|
||||
f, err := os.Open(userInput)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open SBOM file: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
_ = f.Close()
|
||||
}()
|
||||
reader = f
|
||||
}
|
||||
|
||||
s, formatID, _, err := format.Decode(reader)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decode SBOM: %w", err)
|
||||
}
|
||||
|
||||
if s == nil {
|
||||
return fmt.Errorf("no SBOM produced")
|
||||
}
|
||||
|
||||
// warn if not syft-json format
|
||||
if formatID != syftjson.ID {
|
||||
log.Warnf("input SBOM is not syft-json format (detected: %s), some data may be lost", formatID)
|
||||
}
|
||||
|
||||
// determine target packages
|
||||
var targetPackages []pkg.Package
|
||||
if len(opts.Packages) == 0 {
|
||||
// split all packages
|
||||
targetPackages = s.Artifacts.Packages.Sorted()
|
||||
} else {
|
||||
// match specified packages
|
||||
targetPackages = split.MatchPackages(s.Artifacts.Packages, opts.Packages)
|
||||
if len(targetPackages) == 0 {
|
||||
return fmt.Errorf("no packages matched the specified queries: %v", opts.Packages)
|
||||
}
|
||||
log.Infof("matched %d package(s) for splitting", len(targetPackages))
|
||||
}
|
||||
|
||||
// ensure output directory exists
|
||||
if err := os.MkdirAll(opts.OutputDir, 0o755); err != nil {
|
||||
return fmt.Errorf("failed to create output directory: %w", err)
|
||||
}
|
||||
|
||||
// split SBOM
|
||||
results := split.Split(*s, targetPackages, dropLocationFSID, dropNonPrimaryEvidence)
|
||||
|
||||
// create encoder
|
||||
encoder, err := syftjson.NewFormatEncoderWithConfig(syftjson.EncoderConfig{
|
||||
Pretty: true,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create encoder: %w", err)
|
||||
}
|
||||
|
||||
// get fields to remove from JSON output
|
||||
fieldsToRemove := split.GetJSONFieldsToRemove(dropOpts)
|
||||
|
||||
// write output files
|
||||
for _, result := range results {
|
||||
// apply drop options (excluding location:* options which are handled in Split)
|
||||
filteredDropOpts := make([]split.DropOption, 0, len(dropOpts))
|
||||
for _, opt := range dropOpts {
|
||||
if opt != split.DropLocationFSID && opt != split.DropLocationNonPrimaryEvidence {
|
||||
filteredDropOpts = append(filteredDropOpts, opt)
|
||||
}
|
||||
}
|
||||
split.ApplyDropOptions(&result.SBOM, filteredDropOpts)
|
||||
|
||||
// generate output filename using package ID
|
||||
outputFile := filepath.Join(opts.OutputDir, fmt.Sprintf("%s.json", result.TargetPackage.ID()))
|
||||
|
||||
if err := writeSBOMWithFieldRemoval(encoder, result.SBOM, outputFile, fieldsToRemove); err != nil {
|
||||
return fmt.Errorf("failed to write SBOM for package %s: %w", result.TargetPackage.Name, err)
|
||||
}
|
||||
|
||||
log.Infof("wrote %s (%s@%s)", outputFile, result.TargetPackage.Name, result.TargetPackage.Version)
|
||||
}
|
||||
|
||||
log.Infof("split complete: %d SBOM(s) written to %s", len(results), opts.OutputDir)
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeSBOMWithFieldRemoval(encoder sbom.FormatEncoder, s sbom.SBOM, outputFile string, fieldsToRemove []string) error {
|
||||
// if no fields to remove, use direct encoding
|
||||
if len(fieldsToRemove) == 0 {
|
||||
f, err := os.Create(outputFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create file: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
_ = f.Close()
|
||||
}()
|
||||
return encoder.Encode(f, s)
|
||||
}
|
||||
|
||||
// encode to buffer first
|
||||
var buf bytes.Buffer
|
||||
if err := encoder.Encode(&buf, s); err != nil {
|
||||
return fmt.Errorf("failed to encode SBOM: %w", err)
|
||||
}
|
||||
|
||||
// parse as generic map to remove fields
|
||||
var doc map[string]interface{}
|
||||
if err := json.Unmarshal(buf.Bytes(), &doc); err != nil {
|
||||
return fmt.Errorf("failed to parse encoded SBOM: %w", err)
|
||||
}
|
||||
|
||||
// remove specified fields
|
||||
for _, field := range fieldsToRemove {
|
||||
delete(doc, field)
|
||||
}
|
||||
|
||||
// re-encode with pretty printing
|
||||
output, err := json.MarshalIndent(doc, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to re-encode SBOM: %w", err)
|
||||
}
|
||||
|
||||
// write to file
|
||||
if err := os.WriteFile(outputFile, append(output, '\n'), 0o644); err != nil {
|
||||
return fmt.Errorf("failed to write file: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestRunSplit_BasicExecution(t *testing.T) {
|
||||
// use the example SBOM
|
||||
sbomPath := "../../../../examples/decode_sbom/alpine.syft.json"
|
||||
if _, err := os.Stat(sbomPath); os.IsNotExist(err) {
|
||||
t.Skip("example SBOM not found, skipping integration test")
|
||||
}
|
||||
|
||||
// create temporary output directory
|
||||
tmpDir, err := os.MkdirTemp("", "syft-split-test-*")
|
||||
require.NoError(t, err)
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
opts := &SplitOptions{
|
||||
Packages: []string{"alpine-baselayout"},
|
||||
OutputDir: tmpDir,
|
||||
Drop: []string{},
|
||||
}
|
||||
|
||||
err = RunSplit(opts, sbomPath)
|
||||
require.NoError(t, err)
|
||||
|
||||
// verify output files were created
|
||||
entries, err := os.ReadDir(tmpDir)
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, entries, "expected at least one output file")
|
||||
|
||||
// verify output file is valid JSON
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
content, err := os.ReadFile(filepath.Join(tmpDir, entry.Name()))
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, content)
|
||||
// basic JSON validation - should start with {
|
||||
assert.True(t, len(content) > 0 && content[0] == '{', "output should be valid JSON")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunSplit_InvalidDropOption(t *testing.T) {
|
||||
opts := &SplitOptions{
|
||||
Packages: []string{"test"},
|
||||
OutputDir: ".",
|
||||
Drop: []string{"invalid-option"},
|
||||
}
|
||||
|
||||
err := RunSplit(opts, "some-sbom.json")
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "invalid drop option")
|
||||
}
|
||||
|
||||
func TestRunSplit_NoMatchingPackages(t *testing.T) {
|
||||
sbomPath := "../../../../examples/decode_sbom/alpine.syft.json"
|
||||
if _, err := os.Stat(sbomPath); os.IsNotExist(err) {
|
||||
t.Skip("example SBOM not found, skipping integration test")
|
||||
}
|
||||
|
||||
tmpDir, err := os.MkdirTemp("", "syft-split-test-*")
|
||||
require.NoError(t, err)
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
opts := &SplitOptions{
|
||||
Packages: []string{"nonexistent-package-xyz"},
|
||||
OutputDir: tmpDir,
|
||||
Drop: []string{},
|
||||
}
|
||||
|
||||
err = RunSplit(opts, sbomPath)
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "no packages matched")
|
||||
}
|
||||
|
||||
func TestRunSplit_WithDropOptions(t *testing.T) {
|
||||
sbomPath := "../../../../examples/decode_sbom/alpine.syft.json"
|
||||
if _, err := os.Stat(sbomPath); os.IsNotExist(err) {
|
||||
t.Skip("example SBOM not found, skipping integration test")
|
||||
}
|
||||
|
||||
tmpDir, err := os.MkdirTemp("", "syft-split-test-*")
|
||||
require.NoError(t, err)
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
opts := &SplitOptions{
|
||||
Packages: []string{"alpine-baselayout"},
|
||||
OutputDir: tmpDir,
|
||||
Drop: []string{"source", "location:fsid"},
|
||||
}
|
||||
|
||||
err = RunSplit(opts, sbomPath)
|
||||
require.NoError(t, err)
|
||||
|
||||
// verify output file was created
|
||||
entries, err := os.ReadDir(tmpDir)
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, entries)
|
||||
}
|
||||
|
||||
func TestRunSplit_FileNotFound(t *testing.T) {
|
||||
opts := &SplitOptions{
|
||||
Packages: []string{"test"},
|
||||
OutputDir: ".",
|
||||
Drop: []string{},
|
||||
}
|
||||
|
||||
err := RunSplit(opts, "/nonexistent/path/sbom.json")
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "failed to open SBOM file")
|
||||
}
|
||||
@@ -107,7 +107,7 @@ func fetchLatestApplicationVersion(id clio.Identification) (*hashiVersion.Versio
|
||||
return nil, fmt.Errorf("HTTP %d on fetching latest version: %s", resp.StatusCode, resp.Status)
|
||||
}
|
||||
|
||||
versionBytes, err := io.ReadAll(resp.Body)
|
||||
versionBytes, err := io.ReadAll(io.LimitReader(resp.Body, 500))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read latest version: %w", err)
|
||||
}
|
||||
|
||||
@@ -1,307 +0,0 @@
|
||||
package split
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
// DropOption represents a drop option for filtering SBOM contents
|
||||
type DropOption string
|
||||
|
||||
const (
|
||||
DropSource DropOption = "source"
|
||||
DropDescriptor DropOption = "descriptor"
|
||||
DropDistro DropOption = "distro"
|
||||
DropPkgLicenses DropOption = "pkg:licenses"
|
||||
DropPkgMetadataFiles DropOption = "pkg:metadata.files"
|
||||
DropFileMetadata DropOption = "file:metadata"
|
||||
DropFileDigests DropOption = "file:digests"
|
||||
DropFileExecutable DropOption = "file:executable"
|
||||
DropFileUnknowns DropOption = "file:unknowns"
|
||||
DropFileLicenses DropOption = "file:licenses"
|
||||
DropFileContents DropOption = "file:contents"
|
||||
DropLocationFSID DropOption = "location:fsid"
|
||||
DropLocationNonPrimaryEvidence DropOption = "location:non-primary-evidence"
|
||||
DropAll DropOption = "all"
|
||||
)
|
||||
|
||||
// AllDropOptions returns all valid drop options (excluding "all")
|
||||
func AllDropOptions() []DropOption {
|
||||
return []DropOption{
|
||||
DropSource,
|
||||
DropDescriptor,
|
||||
DropDistro,
|
||||
DropPkgLicenses,
|
||||
DropPkgMetadataFiles,
|
||||
DropFileMetadata,
|
||||
DropFileDigests,
|
||||
DropFileExecutable,
|
||||
DropFileUnknowns,
|
||||
DropFileLicenses,
|
||||
DropFileContents,
|
||||
DropLocationFSID,
|
||||
DropLocationNonPrimaryEvidence,
|
||||
}
|
||||
}
|
||||
|
||||
// ParseDropOptions parses string values into DropOption values
|
||||
func ParseDropOptions(values []string) []DropOption {
|
||||
var opts []DropOption
|
||||
for _, v := range values {
|
||||
opt := DropOption(strings.ToLower(strings.TrimSpace(v)))
|
||||
if opt == DropAll {
|
||||
return AllDropOptions()
|
||||
}
|
||||
opts = append(opts, opt)
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
// ApplyDropOptions applies the specified drop options to the SBOM
|
||||
func ApplyDropOptions(s *sbom.SBOM, opts []DropOption) {
|
||||
if s == nil || len(opts) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
switch opt {
|
||||
case DropSource:
|
||||
s.Source = source.Description{}
|
||||
case DropDescriptor:
|
||||
s.Descriptor = sbom.Descriptor{}
|
||||
case DropDistro:
|
||||
s.Artifacts.LinuxDistribution = nil
|
||||
case DropPkgLicenses:
|
||||
clearPackageLicenses(s)
|
||||
case DropPkgMetadataFiles:
|
||||
clearPackageMetadataFiles(s)
|
||||
case DropFileMetadata:
|
||||
s.Artifacts.FileMetadata = nil
|
||||
case DropFileDigests:
|
||||
s.Artifacts.FileDigests = nil
|
||||
case DropFileExecutable:
|
||||
s.Artifacts.Executables = nil
|
||||
case DropFileUnknowns:
|
||||
s.Artifacts.Unknowns = nil
|
||||
case DropFileLicenses:
|
||||
s.Artifacts.FileLicenses = nil
|
||||
case DropFileContents:
|
||||
s.Artifacts.FileContents = nil
|
||||
case DropLocationFSID:
|
||||
clearFileSystemIDs(s)
|
||||
case DropLocationNonPrimaryEvidence:
|
||||
clearNonPrimaryEvidenceLocations(s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetJSONFieldsToRemove returns the JSON field names that should be completely removed from output
|
||||
func GetJSONFieldsToRemove(opts []DropOption) []string {
|
||||
var fields []string
|
||||
for _, opt := range opts {
|
||||
switch opt {
|
||||
case DropSource:
|
||||
fields = append(fields, "source")
|
||||
case DropDescriptor:
|
||||
fields = append(fields, "descriptor")
|
||||
case DropDistro:
|
||||
fields = append(fields, "distro")
|
||||
}
|
||||
}
|
||||
return fields
|
||||
}
|
||||
|
||||
// clearFileSystemIDs clears FileSystemID from all coordinates in file artifacts and relationships.
|
||||
// Note: package locations are handled separately in the splitter when creating new packages.
|
||||
func clearFileSystemIDs(s *sbom.SBOM) {
|
||||
// clear from file metadata
|
||||
if s.Artifacts.FileMetadata != nil {
|
||||
newMetadata := make(map[file.Coordinates]file.Metadata)
|
||||
for coord, meta := range s.Artifacts.FileMetadata {
|
||||
newCoord := file.Coordinates{RealPath: coord.RealPath}
|
||||
newMetadata[newCoord] = meta
|
||||
}
|
||||
s.Artifacts.FileMetadata = newMetadata
|
||||
}
|
||||
|
||||
// clear from file digests
|
||||
if s.Artifacts.FileDigests != nil {
|
||||
newDigests := make(map[file.Coordinates][]file.Digest)
|
||||
for coord, digests := range s.Artifacts.FileDigests {
|
||||
newCoord := file.Coordinates{RealPath: coord.RealPath}
|
||||
newDigests[newCoord] = digests
|
||||
}
|
||||
s.Artifacts.FileDigests = newDigests
|
||||
}
|
||||
|
||||
// clear from file contents
|
||||
if s.Artifacts.FileContents != nil {
|
||||
newContents := make(map[file.Coordinates]string)
|
||||
for coord, contents := range s.Artifacts.FileContents {
|
||||
newCoord := file.Coordinates{RealPath: coord.RealPath}
|
||||
newContents[newCoord] = contents
|
||||
}
|
||||
s.Artifacts.FileContents = newContents
|
||||
}
|
||||
|
||||
// clear from file licenses
|
||||
if s.Artifacts.FileLicenses != nil {
|
||||
newLicenses := make(map[file.Coordinates][]file.License)
|
||||
for coord, licenses := range s.Artifacts.FileLicenses {
|
||||
newCoord := file.Coordinates{RealPath: coord.RealPath}
|
||||
newLicenses[newCoord] = licenses
|
||||
}
|
||||
s.Artifacts.FileLicenses = newLicenses
|
||||
}
|
||||
|
||||
// clear from executables
|
||||
if s.Artifacts.Executables != nil {
|
||||
newExec := make(map[file.Coordinates]file.Executable)
|
||||
for coord, exec := range s.Artifacts.Executables {
|
||||
newCoord := file.Coordinates{RealPath: coord.RealPath}
|
||||
newExec[newCoord] = exec
|
||||
}
|
||||
s.Artifacts.Executables = newExec
|
||||
}
|
||||
|
||||
// clear from unknowns
|
||||
if s.Artifacts.Unknowns != nil {
|
||||
newUnknowns := make(map[file.Coordinates][]string)
|
||||
for coord, unknowns := range s.Artifacts.Unknowns {
|
||||
newCoord := file.Coordinates{RealPath: coord.RealPath}
|
||||
newUnknowns[newCoord] = unknowns
|
||||
}
|
||||
s.Artifacts.Unknowns = newUnknowns
|
||||
}
|
||||
|
||||
// clear from relationships that reference file coordinates
|
||||
newRelationships := make([]artifact.Relationship, 0, len(s.Relationships))
|
||||
for _, rel := range s.Relationships {
|
||||
newRel := rel
|
||||
|
||||
if coord, ok := rel.From.(file.Coordinates); ok {
|
||||
newRel.From = file.Coordinates{RealPath: coord.RealPath}
|
||||
}
|
||||
if coord, ok := rel.To.(file.Coordinates); ok {
|
||||
newRel.To = file.Coordinates{RealPath: coord.RealPath}
|
||||
}
|
||||
|
||||
newRelationships = append(newRelationships, newRel)
|
||||
}
|
||||
s.Relationships = newRelationships
|
||||
}
|
||||
|
||||
// clearPackageLicenses removes licenses from all packages in the SBOM
|
||||
func clearPackageLicenses(s *sbom.SBOM) {
|
||||
if s.Artifacts.Packages == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for p := range s.Artifacts.Packages.Enumerate() {
|
||||
p.Licenses = pkg.NewLicenseSet()
|
||||
s.Artifacts.Packages.Delete(p.ID())
|
||||
s.Artifacts.Packages.Add(p)
|
||||
}
|
||||
}
|
||||
|
||||
// clearNonPrimaryEvidenceLocations removes locations that don't have "evidence": "primary" annotation
|
||||
func clearNonPrimaryEvidenceLocations(s *sbom.SBOM) {
|
||||
if s.Artifacts.Packages == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for p := range s.Artifacts.Packages.Enumerate() {
|
||||
newLocations := file.NewLocationSet()
|
||||
for _, loc := range p.Locations.ToSlice() {
|
||||
if loc.Annotations != nil && loc.Annotations["evidence"] == "primary" {
|
||||
newLocations.Add(loc)
|
||||
}
|
||||
}
|
||||
p.Locations = newLocations
|
||||
s.Artifacts.Packages.Delete(p.ID())
|
||||
s.Artifacts.Packages.Add(p)
|
||||
}
|
||||
}
|
||||
|
||||
// clearPackageMetadataFiles clears the Files field from any package metadata that implements FileOwner
|
||||
func clearPackageMetadataFiles(s *sbom.SBOM) {
|
||||
if s.Artifacts.Packages == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for p := range s.Artifacts.Packages.Enumerate() {
|
||||
if p.Metadata == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
newMetadata := clearMetadataFiles(p.Metadata)
|
||||
if newMetadata != nil {
|
||||
p.Metadata = newMetadata
|
||||
s.Artifacts.Packages.Delete(p.ID())
|
||||
s.Artifacts.Packages.Add(p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// clearMetadataFiles returns a copy of the metadata with Files field cleared if it implements FileOwner.
|
||||
// Returns nil if the metadata type is not recognized as a FileOwner implementer.
|
||||
func clearMetadataFiles(metadata any) any {
|
||||
switch m := metadata.(type) {
|
||||
case pkg.ApkDBEntry:
|
||||
m.Files = nil
|
||||
return m
|
||||
case pkg.RpmDBEntry:
|
||||
m.Files = nil
|
||||
return m
|
||||
case pkg.DpkgDBEntry:
|
||||
m.Files = nil
|
||||
return m
|
||||
case pkg.AlpmDBEntry:
|
||||
m.Files = nil
|
||||
return m
|
||||
case pkg.PortageEntry:
|
||||
m.Files = nil
|
||||
return m
|
||||
case pkg.NixStoreEntry:
|
||||
m.Files = nil
|
||||
return m
|
||||
case pkg.PythonPackage:
|
||||
m.Files = nil
|
||||
return m
|
||||
case pkg.CondaMetaPackage:
|
||||
m.Files = nil
|
||||
return m
|
||||
case pkg.BitnamiSBOMEntry:
|
||||
m.Files = nil
|
||||
return m
|
||||
case pkg.JavaVMInstallation:
|
||||
m.Files = nil
|
||||
return m
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidDropOption checks if a string is a valid drop option
|
||||
func ValidDropOption(s string) bool {
|
||||
opt := DropOption(strings.ToLower(strings.TrimSpace(s)))
|
||||
if opt == DropAll {
|
||||
return true
|
||||
}
|
||||
return slices.Contains(AllDropOptions(), opt)
|
||||
}
|
||||
|
||||
// HasDropLocationFSID checks if the drop options include location:fsid
|
||||
func HasDropLocationFSID(opts []DropOption) bool {
|
||||
return slices.Contains(opts, DropLocationFSID)
|
||||
}
|
||||
|
||||
// HasDropLocationNonPrimaryEvidence checks if the drop options include location:non-primary-evidence
|
||||
func HasDropLocationNonPrimaryEvidence(opts []DropOption) bool {
|
||||
return slices.Contains(opts, DropLocationNonPrimaryEvidence)
|
||||
}
|
||||
@@ -1,499 +0,0 @@
|
||||
package split
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func TestParseDropOptions(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
values []string
|
||||
want []DropOption
|
||||
}{
|
||||
{
|
||||
name: "single option",
|
||||
values: []string{"source"},
|
||||
want: []DropOption{DropSource},
|
||||
},
|
||||
{
|
||||
name: "multiple options",
|
||||
values: []string{"source", "descriptor", "file:digests"},
|
||||
want: []DropOption{DropSource, DropDescriptor, DropFileDigests},
|
||||
},
|
||||
{
|
||||
name: "all option expands",
|
||||
values: []string{"all"},
|
||||
want: AllDropOptions(),
|
||||
},
|
||||
{
|
||||
name: "case insensitive",
|
||||
values: []string{"SOURCE", "File:Digests"},
|
||||
want: []DropOption{DropSource, DropFileDigests},
|
||||
},
|
||||
{
|
||||
name: "with whitespace",
|
||||
values: []string{" source ", " location:fsid"},
|
||||
want: []DropOption{DropSource, DropLocationFSID},
|
||||
},
|
||||
{
|
||||
name: "empty values",
|
||||
values: []string{},
|
||||
want: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := ParseDropOptions(tt.values)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidDropOption(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want bool
|
||||
}{
|
||||
{name: "valid source", input: "source", want: true},
|
||||
{name: "valid descriptor", input: "descriptor", want: true},
|
||||
{name: "valid distro", input: "distro", want: true},
|
||||
{name: "valid file:metadata", input: "file:metadata", want: true},
|
||||
{name: "valid file:digests", input: "file:digests", want: true},
|
||||
{name: "valid file:executable", input: "file:executable", want: true},
|
||||
{name: "valid file:unknowns", input: "file:unknowns", want: true},
|
||||
{name: "valid file:licenses", input: "file:licenses", want: true},
|
||||
{name: "valid file:contents", input: "file:contents", want: true},
|
||||
{name: "valid location:fsid", input: "location:fsid", want: true},
|
||||
{name: "valid location:non-primary-evidence", input: "location:non-primary-evidence", want: true},
|
||||
{name: "valid pkg:licenses", input: "pkg:licenses", want: true},
|
||||
{name: "valid pkg:metadata.files", input: "pkg:metadata.files", want: true},
|
||||
{name: "valid all", input: "all", want: true},
|
||||
{name: "case insensitive", input: "SOURCE", want: true},
|
||||
{name: "invalid option", input: "invalid", want: false},
|
||||
{name: "partial match", input: "file", want: false},
|
||||
{name: "empty string", input: "", want: false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := ValidDropOption(tt.input)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyDropOptions(t *testing.T) {
|
||||
coord := file.Coordinates{
|
||||
RealPath: "/test/path",
|
||||
FileSystemID: "layer123",
|
||||
}
|
||||
|
||||
baseSBOM := func() *sbom.SBOM {
|
||||
return &sbom.SBOM{
|
||||
Source: source.Description{
|
||||
ID: "test-id",
|
||||
Name: "test-source",
|
||||
},
|
||||
Descriptor: sbom.Descriptor{
|
||||
Name: "syft",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
Artifacts: sbom.Artifacts{
|
||||
Packages: pkg.NewCollection(),
|
||||
FileMetadata: map[file.Coordinates]file.Metadata{coord: {}},
|
||||
FileDigests: map[file.Coordinates][]file.Digest{coord: {{Algorithm: "sha256", Value: "abc"}}},
|
||||
FileContents: map[file.Coordinates]string{coord: "contents"},
|
||||
FileLicenses: map[file.Coordinates][]file.License{coord: {{Value: "MIT"}}},
|
||||
Executables: map[file.Coordinates]file.Executable{coord: {}},
|
||||
Unknowns: map[file.Coordinates][]string{coord: {"unknown"}},
|
||||
},
|
||||
Relationships: []artifact.Relationship{
|
||||
{
|
||||
From: coord,
|
||||
To: coord,
|
||||
Type: artifact.EvidentByRelationship,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
opts []DropOption
|
||||
verify func(t *testing.T, s *sbom.SBOM)
|
||||
}{
|
||||
{
|
||||
name: "drop source",
|
||||
opts: []DropOption{DropSource},
|
||||
verify: func(t *testing.T, s *sbom.SBOM) {
|
||||
assert.Empty(t, s.Source.ID)
|
||||
assert.Empty(t, s.Source.Name)
|
||||
// other fields should be preserved
|
||||
assert.Equal(t, "syft", s.Descriptor.Name)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "drop descriptor",
|
||||
opts: []DropOption{DropDescriptor},
|
||||
verify: func(t *testing.T, s *sbom.SBOM) {
|
||||
assert.Empty(t, s.Descriptor.Name)
|
||||
assert.Empty(t, s.Descriptor.Version)
|
||||
// other fields should be preserved
|
||||
assert.Equal(t, "test-id", s.Source.ID)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "drop distro",
|
||||
opts: []DropOption{DropDistro},
|
||||
verify: func(t *testing.T, s *sbom.SBOM) {
|
||||
assert.Nil(t, s.Artifacts.LinuxDistribution)
|
||||
// other fields should be preserved
|
||||
assert.Equal(t, "test-id", s.Source.ID)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "drop file:metadata",
|
||||
opts: []DropOption{DropFileMetadata},
|
||||
verify: func(t *testing.T, s *sbom.SBOM) {
|
||||
assert.Nil(t, s.Artifacts.FileMetadata)
|
||||
// other file artifacts should be preserved
|
||||
assert.NotNil(t, s.Artifacts.FileDigests)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "drop file:digests",
|
||||
opts: []DropOption{DropFileDigests},
|
||||
verify: func(t *testing.T, s *sbom.SBOM) {
|
||||
assert.Nil(t, s.Artifacts.FileDigests)
|
||||
// other file artifacts should be preserved
|
||||
assert.NotNil(t, s.Artifacts.FileMetadata)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "drop file:executable",
|
||||
opts: []DropOption{DropFileExecutable},
|
||||
verify: func(t *testing.T, s *sbom.SBOM) {
|
||||
assert.Nil(t, s.Artifacts.Executables)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "drop file:unknowns",
|
||||
opts: []DropOption{DropFileUnknowns},
|
||||
verify: func(t *testing.T, s *sbom.SBOM) {
|
||||
assert.Nil(t, s.Artifacts.Unknowns)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "drop file:licenses",
|
||||
opts: []DropOption{DropFileLicenses},
|
||||
verify: func(t *testing.T, s *sbom.SBOM) {
|
||||
assert.Nil(t, s.Artifacts.FileLicenses)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "drop file:contents",
|
||||
opts: []DropOption{DropFileContents},
|
||||
verify: func(t *testing.T, s *sbom.SBOM) {
|
||||
assert.Nil(t, s.Artifacts.FileContents)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "drop location:fsid clears FileSystemID",
|
||||
opts: []DropOption{DropLocationFSID},
|
||||
verify: func(t *testing.T, s *sbom.SBOM) {
|
||||
// check that FileSystemID is cleared from file metadata
|
||||
for coord := range s.Artifacts.FileMetadata {
|
||||
assert.Empty(t, coord.FileSystemID, "FileSystemID should be empty in FileMetadata")
|
||||
assert.Equal(t, "/test/path", coord.RealPath)
|
||||
}
|
||||
// check relationships
|
||||
for _, rel := range s.Relationships {
|
||||
if c, ok := rel.From.(file.Coordinates); ok {
|
||||
assert.Empty(t, c.FileSystemID, "FileSystemID should be empty in relationship From")
|
||||
}
|
||||
if c, ok := rel.To.(file.Coordinates); ok {
|
||||
assert.Empty(t, c.FileSystemID, "FileSystemID should be empty in relationship To")
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "drop pkg:metadata.files clears files from ApkDBEntry",
|
||||
opts: []DropOption{DropPkgMetadataFiles},
|
||||
verify: func(t *testing.T, s *sbom.SBOM) {
|
||||
// first add a package with ApkDBEntry metadata
|
||||
p := pkg.Package{
|
||||
Name: "test-apk",
|
||||
Version: "1.0.0",
|
||||
Type: pkg.ApkPkg,
|
||||
Metadata: pkg.ApkDBEntry{
|
||||
Package: "test-apk",
|
||||
Files: []pkg.ApkFileRecord{
|
||||
{Path: "/usr/bin/test"},
|
||||
},
|
||||
},
|
||||
}
|
||||
s.Artifacts.Packages.Add(p)
|
||||
|
||||
ApplyDropOptions(s, []DropOption{DropPkgMetadataFiles})
|
||||
|
||||
// verify files were cleared
|
||||
for p := range s.Artifacts.Packages.Enumerate() {
|
||||
if p.Name == "test-apk" {
|
||||
meta, ok := p.Metadata.(pkg.ApkDBEntry)
|
||||
require.True(t, ok)
|
||||
assert.Nil(t, meta.Files)
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "drop pkg:metadata.files clears files from RpmDBEntry",
|
||||
opts: []DropOption{DropPkgMetadataFiles},
|
||||
verify: func(t *testing.T, s *sbom.SBOM) {
|
||||
p := pkg.Package{
|
||||
Name: "test-rpm",
|
||||
Version: "1.0.0",
|
||||
Type: pkg.RpmPkg,
|
||||
Metadata: pkg.RpmDBEntry{
|
||||
Name: "test-rpm",
|
||||
Files: []pkg.RpmFileRecord{
|
||||
{Path: "/usr/bin/test"},
|
||||
},
|
||||
},
|
||||
}
|
||||
s.Artifacts.Packages.Add(p)
|
||||
|
||||
ApplyDropOptions(s, []DropOption{DropPkgMetadataFiles})
|
||||
|
||||
for p := range s.Artifacts.Packages.Enumerate() {
|
||||
if p.Name == "test-rpm" {
|
||||
meta, ok := p.Metadata.(pkg.RpmDBEntry)
|
||||
require.True(t, ok)
|
||||
assert.Nil(t, meta.Files)
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "drop pkg:metadata.files clears files from DpkgDBEntry",
|
||||
opts: []DropOption{DropPkgMetadataFiles},
|
||||
verify: func(t *testing.T, s *sbom.SBOM) {
|
||||
p := pkg.Package{
|
||||
Name: "test-deb",
|
||||
Version: "1.0.0",
|
||||
Type: pkg.DebPkg,
|
||||
Metadata: pkg.DpkgDBEntry{
|
||||
Package: "test-deb",
|
||||
Files: []pkg.DpkgFileRecord{
|
||||
{Path: "/usr/bin/test"},
|
||||
},
|
||||
},
|
||||
}
|
||||
s.Artifacts.Packages.Add(p)
|
||||
|
||||
ApplyDropOptions(s, []DropOption{DropPkgMetadataFiles})
|
||||
|
||||
for p := range s.Artifacts.Packages.Enumerate() {
|
||||
if p.Name == "test-deb" {
|
||||
meta, ok := p.Metadata.(pkg.DpkgDBEntry)
|
||||
require.True(t, ok)
|
||||
assert.Nil(t, meta.Files)
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "drop pkg:metadata.files clears files from PythonPackage",
|
||||
opts: []DropOption{DropPkgMetadataFiles},
|
||||
verify: func(t *testing.T, s *sbom.SBOM) {
|
||||
p := pkg.Package{
|
||||
Name: "test-python",
|
||||
Version: "1.0.0",
|
||||
Type: pkg.PythonPkg,
|
||||
Metadata: pkg.PythonPackage{
|
||||
Name: "test-python",
|
||||
Files: []pkg.PythonFileRecord{
|
||||
{Path: "/usr/lib/python/test.py"},
|
||||
},
|
||||
},
|
||||
}
|
||||
s.Artifacts.Packages.Add(p)
|
||||
|
||||
ApplyDropOptions(s, []DropOption{DropPkgMetadataFiles})
|
||||
|
||||
for p := range s.Artifacts.Packages.Enumerate() {
|
||||
if p.Name == "test-python" {
|
||||
meta, ok := p.Metadata.(pkg.PythonPackage)
|
||||
require.True(t, ok)
|
||||
assert.Nil(t, meta.Files)
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "drop pkg:metadata.files does nothing for non-FileOwner metadata",
|
||||
opts: []DropOption{DropPkgMetadataFiles},
|
||||
verify: func(t *testing.T, s *sbom.SBOM) {
|
||||
// add a package with metadata that doesn't implement FileOwner
|
||||
p := pkg.Package{
|
||||
Name: "test-npm",
|
||||
Version: "1.0.0",
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.NpmPackage{
|
||||
Name: "test-npm",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
}
|
||||
s.Artifacts.Packages.Add(p)
|
||||
|
||||
ApplyDropOptions(s, []DropOption{DropPkgMetadataFiles})
|
||||
|
||||
// verify package still exists with metadata intact
|
||||
for p := range s.Artifacts.Packages.Enumerate() {
|
||||
if p.Name == "test-npm" {
|
||||
meta, ok := p.Metadata.(pkg.NpmPackage)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, "test-npm", meta.Name)
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "nil SBOM does not panic",
|
||||
opts: []DropOption{DropSource},
|
||||
verify: func(t *testing.T, s *sbom.SBOM) {
|
||||
// nothing to verify, just ensure no panic
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty options does nothing",
|
||||
opts: []DropOption{},
|
||||
verify: func(t *testing.T, s *sbom.SBOM) {
|
||||
assert.Equal(t, "test-id", s.Source.ID)
|
||||
assert.Equal(t, "syft", s.Descriptor.Name)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var s *sbom.SBOM
|
||||
if tt.name != "nil SBOM does not panic" {
|
||||
s = baseSBOM()
|
||||
}
|
||||
|
||||
// should not panic
|
||||
require.NotPanics(t, func() {
|
||||
ApplyDropOptions(s, tt.opts)
|
||||
})
|
||||
|
||||
if s != nil {
|
||||
tt.verify(t, s)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasDropLocationFSID(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
opts []DropOption
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "has location:fsid",
|
||||
opts: []DropOption{DropSource, DropLocationFSID, DropDescriptor},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "no location:fsid",
|
||||
opts: []DropOption{DropSource, DropDescriptor},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "empty opts",
|
||||
opts: []DropOption{},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "nil opts",
|
||||
opts: nil,
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := HasDropLocationFSID(tt.opts)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetJSONFieldsToRemove(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
opts []DropOption
|
||||
want []string
|
||||
}{
|
||||
{
|
||||
name: "source only",
|
||||
opts: []DropOption{DropSource},
|
||||
want: []string{"source"},
|
||||
},
|
||||
{
|
||||
name: "descriptor only",
|
||||
opts: []DropOption{DropDescriptor},
|
||||
want: []string{"descriptor"},
|
||||
},
|
||||
{
|
||||
name: "distro only",
|
||||
opts: []DropOption{DropDistro},
|
||||
want: []string{"distro"},
|
||||
},
|
||||
{
|
||||
name: "all three",
|
||||
opts: []DropOption{DropSource, DropDescriptor, DropDistro},
|
||||
want: []string{"source", "descriptor", "distro"},
|
||||
},
|
||||
{
|
||||
name: "mixed with file options",
|
||||
opts: []DropOption{DropSource, DropFileDigests, DropDescriptor, DropFileMetadata},
|
||||
want: []string{"source", "descriptor"},
|
||||
},
|
||||
{
|
||||
name: "file options only returns empty",
|
||||
opts: []DropOption{DropFileDigests, DropFileMetadata, DropLocationFSID},
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
name: "empty opts",
|
||||
opts: []DropOption{},
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
name: "nil opts",
|
||||
opts: nil,
|
||||
want: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := GetJSONFieldsToRemove(tt.opts)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
package split
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
// MatchPackages finds packages in the collection that match the given queries.
|
||||
// Match order (first match wins):
|
||||
// 1. Exact package ID match
|
||||
// 2. Exact PURL match or PURL prefix match
|
||||
// 3. Case-insensitive name match
|
||||
// 4. name@version format match
|
||||
func MatchPackages(collection *pkg.Collection, queries []string) []pkg.Package {
|
||||
if collection == nil || len(queries) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// build indexes for efficient lookups
|
||||
byID := make(map[artifact.ID]pkg.Package)
|
||||
byPURL := make(map[string]pkg.Package)
|
||||
byNameLower := make(map[string][]pkg.Package)
|
||||
byNameVersion := make(map[string]pkg.Package)
|
||||
|
||||
for p := range collection.Enumerate() {
|
||||
byID[p.ID()] = p
|
||||
if p.PURL != "" {
|
||||
byPURL[p.PURL] = p
|
||||
}
|
||||
nameLower := strings.ToLower(p.Name)
|
||||
byNameLower[nameLower] = append(byNameLower[nameLower], p)
|
||||
nameVersion := strings.ToLower(p.Name + "@" + p.Version)
|
||||
byNameVersion[nameVersion] = p
|
||||
}
|
||||
|
||||
// track matched packages to avoid duplicates
|
||||
matched := make(map[artifact.ID]pkg.Package)
|
||||
|
||||
for _, query := range queries {
|
||||
// 1. exact package ID match
|
||||
if p, ok := byID[artifact.ID(query)]; ok {
|
||||
matched[p.ID()] = p
|
||||
continue
|
||||
}
|
||||
|
||||
// 2. exact PURL match
|
||||
if p, ok := byPURL[query]; ok {
|
||||
matched[p.ID()] = p
|
||||
continue
|
||||
}
|
||||
|
||||
// 2b. PURL prefix match (e.g., "pkg:apk/alpine/musl" matches "pkg:apk/alpine/musl@1.2.2")
|
||||
if strings.HasPrefix(query, "pkg:") {
|
||||
for purl, p := range byPURL {
|
||||
if strings.HasPrefix(purl, query) {
|
||||
matched[p.ID()] = p
|
||||
}
|
||||
}
|
||||
if len(matched) > 0 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
queryLower := strings.ToLower(query)
|
||||
|
||||
// 3. case-insensitive name match
|
||||
if pkgs, ok := byNameLower[queryLower]; ok {
|
||||
for _, p := range pkgs {
|
||||
matched[p.ID()] = p
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// 4. name@version format match
|
||||
if p, ok := byNameVersion[queryLower]; ok {
|
||||
matched[p.ID()] = p
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// convert map to slice
|
||||
result := make([]pkg.Package, 0, len(matched))
|
||||
for _, p := range matched {
|
||||
result = append(result, p)
|
||||
}
|
||||
|
||||
// sort for stable output
|
||||
pkg.Sort(result)
|
||||
return result
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
package split
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
func TestMatchPackages(t *testing.T) {
|
||||
// create test packages
|
||||
pkgA := pkg.Package{
|
||||
Name: "alpine-baselayout",
|
||||
Version: "3.2.0-r7",
|
||||
PURL: "pkg:apk/alpine/alpine-baselayout@3.2.0-r7",
|
||||
}
|
||||
pkgA.SetID()
|
||||
|
||||
pkgB := pkg.Package{
|
||||
Name: "musl",
|
||||
Version: "1.2.2-r0",
|
||||
PURL: "pkg:apk/alpine/musl@1.2.2-r0",
|
||||
}
|
||||
pkgB.SetID()
|
||||
|
||||
pkgC := pkg.Package{
|
||||
Name: "Musl", // different case
|
||||
Version: "2.0.0",
|
||||
PURL: "pkg:apk/alpine/Musl@2.0.0",
|
||||
}
|
||||
pkgC.SetID()
|
||||
|
||||
collection := pkg.NewCollection(pkgA, pkgB, pkgC)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
queries []string
|
||||
want []pkg.Package
|
||||
}{
|
||||
{
|
||||
name: "match by exact package ID",
|
||||
queries: []string{string(pkgA.ID())},
|
||||
want: []pkg.Package{pkgA},
|
||||
},
|
||||
{
|
||||
name: "match by exact PURL",
|
||||
queries: []string{"pkg:apk/alpine/musl@1.2.2-r0"},
|
||||
want: []pkg.Package{pkgB},
|
||||
},
|
||||
{
|
||||
name: "match by PURL prefix",
|
||||
queries: []string{"pkg:apk/alpine/musl"},
|
||||
want: []pkg.Package{pkgB},
|
||||
},
|
||||
{
|
||||
name: "match by case-insensitive name",
|
||||
queries: []string{"musl"},
|
||||
want: []pkg.Package{pkgB, pkgC},
|
||||
},
|
||||
{
|
||||
name: "match by name@version",
|
||||
queries: []string{"musl@1.2.2-r0"},
|
||||
want: []pkg.Package{pkgB},
|
||||
},
|
||||
{
|
||||
name: "multiple queries",
|
||||
queries: []string{"alpine-baselayout", "musl@1.2.2-r0"},
|
||||
want: []pkg.Package{pkgA, pkgB},
|
||||
},
|
||||
{
|
||||
name: "no match",
|
||||
queries: []string{"nonexistent"},
|
||||
want: []pkg.Package{},
|
||||
},
|
||||
{
|
||||
name: "empty queries",
|
||||
queries: []string{},
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
name: "nil collection returns nil",
|
||||
queries: []string{"musl"},
|
||||
want: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
coll := collection
|
||||
if tt.name == "nil collection returns nil" {
|
||||
coll = nil
|
||||
}
|
||||
|
||||
got := MatchPackages(coll, tt.queries)
|
||||
|
||||
if tt.want == nil {
|
||||
require.Nil(t, got)
|
||||
return
|
||||
}
|
||||
|
||||
require.Len(t, got, len(tt.want))
|
||||
|
||||
if len(tt.want) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// sort both for comparison
|
||||
pkg.Sort(tt.want)
|
||||
|
||||
// compare using cmp.Diff, ignoring unexported fields
|
||||
opts := []cmp.Option{
|
||||
cmpopts.IgnoreUnexported(pkg.Package{}),
|
||||
cmpopts.IgnoreFields(pkg.Package{}, "Locations", "Licenses", "CPEs"),
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(tt.want, got, opts...); diff != "" {
|
||||
t.Errorf("MatchPackages() mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,158 +0,0 @@
|
||||
package split
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
)
|
||||
|
||||
// relationshipIndex indexes relationships for efficient lookup by from/to IDs.
|
||||
// This is a simplified version tailored for the split functionality.
|
||||
type relationshipIndex struct {
|
||||
relationships []*sortableRelationship
|
||||
fromID map[artifact.ID]*mappedRelationships
|
||||
toID map[artifact.ID]*mappedRelationships
|
||||
}
|
||||
|
||||
// newRelationshipIndex creates a new relationship index from the given relationships
|
||||
func newRelationshipIndex(relationships ...artifact.Relationship) *relationshipIndex {
|
||||
idx := &relationshipIndex{
|
||||
fromID: make(map[artifact.ID]*mappedRelationships),
|
||||
toID: make(map[artifact.ID]*mappedRelationships),
|
||||
}
|
||||
|
||||
for _, r := range relationships {
|
||||
// prevent duplicates
|
||||
if idx.contains(r) {
|
||||
continue
|
||||
}
|
||||
|
||||
fromID := r.From.ID()
|
||||
toID := r.To.ID()
|
||||
|
||||
sr := &sortableRelationship{
|
||||
from: fromID,
|
||||
to: toID,
|
||||
relationship: r,
|
||||
}
|
||||
|
||||
idx.relationships = append(idx.relationships, sr)
|
||||
|
||||
// add from -> to mapping
|
||||
if idx.fromID[fromID] == nil {
|
||||
idx.fromID[fromID] = &mappedRelationships{}
|
||||
}
|
||||
idx.fromID[fromID].add(toID, sr)
|
||||
|
||||
// add to -> from mapping
|
||||
if idx.toID[toID] == nil {
|
||||
idx.toID[toID] = &mappedRelationships{}
|
||||
}
|
||||
idx.toID[toID].add(fromID, sr)
|
||||
}
|
||||
|
||||
return idx
|
||||
}
|
||||
|
||||
// from returns all relationships from the given identifiable, filtered by types
|
||||
func (i *relationshipIndex) from(identifiable artifact.Identifiable, types ...artifact.RelationshipType) []artifact.Relationship {
|
||||
return toSortedSlice(fromMapped(i.fromID, identifiable), types)
|
||||
}
|
||||
|
||||
// to returns all relationships to the given identifiable, filtered by types
|
||||
func (i *relationshipIndex) to(identifiable artifact.Identifiable, types ...artifact.RelationshipType) []artifact.Relationship {
|
||||
return toSortedSlice(fromMapped(i.toID, identifiable), types)
|
||||
}
|
||||
|
||||
// coordinates returns all file coordinates referenced by relationships for the given identifiable
|
||||
func (i *relationshipIndex) coordinates(identifiable artifact.Identifiable, types ...artifact.RelationshipType) []file.Coordinates {
|
||||
// get relationships in both directions
|
||||
rels := append(fromMapped(i.fromID, identifiable), fromMapped(i.toID, identifiable)...)
|
||||
sorted := toSortedSlice(rels, types)
|
||||
|
||||
var coords []file.Coordinates
|
||||
for _, rel := range sorted {
|
||||
if c, ok := rel.From.(file.Coordinates); ok {
|
||||
coords = append(coords, c)
|
||||
}
|
||||
if c, ok := rel.To.(file.Coordinates); ok {
|
||||
coords = append(coords, c)
|
||||
}
|
||||
}
|
||||
return coords
|
||||
}
|
||||
|
||||
// all returns all relationships, optionally filtered by types
|
||||
func (i *relationshipIndex) all(types ...artifact.RelationshipType) []artifact.Relationship {
|
||||
return toSortedSlice(i.relationships, types)
|
||||
}
|
||||
|
||||
// contains checks if the relationship is already in the index
|
||||
func (i *relationshipIndex) contains(r artifact.Relationship) bool {
|
||||
mapped := i.fromID[r.From.ID()]
|
||||
if mapped == nil {
|
||||
return false
|
||||
}
|
||||
typeMap := mapped.typeMap[r.Type]
|
||||
if typeMap == nil {
|
||||
return false
|
||||
}
|
||||
return typeMap[r.To.ID()] != nil
|
||||
}
|
||||
|
||||
type mappedRelationships struct {
|
||||
typeMap map[artifact.RelationshipType]map[artifact.ID]*sortableRelationship
|
||||
allRelated []*sortableRelationship
|
||||
}
|
||||
|
||||
func (m *mappedRelationships) add(id artifact.ID, sr *sortableRelationship) {
|
||||
m.allRelated = append(m.allRelated, sr)
|
||||
if m.typeMap == nil {
|
||||
m.typeMap = make(map[artifact.RelationshipType]map[artifact.ID]*sortableRelationship)
|
||||
}
|
||||
if m.typeMap[sr.relationship.Type] == nil {
|
||||
m.typeMap[sr.relationship.Type] = make(map[artifact.ID]*sortableRelationship)
|
||||
}
|
||||
m.typeMap[sr.relationship.Type][id] = sr
|
||||
}
|
||||
|
||||
type sortableRelationship struct {
|
||||
from artifact.ID
|
||||
to artifact.ID
|
||||
relationship artifact.Relationship
|
||||
}
|
||||
|
||||
func fromMapped(idMap map[artifact.ID]*mappedRelationships, identifiable artifact.Identifiable) []*sortableRelationship {
|
||||
if identifiable == nil {
|
||||
return nil
|
||||
}
|
||||
mapped := idMap[identifiable.ID()]
|
||||
if mapped == nil {
|
||||
return nil
|
||||
}
|
||||
return mapped.allRelated
|
||||
}
|
||||
|
||||
func toSortedSlice(relationships []*sortableRelationship, types []artifact.RelationshipType) []artifact.Relationship {
|
||||
slices.SortFunc(relationships, func(a, b *sortableRelationship) int {
|
||||
cmp := strings.Compare(string(a.relationship.Type), string(b.relationship.Type))
|
||||
if cmp != 0 {
|
||||
return cmp
|
||||
}
|
||||
cmp = strings.Compare(string(a.from), string(b.from))
|
||||
if cmp != 0 {
|
||||
return cmp
|
||||
}
|
||||
return strings.Compare(string(a.to), string(b.to))
|
||||
})
|
||||
|
||||
var out []artifact.Relationship
|
||||
for _, r := range relationships {
|
||||
if len(types) == 0 || slices.Contains(types, r.relationship.Type) {
|
||||
out = append(out, r.relationship)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -1,314 +0,0 @@
|
||||
package split
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
)
|
||||
|
||||
// allowedRelationshipTypes are the only relationship types allowed in split output
|
||||
var allowedRelationshipTypes = []artifact.RelationshipType{
|
||||
artifact.OwnershipByFileOverlapRelationship,
|
||||
artifact.EvidentByRelationship,
|
||||
}
|
||||
|
||||
// Result represents the result of splitting an SBOM for a single target package
|
||||
type Result struct {
|
||||
TargetPackage pkg.Package
|
||||
SBOM sbom.SBOM
|
||||
}
|
||||
|
||||
// Split splits the source SBOM into separate SBOMs, one for each target package.
|
||||
// Each output SBOM contains the target package, its connected packages (via ownership-by-file-overlap
|
||||
// and evident-by relationships), and all related files.
|
||||
func Split(source sbom.SBOM, targetPackages []pkg.Package, dropLocationFSID, dropNonPrimaryEvidence bool) []Result {
|
||||
if len(targetPackages) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
relIndex := newRelationshipIndex(source.Relationships...)
|
||||
results := make([]Result, 0, len(targetPackages))
|
||||
|
||||
for _, target := range targetPackages {
|
||||
result := splitForPackage(source, target, relIndex, dropLocationFSID, dropNonPrimaryEvidence)
|
||||
results = append(results, result)
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
// splitForPackage creates a new SBOM containing only the target package and its related artifacts
|
||||
func splitForPackage(source sbom.SBOM, target pkg.Package, relIndex *relationshipIndex, dropLocationFSID, dropNonPrimaryEvidence bool) Result {
|
||||
// find all connected packages via BFS
|
||||
connectedPkgs := findConnectedPackages(source, target, relIndex)
|
||||
|
||||
// collect all kept package IDs for relationship filtering
|
||||
keptPkgIDs := make(map[artifact.ID]bool)
|
||||
for _, p := range connectedPkgs {
|
||||
keptPkgIDs[p.ID()] = true
|
||||
}
|
||||
|
||||
// find related file coordinates from relationships and package locations
|
||||
keptCoords := collectFileCoordinates(connectedPkgs, relIndex, dropLocationFSID, dropNonPrimaryEvidence)
|
||||
|
||||
// build filtered SBOM
|
||||
filteredSBOM := buildFilteredSBOM(source, connectedPkgs, keptPkgIDs, keptCoords, relIndex, dropLocationFSID, dropNonPrimaryEvidence)
|
||||
|
||||
return Result{
|
||||
TargetPackage: target,
|
||||
SBOM: filteredSBOM,
|
||||
}
|
||||
}
|
||||
|
||||
// findConnectedPackages uses BFS to find all packages connected to the target via traversal relationships
|
||||
func findConnectedPackages(source sbom.SBOM, target pkg.Package, relIndex *relationshipIndex) []pkg.Package {
|
||||
visited := make(map[artifact.ID]bool)
|
||||
var result []pkg.Package
|
||||
|
||||
queue := []pkg.Package{target}
|
||||
visited[target.ID()] = true
|
||||
|
||||
for len(queue) > 0 {
|
||||
current := queue[0]
|
||||
queue = queue[1:]
|
||||
result = append(result, current)
|
||||
|
||||
// find connected packages through relationships (both directions)
|
||||
for _, rel := range relIndex.from(current, allowedRelationshipTypes...) {
|
||||
toID := rel.To.ID()
|
||||
if visited[toID] {
|
||||
continue
|
||||
}
|
||||
// check if the target is a package in the source collection
|
||||
if p := source.Artifacts.Packages.Package(toID); p != nil {
|
||||
visited[toID] = true
|
||||
queue = append(queue, *p)
|
||||
}
|
||||
}
|
||||
|
||||
for _, rel := range relIndex.to(current, allowedRelationshipTypes...) {
|
||||
fromID := rel.From.ID()
|
||||
if visited[fromID] {
|
||||
continue
|
||||
}
|
||||
// check if the source is a package in the source collection
|
||||
if p := source.Artifacts.Packages.Package(fromID); p != nil {
|
||||
visited[fromID] = true
|
||||
queue = append(queue, *p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// collectFileCoordinates gathers all file coordinates related to the kept packages
|
||||
func collectFileCoordinates(packages []pkg.Package, relIndex *relationshipIndex, dropLocationFSID, dropNonPrimaryEvidence bool) map[file.Coordinates]bool {
|
||||
coords := make(map[file.Coordinates]bool)
|
||||
|
||||
for _, p := range packages {
|
||||
// collect coordinates from package locations
|
||||
for _, loc := range p.Locations.ToSlice() {
|
||||
// skip non-primary evidence locations if requested
|
||||
if dropNonPrimaryEvidence {
|
||||
if loc.Annotations == nil || loc.Annotations["evidence"] != "primary" {
|
||||
continue
|
||||
}
|
||||
}
|
||||
coord := loc.Coordinates
|
||||
if dropLocationFSID {
|
||||
coord = file.Coordinates{RealPath: coord.RealPath}
|
||||
}
|
||||
coords[coord] = true
|
||||
}
|
||||
|
||||
// collect coordinates from allowed relationship types only
|
||||
for _, c := range relIndex.coordinates(p, allowedRelationshipTypes...) {
|
||||
coord := c
|
||||
if dropLocationFSID {
|
||||
coord = file.Coordinates{RealPath: c.RealPath}
|
||||
}
|
||||
coords[coord] = true
|
||||
}
|
||||
}
|
||||
|
||||
return coords
|
||||
}
|
||||
|
||||
// buildFilteredSBOM creates a new SBOM with only the kept packages, files, and relationships
|
||||
func buildFilteredSBOM(source sbom.SBOM, packages []pkg.Package, keptPkgIDs map[artifact.ID]bool, keptCoords map[file.Coordinates]bool, relIndex *relationshipIndex, dropLocationFSID, dropNonPrimaryEvidence bool) sbom.SBOM {
|
||||
// create new package collection
|
||||
newPkgCollection := pkg.NewCollection()
|
||||
for _, p := range packages {
|
||||
// filter non-primary evidence locations if requested
|
||||
if dropNonPrimaryEvidence {
|
||||
p = filterPackageNonPrimaryLocations(p)
|
||||
}
|
||||
// if dropLocationFSID is enabled, we need to clear FileSystemID from package locations
|
||||
if dropLocationFSID {
|
||||
p = clearPackageFileSystemIDs(p)
|
||||
}
|
||||
newPkgCollection.Add(p)
|
||||
}
|
||||
|
||||
// filter file artifacts
|
||||
filteredSBOM := sbom.SBOM{
|
||||
Artifacts: sbom.Artifacts{
|
||||
Packages: newPkgCollection,
|
||||
FileMetadata: filterFileMap(source.Artifacts.FileMetadata, keptCoords, dropLocationFSID),
|
||||
FileDigests: filterFileMap(source.Artifacts.FileDigests, keptCoords, dropLocationFSID),
|
||||
FileContents: filterFileMap(source.Artifacts.FileContents, keptCoords, dropLocationFSID),
|
||||
FileLicenses: filterFileMap(source.Artifacts.FileLicenses, keptCoords, dropLocationFSID),
|
||||
Executables: filterFileMap(source.Artifacts.Executables, keptCoords, dropLocationFSID),
|
||||
Unknowns: filterFileMap(source.Artifacts.Unknowns, keptCoords, dropLocationFSID),
|
||||
LinuxDistribution: source.Artifacts.LinuxDistribution,
|
||||
},
|
||||
Relationships: filterRelationships(relIndex.all(), keptPkgIDs, keptCoords, dropLocationFSID),
|
||||
Source: source.Source,
|
||||
Descriptor: source.Descriptor,
|
||||
}
|
||||
|
||||
return filteredSBOM
|
||||
}
|
||||
|
||||
// filterPackageNonPrimaryLocations creates a copy of the package with only primary evidence locations
|
||||
func filterPackageNonPrimaryLocations(p pkg.Package) pkg.Package {
|
||||
newLocations := file.NewLocationSet()
|
||||
for _, loc := range p.Locations.ToSlice() {
|
||||
if loc.Annotations != nil && loc.Annotations["evidence"] == "primary" {
|
||||
newLocations.Add(loc)
|
||||
}
|
||||
}
|
||||
p.Locations = newLocations
|
||||
return p
|
||||
}
|
||||
|
||||
// filterFileMap filters a map of file.Coordinates to only include kept coordinates
|
||||
func filterFileMap[T any](m map[file.Coordinates]T, keptCoords map[file.Coordinates]bool, dropLocationFSID bool) map[file.Coordinates]T {
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
result := make(map[file.Coordinates]T)
|
||||
for coord, value := range m {
|
||||
checkCoord := coord
|
||||
if dropLocationFSID {
|
||||
checkCoord = file.Coordinates{RealPath: coord.RealPath}
|
||||
}
|
||||
if keptCoords[checkCoord] {
|
||||
outputCoord := coord
|
||||
if dropLocationFSID {
|
||||
outputCoord = file.Coordinates{RealPath: coord.RealPath}
|
||||
}
|
||||
result[outputCoord] = value
|
||||
}
|
||||
}
|
||||
|
||||
if len(result) == 0 {
|
||||
return nil
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// filterRelationships filters relationships to only include allowed types referencing kept artifacts
|
||||
func filterRelationships(relationships []artifact.Relationship, keptPkgIDs map[artifact.ID]bool, keptCoords map[file.Coordinates]bool, dropLocationFSID bool) []artifact.Relationship {
|
||||
var result []artifact.Relationship
|
||||
|
||||
for _, rel := range relationships {
|
||||
// only keep allowed relationship types
|
||||
if !isAllowedRelationshipType(rel.Type) {
|
||||
continue
|
||||
}
|
||||
|
||||
// check if both ends of the relationship reference kept artifacts
|
||||
fromKept := isArtifactKept(rel.From, keptPkgIDs, keptCoords, dropLocationFSID)
|
||||
toKept := isArtifactKept(rel.To, keptPkgIDs, keptCoords, dropLocationFSID)
|
||||
|
||||
if fromKept && toKept {
|
||||
newRel := rel
|
||||
if dropLocationFSID {
|
||||
if coord, ok := rel.From.(file.Coordinates); ok {
|
||||
newRel.From = file.Coordinates{RealPath: coord.RealPath}
|
||||
}
|
||||
if coord, ok := rel.To.(file.Coordinates); ok {
|
||||
newRel.To = file.Coordinates{RealPath: coord.RealPath}
|
||||
}
|
||||
}
|
||||
result = append(result, newRel)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// isAllowedRelationshipType checks if a relationship type is in the allowed list
|
||||
func isAllowedRelationshipType(t artifact.RelationshipType) bool {
|
||||
for _, allowed := range allowedRelationshipTypes {
|
||||
if t == allowed {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isArtifactKept checks if an artifact (package or file) is in the kept set
|
||||
func isArtifactKept(a artifact.Identifiable, keptPkgIDs map[artifact.ID]bool, keptCoords map[file.Coordinates]bool, dropLocationFSID bool) bool {
|
||||
if a == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// check if it's a file coordinate
|
||||
if coord, ok := a.(file.Coordinates); ok {
|
||||
checkCoord := coord
|
||||
if dropLocationFSID {
|
||||
checkCoord = file.Coordinates{RealPath: coord.RealPath}
|
||||
}
|
||||
return keptCoords[checkCoord]
|
||||
}
|
||||
|
||||
// otherwise check package ID
|
||||
return keptPkgIDs[a.ID()]
|
||||
}
|
||||
|
||||
// clearPackageFileSystemIDs creates a copy of the package with FileSystemID cleared from all locations
|
||||
func clearPackageFileSystemIDs(p pkg.Package) pkg.Package {
|
||||
newLocations := file.NewLocationSet()
|
||||
for _, loc := range p.Locations.ToSlice() {
|
||||
newLoc := file.Location{
|
||||
LocationData: file.LocationData{
|
||||
Coordinates: file.Coordinates{
|
||||
RealPath: loc.RealPath,
|
||||
// FileSystemID intentionally left empty
|
||||
},
|
||||
AccessPath: loc.AccessPath,
|
||||
},
|
||||
LocationMetadata: loc.LocationMetadata,
|
||||
}
|
||||
newLocations.Add(newLoc)
|
||||
}
|
||||
p.Locations = newLocations
|
||||
|
||||
// also clear from license locations
|
||||
newLicenses := pkg.NewLicenseSet()
|
||||
for _, lic := range p.Licenses.ToSlice() {
|
||||
newLicLocs := file.NewLocationSet()
|
||||
for _, loc := range lic.Locations.ToSlice() {
|
||||
newLoc := file.Location{
|
||||
LocationData: file.LocationData{
|
||||
Coordinates: file.Coordinates{
|
||||
RealPath: loc.RealPath,
|
||||
},
|
||||
AccessPath: loc.AccessPath,
|
||||
},
|
||||
LocationMetadata: loc.LocationMetadata,
|
||||
}
|
||||
newLicLocs.Add(newLoc)
|
||||
}
|
||||
lic.Locations = newLicLocs
|
||||
newLicenses.Add(lic)
|
||||
}
|
||||
p.Licenses = newLicenses
|
||||
|
||||
return p
|
||||
}
|
||||
@@ -1,606 +0,0 @@
|
||||
package split
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func TestSplit(t *testing.T) {
|
||||
// create test coordinates
|
||||
coord1 := file.Coordinates{RealPath: "/lib/apk/db/installed", FileSystemID: "layer1"}
|
||||
coord2 := file.Coordinates{RealPath: "/usr/lib/libmusl.so", FileSystemID: "layer1"}
|
||||
coord3 := file.Coordinates{RealPath: "/unrelated/file", FileSystemID: "layer1"}
|
||||
|
||||
// create test packages
|
||||
pkgA := pkg.Package{
|
||||
Name: "alpine-baselayout",
|
||||
Version: "3.2.0-r7",
|
||||
Type: pkg.ApkPkg,
|
||||
Locations: file.NewLocationSet(file.NewLocationFromCoordinates(coord1)),
|
||||
}
|
||||
pkgA.SetID()
|
||||
|
||||
pkgB := pkg.Package{
|
||||
Name: "musl",
|
||||
Version: "1.2.2-r0",
|
||||
Type: pkg.ApkPkg,
|
||||
Locations: file.NewLocationSet(file.NewLocationFromCoordinates(coord2)),
|
||||
}
|
||||
pkgB.SetID()
|
||||
|
||||
pkgC := pkg.Package{
|
||||
Name: "unrelated",
|
||||
Version: "1.0.0",
|
||||
Type: pkg.ApkPkg,
|
||||
Locations: file.NewLocationSet(file.NewLocationFromCoordinates(coord3)),
|
||||
}
|
||||
pkgC.SetID()
|
||||
|
||||
// create source SBOM with relationships
|
||||
sourceSBOM := sbom.SBOM{
|
||||
Artifacts: sbom.Artifacts{
|
||||
Packages: pkg.NewCollection(pkgA, pkgB, pkgC),
|
||||
FileMetadata: map[file.Coordinates]file.Metadata{
|
||||
coord1: {MIMEType: "text/plain"},
|
||||
coord2: {MIMEType: "application/x-sharedlib"},
|
||||
coord3: {MIMEType: "text/plain"},
|
||||
},
|
||||
FileDigests: map[file.Coordinates][]file.Digest{
|
||||
coord1: {{Algorithm: "sha256", Value: "abc123"}},
|
||||
coord2: {{Algorithm: "sha256", Value: "def456"}},
|
||||
coord3: {{Algorithm: "sha256", Value: "ghi789"}},
|
||||
},
|
||||
LinuxDistribution: &linux.Release{ID: "alpine", VersionID: "3.12"},
|
||||
},
|
||||
Relationships: []artifact.Relationship{
|
||||
// pkgA owns pkgB via file overlap
|
||||
{
|
||||
From: pkgA,
|
||||
To: pkgB,
|
||||
Type: artifact.OwnershipByFileOverlapRelationship,
|
||||
},
|
||||
// pkgA is evident by coord1
|
||||
{
|
||||
From: pkgA,
|
||||
To: coord1,
|
||||
Type: artifact.EvidentByRelationship,
|
||||
},
|
||||
// pkgB is evident by coord2
|
||||
{
|
||||
From: pkgB,
|
||||
To: coord2,
|
||||
Type: artifact.EvidentByRelationship,
|
||||
},
|
||||
// pkgC is evident by coord3 (unrelated)
|
||||
{
|
||||
From: pkgC,
|
||||
To: coord3,
|
||||
Type: artifact.EvidentByRelationship,
|
||||
},
|
||||
},
|
||||
Source: source.Description{
|
||||
ID: "test-source-id",
|
||||
Name: "test-image",
|
||||
},
|
||||
Descriptor: sbom.Descriptor{
|
||||
Name: "syft",
|
||||
Version: "test",
|
||||
},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
targetPackages []pkg.Package
|
||||
dropLocationFSID bool
|
||||
dropNonPrimaryEvidence bool
|
||||
wantCount int
|
||||
verify func(t *testing.T, results []Result)
|
||||
}{
|
||||
{
|
||||
name: "split single package with connected packages",
|
||||
targetPackages: []pkg.Package{pkgA},
|
||||
dropLocationFSID: false,
|
||||
wantCount: 1,
|
||||
verify: func(t *testing.T, results []Result) {
|
||||
require.Len(t, results, 1)
|
||||
result := results[0]
|
||||
|
||||
// target package should be pkgA
|
||||
assert.Equal(t, pkgA.Name, result.TargetPackage.Name)
|
||||
|
||||
// should include both pkgA and pkgB (connected via ownership)
|
||||
assert.Equal(t, 2, result.SBOM.Artifacts.Packages.PackageCount())
|
||||
|
||||
// should include coord1 and coord2 (related to pkgA and pkgB)
|
||||
assert.Contains(t, result.SBOM.Artifacts.FileMetadata, coord1)
|
||||
assert.Contains(t, result.SBOM.Artifacts.FileMetadata, coord2)
|
||||
|
||||
// should NOT include coord3 (unrelated)
|
||||
assert.NotContains(t, result.SBOM.Artifacts.FileMetadata, coord3)
|
||||
|
||||
// source and descriptor should be preserved
|
||||
assert.Equal(t, "test-source-id", result.SBOM.Source.ID)
|
||||
assert.Equal(t, "syft", result.SBOM.Descriptor.Name)
|
||||
|
||||
// linux distribution should be preserved
|
||||
require.NotNil(t, result.SBOM.Artifacts.LinuxDistribution)
|
||||
assert.Equal(t, "alpine", result.SBOM.Artifacts.LinuxDistribution.ID)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "split unrelated package",
|
||||
targetPackages: []pkg.Package{pkgC},
|
||||
dropLocationFSID: false,
|
||||
wantCount: 1,
|
||||
verify: func(t *testing.T, results []Result) {
|
||||
require.Len(t, results, 1)
|
||||
result := results[0]
|
||||
|
||||
// should only include pkgC
|
||||
assert.Equal(t, 1, result.SBOM.Artifacts.Packages.PackageCount())
|
||||
|
||||
// should only include coord3
|
||||
assert.Contains(t, result.SBOM.Artifacts.FileMetadata, coord3)
|
||||
assert.NotContains(t, result.SBOM.Artifacts.FileMetadata, coord1)
|
||||
assert.NotContains(t, result.SBOM.Artifacts.FileMetadata, coord2)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "split with dropLocationFSID",
|
||||
targetPackages: []pkg.Package{pkgA},
|
||||
dropLocationFSID: true,
|
||||
wantCount: 1,
|
||||
verify: func(t *testing.T, results []Result) {
|
||||
require.Len(t, results, 1)
|
||||
result := results[0]
|
||||
|
||||
// FileSystemID should be cleared from coordinates
|
||||
for coord := range result.SBOM.Artifacts.FileMetadata {
|
||||
assert.Empty(t, coord.FileSystemID, "FileSystemID should be empty")
|
||||
}
|
||||
|
||||
// package locations should also have FileSystemID cleared
|
||||
for p := range result.SBOM.Artifacts.Packages.Enumerate() {
|
||||
for _, loc := range p.Locations.ToSlice() {
|
||||
assert.Empty(t, loc.FileSystemID, "package location FileSystemID should be empty")
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "split multiple packages",
|
||||
targetPackages: []pkg.Package{pkgA, pkgC},
|
||||
dropLocationFSID: false,
|
||||
wantCount: 2,
|
||||
verify: func(t *testing.T, results []Result) {
|
||||
require.Len(t, results, 2)
|
||||
|
||||
// first result should be for pkgA
|
||||
assert.Equal(t, pkgA.Name, results[0].TargetPackage.Name)
|
||||
|
||||
// second result should be for pkgC
|
||||
assert.Equal(t, pkgC.Name, results[1].TargetPackage.Name)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty target packages",
|
||||
targetPackages: []pkg.Package{},
|
||||
dropLocationFSID: false,
|
||||
wantCount: 0,
|
||||
verify: func(t *testing.T, results []Result) {
|
||||
assert.Nil(t, results)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
results := Split(sourceSBOM, tt.targetPackages, tt.dropLocationFSID, tt.dropNonPrimaryEvidence)
|
||||
|
||||
if tt.wantCount == 0 {
|
||||
assert.Nil(t, results)
|
||||
} else {
|
||||
require.Len(t, results, tt.wantCount)
|
||||
}
|
||||
|
||||
if tt.verify != nil {
|
||||
tt.verify(t, results)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindConnectedPackages(t *testing.T) {
|
||||
// create test packages
|
||||
pkgA := pkg.Package{Name: "a", Version: "1.0"}
|
||||
pkgA.SetID()
|
||||
|
||||
pkgB := pkg.Package{Name: "b", Version: "1.0"}
|
||||
pkgB.SetID()
|
||||
|
||||
pkgC := pkg.Package{Name: "c", Version: "1.0"}
|
||||
pkgC.SetID()
|
||||
|
||||
pkgD := pkg.Package{Name: "d", Version: "1.0"}
|
||||
pkgD.SetID()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
target pkg.Package
|
||||
relationships []artifact.Relationship
|
||||
wantNames []string
|
||||
}{
|
||||
{
|
||||
name: "single package no relationships",
|
||||
target: pkgA,
|
||||
relationships: []artifact.Relationship{
|
||||
// pkgB -> pkgC (unrelated)
|
||||
{From: pkgB, To: pkgC, Type: artifact.OwnershipByFileOverlapRelationship},
|
||||
},
|
||||
wantNames: []string{"a"},
|
||||
},
|
||||
{
|
||||
name: "connected via ownership-by-file-overlap",
|
||||
target: pkgA,
|
||||
relationships: []artifact.Relationship{
|
||||
{From: pkgA, To: pkgB, Type: artifact.OwnershipByFileOverlapRelationship},
|
||||
},
|
||||
wantNames: []string{"a", "b"},
|
||||
},
|
||||
{
|
||||
name: "connected via evident-by (to package)",
|
||||
target: pkgA,
|
||||
relationships: []artifact.Relationship{
|
||||
{From: pkgA, To: pkgB, Type: artifact.EvidentByRelationship},
|
||||
},
|
||||
wantNames: []string{"a", "b"},
|
||||
},
|
||||
{
|
||||
name: "chain of connections",
|
||||
target: pkgA,
|
||||
relationships: []artifact.Relationship{
|
||||
{From: pkgA, To: pkgB, Type: artifact.OwnershipByFileOverlapRelationship},
|
||||
{From: pkgB, To: pkgC, Type: artifact.OwnershipByFileOverlapRelationship},
|
||||
},
|
||||
wantNames: []string{"a", "b", "c"},
|
||||
},
|
||||
{
|
||||
name: "circular relationships",
|
||||
target: pkgA,
|
||||
relationships: []artifact.Relationship{
|
||||
{From: pkgA, To: pkgB, Type: artifact.OwnershipByFileOverlapRelationship},
|
||||
{From: pkgB, To: pkgC, Type: artifact.OwnershipByFileOverlapRelationship},
|
||||
{From: pkgC, To: pkgA, Type: artifact.OwnershipByFileOverlapRelationship},
|
||||
},
|
||||
wantNames: []string{"a", "b", "c"},
|
||||
},
|
||||
{
|
||||
name: "ignores contains relationship",
|
||||
target: pkgA,
|
||||
relationships: []artifact.Relationship{
|
||||
{From: pkgA, To: pkgB, Type: artifact.ContainsRelationship},
|
||||
},
|
||||
wantNames: []string{"a"},
|
||||
},
|
||||
{
|
||||
name: "ignores dependency-of relationship",
|
||||
target: pkgA,
|
||||
relationships: []artifact.Relationship{
|
||||
{From: pkgA, To: pkgB, Type: artifact.DependencyOfRelationship},
|
||||
},
|
||||
wantNames: []string{"a"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
sourceSBOM := sbom.SBOM{
|
||||
Artifacts: sbom.Artifacts{
|
||||
Packages: pkg.NewCollection(pkgA, pkgB, pkgC, pkgD),
|
||||
},
|
||||
Relationships: tt.relationships,
|
||||
}
|
||||
|
||||
relIndex := newRelationshipIndex(sourceSBOM.Relationships...)
|
||||
got := findConnectedPackages(sourceSBOM, tt.target, relIndex)
|
||||
|
||||
gotNames := make([]string, len(got))
|
||||
for i, p := range got {
|
||||
gotNames[i] = p.Name
|
||||
}
|
||||
|
||||
// sort for comparison
|
||||
assert.ElementsMatch(t, tt.wantNames, gotNames)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestClearPackageFileSystemIDs(t *testing.T) {
|
||||
coord := file.Coordinates{RealPath: "/test/path", FileSystemID: "layer123"}
|
||||
loc := file.NewLocationFromCoordinates(coord)
|
||||
loc.AccessPath = "/test/access"
|
||||
|
||||
licLoc := file.NewLocationFromCoordinates(coord)
|
||||
lic := pkg.License{
|
||||
Value: "MIT",
|
||||
Locations: file.NewLocationSet(licLoc),
|
||||
}
|
||||
|
||||
p := pkg.Package{
|
||||
Name: "test",
|
||||
Version: "1.0",
|
||||
Locations: file.NewLocationSet(loc),
|
||||
Licenses: pkg.NewLicenseSet(lic),
|
||||
}
|
||||
|
||||
result := clearPackageFileSystemIDs(p)
|
||||
|
||||
// check package locations
|
||||
for _, l := range result.Locations.ToSlice() {
|
||||
assert.Empty(t, l.FileSystemID, "package location FileSystemID should be empty")
|
||||
assert.Equal(t, "/test/path", l.RealPath)
|
||||
assert.Equal(t, "/test/access", l.AccessPath)
|
||||
}
|
||||
|
||||
// check license locations
|
||||
for _, l := range result.Licenses.ToSlice() {
|
||||
for _, ll := range l.Locations.ToSlice() {
|
||||
assert.Empty(t, ll.FileSystemID, "license location FileSystemID should be empty")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPackageIDStabilityThroughSplit(t *testing.T) {
|
||||
// this test verifies that package IDs remain stable through all split transformations,
|
||||
// including dropping licenses, filtering locations, and clearing filesystem IDs.
|
||||
// Package IDs are content-addressable and should NOT change when we modify packages
|
||||
// during the split process.
|
||||
|
||||
// create a package with multiple locations (primary and non-primary evidence)
|
||||
primaryLoc := file.NewLocation("/lib/apk/db/installed")
|
||||
primaryLoc.Annotations = map[string]string{"evidence": "primary"}
|
||||
|
||||
nonPrimaryLoc := file.NewLocation("/some/other/path")
|
||||
nonPrimaryLoc.Annotations = map[string]string{"evidence": "supporting"}
|
||||
|
||||
// add filesystem IDs to both
|
||||
primaryLoc.Coordinates.FileSystemID = "layer123"
|
||||
nonPrimaryLoc.Coordinates.FileSystemID = "layer456"
|
||||
|
||||
// create license with location
|
||||
licLoc := file.NewLocation("/lib/apk/db/installed")
|
||||
licLoc.Coordinates.FileSystemID = "layer123"
|
||||
lic := pkg.License{
|
||||
Value: "MIT",
|
||||
Locations: file.NewLocationSet(licLoc),
|
||||
}
|
||||
|
||||
testPkg := pkg.Package{
|
||||
Name: "test-package",
|
||||
Version: "1.0.0",
|
||||
Type: pkg.ApkPkg,
|
||||
Locations: file.NewLocationSet(primaryLoc, nonPrimaryLoc),
|
||||
Licenses: pkg.NewLicenseSet(lic),
|
||||
}
|
||||
testPkg.SetID()
|
||||
originalID := testPkg.ID()
|
||||
|
||||
// verify original ID is set
|
||||
require.NotEmpty(t, originalID, "original package ID should be set")
|
||||
|
||||
// create a simple SBOM for the split operation
|
||||
sourceSBOM := sbom.SBOM{
|
||||
Artifacts: sbom.Artifacts{
|
||||
Packages: pkg.NewCollection(testPkg),
|
||||
FileMetadata: map[file.Coordinates]file.Metadata{
|
||||
primaryLoc.Coordinates: {MIMEType: "text/plain"},
|
||||
nonPrimaryLoc.Coordinates: {MIMEType: "text/plain"},
|
||||
},
|
||||
},
|
||||
Relationships: []artifact.Relationship{
|
||||
{
|
||||
From: testPkg,
|
||||
To: primaryLoc.Coordinates,
|
||||
Type: artifact.EvidentByRelationship,
|
||||
},
|
||||
},
|
||||
Source: source.Description{
|
||||
ID: "test-source",
|
||||
Name: "test",
|
||||
},
|
||||
Descriptor: sbom.Descriptor{
|
||||
Name: "syft",
|
||||
Version: "test",
|
||||
},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
dropLocationFSID bool
|
||||
dropNonPrimaryEvidence bool
|
||||
}{
|
||||
{
|
||||
name: "no drop options",
|
||||
dropLocationFSID: false,
|
||||
dropNonPrimaryEvidence: false,
|
||||
},
|
||||
{
|
||||
name: "drop location:fsid only",
|
||||
dropLocationFSID: true,
|
||||
dropNonPrimaryEvidence: false,
|
||||
},
|
||||
{
|
||||
name: "drop location:non-primary-evidence only",
|
||||
dropLocationFSID: false,
|
||||
dropNonPrimaryEvidence: true,
|
||||
},
|
||||
{
|
||||
name: "drop both location options",
|
||||
dropLocationFSID: true,
|
||||
dropNonPrimaryEvidence: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
results := Split(sourceSBOM, []pkg.Package{testPkg}, tt.dropLocationFSID, tt.dropNonPrimaryEvidence)
|
||||
require.Len(t, results, 1)
|
||||
|
||||
resultSBOM := results[0].SBOM
|
||||
|
||||
// verify there's exactly one package in the result
|
||||
require.Equal(t, 1, resultSBOM.Artifacts.Packages.PackageCount())
|
||||
|
||||
// get the package from the result
|
||||
var resultPkg *pkg.Package
|
||||
for p := range resultSBOM.Artifacts.Packages.Enumerate() {
|
||||
resultPkg = &p
|
||||
break
|
||||
}
|
||||
require.NotNil(t, resultPkg)
|
||||
|
||||
// THE CRITICAL ASSERTION: package ID must remain stable
|
||||
assert.Equal(t, originalID, resultPkg.ID(),
|
||||
"package ID changed after split with dropLocationFSID=%v, dropNonPrimaryEvidence=%v",
|
||||
tt.dropLocationFSID, tt.dropNonPrimaryEvidence)
|
||||
|
||||
// verify the target package ID also matches
|
||||
assert.Equal(t, originalID, results[0].TargetPackage.ID(),
|
||||
"target package ID changed")
|
||||
|
||||
// additional verification: if we dropped non-primary evidence, locations should be filtered
|
||||
if tt.dropNonPrimaryEvidence {
|
||||
locs := resultPkg.Locations.ToSlice()
|
||||
for _, loc := range locs {
|
||||
assert.Equal(t, "primary", loc.Annotations["evidence"],
|
||||
"non-primary locations should be filtered out")
|
||||
}
|
||||
}
|
||||
|
||||
// additional verification: if we dropped fsid, it should be empty
|
||||
if tt.dropLocationFSID {
|
||||
for _, loc := range resultPkg.Locations.ToSlice() {
|
||||
assert.Empty(t, loc.FileSystemID, "FileSystemID should be cleared")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPackageIDStabilityWithDropOptions(t *testing.T) {
|
||||
// this test verifies that package IDs remain stable when using ApplyDropOptions
|
||||
// which includes pkg:licenses
|
||||
|
||||
coord := file.Coordinates{RealPath: "/test/path", FileSystemID: "layer123"}
|
||||
loc := file.NewLocationFromCoordinates(coord)
|
||||
loc.Annotations = map[string]string{"evidence": "primary"}
|
||||
|
||||
lic := pkg.License{
|
||||
Value: "GPL-2.0",
|
||||
Locations: file.NewLocationSet(loc),
|
||||
}
|
||||
|
||||
testPkg := pkg.Package{
|
||||
Name: "license-test-pkg",
|
||||
Version: "2.0.0",
|
||||
Type: pkg.ApkPkg,
|
||||
Locations: file.NewLocationSet(loc),
|
||||
Licenses: pkg.NewLicenseSet(lic),
|
||||
Metadata: pkg.ApkDBEntry{
|
||||
Package: "license-test-pkg",
|
||||
Files: []pkg.ApkFileRecord{
|
||||
{Path: "/usr/bin/test"},
|
||||
},
|
||||
},
|
||||
}
|
||||
testPkg.SetID()
|
||||
originalID := testPkg.ID()
|
||||
|
||||
require.NotEmpty(t, originalID)
|
||||
|
||||
// create SBOM
|
||||
testSBOM := sbom.SBOM{
|
||||
Artifacts: sbom.Artifacts{
|
||||
Packages: pkg.NewCollection(testPkg),
|
||||
},
|
||||
Source: source.Description{
|
||||
ID: "test",
|
||||
Name: "test",
|
||||
},
|
||||
Descriptor: sbom.Descriptor{
|
||||
Name: "syft",
|
||||
Version: "test",
|
||||
},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
opts []DropOption
|
||||
}{
|
||||
{
|
||||
name: "drop pkg:licenses",
|
||||
opts: []DropOption{DropPkgLicenses},
|
||||
},
|
||||
{
|
||||
name: "drop location:fsid via ApplyDropOptions",
|
||||
opts: []DropOption{DropLocationFSID},
|
||||
},
|
||||
{
|
||||
name: "drop location:non-primary-evidence via ApplyDropOptions",
|
||||
opts: []DropOption{DropLocationNonPrimaryEvidence},
|
||||
},
|
||||
{
|
||||
name: "drop all location and pkg options",
|
||||
opts: []DropOption{DropPkgLicenses, DropLocationFSID, DropLocationNonPrimaryEvidence},
|
||||
},
|
||||
{
|
||||
name: "drop pkg:metadata.files",
|
||||
opts: []DropOption{DropPkgMetadataFiles},
|
||||
},
|
||||
{
|
||||
name: "drop all pkg options",
|
||||
opts: []DropOption{DropPkgLicenses, DropPkgMetadataFiles},
|
||||
},
|
||||
{
|
||||
name: "drop source and descriptor (should not affect package ID)",
|
||||
opts: []DropOption{DropSource, DropDescriptor, DropDistro},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// make a copy of the SBOM for this test
|
||||
sbomCopy := sbom.SBOM{
|
||||
Artifacts: sbom.Artifacts{
|
||||
Packages: pkg.NewCollection(testPkg),
|
||||
},
|
||||
Source: testSBOM.Source,
|
||||
Descriptor: testSBOM.Descriptor,
|
||||
}
|
||||
|
||||
// apply drop options
|
||||
ApplyDropOptions(&sbomCopy, tt.opts)
|
||||
|
||||
// get the package from the modified SBOM
|
||||
var resultPkg *pkg.Package
|
||||
for p := range sbomCopy.Artifacts.Packages.Enumerate() {
|
||||
resultPkg = &p
|
||||
break
|
||||
}
|
||||
require.NotNil(t, resultPkg)
|
||||
|
||||
// THE CRITICAL ASSERTION: package ID must remain stable
|
||||
assert.Equal(t, originalID, resultPkg.ID(),
|
||||
"package ID changed after applying drop options: %v", tt.opts)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -94,7 +94,7 @@ func getVersion(resolver file.Resolver) (string, []file.Location, error) {
|
||||
}
|
||||
defer internal.CloseAndLogError(reader, locations[0].RealPath)
|
||||
|
||||
version, err := io.ReadAll(reader)
|
||||
version, err := io.ReadAll(reader) //nolint:gocritic // example code
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("unable to read alpine version: %w", err)
|
||||
}
|
||||
@@ -117,7 +117,7 @@ func getAPKKeys(resolver file.Resolver) (map[string]string, []file.Location, err
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to resolve file contents by location at %s: %w", location.RealPath, err)
|
||||
}
|
||||
content, err := io.ReadAll(reader)
|
||||
content, err := io.ReadAll(reader) //nolint:gocritic // example code
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to read apk key content at %s: %w", location.RealPath, err)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module github.com/anchore/syft
|
||||
|
||||
go 1.25.6
|
||||
go 1.25.8
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.6.0
|
||||
@@ -9,7 +9,6 @@ require (
|
||||
github.com/Masterminds/sprig/v3 v3.3.0
|
||||
github.com/OneOfOne/xxhash v1.2.8
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
|
||||
github.com/acobaugh/osrelease v0.1.0
|
||||
github.com/adrg/xdg v0.5.3
|
||||
github.com/anchore/bubbly v0.0.0-20231115134915-def0aba654a9
|
||||
github.com/anchore/clio v0.0.0-20250319180342-2cfe4b0cb716
|
||||
@@ -22,7 +21,7 @@ require (
|
||||
github.com/anchore/go-sync v0.0.0-20250326131806-4eda43a485b6
|
||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20250220190351-d62adb6e1115
|
||||
github.com/anchore/stereoscope v0.1.21
|
||||
github.com/anchore/stereoscope v0.1.22
|
||||
github.com/aquasecurity/go-pep440-version v0.0.1
|
||||
github.com/bitnami/go-version v0.0.0-20250131085805-b1f57a8634ef
|
||||
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb
|
||||
@@ -45,12 +44,12 @@ require (
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0
|
||||
github.com/gohugoio/hashstructure v0.6.0
|
||||
github.com/google/go-cmp v0.7.0
|
||||
github.com/google/go-containerregistry v0.21.1
|
||||
github.com/google/go-containerregistry v0.21.2
|
||||
github.com/google/licensecheck v0.3.1
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gookit/color v1.6.0
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2
|
||||
github.com/hashicorp/go-getter v1.8.4
|
||||
github.com/hashicorp/go-getter v1.8.5
|
||||
github.com/hashicorp/go-multierror v1.1.1
|
||||
github.com/hashicorp/hcl/v2 v2.24.0
|
||||
github.com/iancoleman/strcase v0.3.0
|
||||
@@ -62,7 +61,7 @@ require (
|
||||
github.com/mholt/archives v0.1.5
|
||||
github.com/moby/sys/mountinfo v0.7.2
|
||||
github.com/nix-community/go-nix v0.0.0-20250101154619-4bdde671e0a1
|
||||
github.com/olekukonko/tablewriter v1.1.3
|
||||
github.com/olekukonko/tablewriter v1.1.4
|
||||
github.com/opencontainers/go-digest v1.0.0
|
||||
github.com/pelletier/go-toml v1.9.5
|
||||
github.com/quasilyte/go-ruleguard/dsl v0.3.23
|
||||
@@ -87,24 +86,24 @@ require (
|
||||
go.uber.org/goleak v1.3.0
|
||||
go.yaml.in/yaml/v3 v3.0.4
|
||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546
|
||||
golang.org/x/mod v0.33.0
|
||||
golang.org/x/net v0.51.0
|
||||
golang.org/x/mod v0.34.0
|
||||
golang.org/x/net v0.52.0
|
||||
modernc.org/sqlite v1.46.1
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.123.0 // indirect
|
||||
cloud.google.com/go/auth v0.17.0 // indirect
|
||||
cloud.google.com/go/auth v0.18.1 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.9.0 // indirect
|
||||
cloud.google.com/go/iam v1.5.3 // indirect
|
||||
cloud.google.com/go/storage v1.58.0 // indirect
|
||||
cloud.google.com/go/storage v1.60.0 // indirect
|
||||
dario.cat/mergo v1.0.2 // indirect
|
||||
github.com/DataDog/zstd v1.5.5 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/Microsoft/hcsshim v0.14.0-rc.1 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.3.0 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.4.0 // indirect
|
||||
github.com/STARRY-S/zip v0.2.3 // indirect
|
||||
github.com/agext/levenshtein v1.2.1 // indirect; indirectt
|
||||
github.com/anchore/go-lzo v0.1.0 // indirect
|
||||
@@ -137,7 +136,7 @@ require (
|
||||
github.com/containerd/typeurl/v2 v2.2.3 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.6.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/docker/cli v29.2.1+incompatible // indirect
|
||||
github.com/docker/cli v29.3.0+incompatible // indirect
|
||||
github.com/docker/distribution v2.8.3+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.9.5 // indirect
|
||||
github.com/docker/go-connections v0.6.0 // indirect
|
||||
@@ -160,8 +159,8 @@ require (
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e // indirect
|
||||
github.com/google/s2a-go v0.1.9 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.15.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.11 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.17.0 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-version v1.8.0 // indirect
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||
@@ -222,7 +221,7 @@ require (
|
||||
github.com/spf13/viper v1.20.0 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/sylabs/sif/v2 v2.23.0 // indirect
|
||||
github.com/sylabs/sif/v2 v2.24.0 // indirect
|
||||
github.com/sylabs/squashfs v1.0.6 // indirect
|
||||
github.com/therootcompany/xz v1.0.1 // indirect
|
||||
github.com/tidwall/gjson v1.18.0 // indirect
|
||||
@@ -247,20 +246,20 @@ require (
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
go.uber.org/multierr v1.9.0 // indirect
|
||||
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
|
||||
golang.org/x/crypto v0.48.0 // indirect
|
||||
golang.org/x/oauth2 v0.35.0 // indirect
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
golang.org/x/sys v0.41.0 // indirect
|
||||
golang.org/x/term v0.40.0 // indirect
|
||||
golang.org/x/text v0.34.0 // indirect
|
||||
golang.org/x/time v0.14.0
|
||||
golang.org/x/tools v0.42.0
|
||||
golang.org/x/crypto v0.49.0 // indirect
|
||||
golang.org/x/oauth2 v0.36.0 // indirect
|
||||
golang.org/x/sync v0.20.0 // indirect
|
||||
golang.org/x/sys v0.42.0 // indirect
|
||||
golang.org/x/term v0.41.0 // indirect
|
||||
golang.org/x/text v0.35.0 // indirect
|
||||
golang.org/x/time v0.15.0
|
||||
golang.org/x/tools v0.43.0
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
|
||||
google.golang.org/api v0.256.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20250922171735-9219d122eba9 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba // indirect
|
||||
google.golang.org/grpc v1.76.0 // indirect
|
||||
google.golang.org/api v0.267.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 // indirect
|
||||
google.golang.org/grpc v1.79.3 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
@@ -270,6 +269,7 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/acobaugh/osrelease v0.1.0
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be
|
||||
github.com/cespare/xxhash/v2 v2.3.0
|
||||
github.com/gpustack/gguf-parser-go v0.24.0
|
||||
@@ -277,12 +277,12 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
cel.dev/expr v0.24.0 // indirect
|
||||
cloud.google.com/go/monitoring v1.24.2 // indirect
|
||||
cel.dev/expr v0.25.1 // indirect
|
||||
cloud.google.com/go/monitoring v1.24.3 // indirect
|
||||
cyphar.com/go-pathrs v0.2.1 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.10 // indirect
|
||||
@@ -291,46 +291,44 @@ require (
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.18 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.18 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.16 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.17 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.8 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.18 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.16 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.95.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.17 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.96.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.15 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.7 // indirect
|
||||
github.com/aws/smithy-go v1.24.1 // indirect
|
||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||
github.com/buger/jsonparser v1.1.1 // indirect
|
||||
github.com/clipperhouse/displaywidth v0.9.0 // indirect
|
||||
github.com/clipperhouse/stringish v0.1.1 // indirect
|
||||
github.com/clipperhouse/uax29/v2 v2.5.0 // indirect
|
||||
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect
|
||||
github.com/buger/jsonparser v1.1.2 // indirect
|
||||
github.com/clipperhouse/displaywidth v0.10.0 // indirect
|
||||
github.com/clipperhouse/uax29/v2 v2.6.0 // indirect
|
||||
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 // indirect
|
||||
github.com/containerd/cgroups/v3 v3.1.2 // indirect
|
||||
github.com/containerd/containerd/v2 v2.2.1 // indirect
|
||||
github.com/containerd/plugin v1.0.0 // indirect
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect
|
||||
github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.2 // indirect
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.36.0 // indirect
|
||||
github.com/envoyproxy/protoc-gen-validate v1.3.0 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
|
||||
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.70 // indirect
|
||||
github.com/henvic/httpretty v0.1.4 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/moby/moby/api v1.53.0 // indirect
|
||||
github.com/moby/moby/client v0.2.2 // indirect
|
||||
github.com/moby/moby/api v1.54.0 // indirect
|
||||
github.com/moby/moby/client v0.3.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
|
||||
github.com/nwaples/rardecode/v2 v2.2.0 // indirect
|
||||
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect
|
||||
github.com/olekukonko/errors v1.1.0 // indirect
|
||||
github.com/olekukonko/ll v0.1.4-0.20260115111900-9e59c2286df0 // indirect
|
||||
github.com/olekukonko/errors v1.2.0 // indirect
|
||||
github.com/olekukonko/ll v0.1.6 // indirect
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
|
||||
github.com/smallnest/ringbuffer v0.0.0-20241116012123-461381446e3d // indirect
|
||||
github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect
|
||||
github.com/zeebo/errs v1.4.0 // indirect
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect
|
||||
github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.40.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.40.0 // indirect
|
||||
gonum.org/v1/gonum v0.16.0 // indirect
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=
|
||||
cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
|
||||
cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4=
|
||||
cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
@@ -31,8 +31,8 @@ cloud.google.com/go v0.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0c
|
||||
cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=
|
||||
cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE=
|
||||
cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU=
|
||||
cloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4=
|
||||
cloud.google.com/go/auth v0.17.0/go.mod h1:6wv/t5/6rOPAX4fJiRjKkJCvswLwdet7G8+UGXt7nCQ=
|
||||
cloud.google.com/go/auth v0.18.1 h1:IwTEx92GFUo2pJ6Qea0EU3zYvKnTAeRCODxfA/G5UWs=
|
||||
cloud.google.com/go/auth v0.18.1/go.mod h1:GfTYoS9G3CWpRA3Va9doKN9mjPGRS+v41jmZAhBzbrA=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
@@ -48,12 +48,12 @@ cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1
|
||||
cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY=
|
||||
cloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc=
|
||||
cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU=
|
||||
cloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc=
|
||||
cloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA=
|
||||
cloud.google.com/go/longrunning v0.7.0 h1:FV0+SYF1RIj59gyoWDRi45GiYUMM3K1qO51qoboQT1E=
|
||||
cloud.google.com/go/longrunning v0.7.0/go.mod h1:ySn2yXmjbK9Ba0zsQqunhDkYi0+9rlXIwnoAf+h+TPY=
|
||||
cloud.google.com/go/monitoring v1.24.2 h1:5OTsoJ1dXYIiMiuL+sYscLc9BumrL3CarVLL7dd7lHM=
|
||||
cloud.google.com/go/monitoring v1.24.2/go.mod h1:x7yzPWcgDRnPEv3sI+jJGBkwl5qINf+6qY4eq0I9B4U=
|
||||
cloud.google.com/go/logging v1.13.1 h1:O7LvmO0kGLaHY/gq8cV7T0dyp6zJhYAOtZPX4TF3QtY=
|
||||
cloud.google.com/go/logging v1.13.1/go.mod h1:XAQkfkMBxQRjQek96WLPNze7vsOmay9H5PqfsNYDqvw=
|
||||
cloud.google.com/go/longrunning v0.8.0 h1:LiKK77J3bx5gDLi4SMViHixjD2ohlkwBi+mKA7EhfW8=
|
||||
cloud.google.com/go/longrunning v0.8.0/go.mod h1:UmErU2Onzi+fKDg2gR7dusz11Pe26aknR4kHmJJqIfk=
|
||||
cloud.google.com/go/monitoring v1.24.3 h1:dde+gMNc0UhPZD1Azu6at2e79bfdztVDS5lvhOdsgaE=
|
||||
cloud.google.com/go/monitoring v1.24.3/go.mod h1:nYP6W0tm3N9H/bOw8am7t62YTzZY+zUeQ+Bi6+2eonI=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||
@@ -63,10 +63,10 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo
|
||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
cloud.google.com/go/storage v1.58.0 h1:PflFXlmFJjG/nBeR9B7pKddLQWaFaRWx4uUi/LyNxxo=
|
||||
cloud.google.com/go/storage v1.58.0/go.mod h1:cMWbtM+anpC74gn6qjLh+exqYcfmB9Hqe5z6adx+CLI=
|
||||
cloud.google.com/go/trace v1.11.6 h1:2O2zjPzqPYAHrn3OKl029qlqG6W8ZdYaOWRyr8NgMT4=
|
||||
cloud.google.com/go/trace v1.11.6/go.mod h1:GA855OeDEBiBMzcckLPE2kDunIpC72N+Pq8WFieFjnI=
|
||||
cloud.google.com/go/storage v1.60.0 h1:oBfZrSOCimggVNz9Y/bXY35uUcts7OViubeddTTVzQ8=
|
||||
cloud.google.com/go/storage v1.60.0/go.mod h1:q+5196hXfejkctrnx+VYU8RKQr/L3c0cBIlrjmiAKE0=
|
||||
cloud.google.com/go/trace v1.11.7 h1:kDNDX8JkaAG3R2nq1lIdkb7FCSi1rCmsEtKVsty7p+U=
|
||||
cloud.google.com/go/trace v1.11.7/go.mod h1:TNn9d5V3fQVf6s4SCveVMIBS2LJUqo73GACmq/Tky0s=
|
||||
cyphar.com/go-pathrs v0.2.1 h1:9nx1vOgwVvX1mNBWDu93+vaceedpbsDqo+XuBGL40b8=
|
||||
cyphar.com/go-pathrs v0.2.1/go.mod h1:y8f1EMG7r+hCuFf/rXsKqMJrJAUoADZGNh5/vZPKcGc=
|
||||
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
|
||||
@@ -84,14 +84,14 @@ github.com/CycloneDX/cyclonedx-go v0.10.0/go.mod h1:vUvbCXQsEm48OI6oOlanxstwNByX
|
||||
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
||||
github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ=
|
||||
github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 h1:UQUsRi8WTzhZntp5313l+CHIAT95ojUI2lpP/ExlZa4=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0/go.mod h1:Cz6ft6Dkn3Et6l2v2a9/RpN7epQ1GtDlO6lj8bEcOvw=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0 h1:lhhYARPUu3LmHysQ/igznQphfzynnqI3D75oUyw1HXk=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0/go.mod h1:l9rva3ApbBpEJxSNYnwT9N4CDLrWgtq3u8736C5hyJw=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.54.0 h1:xfK3bbi6F2RDtaZFtUdKO3osOBIhNb+xTs8lFW6yx9o=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.54.0/go.mod h1:vB2GH9GAYYJTO3mEn8oYwzEdhlayZIdQz6zdzgUIRvA=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0 h1:s0WlVbf9qpvkh1c/uDAPElam0WrL7fHRIidgZJ7UqZI=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0/go.mod h1:Mf6O40IAyB9zR/1J8nGDDPirZQQPbYJni8Yisy7NTMc=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 h1:sBEjpZlNHzK1voKq9695PJSX2o5NEXl7/OL3coiIY0c=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0 h1:UnDZ/zFfG1JhH/DqxIZYU/1CUAlTUScoXD/LcM2Ykk8=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0/go.mod h1:IA1C1U7jO/ENqm/vhi7V9YYpBsp+IMyqNrEN94N7tVc=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.55.0 h1:7t/qx5Ost0s0wbA/VDrByOooURhp+ikYwv20i9Y07TQ=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.55.0/go.mod h1:vB2GH9GAYYJTO3mEn8oYwzEdhlayZIdQz6zdzgUIRvA=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0 h1:0s6TxfCu2KHkkZPnBfsQ2y5qia0jl3MMrmBhu3nCOYk=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0/go.mod h1:Mf6O40IAyB9zR/1J8nGDDPirZQQPbYJni8Yisy7NTMc=
|
||||
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
||||
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
|
||||
@@ -106,8 +106,8 @@ github.com/Microsoft/hcsshim v0.14.0-rc.1/go.mod h1:hTKFGbnDtQb1wHiOWv4v0eN+7boS
|
||||
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.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw=
|
||||
github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
|
||||
github.com/ProtonMail/go-crypto v1.4.0 h1:Zq/pbM3F5DFgJiMouxEdSVY44MVoQNEKp5d5QxIQceQ=
|
||||
github.com/ProtonMail/go-crypto v1.4.0/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo=
|
||||
github.com/STARRY-S/zip v0.2.3 h1:luE4dMvRPDOWQdeDdUxUoZkzUIpTccdKdhHHsQJ1fm4=
|
||||
github.com/STARRY-S/zip v0.2.3/go.mod h1:lqJ9JdeRipyOQJrYSOtpNAiaesFO6zVDsE8GIGFaoSk=
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=
|
||||
@@ -148,8 +148,8 @@ github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b h1:e1bmaoJfZV
|
||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b/go.mod h1:Bkc+JYWjMCF8OyZ340IMSIi2Ebf3uwByOk6ho4wne1E=
|
||||
github.com/anchore/packageurl-go v0.1.1-0.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.1.21 h1:rIwPDjJQdOdIvMrkNyZFkUS5dSyQPDjAwUqU0XjVF74=
|
||||
github.com/anchore/stereoscope v0.1.21/go.mod h1:8652axKmO8itOcGX8gzAvHW3cVWvbxpJ4woLNMEO4a0=
|
||||
github.com/anchore/stereoscope v0.1.22 h1:L807G/kk0WZzOCGuRGF7knxMKzwW2PGdbPVRystryd8=
|
||||
github.com/anchore/stereoscope v0.1.22/go.mod h1:FikPtAb/WnbqwgLHAvQA9O+fWez0K4pbjxzghz++iy4=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
|
||||
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||
@@ -187,18 +187,18 @@ github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.18 h1:xCeWVjj0ki0l3nruoy
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.18/go.mod h1:r/eLGuGCBw6l36ZRWiw6PaZwPXb6YOj+i/7MizNl5/k=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.16 h1:CjMzUs78RDDv4ROu3JnJn/Ig1r6ZD7/T2DXLLRpejic=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.16/go.mod h1:uVW4OLBqbJXSHJYA9svT9BluSvvwbzLQ2Crf6UPzR3c=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.17 h1:JqcdRG//czea7Ppjb+g/n4o8i/R50aTBHkA7vu0lK+k=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.17/go.mod h1:CO+WeGmIdj/MlPel2KwID9Gt7CNq4M65HUfBW97liM0=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.5 h1:CeY9LUdur+Dxoeldqoun6y4WtJ3RQtzk0JMP2gfUay0=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.5/go.mod h1:AZLZf2fMaahW5s/wMRciu1sYbdsikT/UHwbUjOdEVTc=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.7 h1:DIBqIrJ7hv+e4CmIk2z3pyKT+3B6qVMgRsawHiR3qso=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.7/go.mod h1:vLm00xmBke75UmpNvOcZQ/Q30ZFjbczeLFqGx5urmGo=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.8 h1:Z5EiPIzXKewUQK0QTMkutjiaPVeVYXX7KIqhXu/0fXs=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.8/go.mod h1:FsTpJtvC4U1fyDXk7c71XoDv3HlRm8V3NiYLeYLh5YE=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.18 h1:LTRCYFlnnKFlKsyIQxKhJuDuA3ZkrDQMRYm6rXiHlLY=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.18/go.mod h1:XhwkgGG6bHSd00nO/mexWTcTjgd6PjuvWQMqSn2UaEk=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.16 h1:NSbvS17MlI2lurYgXnCOLvCFX38sBW4eiVER7+kkgsU=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.16/go.mod h1:SwT8Tmqd4sA6G1qaGdzWCJN99bUmPGHfRwwq3G5Qb+A=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.95.0 h1:MIWra+MSq53CFaXXAywB2qg9YvVZifkk6vEGl/1Qor0=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.95.0/go.mod h1:79S2BdqCJpScXZA2y+cpZuocWsjGjJINyXnOsf5DTz8=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.17 h1:bGeHBsGZx0Dvu/eJC0Lh9adJa3M1xREcndxLNZlve2U=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.17/go.mod h1:dcW24lbU0CzHusTE8LLHhRLI42ejmINN8Lcr22bwh/g=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.96.0 h1:oeu8VPlOre74lBA/PMhxa5vewaMIMmILM+RraSyB8KA=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.96.0/go.mod h1:5jggDlZ2CLQhwJBiZJb4vfk4f0GxWdEDruWKEJ1xOdo=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.6 h1:MzORe+J94I+hYu2a6XmV5yC9huoTv8NRcCrUNedDypQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.6/go.mod h1:hXzcHLARD7GeWnifd8j9RWqtfIgxj4/cAtIVIK7hg8g=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.11 h1:7oGD8KPfBOJGXiCoRKrrrQkbvCp8N++u36hrLMPey6o=
|
||||
@@ -237,8 +237,8 @@ github.com/bodgit/windows v1.0.1 h1:tF7K6KOluPYygXa3Z2594zxlkbKPAOvqr97etrGNIz4=
|
||||
github.com/bodgit/windows v1.0.1/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM=
|
||||
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/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
|
||||
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
|
||||
github.com/buger/jsonparser v1.1.2 h1:frqHqw7otoVbk5M8LlE/L7HTnIq2v9RX6EJ48i9AxJk=
|
||||
github.com/buger/jsonparser v1.1.2/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
@@ -276,12 +276,10 @@ github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38
|
||||
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=
|
||||
github.com/clipperhouse/displaywidth v0.9.0 h1:Qb4KOhYwRiN3viMv1v/3cTBlz3AcAZX3+y9OLhMtAtA=
|
||||
github.com/clipperhouse/displaywidth v0.9.0/go.mod h1:aCAAqTlh4GIVkhQnJpbL0T/WfcrJXHcj8C0yjYcjOZA=
|
||||
github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
|
||||
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
|
||||
github.com/clipperhouse/uax29/v2 v2.5.0 h1:x7T0T4eTHDONxFJsL94uKNKPHrclyFI0lm7+w94cO8U=
|
||||
github.com/clipperhouse/uax29/v2 v2.5.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
|
||||
github.com/clipperhouse/displaywidth v0.10.0 h1:GhBG8WuerxjFQQYeuZAeVTuyxuX+UraiZGD4HJQ3Y8g=
|
||||
github.com/clipperhouse/displaywidth v0.10.0/go.mod h1:XqJajYsaiEwkxOj4bowCTMcT1SgvHo9flfF3jQasdbs=
|
||||
github.com/clipperhouse/uax29/v2 v2.6.0 h1:z0cDbUV+aPASdFb2/ndFnS9ts/WNXgTNNGFoKXuhpos=
|
||||
github.com/clipperhouse/uax29/v2 v2.6.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
|
||||
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
|
||||
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
@@ -294,8 +292,8 @@ github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWH
|
||||
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 h1:aQ3y1lwWyqYPiWZThqv1aFbZMiM9vblcSArJRf2Irls=
|
||||
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
|
||||
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 h1:6xNmx7iTtyBRev0+D/Tv1FZd4SCg8axKApyNyRsAt/w=
|
||||
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI=
|
||||
github.com/containerd/cgroups/v3 v3.1.2 h1:OSosXMtkhI6Qove637tg1XgK4q+DhR0mX8Wi8EhrHa4=
|
||||
github.com/containerd/cgroups/v3 v3.1.2/go.mod h1:PKZ2AcWmSBsY/tJUVhtS/rluX0b1uq1GmPO1ElCmbOw=
|
||||
github.com/containerd/containerd/api v1.10.0 h1:5n0oHYVBwN4VhoX9fFykCV9dF1/BvAXeg2F8W6UYq1o=
|
||||
@@ -345,8 +343,8 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
|
||||
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
|
||||
github.com/docker/cli v29.2.1+incompatible h1:n3Jt0QVCN65eiVBoUTZQM9mcQICCJt3akW4pKAbKdJg=
|
||||
github.com/docker/cli v29.2.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/cli v29.3.0+incompatible h1:z3iWveU7h19Pqx7alZES8j+IeFQZ1lhTwb2F+V9SVvk=
|
||||
github.com/docker/cli v29.3.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-credential-helpers v0.9.5 h1:EFNN8DHvaiK8zVqFA2DT6BjXE0GzfLOZ38ggPTKePkY=
|
||||
@@ -377,16 +375,16 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.m
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
|
||||
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
|
||||
github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ=
|
||||
github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M=
|
||||
github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA=
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A=
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw=
|
||||
github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA=
|
||||
github.com/envoyproxy/go-control-plane v0.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU=
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.36.0 h1:yg/JjO5E7ubRyKX3m07GF3reDNEnfOboJ0QySbH736g=
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98=
|
||||
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI=
|
||||
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.3.0 h1:TvGH1wof4H33rezVKWSpqKz5NXWg5VPuZ0uONDT6eb4=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA=
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
||||
github.com/facebookincubator/flog v0.0.0-20190930132826-d2511d0ce33c/go.mod h1:QGzNH9ujQ2ZUr/CjDGZGWeDAVStrWNjHeEcjJL96Nuk=
|
||||
@@ -433,8 +431,8 @@ github.com/go-git/go-git/v5 v5.17.0/go.mod h1:f82C4YiLx+Lhi8eHxltLeGC5uBTXSFa6PC
|
||||
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=
|
||||
github.com/go-jose/go-jose/v4 v4.1.2 h1:TK/7NqRQZfgAh+Td8AlsrvtPoUyiHh0LqVvokh+1vHI=
|
||||
github.com/go-jose/go-jose/v4 v4.1.2/go.mod h1:22cg9HWM1pOlnRiY+9cQYJ9XHmya1bYW8OeDM6Ku6Oo=
|
||||
github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
|
||||
github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
@@ -515,8 +513,8 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/go-containerregistry v0.21.1 h1:sOt/o9BS2b87FnR7wxXPvRKU1XVJn2QCwOS5g8zQXlc=
|
||||
github.com/google/go-containerregistry v0.21.1/go.mod h1:ctO5aCaewH4AK1AumSF5DPW+0+R+d2FmylMJdp5G7p0=
|
||||
github.com/google/go-containerregistry v0.21.2 h1:vYaMU4nU55JJGFC9JR/s8NZcTjbE9DBBbvusTW9NeS0=
|
||||
github.com/google/go-containerregistry v0.21.2/go.mod h1:ctO5aCaewH4AK1AumSF5DPW+0+R+d2FmylMJdp5G7p0=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/licensecheck v0.3.1 h1:QoxgoDkaeC4nFrtGN1jV7IPmDCHFNIVh54e5hSt6sPs=
|
||||
github.com/google/licensecheck v0.3.1/go.mod h1:ORkR35t/JjW+emNKtfJDII0zlciG9JgbT7SmsohlHmY=
|
||||
@@ -551,14 +549,14 @@ github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.7 h1:zrn2Ee/nWmHulBx5sAVrGgAa0f2/R35S4DJwfFaUPFQ=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.11 h1:vAe81Msw+8tKUxi2Dqh/NZMz7475yUvmRIkXr4oN2ao=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.11/go.mod h1:RFV7MUdlb7AgEq2v7FmMCfeSMCllAzWxFgRdusoGks8=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
|
||||
github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
|
||||
github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo=
|
||||
github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc=
|
||||
github.com/googleapis/gax-go/v2 v2.17.0 h1:RksgfBpxqff0EZkDWYuz9q/uWsTVz+kf43LsZ1J6SMc=
|
||||
github.com/googleapis/gax-go/v2 v2.17.0/go.mod h1:mzaqghpQp4JDh3HvADwrat+6M3MOIDp5YKHhb9PAgDY=
|
||||
github.com/gookit/assert v0.1.1 h1:lh3GcawXe/p+cU7ESTZ5Ui3Sm/x8JWpIis4/1aF0mY0=
|
||||
github.com/gookit/assert v0.1.1/go.mod h1:jS5bmIVQZTIwk42uXl4lyj4iaaxx32tqH16CFj0VX2E=
|
||||
github.com/gookit/color v1.2.5/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg=
|
||||
@@ -578,8 +576,8 @@ github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtng
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
||||
github.com/hashicorp/go-getter v1.8.4 h1:hGEd2xsuVKgwkMtPVufq73fAmZU/x65PPcqH3cb0D9A=
|
||||
github.com/hashicorp/go-getter v1.8.4/go.mod h1:x27pPGSg9kzoB147QXI8d/nDvp2IgYGcwuRjpaXE9Yg=
|
||||
github.com/hashicorp/go-getter v1.8.5 h1:DMPV5CSw5JrNg/IK7kDZt3+l2REKXOi3oAw7uYLh2NM=
|
||||
github.com/hashicorp/go-getter v1.8.5/go.mod h1:WIffejwAyDSJhoVptc3UEshEMkR9O63rw34V7k43O3Q=
|
||||
github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
|
||||
github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
@@ -727,10 +725,10 @@ github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3N
|
||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||
github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg=
|
||||
github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
|
||||
github.com/moby/moby/api v1.53.0 h1:PihqG1ncw4W+8mZs69jlwGXdaYBeb5brF6BL7mPIS/w=
|
||||
github.com/moby/moby/api v1.53.0/go.mod h1:8mb+ReTlisw4pS6BRzCMts5M49W5M7bKt1cJy/YbAqc=
|
||||
github.com/moby/moby/client v0.2.2 h1:Pt4hRMCAIlyjL3cr8M5TrXCwKzguebPAc2do2ur7dEM=
|
||||
github.com/moby/moby/client v0.2.2/go.mod h1:2EkIPVNCqR05CMIzL1mfA07t0HvVUUOl85pasRz/GmQ=
|
||||
github.com/moby/moby/api v1.54.0 h1:7kbUgyiKcoBhm0UrWbdrMs7RX8dnwzURKVbZGy2GnL0=
|
||||
github.com/moby/moby/api v1.54.0/go.mod h1:8mb+ReTlisw4pS6BRzCMts5M49W5M7bKt1cJy/YbAqc=
|
||||
github.com/moby/moby/client v0.3.0 h1:UUGL5okry+Aomj3WhGt9Aigl3ZOxZGqR7XPo+RLPlKs=
|
||||
github.com/moby/moby/client v0.3.0/go.mod h1:HJgFbJRvogDQjbM8fqc1MCEm4mIAGMLjXbgwoZp6jCQ=
|
||||
github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg=
|
||||
github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4=
|
||||
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
|
||||
@@ -766,12 +764,12 @@ github.com/nwaples/rardecode/v2 v2.2.0 h1:4ufPGHiNe1rYJxYfehALLjup4Ls3ck42CWwjKi
|
||||
github.com/nwaples/rardecode/v2 v2.2.0/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw=
|
||||
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 h1:zrbMGy9YXpIeTnGj4EljqMiZsIcE09mmF8XsD5AYOJc=
|
||||
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6/go.mod h1:rEKTHC9roVVicUIfZK7DYrdIoM0EOr8mK1Hj5s3JjH0=
|
||||
github.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM=
|
||||
github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y=
|
||||
github.com/olekukonko/ll v0.1.4-0.20260115111900-9e59c2286df0 h1:jrYnow5+hy3WRDCBypUFvVKNSPPCdqgSXIE9eJDD8LM=
|
||||
github.com/olekukonko/ll v0.1.4-0.20260115111900-9e59c2286df0/go.mod h1:b52bVQRRPObe+yyBl0TxNfhesL0nedD4Cht0/zx55Ew=
|
||||
github.com/olekukonko/tablewriter v1.1.3 h1:VSHhghXxrP0JHl+0NnKid7WoEmd9/urKRJLysb70nnA=
|
||||
github.com/olekukonko/tablewriter v1.1.3/go.mod h1:9VU0knjhmMkXjnMKrZ3+L2JhhtsQ/L38BbL3CRNE8tM=
|
||||
github.com/olekukonko/errors v1.2.0 h1:10Zcn4GeV59t/EGqJc8fUjtFT/FuUh5bTMzZ1XwmCRo=
|
||||
github.com/olekukonko/errors v1.2.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y=
|
||||
github.com/olekukonko/ll v0.1.6 h1:lGVTHO+Qc4Qm+fce/2h2m5y9LvqaW+DCN7xW9hsU3uA=
|
||||
github.com/olekukonko/ll v0.1.6/go.mod h1:NVUmjBb/aCtUpjKk75BhWrOlARz3dqsM+OtszpY4o88=
|
||||
github.com/olekukonko/tablewriter v1.1.4 h1:ORUMI3dXbMnRlRggJX3+q7OzQFDdvgbN9nVWj1drm6I=
|
||||
github.com/olekukonko/tablewriter v1.1.4/go.mod h1:+kedxuyTtgoZLwif3P1Em4hARJs+mVnzKxmsCL/C5RY=
|
||||
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
|
||||
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
@@ -902,8 +900,8 @@ github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3A
|
||||
github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM=
|
||||
github.com/spf13/viper v1.20.0 h1:zrxIyR3RQIOsarIrgL8+sAvALXul9jeEPa06Y0Ph6vY=
|
||||
github.com/spf13/viper v1.20.0/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
|
||||
github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE=
|
||||
github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g=
|
||||
github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo=
|
||||
github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
@@ -925,8 +923,8 @@ github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/sylabs/sif/v2 v2.23.0 h1:VWJC7iryINdIgyIK8EdxMCJkx8EDPVlvFaskgY+ruFk=
|
||||
github.com/sylabs/sif/v2 v2.23.0/go.mod h1:0UJk7MMSQS2n/95yYd+CvAfK7K2mE0aWHRYYybd+ouw=
|
||||
github.com/sylabs/sif/v2 v2.24.0 h1:1wB5uMDUQYjk8AckTySaDcP9YnpMb1LyDRr1Jt9A10w=
|
||||
github.com/sylabs/sif/v2 v2.24.0/go.mod h1:DbXWqWZ1hdLSU+K9ipdds5AmZeHWsyxCOj/oQakBa88=
|
||||
github.com/sylabs/squashfs v1.0.6 h1:PvJcDzxr+vIm2kH56mEMbaOzvGu79gK7P7IX+R7BDZI=
|
||||
github.com/sylabs/squashfs v1.0.6/go.mod h1:DlDeUawVXLWAsSRa085Eo0ZenGzAB32JdAUFaB0LZfE=
|
||||
github.com/terminalstatic/go-xsd-validate v0.1.6 h1:TenYeQ3eY631qNi1/cTmLH/s2slHPRKTTHT+XSHkepo=
|
||||
@@ -984,8 +982,6 @@ github.com/zclconf/go-cty v1.16.3 h1:osr++gw2T61A8KVYHoQiFbFd1Lh3JOCXc/jFLJXKTxk
|
||||
github.com/zclconf/go-cty v1.16.3/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
|
||||
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/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM=
|
||||
github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
|
||||
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=
|
||||
@@ -1002,16 +998,16 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8FktLnIcy6jJNyOCQw=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.39.0 h1:kWRNZMsfBHZ+uHjiH4y7Etn2FK26LAGkNFw7RHv1DhE=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.39.0/go.mod h1:t/OGqzHBa5v6RHZwrDBJ2OirWc+4q/w2fTbLZwAKjTk=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
|
||||
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
|
||||
go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 h1:wm/Q0GAAykXv83wzcKzGGqAnnfLFyFe7RslekZuv+VI=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0/go.mod h1:ra3Pa40+oKjvYh+ZD3EdxFZZB0xdMfuileHAm4nNN7w=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.39.0 h1:5gn2urDL/FBnK8OkCfD1j3/ER79rUuTYmCvlXBKeYL8=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.39.0/go.mod h1:0fBG6ZJxhqByfFZDwSwpZGzJU671HkwpWaNe2t4VUPI=
|
||||
go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g=
|
||||
go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc=
|
||||
go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8=
|
||||
@@ -1046,8 +1042,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.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
|
||||
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
|
||||
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=
|
||||
@@ -1087,8 +1083,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.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
|
||||
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
|
||||
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
|
||||
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
|
||||
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=
|
||||
@@ -1133,8 +1129,8 @@ golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qx
|
||||
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.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
|
||||
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
|
||||
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
||||
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
||||
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=
|
||||
@@ -1152,8 +1148,8 @@ golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ
|
||||
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=
|
||||
golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
||||
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -1166,8 +1162,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.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
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=
|
||||
@@ -1242,13 +1238,13 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
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.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
|
||||
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
|
||||
golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
|
||||
golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
|
||||
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=
|
||||
@@ -1259,13 +1255,13 @@ 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.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
|
||||
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
|
||||
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=
|
||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
||||
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
@@ -1321,8 +1317,8 @@ golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
|
||||
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
|
||||
golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
|
||||
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -1363,8 +1359,8 @@ google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdr
|
||||
google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU=
|
||||
google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
|
||||
google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw=
|
||||
google.golang.org/api v0.256.0 h1:u6Khm8+F9sxbCTYNoBHg6/Hwv0N/i+V94MvkOSor6oI=
|
||||
google.golang.org/api v0.256.0/go.mod h1:KIgPhksXADEKJlnEoRa9qAII4rXcy40vfI8HRqcU964=
|
||||
google.golang.org/api v0.267.0 h1:w+vfWPMPYeRs8qH1aYYsFX68jMls5acWl/jocfLomwE=
|
||||
google.golang.org/api v0.267.0/go.mod h1:Jzc0+ZfLnyvXma3UtaTl023TdhZu6OMBP9tJ+0EmFD0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
@@ -1435,12 +1431,12 @@ google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ6
|
||||
google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20250922171735-9219d122eba9 h1:LvZVVaPE0JSqL+ZWb6ErZfnEOKIqqFWUJE2D0fObSmc=
|
||||
google.golang.org/genproto v0.0.0-20250922171735-9219d122eba9/go.mod h1:QFOrLhdAe2PsTp3vQY4quuLKTi9j3XG3r6JPPaw7MSc=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba h1:B14OtaXuMaCQsl2deSvNkyPKIzq3BjfxQp8d00QyWx4=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:G5IanEx8/PgI9w6CFcYQf7jMtHQhZruvfM1i3qOqk5U=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 h1:VQZ/yAbAtjkHgH80teYd2em3xtIkkHd7ZhqfH2N9CsM=
|
||||
google.golang.org/genproto v0.0.0-20260128011058-8636f8732409/go.mod h1:rxKD3IEILWEu3P44seeNOAwZN4SaoKaQ/2eTg4mM6EM=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 h1:7ei4lp52gK1uSejlA8AZl5AJjeLUOHBQscRQZUgAcu0=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20/go.mod h1:ZdbssH/1SOVnjnDlXzxDHK2MCidiqXtbYccJNzNYPEE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 h1:Jr5R2J6F6qWyzINc+4AM8t5pfUz6beZpHp678GNrMbE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
@@ -1468,8 +1464,8 @@ google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnD
|
||||
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||
google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||
google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
|
||||
google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
|
||||
google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
|
||||
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
|
||||
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
|
||||
@@ -215,7 +215,7 @@ func write(schema []byte) {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
existingSchemaBytes, err := io.ReadAll(existingFh)
|
||||
existingSchemaBytes, err := io.ReadAll(existingFh) //nolint:gocritic // offline code generator
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ func (s *scanner) FindEvidence(_ context.Context, reader io.Reader) (evidence []
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
content, err = io.ReadAll(reader)
|
||||
content, err = io.ReadAll(reader) //nolint:gocritic // license scanner requires full content
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ func UpdateGoldenFileContents(t *testing.T, contents []byte) {
|
||||
|
||||
t.Log(dangerText("!!! UPDATING GOLDEN FILE !!!"), goldenFilePath)
|
||||
|
||||
err := os.WriteFile(goldenFilePath, contents, 0600)
|
||||
err := os.WriteFile(goldenFilePath, contents, 0o600)
|
||||
if err != nil {
|
||||
t.Fatalf("could not update golden file (%s): %+v", goldenFilePath, err)
|
||||
}
|
||||
@@ -50,7 +50,9 @@ func GetGoldenFileContents(t *testing.T) []byte {
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
bytes, err := io.ReadAll(f)
|
||||
// suppress lint prohibiting ReadAll since golden files are source files in Syft's repository
|
||||
// and not user provided artifacts.
|
||||
bytes, err := io.ReadAll(f) //nolint:gocritic // golden files are trusted source files, not user artifacts
|
||||
if err != nil {
|
||||
t.Fatalf("could not read file (%s): %+v", goldenPath, err)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
package tmpdir
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type ctxKey struct{}
|
||||
|
||||
// Root creates a new root temp directory with the given prefix and returns a context with the
|
||||
// TempDir attached. Callers should defer Cleanup() on the returned TempDir to ensure all
|
||||
// temp files are removed.
|
||||
func Root(ctx context.Context, prefix string) (context.Context, *TempDir) {
|
||||
td := &TempDir{prefix: prefix}
|
||||
return context.WithValue(ctx, ctxKey{}, td), td
|
||||
}
|
||||
|
||||
// FromPath creates a TempDir backed by an existing directory. The caller owns the lifecycle of
|
||||
// the directory; Cleanup() is a no-op. This is useful for wrapping a directory from t.TempDir()
|
||||
// where the test framework handles cleanup automatically.
|
||||
func FromPath(dir string) *TempDir {
|
||||
td := &TempDir{}
|
||||
td.root = dir
|
||||
td.initOnce.Do(func() {}) // mark as initialized
|
||||
return td
|
||||
}
|
||||
|
||||
// WithValue returns a new context with the given TempDir attached. Use this to inject an
|
||||
// existing TempDir into a context (e.g., sharing a TempDir across multiple test contexts).
|
||||
func WithValue(ctx context.Context, td *TempDir) context.Context {
|
||||
return context.WithValue(ctx, ctxKey{}, td)
|
||||
}
|
||||
|
||||
// FromContext returns the TempDir from the context, or nil if none is set.
|
||||
func FromContext(ctx context.Context) *TempDir {
|
||||
td, _ := ctx.Value(ctxKey{}).(*TempDir)
|
||||
return td
|
||||
}
|
||||
|
||||
// TempDir manages a tree of temporary directories. All files and child directories live under
|
||||
// a single root path that can be removed in one shot via Cleanup(). After initialization, the
|
||||
// struct has no mutable state — NewChild and NewFile delegate uniqueness to os.MkdirTemp and
|
||||
// os.CreateTemp respectively, so no locking is needed on the hot path.
|
||||
type TempDir struct {
|
||||
prefix string
|
||||
root string // set exactly once by initOnce
|
||||
initOnce sync.Once
|
||||
initErr error
|
||||
cleanupOnce sync.Once
|
||||
cleaned atomic.Bool
|
||||
}
|
||||
|
||||
func noop() {}
|
||||
|
||||
// path returns the root directory, lazily creating it on the first call.
|
||||
func (t *TempDir) path() (string, error) {
|
||||
t.initOnce.Do(func() {
|
||||
t.root, t.initErr = os.MkdirTemp("", t.prefix+"-")
|
||||
})
|
||||
if t.initErr != nil {
|
||||
return "", fmt.Errorf("failed to create root temp dir: %w", t.initErr)
|
||||
}
|
||||
if t.cleaned.Load() {
|
||||
return "", fmt.Errorf("temp dir has been cleaned up")
|
||||
}
|
||||
return t.root, nil
|
||||
}
|
||||
|
||||
// NewChild creates a named subdirectory under this TempDir. The returned cleanup function removes
|
||||
// the subdirectory and all contents; callers should defer it to reclaim space early. The root
|
||||
// Cleanup acts as a safety net if the per-child cleanup is missed. The cleanup function is safe
|
||||
// to call multiple times and is safe to call after the root has already been cleaned up.
|
||||
func (t *TempDir) NewChild(name string) (string, func(), error) {
|
||||
root, err := t.path()
|
||||
if err != nil {
|
||||
return "", noop, err
|
||||
}
|
||||
dir, err := os.MkdirTemp(root, name+"-")
|
||||
if err != nil {
|
||||
return "", noop, fmt.Errorf("failed to create child temp dir: %w", err)
|
||||
}
|
||||
cleanup := func() {
|
||||
_ = os.RemoveAll(dir)
|
||||
}
|
||||
return dir, cleanup, nil
|
||||
}
|
||||
|
||||
// NewFile creates a new temp file under this TempDir with the given name pattern (as in os.CreateTemp).
|
||||
// The caller is responsible for closing the file. The returned cleanup function removes the file;
|
||||
// callers should defer it to reclaim space early. The root Cleanup acts as a safety net if the
|
||||
// per-file cleanup is missed. The cleanup function is safe to call multiple times.
|
||||
func (t *TempDir) NewFile(pattern string) (*os.File, func(), error) {
|
||||
root, err := t.path()
|
||||
if err != nil {
|
||||
return nil, noop, err
|
||||
}
|
||||
f, err := os.CreateTemp(root, pattern)
|
||||
if err != nil {
|
||||
return nil, noop, fmt.Errorf("failed to create temp file: %w", err)
|
||||
}
|
||||
cleanup := func() {
|
||||
_ = os.Remove(f.Name())
|
||||
}
|
||||
return f, cleanup, nil
|
||||
}
|
||||
|
||||
// Cleanup removes the entire root directory and all contents. Safe to call multiple times.
|
||||
func (t *TempDir) Cleanup() error {
|
||||
var err error
|
||||
t.cleanupOnce.Do(func() {
|
||||
t.cleaned.Store(true)
|
||||
if t.root == "" {
|
||||
return
|
||||
}
|
||||
err = os.RemoveAll(t.root)
|
||||
})
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,295 @@
|
||||
package tmpdir
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestRootAndFromContext(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
assert.Nil(t, FromContext(ctx))
|
||||
|
||||
ctx, td := Root(ctx, "test")
|
||||
require.NotNil(t, FromContext(ctx))
|
||||
assert.Same(t, td, FromContext(ctx))
|
||||
}
|
||||
|
||||
func TestWithValue(t *testing.T) {
|
||||
_, td := Root(context.Background(), "test")
|
||||
defer td.Cleanup()
|
||||
|
||||
// inject the existing TempDir into a fresh context
|
||||
ctx := WithValue(context.Background(), td)
|
||||
assert.Same(t, td, FromContext(ctx))
|
||||
|
||||
// the injected TempDir is fully functional
|
||||
f, cleanup, err := FromContext(ctx).NewFile("with-value-*.txt")
|
||||
require.NoError(t, err)
|
||||
defer cleanup()
|
||||
require.NoError(t, f.Close())
|
||||
}
|
||||
|
||||
func TestNewChild(t *testing.T) {
|
||||
ctx, td := Root(context.Background(), "test")
|
||||
defer td.Cleanup()
|
||||
_ = ctx
|
||||
|
||||
child1, cleanup1, err := td.NewChild("sub")
|
||||
require.NoError(t, err)
|
||||
defer cleanup1()
|
||||
child2, cleanup2, err := td.NewChild("sub")
|
||||
require.NoError(t, err)
|
||||
defer cleanup2()
|
||||
|
||||
// children are distinct
|
||||
assert.NotEqual(t, child1, child2)
|
||||
|
||||
// both exist and are under the same root
|
||||
info1, err := os.Stat(child1)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, info1.IsDir())
|
||||
|
||||
info2, err := os.Stat(child2)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, info2.IsDir())
|
||||
|
||||
assert.Equal(t, filepath.Dir(child1), filepath.Dir(child2))
|
||||
}
|
||||
|
||||
func TestNewFile(t *testing.T) {
|
||||
_, td := Root(context.Background(), "test")
|
||||
defer td.Cleanup()
|
||||
|
||||
f, cleanup, err := td.NewFile("hello-*.txt")
|
||||
require.NoError(t, err)
|
||||
defer cleanup()
|
||||
|
||||
_, err = f.WriteString("hello")
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.Close())
|
||||
|
||||
content, err := os.ReadFile(f.Name())
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "hello", string(content))
|
||||
}
|
||||
|
||||
func TestCleanup(t *testing.T) {
|
||||
_, td := Root(context.Background(), "test")
|
||||
|
||||
child, _, err := td.NewChild("sub")
|
||||
require.NoError(t, err)
|
||||
|
||||
f, _, err := td.NewFile("file-*")
|
||||
require.NoError(t, err)
|
||||
fname := f.Name()
|
||||
f.Close()
|
||||
|
||||
// write a file inside the child dir too
|
||||
require.NoError(t, os.WriteFile(filepath.Join(child, "inner.txt"), []byte("x"), 0600))
|
||||
|
||||
// everything exists
|
||||
_, err = os.Stat(child)
|
||||
require.NoError(t, err)
|
||||
_, err = os.Stat(fname)
|
||||
require.NoError(t, err)
|
||||
|
||||
// cleanup
|
||||
require.NoError(t, td.Cleanup())
|
||||
|
||||
// everything is gone
|
||||
_, err = os.Stat(child)
|
||||
assert.True(t, os.IsNotExist(err))
|
||||
_, err = os.Stat(fname)
|
||||
assert.True(t, os.IsNotExist(err))
|
||||
|
||||
// double cleanup is safe
|
||||
require.NoError(t, td.Cleanup())
|
||||
}
|
||||
|
||||
func TestCleanupPreventsNewAllocation(t *testing.T) {
|
||||
_, td := Root(context.Background(), "test")
|
||||
require.NoError(t, td.Cleanup())
|
||||
|
||||
_, _, err := td.NewChild("nope")
|
||||
assert.Error(t, err)
|
||||
|
||||
_, _, err = td.NewFile("nope-*")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestEarlyCleanupFile(t *testing.T) {
|
||||
_, td := Root(context.Background(), "test")
|
||||
defer td.Cleanup()
|
||||
|
||||
f, cleanup, err := td.NewFile("early-*.txt")
|
||||
require.NoError(t, err)
|
||||
|
||||
fname := f.Name()
|
||||
require.NoError(t, f.Close())
|
||||
|
||||
// file exists before cleanup
|
||||
_, err = os.Stat(fname)
|
||||
require.NoError(t, err)
|
||||
|
||||
// early cleanup removes the file
|
||||
cleanup()
|
||||
_, err = os.Stat(fname)
|
||||
assert.True(t, os.IsNotExist(err))
|
||||
|
||||
// calling cleanup again is safe (idempotent)
|
||||
cleanup()
|
||||
}
|
||||
|
||||
func TestEarlyCleanupChild(t *testing.T) {
|
||||
_, td := Root(context.Background(), "test")
|
||||
defer td.Cleanup()
|
||||
|
||||
child, cleanup, err := td.NewChild("early")
|
||||
require.NoError(t, err)
|
||||
|
||||
// child dir exists
|
||||
_, err = os.Stat(child)
|
||||
require.NoError(t, err)
|
||||
|
||||
// early cleanup removes it
|
||||
cleanup()
|
||||
_, err = os.Stat(child)
|
||||
assert.True(t, os.IsNotExist(err))
|
||||
|
||||
// calling cleanup again is safe (idempotent)
|
||||
cleanup()
|
||||
}
|
||||
|
||||
func TestEarlyCleanupThenRootCleanup(t *testing.T) {
|
||||
_, td := Root(context.Background(), "test")
|
||||
|
||||
f, cleanupFile, err := td.NewFile("combo-*.txt")
|
||||
require.NoError(t, err)
|
||||
fname := f.Name()
|
||||
f.Close()
|
||||
|
||||
child, cleanupChild, err := td.NewChild("combo")
|
||||
require.NoError(t, err)
|
||||
|
||||
// early cleanup both
|
||||
cleanupFile()
|
||||
cleanupChild()
|
||||
|
||||
// files are already gone
|
||||
_, err = os.Stat(fname)
|
||||
assert.True(t, os.IsNotExist(err))
|
||||
_, err = os.Stat(child)
|
||||
assert.True(t, os.IsNotExist(err))
|
||||
|
||||
// root cleanup still works (no error on already-removed contents)
|
||||
require.NoError(t, td.Cleanup())
|
||||
}
|
||||
|
||||
func TestConcurrentNewChildAndNewFile(t *testing.T) {
|
||||
_, td := Root(context.Background(), "test")
|
||||
defer td.Cleanup()
|
||||
|
||||
const goroutines = 20
|
||||
errs := make(chan error, goroutines)
|
||||
paths := make(chan string, goroutines)
|
||||
|
||||
for i := 0; i < goroutines; i++ {
|
||||
go func(i int) {
|
||||
if i%2 == 0 {
|
||||
child, cleanup, err := td.NewChild("concurrent")
|
||||
if err != nil {
|
||||
errs <- err
|
||||
return
|
||||
}
|
||||
defer cleanup()
|
||||
paths <- child
|
||||
} else {
|
||||
f, cleanup, err := td.NewFile("concurrent-*.txt")
|
||||
if err != nil {
|
||||
errs <- err
|
||||
return
|
||||
}
|
||||
defer cleanup()
|
||||
_ = f.Close()
|
||||
paths <- f.Name()
|
||||
}
|
||||
errs <- nil
|
||||
}(i)
|
||||
}
|
||||
|
||||
seen := make(map[string]bool)
|
||||
for i := 0; i < goroutines; i++ {
|
||||
err := <-errs
|
||||
require.NoError(t, err)
|
||||
}
|
||||
close(paths)
|
||||
for p := range paths {
|
||||
assert.False(t, seen[p], "duplicate path: %s", p)
|
||||
seen[p] = true
|
||||
}
|
||||
assert.Len(t, seen, goroutines)
|
||||
}
|
||||
|
||||
func TestConcurrentNewChildDuringCleanup(t *testing.T) {
|
||||
_, td := Root(context.Background(), "test")
|
||||
|
||||
// trigger root creation
|
||||
_, cleanup, err := td.NewChild("init")
|
||||
require.NoError(t, err)
|
||||
cleanup()
|
||||
|
||||
// cleanup and concurrent NewChild should not panic
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
_ = td.Cleanup()
|
||||
close(done)
|
||||
}()
|
||||
// try creating children concurrently with cleanup — should get errors, not panics
|
||||
for i := 0; i < 10; i++ {
|
||||
_, c, _ := td.NewChild("race")
|
||||
if c != nil {
|
||||
c()
|
||||
}
|
||||
}
|
||||
<-done
|
||||
}
|
||||
|
||||
func TestLazyCreation(t *testing.T) {
|
||||
_, td := Root(context.Background(), "test")
|
||||
|
||||
// root dir is not created until needed
|
||||
assert.Equal(t, "", td.root)
|
||||
|
||||
_, _, err := td.NewFile("trigger-*")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.NotEmpty(t, td.root)
|
||||
|
||||
require.NoError(t, td.Cleanup())
|
||||
}
|
||||
|
||||
func TestFromPath(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
td := FromPath(dir)
|
||||
|
||||
// can create children
|
||||
child, cleanup, err := td.NewChild("sub")
|
||||
require.NoError(t, err)
|
||||
defer cleanup()
|
||||
assert.DirExists(t, child)
|
||||
|
||||
// can create files
|
||||
f, cleanupFile, err := td.NewFile("file-*.txt")
|
||||
require.NoError(t, err)
|
||||
defer cleanupFile()
|
||||
require.NoError(t, f.Close())
|
||||
assert.FileExists(t, f.Name())
|
||||
|
||||
// root is the provided dir
|
||||
assert.Equal(t, dir, filepath.Dir(child))
|
||||
}
|
||||
+23
-1
@@ -12,8 +12,10 @@ import (
|
||||
"github.com/anchore/go-sync"
|
||||
"github.com/anchore/syft/internal/bus"
|
||||
"github.com/anchore/syft/internal/licenses"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/sbomsync"
|
||||
"github.com/anchore/syft/internal/task"
|
||||
"github.com/anchore/syft/internal/tmpdir"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/cataloging"
|
||||
"github.com/anchore/syft/syft/event/monitor"
|
||||
@@ -24,6 +26,8 @@ import (
|
||||
|
||||
// CreateSBOM creates a software bill-of-materials from the given source. If the CreateSBOMConfig is nil, then
|
||||
// default options will be used.
|
||||
//
|
||||
//nolint:funlen
|
||||
func CreateSBOM(ctx context.Context, src source.Source, cfg *CreateSBOMConfig) (*sbom.SBOM, error) {
|
||||
if cfg == nil {
|
||||
cfg = DefaultCreateSBOMConfig()
|
||||
@@ -65,12 +69,25 @@ func CreateSBOM(ctx context.Context, src source.Source, cfg *CreateSBOMConfig) (
|
||||
},
|
||||
}
|
||||
|
||||
// setup everything we need in context: license scanner, executors, etc.
|
||||
// check if the caller already provided a TempDir; if so, don't clean it up (the caller owns it)
|
||||
callerOwnsTempDir := tmpdir.FromContext(ctx) != nil
|
||||
|
||||
// setup everything we need in context: license scanner, executors, temp storage, etc.
|
||||
ctx, err = setupContext(ctx, cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !callerOwnsTempDir {
|
||||
if td := tmpdir.FromContext(ctx); td != nil {
|
||||
defer func() {
|
||||
if err := td.Cleanup(); err != nil {
|
||||
log.Warnf("failed to clean up temp dir: %v", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
catalogingProgress := monitorCatalogingTask(src.ID(), taskGroups)
|
||||
packageCatalogingProgress := monitorPackageCatalogingTask()
|
||||
|
||||
@@ -95,6 +112,11 @@ func setupContext(ctx context.Context, cfg *CreateSBOMConfig) (context.Context,
|
||||
// configure parallel executors
|
||||
ctx = setContextExecutors(ctx, cfg)
|
||||
|
||||
// configure temp dir factory for catalogers (if not already set)
|
||||
if tmpdir.FromContext(ctx) == nil {
|
||||
ctx, _ = tmpdir.Root(ctx, "syft-cataloger")
|
||||
}
|
||||
|
||||
// configure license scanner
|
||||
// skip injecting a license scanner if one already set on context
|
||||
if licenses.IsContextLicenseScannerSet(ctx) {
|
||||
|
||||
+2
-2
@@ -22,7 +22,7 @@ tools-check:
|
||||
# docker buildx build --platform linux/amd64 -t $(TOOL_IMAGE) .
|
||||
|
||||
tools:
|
||||
@(docker inspect $(TOOL_IMAGE) > /dev/null && make tools-check) || (docker build -t $(TOOL_IMAGE) . && sha256sum Dockerfile > Dockerfile.sha256)
|
||||
@(docker image inspect $(TOOL_IMAGE) > /dev/null 2>&1 && make tools-check) || (docker build -t $(TOOL_IMAGE) . && sha256sum Dockerfile > Dockerfile.sha256)
|
||||
|
||||
build: tools
|
||||
mkdir -p $(BIN)
|
||||
@@ -31,7 +31,7 @@ build: tools
|
||||
verify: tools
|
||||
@rm -f $(VERIFY_FILE)
|
||||
docker run -i -v $(shell pwd):/mount -w /mount/project $(TOOL_IMAGE) make verify > $(VERIFY_FILE)
|
||||
@python ./differ expected_verify $(VERIFY_FILE)
|
||||
@python3 ./differ expected_verify $(VERIFY_FILE)
|
||||
|
||||
debug:
|
||||
docker run -i --rm -v $(shell pwd):/mount -w /mount/project $(TOOL_IMAGE) bash
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
ARG OSXCROSS_VERSION=13.1
|
||||
ARG BUILDPLATFORM=linux/amd64
|
||||
FROM --platform=$BUILDPLATFORM crazymax/osxcross:${OSXCROSS_VERSION}-ubuntu AS osxcross
|
||||
FROM --platform=linux/amd64 crazymax/osxcross:${OSXCROSS_VERSION}-ubuntu AS osxcross
|
||||
|
||||
FROM --platform=$BUILDPLATFORM ubuntu:22.04
|
||||
FROM --platform=linux/amd64 ubuntu:22.04
|
||||
|
||||
RUN apt update -y && apt install -y \
|
||||
curl wget \
|
||||
|
||||
@@ -19,7 +19,7 @@ tools-check:
|
||||
@sha256sum -c Dockerfile.sha256 || (echo "Tools Dockerfile has changed" && exit 1)
|
||||
|
||||
tools:
|
||||
@(docker inspect $(TOOL_IMAGE) > /dev/null && make tools-check) || (docker build -t $(TOOL_IMAGE) . && sha256sum Dockerfile > Dockerfile.sha256)
|
||||
@(docker image inspect $(TOOL_IMAGE) > /dev/null 2>&1 && make tools-check) || (docker build --platform linux/amd64 -t $(TOOL_IMAGE) . && sha256sum Dockerfile > Dockerfile.sha256)
|
||||
|
||||
build: tools
|
||||
@mkdir -p $(BIN)
|
||||
|
||||
@@ -20,7 +20,7 @@ func SeekableReader(reader io.Reader) (io.ReadSeeker, error) {
|
||||
return getOffsetReadSeeker(r)
|
||||
}
|
||||
|
||||
content, err := io.ReadAll(reader)
|
||||
content, err := io.ReadAll(reader) //nolint:gocritic // buffering non-seekable to seekable reader
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ func GetUnionReader(readerCloser io.ReadCloser) (UnionReader, error) {
|
||||
return newReaderAtAdapter(r), nil
|
||||
}
|
||||
|
||||
b, err := io.ReadAll(readerCloser)
|
||||
b, err := io.ReadAll(readerCloser) //nolint:gocritic // buffering non-seekable to ReaderAt
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to read contents from binary: %w", err)
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ func tryParseReleaseInfo(resolver file.Resolver, location file.Location, logger
|
||||
}
|
||||
defer internal.CloseAndLogError(contentReader, location.AccessPath)
|
||||
|
||||
content, err := io.ReadAll(contentReader)
|
||||
content, err := io.ReadAll(io.LimitReader(contentReader, 5*1024*1024))
|
||||
if err != nil {
|
||||
logger.WithFields("error", err, "path", location.RealPath).Trace("unable to read contents")
|
||||
return nil
|
||||
|
||||
@@ -38,7 +38,7 @@ func readDebianVersionFile(resolver file.Resolver, location file.Location) strin
|
||||
return ""
|
||||
}
|
||||
defer internal.CloseAndLogError(rdr, location.RealPath)
|
||||
contents, err := io.ReadAll(rdr)
|
||||
contents, err := io.ReadAll(io.LimitReader(rdr, 5*1024*1024))
|
||||
if err != nil {
|
||||
log.Debugf("error reading %s: %v", location.RealPath, err)
|
||||
return ""
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
@@ -15,6 +14,7 @@ import (
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/tmpdir"
|
||||
"github.com/anchore/syft/internal/unknown"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
@@ -24,17 +24,19 @@ import (
|
||||
|
||||
// parseGGUFModel parses a GGUF model file and returns the discovered package.
|
||||
// This implementation only reads the header portion of the file, not the entire model.
|
||||
func parseGGUFModel(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
func parseGGUFModel(ctx context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
defer internal.CloseAndLogError(reader, reader.Path())
|
||||
|
||||
// Create a temporary file for the library to parse
|
||||
// The library requires a file path, so we create a temp file
|
||||
tempFile, err := os.CreateTemp("", "syft-gguf-*.gguf")
|
||||
td := tmpdir.FromContext(ctx)
|
||||
if td == nil {
|
||||
return nil, nil, fmt.Errorf("no temp dir factory in context")
|
||||
}
|
||||
tempFile, cleanup, err := td.NewFile("syft-gguf-*.gguf")
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to create temp file: %w", err)
|
||||
}
|
||||
defer cleanup()
|
||||
tempPath := tempFile.Name()
|
||||
defer os.Remove(tempPath)
|
||||
|
||||
// Copy and validate the GGUF file header using LimitedReader to prevent OOM
|
||||
// We use LimitedReader to cap reads at maxHeaderSize (50MB)
|
||||
|
||||
@@ -171,7 +171,7 @@ func findReleases(resolver file.Resolver, dbPath string) []linux.Release {
|
||||
func parseReleasesFromAPKRepository(reader file.LocationReadCloser) []linux.Release {
|
||||
var releases []linux.Release
|
||||
|
||||
reposB, err := io.ReadAll(reader)
|
||||
reposB, err := io.ReadAll(reader) //nolint:gocritic // regex matching requires full buffer
|
||||
if err != nil {
|
||||
log.Tracef("unable to read APK repositories file %q: %+v", reader.RealPath, err)
|
||||
return nil
|
||||
|
||||
@@ -61,6 +61,28 @@ func Test_Cataloger_PositiveCases(t *testing.T) {
|
||||
Metadata: metadata("arangodb-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "arangodb/3.12.5/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
Name: "arangodb",
|
||||
Version: "3.12.5",
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/arangodb@3.12.5",
|
||||
Locations: locations("arangosh"),
|
||||
Metadata: metadata("arangodb-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "arangodb/3.12.5-2/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
Name: "arangodb",
|
||||
Version: "3.12.5-2",
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/arangodb@3.12.5-2",
|
||||
Locations: locations("arangosh"),
|
||||
Metadata: metadata("arangodb-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "postgres/15beta4/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
@@ -727,6 +749,16 @@ func Test_Cataloger_PositiveCases(t *testing.T) {
|
||||
{
|
||||
// TODO: find original binary...
|
||||
// note: cannot find the original binary, using a custom snippet based on the original snippet in the repo
|
||||
logicalFixture: "go-version-hint/1.15-dev/any",
|
||||
expected: pkg.Package{
|
||||
Name: "go",
|
||||
Version: "1.15",
|
||||
PURL: "pkg:generic/go@1.15",
|
||||
Locations: locations("bin/go", "VERSION"),
|
||||
Metadata: metadata("go-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "go-version-hint/1.15/any",
|
||||
expected: pkg.Package{
|
||||
Name: "go",
|
||||
|
||||
@@ -76,10 +76,10 @@ func DefaultClassifiers() []binutils.Classifier {
|
||||
`(?m)go(?P<version>[0-9]+\.[0-9]+(\.[0-9]+|beta[0-9]+|alpha[0-9]+|rc[0-9]+)?)\x00`),
|
||||
binutils.SupportingEvidenceMatcher("VERSION*",
|
||||
m.FileContentsVersionMatcher(
|
||||
`(?m)go(?P<version>[0-9]+\.[0-9]+(\.[0-9]+|beta[0-9]+|alpha[0-9]+|rc[0-9]+|-[_0-9a-z]+)?)\s`)),
|
||||
`(?m)go(?P<version>[0-9]+\.[0-9]+(\.[0-9]+|beta[0-9]+|alpha[0-9]+|rc[0-9]+|-[_0-9a-z]+)?)`)),
|
||||
binutils.SupportingEvidenceMatcher("../VERSION*",
|
||||
m.FileContentsVersionMatcher(
|
||||
`(?m)go(?P<version>[0-9]+\.[0-9]+(\.[0-9]+|beta[0-9]+|alpha[0-9]+|rc[0-9]+|-[_0-9a-z]+)?)\s`)),
|
||||
`(?m)go(?P<version>[0-9]+\.[0-9]+(\.[0-9]+|beta[0-9]+|alpha[0-9]+|rc[0-9]+|-[_0-9a-z]+)?)`)),
|
||||
),
|
||||
Package: "go",
|
||||
PURL: mustPURL("pkg:generic/go@version"),
|
||||
@@ -232,7 +232,7 @@ func DefaultClassifiers() []binutils.Classifier {
|
||||
Class: "arangodb-binary",
|
||||
FileGlob: "**/arangosh",
|
||||
EvidenceMatcher: m.FileContentsVersionMatcher(
|
||||
`(?m)\x00*(?P<version>[0-9]+\.[0-9]+\.[0-9]+(-[0-9]+)?)\s\[linux\]`),
|
||||
`(?m)\x00*(?P<version>[0-9]+\.[0-9]+\.[0-9]+(-[0-9]+)?)\s(enterprise\s)?\[linux\]`),
|
||||
Package: "arangodb",
|
||||
PURL: mustPURL("pkg:generic/arangodb@version"),
|
||||
CPEs: singleCPE("cpe:2.3:a:arangodb:arangodb:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
|
||||
|
||||
@@ -127,23 +127,83 @@ func pullDockerImage(imageReference, platform string) error {
|
||||
}
|
||||
|
||||
func checkArchitecturesMatch(imageReference, platform string) (bool, string, error) {
|
||||
// first check if the image exists locally
|
||||
cmd := exec.Command("docker", "image", "inspect", imageReference)
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err := cmd.Run(); err != nil {
|
||||
return false, "", err
|
||||
}
|
||||
|
||||
// prefer the manifest list for platform info — with Docker's containerd image store,
|
||||
// platform metadata lives on the manifest list entry, not in the image config.
|
||||
if found, err := platformInManifest(imageReference, platform); err == nil {
|
||||
return found, platform, nil
|
||||
}
|
||||
|
||||
// fall back to image config for older Docker daemons that don't support "docker manifest inspect"
|
||||
gotPlatform, err := platformFromImageInspect(imageReference)
|
||||
if err != nil {
|
||||
return false, "", err
|
||||
}
|
||||
|
||||
return gotPlatform == platform, gotPlatform, nil
|
||||
}
|
||||
|
||||
func platformFromImageInspect(imageReference string) (string, error) {
|
||||
cmd := exec.Command("docker", "image", "inspect", imageReference)
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var inspect []imageInspect
|
||||
if err := json.Unmarshal(out, &inspect); err != nil {
|
||||
return false, "", fmt.Errorf("unable to unmarshal image inspect: %w", err)
|
||||
return "", fmt.Errorf("unable to unmarshal image inspect: %w", err)
|
||||
}
|
||||
|
||||
if len(inspect) != 1 {
|
||||
return false, "", fmt.Errorf("expected 1 image inspect, got %d", len(inspect))
|
||||
return "", fmt.Errorf("expected 1 image inspect, got %d", len(inspect))
|
||||
}
|
||||
gotPlatform := inspect[0].Platform()
|
||||
|
||||
return gotPlatform == platform, gotPlatform, nil
|
||||
return inspect[0].Platform(), nil
|
||||
}
|
||||
|
||||
type manifestList struct {
|
||||
Manifests []manifestEntry `json:"manifests"`
|
||||
}
|
||||
|
||||
type manifestEntry struct {
|
||||
Platform manifestPlatform `json:"platform"`
|
||||
}
|
||||
|
||||
type manifestPlatform struct {
|
||||
Architecture string `json:"architecture"`
|
||||
OS string `json:"os"`
|
||||
}
|
||||
|
||||
// platformInManifest checks whether the wanted platform is available in the image's manifest list.
|
||||
// With Docker's containerd image store, all images are distributed via manifest lists (indexes),
|
||||
// and platform metadata may only be on the index entry — not in the image config that
|
||||
// "docker image inspect" reads. "docker manifest inspect" reads the index directly.
|
||||
func platformInManifest(imageReference, wantPlatform string) (bool, error) {
|
||||
cmd := exec.Command("docker", "manifest", "inspect", imageReference)
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("manifest inspect failed: %w", err)
|
||||
}
|
||||
|
||||
var ml manifestList
|
||||
if err := json.Unmarshal(out, &ml); err != nil {
|
||||
return false, fmt.Errorf("unable to unmarshal manifest: %w", err)
|
||||
}
|
||||
|
||||
for _, m := range ml.Manifests {
|
||||
p := fmt.Sprintf("%s/%s", m.Platform.OS, m.Platform.Architecture)
|
||||
if p == wantPlatform {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func copyBinariesFromDockerImages(config config.BinaryFromImage, destination string) (err error) {
|
||||
|
||||
+1
-1
@@ -151,4 +151,4 @@ while $CONTINUE_LOOP; do
|
||||
|
||||
done
|
||||
|
||||
go run ./manager write-snippet "$BINARY_FILE" --offset "$OFFSET" --length "$LENGTH" --name "$GROUP_NAME" --version "$VERSION"
|
||||
go run ../internal/manager write-snippet "$BINARY_FILE" --offset "$OFFSET" --length "$LENGTH" --name "$GROUP_NAME" --version "$VERSION"
|
||||
|
||||
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
+1
@@ -0,0 +1 @@
|
||||
go1.15 Fri 2003
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
no version in this binary
|
||||
+1
-1
@@ -1 +1 @@
|
||||
go1.15 Fri 2003
|
||||
go1.15
|
||||
+14
@@ -458,6 +458,20 @@ from-images:
|
||||
platform: linux/amd64
|
||||
paths:
|
||||
- /usr/bin/arangosh
|
||||
- name: arangodb
|
||||
version: 3.12.5
|
||||
images:
|
||||
- ref: arangodb:3.12.5@sha256:1f9278fe17b200cf3aea2c7bd7fd571221b5b41a49b835a397c47eb970c952d6
|
||||
platform: linux/amd64
|
||||
paths:
|
||||
- /usr/bin/arangosh
|
||||
- name: arangodb
|
||||
version: 3.12.5-2
|
||||
images:
|
||||
- ref: arangodb:3.12.5.2@sha256:5b0d1d2911ea864ea61d7e2357789004fe912606f5980cf481739601d7cb17a1
|
||||
platform: linux/amd64
|
||||
paths:
|
||||
- /usr/bin/arangosh
|
||||
- version: 15.1
|
||||
images:
|
||||
- ref: postgres:15.1@sha256:b4140dd3a62f364f16a82c1bd88d28b9887ecb47f07dbe2941237d073574d428
|
||||
|
||||
@@ -47,7 +47,7 @@ func node(value interface{}) erlangNode {
|
||||
|
||||
// parseErlang basic parser for erlang, used by rebar.lock
|
||||
func parseErlang(reader io.Reader) (erlangNode, error) {
|
||||
data, err := io.ReadAll(reader)
|
||||
data, err := io.ReadAll(reader) //nolint:gocritic // custom parser requires []byte
|
||||
if err != nil {
|
||||
return node(nil), err
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package githubactions
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"go.yaml.in/yaml/v3"
|
||||
|
||||
@@ -25,13 +24,9 @@ type compositeActionRunsDef struct {
|
||||
}
|
||||
|
||||
func parseCompositeActionForActionUsage(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
contents, errs := io.ReadAll(reader)
|
||||
if errs != nil {
|
||||
return nil, nil, fmt.Errorf("unable to read yaml composite action file: %w", errs)
|
||||
}
|
||||
|
||||
var ca compositeActionDef
|
||||
if errs = yaml.Unmarshal(contents, &ca); errs != nil {
|
||||
var errs error
|
||||
if errs = yaml.NewDecoder(reader).Decode(&ca); errs != nil {
|
||||
return nil, nil, fmt.Errorf("unable to parse yaml composite action file: %w", errs)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ package githubactions
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
|
||||
"go.yaml.in/yaml/v3"
|
||||
@@ -41,14 +40,10 @@ type stepDef struct {
|
||||
}
|
||||
|
||||
func parseWorkflowForWorkflowUsage(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
contents, errs := io.ReadAll(reader)
|
||||
if errs != nil {
|
||||
return nil, nil, fmt.Errorf("unable to read yaml workflow file: %w", errs)
|
||||
}
|
||||
|
||||
// parse the yaml file into a generic node to preserve comments
|
||||
var node yaml.Node
|
||||
if errs = yaml.Unmarshal(contents, &node); errs != nil {
|
||||
var errs error
|
||||
if errs = yaml.NewDecoder(reader).Decode(&node); errs != nil {
|
||||
return nil, nil, fmt.Errorf("unable to parse yaml workflow file: %w", errs)
|
||||
}
|
||||
|
||||
@@ -79,14 +74,10 @@ func parseWorkflowForWorkflowUsage(_ context.Context, _ file.Resolver, _ *generi
|
||||
}
|
||||
|
||||
func parseWorkflowForActionUsage(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
contents, errs := io.ReadAll(reader)
|
||||
if errs != nil {
|
||||
return nil, nil, fmt.Errorf("unable to read yaml workflow file: %w", errs)
|
||||
}
|
||||
|
||||
// parse the yaml file into a generic node to preserve comments
|
||||
var node yaml.Node
|
||||
if errs = yaml.Unmarshal(contents, &node); errs != nil {
|
||||
var errs error
|
||||
if errs = yaml.NewDecoder(reader).Decode(&node); errs != nil {
|
||||
return nil, nil, fmt.Errorf("unable to parse yaml workflow file: %w", errs)
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/internal/cache"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/tmpdir"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/licenses"
|
||||
@@ -163,7 +164,10 @@ func (c *goLicenseResolver) getLicensesFromRemote(ctx context.Context, moduleNam
|
||||
return c.licenseCache.Resolve(fmt.Sprintf("%s/%s", moduleName, moduleVersion), func() ([]pkg.License, error) {
|
||||
proxies := remotesForModule(c.opts.Proxies, c.opts.NoProxy, moduleName)
|
||||
|
||||
urlPrefix, fsys, err := getModule(proxies, moduleName, moduleVersion)
|
||||
urlPrefix, fsys, cleanup, err := getModule(ctx, proxies, moduleName, moduleVersion)
|
||||
if cleanup != nil {
|
||||
defer cleanup()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -258,7 +262,7 @@ func processCaps(s string) string {
|
||||
})
|
||||
}
|
||||
|
||||
func getModule(proxies []string, moduleName, moduleVersion string) (urlPrefix string, fsys fs.FS, err error) {
|
||||
func getModule(ctx context.Context, proxies []string, moduleName, moduleVersion string) (urlPrefix string, fsys fs.FS, cleanup func(), err error) {
|
||||
for _, proxy := range proxies {
|
||||
u, _ := url.Parse(proxy)
|
||||
if proxy == "direct" {
|
||||
@@ -267,7 +271,7 @@ func getModule(proxies []string, moduleName, moduleVersion string) (urlPrefix st
|
||||
}
|
||||
switch u.Scheme {
|
||||
case "https", "http":
|
||||
urlPrefix, fsys, err = getModuleProxy(proxy, moduleName, moduleVersion)
|
||||
urlPrefix, fsys, cleanup, err = getModuleProxy(ctx, proxy, moduleName, moduleVersion)
|
||||
case "file":
|
||||
p := filepath.Join(u.Path, moduleName, "@v", moduleVersion)
|
||||
urlPrefix = path.Join("file://", p) + "/"
|
||||
@@ -281,42 +285,78 @@ func getModule(proxies []string, moduleName, moduleVersion string) (urlPrefix st
|
||||
return
|
||||
}
|
||||
|
||||
func getModuleProxy(proxy string, moduleName string, moduleVersion string) (moduleURL string, out fs.FS, _ error) {
|
||||
// getModuleProxy downloads a Go module zip from the given proxy and returns a filesystem view of its contents.
|
||||
// The returned cleanup function closes the underlying temp file and removes it; callers must not use the
|
||||
// returned fs.FS after calling cleanup.
|
||||
func getModuleProxy(ctx context.Context, proxy string, moduleName string, moduleVersion string) (moduleURL string, out fs.FS, cleanup func(), _ error) {
|
||||
u := fmt.Sprintf("%s/%s/@v/%s.zip", proxy, moduleName, moduleVersion)
|
||||
|
||||
// get the module zip
|
||||
log.WithFields("url", u).Info("downloading go module from proxy")
|
||||
resp, err := http.Get(u) //nolint:gosec
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
return "", nil, nil, err
|
||||
}
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
// close the first response before retrying with a lowercased module name
|
||||
_ = resp.Body.Close()
|
||||
|
||||
u = fmt.Sprintf("%s/%s/@v/%s.zip", proxy, strings.ToLower(moduleName), moduleVersion)
|
||||
|
||||
// try lowercasing it; some packages have mixed casing that really messes up the proxy
|
||||
resp, err = http.Get(u) //nolint:gosec
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
return "", nil, nil, err
|
||||
}
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", nil, fmt.Errorf("failed to get module zip: %s", resp.Status)
|
||||
return "", nil, nil, fmt.Errorf("failed to get module zip: %s", resp.Status)
|
||||
}
|
||||
}
|
||||
|
||||
// read the zip
|
||||
b, err := io.ReadAll(resp.Body)
|
||||
// stream the zip to a temp file to avoid unbounded memory usage
|
||||
td := tmpdir.FromContext(ctx)
|
||||
if td == nil {
|
||||
return "", nil, nil, fmt.Errorf("no temp dir factory in context")
|
||||
}
|
||||
tmpFile, cleanupFile, err := td.NewFile("gomodule-*.zip") //nolint:gocritic // cleanup is returned to caller, not deferred here
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
return "", nil, nil, fmt.Errorf("failed to create temp file for module zip: %w", err)
|
||||
}
|
||||
|
||||
out, err = zip.NewReader(bytes.NewReader(b), resp.ContentLength)
|
||||
cleanup = func() {
|
||||
_ = tmpFile.Close()
|
||||
cleanupFile()
|
||||
}
|
||||
|
||||
// cap downloads at 500MB to prevent disk exhaustion from malicious proxies
|
||||
const maxModuleZipSize = 500 * 1024 * 1024
|
||||
size, err := io.Copy(tmpFile, io.LimitReader(resp.Body, maxModuleZipSize))
|
||||
if err != nil {
|
||||
cleanup()
|
||||
return "", nil, nil, fmt.Errorf("failed to download module zip: %w", err)
|
||||
}
|
||||
if size >= maxModuleZipSize {
|
||||
cleanup()
|
||||
return "", nil, nil, fmt.Errorf("module zip exceeds %d byte size limit", maxModuleZipSize)
|
||||
}
|
||||
|
||||
if _, err := tmpFile.Seek(0, io.SeekStart); err != nil {
|
||||
cleanup()
|
||||
return "", nil, nil, fmt.Errorf("failed to seek module zip: %w", err)
|
||||
}
|
||||
|
||||
out, err = zip.NewReader(tmpFile, size)
|
||||
if err != nil {
|
||||
cleanup()
|
||||
return "", nil, nil, fmt.Errorf("failed to read module zip: %w", err)
|
||||
}
|
||||
versionPath := findVersionPath(out, ".")
|
||||
out = getSubFS(out, versionPath)
|
||||
|
||||
return u + "#" + versionPath + "/", out, err
|
||||
return u + "#" + versionPath + "/", out, cleanup, err
|
||||
}
|
||||
|
||||
func findVersionPath(f fs.FS, dir string) string {
|
||||
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
)
|
||||
|
||||
func Test_LicenseSearch(t *testing.T) {
|
||||
ctx := pkgtest.Context()
|
||||
ctx := pkgtest.Context(t)
|
||||
|
||||
loc1 := file.NewLocation("github.com/someorg/somename@v0.3.2/LICENSE")
|
||||
loc2 := file.NewLocation("github.com/!cap!o!r!g/!cap!project@v4.111.5/LICENSE.txt")
|
||||
@@ -321,7 +321,7 @@ func Test_noLocalGoModDir(t *testing.T) {
|
||||
|
||||
validTmp := t.TempDir()
|
||||
require.NoError(t, os.MkdirAll(filepath.Join(validTmp, "mod@ver"), 0700|os.ModeDir))
|
||||
ctx := pkgtest.Context()
|
||||
ctx := pkgtest.Context(t)
|
||||
tests := []struct {
|
||||
name string
|
||||
dir string
|
||||
|
||||
@@ -324,7 +324,7 @@ func buildModuleRelationships(
|
||||
}
|
||||
|
||||
func (c *goModCataloger) parseModFileContents(reader file.LocationReadCloser) (*modfile.File, error) {
|
||||
contents, err := io.ReadAll(reader)
|
||||
contents, err := io.ReadAll(reader) //nolint:gocritic // modfile.Parse requires []byte
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read go module: %w", err)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package haskell
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"go.yaml.in/yaml/v3"
|
||||
@@ -42,16 +41,11 @@ type completedSnapshot struct {
|
||||
|
||||
// parseStackLock is a parser function for stack.yaml.lock contents, returning all packages discovered.
|
||||
func parseStackLock(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
bytes, err := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to load stack.yaml.lock file: %w", err)
|
||||
}
|
||||
|
||||
var lockFile stackLock
|
||||
|
||||
if err := yaml.Unmarshal(bytes, &lockFile); err != nil {
|
||||
if err := yaml.NewDecoder(reader).Decode(&lockFile); err != nil {
|
||||
log.WithFields("error", err, "path", reader.RealPath).Trace("failed to parse stack.yaml.lock")
|
||||
return nil, nil, fmt.Errorf("failed to parse stack.yaml.lock file")
|
||||
return nil, nil, fmt.Errorf("failed to parse stack.yaml.lock file: %w", err)
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
@@ -3,7 +3,6 @@ package haskell
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"go.yaml.in/yaml/v3"
|
||||
|
||||
@@ -23,16 +22,11 @@ type stackYaml struct {
|
||||
|
||||
// parseStackYaml is a parser function for stack.yaml contents, returning all packages discovered.
|
||||
func parseStackYaml(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
bytes, err := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to load stack.yaml file: %w", err)
|
||||
}
|
||||
|
||||
var stackFile stackYaml
|
||||
|
||||
if err := yaml.Unmarshal(bytes, &stackFile); err != nil {
|
||||
if err := yaml.NewDecoder(reader).Decode(&stackFile); err != nil {
|
||||
log.WithFields("error", err, "path", reader.RealPath).Trace("failed to parse stack.yaml")
|
||||
return nil, nil, fmt.Errorf("failed to parse stack.yaml file")
|
||||
return nil, nil, fmt.Errorf("failed to parse stack.yaml file: %w", err)
|
||||
}
|
||||
|
||||
var pkgs []pkg.Package
|
||||
|
||||
@@ -52,6 +52,12 @@
|
||||
"github.com/ecnepsnai/web": [
|
||||
"cpe:2.3:a:web_project:web:*:*:*:*:*:go:*:*"
|
||||
],
|
||||
"github.com/free5gc/amf": [
|
||||
"cpe:2.3:a:free5gc:amf:*:*:*:*:*:go:*:*"
|
||||
],
|
||||
"github.com/free5gc/go-upf": [
|
||||
"cpe:2.3:a:free5gc:go-upf:*:*:*:*:*:go:*:*"
|
||||
],
|
||||
"github.com/free5gc/smf": [
|
||||
"cpe:2.3:a:free5gc:smf:*:*:*:*:*:go:*:*"
|
||||
],
|
||||
@@ -136,6 +142,9 @@
|
||||
"github.com/quic-go/webtransport-go#section-readme": [
|
||||
"cpe:2.3:a:quic-go:webtransport-go:*:*:*:*:*:go:*:*"
|
||||
],
|
||||
"github.com/rezmoss/axios4go": [
|
||||
"cpe:2.3:a:rezmoss:axios4go:*:*:*:*:*:go:*:*"
|
||||
],
|
||||
"github.com/sap/cloud-security-client-go": [
|
||||
"cpe:2.3:a:sap:cloud-security-client-go:*:*:*:*:*:go:*:*"
|
||||
],
|
||||
@@ -2311,6 +2320,9 @@
|
||||
"@ckeditor/ckeditor5-widget": [
|
||||
"cpe:2.3:a:ckeditor:ckeditor5-widget:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"@coding-solo/godot-mcp": [
|
||||
"cpe:2.3:a:coding-solo:godot_mcp:*:*:*:*:*:*:*:*"
|
||||
],
|
||||
"@cookiex/deep": [
|
||||
"cpe:2.3:a:cookiex-deep_project:cookiex-deep:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
@@ -2350,6 +2362,9 @@
|
||||
"@google-cloud/firestore": [
|
||||
"cpe:2.3:a:google:cloud_firestore:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"@graphprotocol/contracts": [
|
||||
"cpe:2.3:a:thegraph:graph_protocol_contracts:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"@grpc/grpc-js": [
|
||||
"cpe:2.3:a:grpc:grpc:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
@@ -2471,6 +2486,12 @@
|
||||
"@thi.ng/egf": [
|
||||
"cpe:2.3:a:\\@thi.ng\\/egf_project:\\@thi.ng\\/egf:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"@tinacms/cli": [
|
||||
"cpe:2.3:a:ssw:tinacms\\/cli:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"@tinacms/graphql": [
|
||||
"cpe:2.3:a:ssw:tinacms\\/graphql:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"@tiptap/extension-link": [
|
||||
"cpe:2.3:a:tiptap:tiptap\\/extension-link:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
@@ -3070,6 +3091,9 @@
|
||||
"defaults-deep": [
|
||||
"cpe:2.3:a:defaults-deep_project:defaults-deep:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"defuddle": [
|
||||
"cpe:2.3:a:kepano:defuddle:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"desafio": [
|
||||
"cpe:2.3:a:desafio_project:desafio:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
@@ -3273,6 +3297,9 @@
|
||||
"express-openid-connect": [
|
||||
"cpe:2.3:a:auth0:express_openid_connect:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"express-rate-limit": [
|
||||
"cpe:2.3:a:express-rate-limit:express-rate-limit:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"express-restify-mongoose": [
|
||||
"cpe:2.3:a:express-restify-mongoose_project:express-restify-mongoose:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
@@ -3319,7 +3346,6 @@
|
||||
"cpe:2.3:a:fast_string_search_project:fast_string_search:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"fast-xml-parser": [
|
||||
"cpe:2.3:a:fast-xml-parser_project:fast-xml-parser:*:*:*:*:*:node.js:*:*",
|
||||
"cpe:2.3:a:naturalintelligence:fast-xml-parser:*:*:*:*:*:*:*:*"
|
||||
],
|
||||
"fastest-json-copy": [
|
||||
@@ -3356,7 +3382,7 @@
|
||||
"cpe:2.3:a:fibjs_project:fibjs:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"file-type": [
|
||||
"cpe:2.3:a:file-type_project:file-type:*:*:*:*:*:node.js:*:*"
|
||||
"cpe:2.3:a:sindresorhus:file-type:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"file-upload-with-preview": [
|
||||
"cpe:2.3:a:johndatserakis:file-upload-with-preview:*:*:*:*:*:node.js:*:*"
|
||||
@@ -5402,6 +5428,9 @@
|
||||
"stimulsoft-dashboards-js": [
|
||||
"cpe:2.3:a:stimulsoft:dashboard.js:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"storybook": [
|
||||
"cpe:2.3:a:storybook:storybook:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"strapi": [
|
||||
"cpe:2.3:a:strapi:strapi:*:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:strapi:strapi:*:*:*:*:*:node.js:*:*"
|
||||
@@ -5468,6 +5497,9 @@
|
||||
"terminal-kit": [
|
||||
"cpe:2.3:a:terminal-kit_project:terminal-kit:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"terriajs-server": [
|
||||
"cpe:2.3:a:terria:terriajs-server:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"terser": [
|
||||
"cpe:2.3:a:terser:terser:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
@@ -5483,6 +5515,9 @@
|
||||
"timespan": [
|
||||
"cpe:2.3:a:timespan_project:timespan:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"tinacms": [
|
||||
"cpe:2.3:a:ssw:tinacms:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"tiny-conf": [
|
||||
"cpe:2.3:a:tiny-conf_project:tiny-conf:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
@@ -5591,6 +5626,9 @@
|
||||
"ungit": [
|
||||
"cpe:2.3:a:ungit_project:ungit:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"unhead": [
|
||||
"cpe:2.3:a:unjs:unhead:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
"unicode": [
|
||||
"cpe:2.3:a:unicode_project:unicode:*:*:*:*:*:node.js:*:*"
|
||||
],
|
||||
@@ -5972,6 +6010,9 @@
|
||||
"b2sdk": [
|
||||
"cpe:2.3:a:backblaze:b2-sdk-python:*:*:*:*:*:*:*:*"
|
||||
],
|
||||
"black": [
|
||||
"cpe:2.3:a:python:black:*:*:*:*:*:python:*:*"
|
||||
],
|
||||
"blackduck": [
|
||||
"cpe:2.3:a:synopsys:hub-rest-api-python:*:*:*:*:*:*:*:*"
|
||||
],
|
||||
@@ -6039,6 +6080,9 @@
|
||||
"datapizza-ai": [
|
||||
"cpe:2.3:a:datapizza:datapizza_ai:*:*:*:*:*:*:*:*"
|
||||
],
|
||||
"dbt-common": [
|
||||
"cpe:2.3:a:getdbt:dbt-common:*:*:*:*:*:*:*:*"
|
||||
],
|
||||
"decorator": [
|
||||
"cpe:2.3:a:python:decorator:*:*:*:*:*:*:*:*"
|
||||
],
|
||||
@@ -6163,12 +6207,18 @@
|
||||
"lxml-html-clean": [
|
||||
"cpe:2.3:a:fedoralovespython:lxml_html_clean:*:*:*:*:*:python:*:*"
|
||||
],
|
||||
"mad-proxy": [
|
||||
"cpe:2.3:a:machphy:mad-proxy:*:*:*:*:*:python:*:*"
|
||||
],
|
||||
"mage-ai": [
|
||||
"cpe:2.3:a:mage:mage-ai:*:*:*:*:*:python:*:*"
|
||||
],
|
||||
"marshmallow": [
|
||||
"cpe:2.3:a:marshmallow_project:marshmallow:*:*:*:*:*:python:*:*"
|
||||
],
|
||||
"mcp-memory-service": [
|
||||
"cpe:2.3:a:doobidoo:mcp-memory-service:*:*:*:*:*:*:*:*"
|
||||
],
|
||||
"mltable": [
|
||||
"cpe:2.3:a:microsoft:azure_machine_learning_software_development_kit:*:*:*:*:*:*:*:*"
|
||||
],
|
||||
@@ -6994,6 +7044,15 @@
|
||||
"autorand": [
|
||||
"cpe:2.3:a:autorand_project:autorand:*:*:*:*:*:rust:*:*"
|
||||
],
|
||||
"aws-lc-fips-sys": [
|
||||
"cpe:2.3:a:amazon:aws-lc-fips-sys:*:*:*:*:*:rust:*:*"
|
||||
],
|
||||
"aws-lc-rs": [
|
||||
"cpe:2.3:a:amazon:aws-lc-rs:*:*:*:*:*:rust:*:*"
|
||||
],
|
||||
"aws-lc-sys": [
|
||||
"cpe:2.3:a:amazon:aws-lc-sys:*:*:*:*:*:rust:*:*"
|
||||
],
|
||||
"axum-core": [
|
||||
"cpe:2.3:a:axum-core_project:axum-core:*:*:*:*:*:rust:*:*"
|
||||
],
|
||||
@@ -9172,6 +9231,9 @@
|
||||
"bravo-translate": [
|
||||
"cpe:2.3:a:guelbetech:bravo_translate:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"bread-butter": [
|
||||
"cpe:2.3:a:breadbutter:bread_\\\u0026_butter:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"breadcrumbs-by-menu": [
|
||||
"cpe:2.3:a:holest:breadcrumbs_by_menu:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -9410,8 +9472,7 @@
|
||||
"cpe:2.3:a:kieranoshea:calendar:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"calendar-booking": [
|
||||
"cpe:2.3:a:startbooking:scheduling_plugin:*:*:*:*:*:wordpress:*:*",
|
||||
"cpe:2.3:a:startbooking:scheduling_plugin_-_online_booking:*:*:*:*:*:wordpress:*:*"
|
||||
"cpe:2.3:a:startbooking:scheduling_plugin:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"calendar-plugin": [
|
||||
"cpe:2.3:a:calendar_plugin_project:calendar_plugin:*:*:*:*:*:wordpress:*:*"
|
||||
@@ -10026,6 +10087,9 @@
|
||||
"contact-form-7-paypal-add-on": [
|
||||
"cpe:2.3:a:wpplugin:paypal_\\\u0026_stripe_add-on:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"contact-form-7-recaptcha": [
|
||||
"cpe:2.3:a:iambriansreed:contact_form_7_recaptcha:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"contact-form-7-simple-recaptcha": [
|
||||
"cpe:2.3:a:contact_form_7_captcha_project:contact_form_7_captcha:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -13449,7 +13513,7 @@
|
||||
"cpe:2.3:a:brijeshk89:ip_based_login:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"ip-blacklist-cloud": [
|
||||
"cpe:2.3:a:ip_blacklist_cloud_project:ip_blacklist_cloud:*:*:*:*:*:wordpress:*:*"
|
||||
"cpe:2.3:a:ad33lx:ip_blacklist_cloud:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"ip-vault-wp-firewall": [
|
||||
"cpe:2.3:a:youtag:two-factor_authentication:*:*:*:*:*:wordpress:*:*"
|
||||
|
||||
+2
-2
@@ -270,7 +270,7 @@ func (c *NVDAPIClient) fetchPage(ctx context.Context, startIndex int, start, end
|
||||
|
||||
// handleRateLimit handles HTTP 429 responses by parsing Retry-After and waiting
|
||||
func (c *NVDAPIClient) handleRateLimit(ctx context.Context, httpResp *http.Response, attempt int) error {
|
||||
body, _ := io.ReadAll(httpResp.Body)
|
||||
body, _ := io.ReadAll(httpResp.Body) //nolint:gocritic // offline code generator
|
||||
httpResp.Body.Close()
|
||||
|
||||
// parse Retry-After header
|
||||
@@ -298,7 +298,7 @@ func checkHTTPStatus(httpResp *http.Response) error {
|
||||
if httpResp.StatusCode == http.StatusOK {
|
||||
return nil
|
||||
}
|
||||
body, _ := io.ReadAll(httpResp.Body)
|
||||
body, _ := io.ReadAll(httpResp.Body) //nolint:gocritic // offline code generator
|
||||
httpResp.Body.Close()
|
||||
return fmt.Errorf("NVD API error (status %d): %s", httpResp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"github.com/anchore/syft/internal/cmptest"
|
||||
"github.com/anchore/syft/internal/licenses"
|
||||
"github.com/anchore/syft/internal/relationship"
|
||||
"github.com/anchore/syft/internal/tmpdir"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/internal/fileresolver"
|
||||
@@ -62,7 +63,7 @@ type CatalogTester struct {
|
||||
skipTestObservations bool
|
||||
}
|
||||
|
||||
func Context() context.Context {
|
||||
func initLicenseScanner() {
|
||||
once.Do(func() {
|
||||
// most of the time in testing is initializing the scanner. Let's do that just once
|
||||
sc := &licenses.ScannerConfig{Scanner: licensecheck.Scan, CoverageThreshold: 75}
|
||||
@@ -72,13 +73,20 @@ func Context() context.Context {
|
||||
}
|
||||
licenseScanner = &scanner
|
||||
})
|
||||
}
|
||||
|
||||
return licenses.SetContextLicenseScanner(context.Background(), *licenseScanner)
|
||||
// Context returns a context with a shared license scanner and a TempDir backed by t.TempDir(),
|
||||
// so cleanup is handled automatically when the test finishes.
|
||||
func Context(t *testing.T) context.Context {
|
||||
t.Helper()
|
||||
initLicenseScanner()
|
||||
td := tmpdir.FromPath(t.TempDir())
|
||||
ctx := tmpdir.WithValue(context.Background(), td)
|
||||
return licenses.SetContextLicenseScanner(ctx, *licenseScanner)
|
||||
}
|
||||
|
||||
func NewCatalogTester() *CatalogTester {
|
||||
return &CatalogTester{
|
||||
context: Context(),
|
||||
locationComparer: cmptest.DefaultLocationComparer,
|
||||
licenseComparer: cmptest.DefaultLicenseComparer,
|
||||
packageStringer: stringPackage,
|
||||
@@ -272,9 +280,18 @@ func (p *CatalogTester) WithoutTestObserver() *CatalogTester {
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *CatalogTester) ensureContext(t *testing.T) context.Context {
|
||||
t.Helper()
|
||||
if p.context != nil {
|
||||
return p.context
|
||||
}
|
||||
return Context(t)
|
||||
}
|
||||
|
||||
func (p *CatalogTester) TestParser(t *testing.T, parser generic.Parser) {
|
||||
t.Helper()
|
||||
pkgs, relationships, err := parser(p.context, p.resolver, p.env, p.reader)
|
||||
ctx := p.ensureContext(t)
|
||||
pkgs, relationships, err := parser(ctx, p.resolver, p.env, p.reader)
|
||||
|
||||
// only test for errors if explicitly requested
|
||||
if p.wantErr != nil {
|
||||
@@ -289,10 +306,11 @@ func (p *CatalogTester) TestParser(t *testing.T, parser generic.Parser) {
|
||||
|
||||
func (p *CatalogTester) TestCataloger(t *testing.T, cataloger pkg.Cataloger) {
|
||||
t.Helper()
|
||||
ctx := p.ensureContext(t)
|
||||
|
||||
resolver := NewObservingResolver(p.resolver)
|
||||
|
||||
pkgs, relationships, err := cataloger.Catalog(p.context, resolver)
|
||||
pkgs, relationships, err := cataloger.Catalog(ctx, resolver)
|
||||
|
||||
// this is a minimum set, the resolver may return more that just this list
|
||||
for _, path := range p.expectedPathResponses {
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"github.com/anchore/syft/internal"
|
||||
intFile "github.com/anchore/syft/internal/file"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/tmpdir"
|
||||
"github.com/anchore/syft/internal/unknown"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
@@ -104,7 +105,11 @@ func newJavaArchiveParser(ctx context.Context, reader file.LocationReadCloser, d
|
||||
virtualElements := strings.Split(reader.Path(), ":")
|
||||
currentFilepath := virtualElements[len(virtualElements)-1]
|
||||
|
||||
contentPath, archivePath, cleanupFn, err := saveArchiveToTmp(currentFilepath, reader)
|
||||
td := tmpdir.FromContext(ctx)
|
||||
if td == nil {
|
||||
return nil, func() {}, fmt.Errorf("no temp dir factory in context")
|
||||
}
|
||||
contentPath, archivePath, cleanupFn, err := saveArchiveToTmp(td, currentFilepath, reader)
|
||||
if err != nil {
|
||||
return nil, cleanupFn, fmt.Errorf("unable to process java archive: %w", err)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package java
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
@@ -30,7 +29,7 @@ import (
|
||||
|
||||
func TestSearchMavenForLicenses(t *testing.T) {
|
||||
url := maventest.MockRepo(t, "internal/maven/testdata/maven-repo")
|
||||
ctx := pkgtest.Context()
|
||||
ctx := pkgtest.Context(t)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -72,7 +71,7 @@ func TestSearchMavenForLicenses(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
// setup parser
|
||||
ap, cleanupFn, err := newJavaArchiveParser(context.Background(),
|
||||
ap, cleanupFn, err := newJavaArchiveParser(pkgtest.Context(t),
|
||||
file.LocationReadCloser{
|
||||
Location: file.NewLocation(fixture.Name()),
|
||||
ReadCloser: fixture,
|
||||
@@ -81,17 +80,17 @@ func TestSearchMavenForLicenses(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
// assert licenses are discovered from upstream
|
||||
_, _, _, parsedPom := ap.discoverMainPackageFromPomInfo(context.Background())
|
||||
_, _, _, parsedPom := ap.discoverMainPackageFromPomInfo(pkgtest.Context(t))
|
||||
require.NotNil(t, parsedPom, "expected to find pom information in the fixture")
|
||||
require.NotNil(t, parsedPom.project, "expected parsedPom to have a project")
|
||||
resolvedLicenses, _ := ap.maven.ResolveLicenses(context.Background(), parsedPom.project)
|
||||
resolvedLicenses, _ := ap.maven.ResolveLicenses(pkgtest.Context(t), parsedPom.project)
|
||||
assert.Equal(t, tc.expectedLicenses, toPkgLicenses(ctx, nil, resolvedLicenses))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseJar(t *testing.T) {
|
||||
ctx := pkgtest.Context()
|
||||
ctx := pkgtest.Context(t)
|
||||
tests := []struct {
|
||||
name string
|
||||
fixture string
|
||||
@@ -372,7 +371,7 @@ func TestParseJar(t *testing.T) {
|
||||
UseNetwork: false,
|
||||
UseMavenLocalRepository: false,
|
||||
}
|
||||
parser, cleanupFn, err := newJavaArchiveParser(context.Background(),
|
||||
parser, cleanupFn, err := newJavaArchiveParser(pkgtest.Context(t),
|
||||
file.LocationReadCloser{
|
||||
Location: file.NewLocation(fixture.Name()),
|
||||
ReadCloser: fixture,
|
||||
@@ -662,7 +661,7 @@ func TestParseNestedJar(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
gap := newGenericArchiveParserAdapter(ArchiveCatalogerConfig{})
|
||||
|
||||
actual, _, err := gap.processJavaArchive(context.Background(), file.LocationReadCloser{
|
||||
actual, _, err := gap.processJavaArchive(pkgtest.Context(t), file.LocationReadCloser{
|
||||
Location: file.NewLocation(fixture.Name()),
|
||||
ReadCloser: fixture,
|
||||
}, nil)
|
||||
@@ -1080,7 +1079,7 @@ func Test_newPackageFromMavenData(t *testing.T) {
|
||||
test.expectedParent.Locations = locations
|
||||
|
||||
r := maven.NewResolver(nil, maven.DefaultConfig())
|
||||
actualPackage := newPackageFromMavenData(context.Background(), r, test.props, test.project, test.parent, file.NewLocation(virtualPath))
|
||||
actualPackage := newPackageFromMavenData(pkgtest.Context(t), r, test.props, test.project, test.parent, file.NewLocation(virtualPath))
|
||||
if test.expectedPackage == nil {
|
||||
require.Nil(t, actualPackage)
|
||||
} else {
|
||||
@@ -1120,7 +1119,7 @@ func Test_artifactIDMatchesFilename(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_parseJavaArchive_regressions(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
ctx := pkgtest.Context(t)
|
||||
apiAll := pkg.Package{
|
||||
Name: "api-all",
|
||||
Version: "2.0.0",
|
||||
@@ -1499,7 +1498,7 @@ func Test_deterministicMatchingPomProperties(t *testing.T) {
|
||||
fixture, err := os.Open(fixturePath)
|
||||
require.NoError(t, err)
|
||||
|
||||
parser, cleanupFn, err := newJavaArchiveParser(context.Background(),
|
||||
parser, cleanupFn, err := newJavaArchiveParser(pkgtest.Context(t),
|
||||
file.LocationReadCloser{
|
||||
Location: file.NewLocation(fixture.Name()),
|
||||
ReadCloser: fixture,
|
||||
@@ -1507,7 +1506,7 @@ func Test_deterministicMatchingPomProperties(t *testing.T) {
|
||||
defer cleanupFn()
|
||||
require.NoError(t, err)
|
||||
|
||||
groupID, artifactID, version, _ := parser.discoverMainPackageFromPomInfo(context.TODO())
|
||||
groupID, artifactID, version, _ := parser.discoverMainPackageFromPomInfo(pkgtest.Context(t))
|
||||
require.Equal(t, test.expected, maven.NewID(groupID, artifactID, version))
|
||||
}()
|
||||
}
|
||||
@@ -1634,9 +1633,9 @@ func Test_jarPomPropertyResolutionDoesNotPanic(t *testing.T) {
|
||||
fixture, err := os.Open(jarName)
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx := context.TODO()
|
||||
ctx := pkgtest.Context(t)
|
||||
// setup parser
|
||||
ap, cleanupFn, err := newJavaArchiveParser(context.Background(),
|
||||
ap, cleanupFn, err := newJavaArchiveParser(pkgtest.Context(t),
|
||||
file.LocationReadCloser{
|
||||
Location: file.NewLocation(fixture.Name()),
|
||||
ReadCloser: fixture,
|
||||
|
||||
@@ -40,7 +40,7 @@ func ParsePomXML(content io.Reader) (project *Project, err error) {
|
||||
}
|
||||
|
||||
func getUtf8Reader(content io.Reader) (io.Reader, error) {
|
||||
pomContents, err := io.ReadAll(content)
|
||||
pomContents, err := io.ReadAll(content) //nolint:gocritic // charset detection requires full buffer
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -463,7 +463,7 @@ func (r *Resolver) cacheResolveReader(key string, resolve func() (io.ReadCloser,
|
||||
defer internal.CloseAndLogError(contentReader, key)
|
||||
|
||||
// store the contents to return a new reader with the same content
|
||||
contents, err := io.ReadAll(contentReader)
|
||||
contents, err := io.ReadAll(contentReader) //nolint:gocritic // caching requires full buffer
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package java
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
@@ -194,7 +193,7 @@ func Test_parseCommonsTextPomXMLProject(t *testing.T) {
|
||||
func Test_parsePomXMLProject(t *testing.T) {
|
||||
// TODO: ideally we would have the path to the contained pom.xml, not the jar
|
||||
jarLocation := file.NewLocation("path/to/archive.jar")
|
||||
ctx := context.TODO()
|
||||
ctx := pkgtest.Context(t)
|
||||
tests := []struct {
|
||||
name string
|
||||
project *pkg.JavaPomProject
|
||||
@@ -265,11 +264,11 @@ func Test_parsePomXMLProject(t *testing.T) {
|
||||
pom, err := maven.ParsePomXML(fixture)
|
||||
require.NoError(t, err)
|
||||
|
||||
actual := newPomProject(context.Background(), r, fixture.Name(), pom)
|
||||
actual := newPomProject(pkgtest.Context(t), r, fixture.Name(), pom)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, test.project, actual)
|
||||
|
||||
licenses, err := r.ResolveLicenses(context.Background(), pom)
|
||||
licenses, err := r.ResolveLicenses(pkgtest.Context(t), pom)
|
||||
//assert.NoError(t, err)
|
||||
assert.Equal(t, test.licenses, toPkgLicenses(ctx, &jarLocation, licenses))
|
||||
})
|
||||
@@ -331,7 +330,7 @@ func Test_pomParent(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
r := maven.NewResolver(nil, maven.DefaultConfig())
|
||||
assert.Equal(t, test.expected, pomParent(context.Background(), r, &maven.Project{Parent: test.input}))
|
||||
assert.Equal(t, test.expected, pomParent(pkgtest.Context(t), r, &maven.Project{Parent: test.input}))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -433,7 +432,7 @@ func Test_resolveLicenses(t *testing.T) {
|
||||
fr, err := ds.FileResolver(source.AllLayersScope)
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx := context.TODO()
|
||||
ctx := pkgtest.Context(t)
|
||||
pkgs, _, err := cat.Catalog(ctx, fr)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
@@ -6,23 +6,16 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/tmpdir"
|
||||
)
|
||||
|
||||
func saveArchiveToTmp(archiveVirtualPath string, reader io.Reader) (string, string, func(), error) {
|
||||
func saveArchiveToTmp(td *tmpdir.TempDir, archiveVirtualPath string, reader io.Reader) (string, string, func(), error) {
|
||||
name := filepath.Base(archiveVirtualPath)
|
||||
tempDir, err := os.MkdirTemp("", "syft-archive-contents-")
|
||||
tempDir, cleanupFn, err := td.NewChild("archive-contents") //nolint:gocritic // cleanup is returned to caller, not deferred here
|
||||
if err != nil {
|
||||
return "", "", func() {}, fmt.Errorf("unable to create tempdir for archive processing: %w", err)
|
||||
}
|
||||
|
||||
cleanupFn := func() {
|
||||
err = os.RemoveAll(tempDir)
|
||||
if err != nil {
|
||||
log.Errorf("unable to cleanup archive tempdir: %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
archivePath := filepath.Join(tempDir, "archive-"+name)
|
||||
contentDir := filepath.Join(tempDir, "contents")
|
||||
|
||||
@@ -37,6 +30,9 @@ func saveArchiveToTmp(archiveVirtualPath string, reader io.Reader) (string, stri
|
||||
}
|
||||
defer archiveFile.Close()
|
||||
|
||||
// note: no size limit here — the reader comes from a file already enumerated by the
|
||||
// resolver, not from an untrusted network source. The file size is bounded by the
|
||||
// source image/directory being scanned.
|
||||
_, err = io.Copy(archiveFile, reader)
|
||||
if err != nil {
|
||||
return contentDir, archivePath, cleanupFn, fmt.Errorf("unable to copy archive: %w", err)
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
intFile "github.com/anchore/syft/internal/file"
|
||||
"github.com/anchore/syft/internal/tmpdir"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
@@ -58,7 +59,11 @@ func newGenericTarWrappedJavaArchiveParser(cfg ArchiveCatalogerConfig) genericTa
|
||||
}
|
||||
|
||||
func (gtp genericTarWrappedJavaArchiveParser) parseTarWrappedJavaArchive(ctx context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
contentPath, archivePath, cleanupFn, err := saveArchiveToTmp(reader.Path(), reader)
|
||||
td := tmpdir.FromContext(ctx)
|
||||
if td == nil {
|
||||
return nil, nil, fmt.Errorf("no temp dir factory in context")
|
||||
}
|
||||
contentPath, archivePath, cleanupFn, err := saveArchiveToTmp(td, reader.Path(), reader)
|
||||
// note: even on error, we should always run cleanup functions
|
||||
defer cleanupFn()
|
||||
if err != nil {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package java
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
@@ -50,7 +49,7 @@ func Test_parseTarWrappedJavaArchive(t *testing.T) {
|
||||
}
|
||||
|
||||
gtp := newGenericTarWrappedJavaArchiveParser(ArchiveCatalogerConfig{})
|
||||
actualPkgs, _, err := gtp.parseTarWrappedJavaArchive(context.Background(), nil, nil, file.LocationReadCloser{
|
||||
actualPkgs, _, err := gtp.parseTarWrappedJavaArchive(pkgtest.Context(t), nil, nil, file.LocationReadCloser{
|
||||
Location: file.NewLocation(test.fixture),
|
||||
ReadCloser: fixture,
|
||||
})
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
intFile "github.com/anchore/syft/internal/file"
|
||||
"github.com/anchore/syft/internal/tmpdir"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
@@ -30,7 +31,11 @@ func newGenericZipWrappedJavaArchiveParser(cfg ArchiveCatalogerConfig) genericZi
|
||||
}
|
||||
|
||||
func (gzp genericZipWrappedJavaArchiveParser) parseZipWrappedJavaArchive(ctx context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
contentPath, archivePath, cleanupFn, err := saveArchiveToTmp(reader.Path(), reader)
|
||||
td := tmpdir.FromContext(ctx)
|
||||
if td == nil {
|
||||
return nil, nil, fmt.Errorf("no temp dir factory in context")
|
||||
}
|
||||
contentPath, archivePath, cleanupFn, err := saveArchiveToTmp(td, reader.Path(), reader)
|
||||
// note: even on error, we should always run cleanup functions
|
||||
defer cleanupFn()
|
||||
if err != nil {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package java
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
@@ -10,6 +9,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
||||
)
|
||||
|
||||
func Test_parseZipWrappedJavaArchive(t *testing.T) {
|
||||
@@ -36,7 +36,7 @@ func Test_parseZipWrappedJavaArchive(t *testing.T) {
|
||||
|
||||
gzp := newGenericZipWrappedJavaArchiveParser(ArchiveCatalogerConfig{})
|
||||
|
||||
actualPkgs, _, err := gzp.parseZipWrappedJavaArchive(context.Background(), nil, nil, file.LocationReadCloser{
|
||||
actualPkgs, _, err := gzp.parseZipWrappedJavaArchive(pkgtest.Context(t), nil, nil, file.LocationReadCloser{
|
||||
Location: file.NewLocation(test.fixture),
|
||||
ReadCloser: fixture,
|
||||
})
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
@@ -257,19 +256,12 @@ func getLicenseFromNpmRegistry(baseURL, packageName, version string) (string, er
|
||||
}
|
||||
}()
|
||||
|
||||
bytes, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to parse package from npm registry: %w", err)
|
||||
}
|
||||
|
||||
dec := json.NewDecoder(strings.NewReader(string(bytes)))
|
||||
|
||||
// Read "license" from the response
|
||||
var license struct {
|
||||
License string `json:"license"`
|
||||
}
|
||||
|
||||
if err := dec.Decode(&license); err != nil {
|
||||
if err := json.NewDecoder(resp.Body).Decode(&license); err != nil {
|
||||
return "", fmt.Errorf("unable to parse license from npm registry: %w", err)
|
||||
}
|
||||
|
||||
@@ -324,14 +316,8 @@ func parseLicensesFromLocation(l file.Location, resolver file.Resolver, pkgFile
|
||||
}
|
||||
defer internal.CloseAndLogError(contentReader, l.RealPath)
|
||||
|
||||
contents, err := io.ReadAll(contentReader)
|
||||
if err != nil {
|
||||
log.Debugf("error reading file contents for %s: %v", pkgFile, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var pkgJSON packageJSON
|
||||
err = json.Unmarshal(contents, &pkgJSON)
|
||||
err = json.NewDecoder(contentReader).Decode(&pkgJSON)
|
||||
if err != nil {
|
||||
log.Debugf("error parsing %s: %v", pkgFile, err)
|
||||
return nil, err
|
||||
|
||||
@@ -179,7 +179,7 @@ func newPnpmLockfileParser(version float64) pnpmLockfileParser {
|
||||
|
||||
// parsePnpmLock is the main parser function for pnpm-lock.yaml files.
|
||||
func (a genericPnpmLockAdapter) parsePnpmLock(ctx context.Context, resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
data, err := io.ReadAll(reader)
|
||||
data, err := io.ReadAll(reader) //nolint:gocritic // multi-pass parse requires []byte
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to load pnpm-lock.yaml file: %w", err)
|
||||
}
|
||||
|
||||
@@ -166,7 +166,8 @@ func findDevOnlyPkgs(yarnPkgs []yarnPackage, prodDeps, devDeps map[string]string
|
||||
}
|
||||
|
||||
func parseYarnV1LockFile(reader io.ReadCloser) ([]yarnPackage, error) {
|
||||
content, err := io.ReadAll(reader)
|
||||
// TODO: refactor to use bufio.Scanner for streaming line-by-line parsing instead of reading the entire file
|
||||
content, err := io.ReadAll(reader) //nolint:gocritic // stateful multi-line parser; candidate for streaming refactor
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read yarn.lock file: %w", err)
|
||||
}
|
||||
@@ -265,7 +266,8 @@ func (a genericYarnLockAdapter) parseYarnLock(ctx context.Context, resolver file
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(reader)
|
||||
// TODO: refactor to detect version from the first line via bufio.Scanner, then dispatch to a streaming parser
|
||||
data, err := io.ReadAll(reader) //nolint:gocritic // two-pass parse: version detection then format-specific parsing
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to load yarn.lock file: %w", err)
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ var noReturn = rockspec{
|
||||
|
||||
// parseRockspec basic parser for rockspec
|
||||
func parseRockspecData(reader io.Reader) (rockspec, error) {
|
||||
data, err := io.ReadAll(reader)
|
||||
data, err := io.ReadAll(reader) //nolint:gocritic // custom parser requires []byte
|
||||
if err != nil {
|
||||
return noReturn, err
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ func (c cataloger) Name() string {
|
||||
|
||||
func (c cataloger) Catalog(ctx context.Context, resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
// always try the DB cataloger first (based off of information recorded by actions taken by nix tooling)
|
||||
pkgs, rels, err := c.dbParser.catalog(resolver)
|
||||
pkgs, rels, err := c.dbParser.catalog(ctx, resolver)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to catalog nix packages from database: %w", err)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package nix
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -17,7 +18,7 @@ import (
|
||||
|
||||
const defaultSchema = 10
|
||||
|
||||
type dbProcessor func(config Config, dbLocation file.Location, resolver file.Resolver, catalogerName string) ([]pkg.Package, []artifact.Relationship, error)
|
||||
type dbProcessor func(ctx context.Context, config Config, dbLocation file.Location, resolver file.Resolver, catalogerName string) ([]pkg.Package, []artifact.Relationship, error)
|
||||
|
||||
type dbCataloger struct {
|
||||
config Config
|
||||
@@ -45,7 +46,7 @@ type dbPackageEntry struct {
|
||||
Files []string
|
||||
}
|
||||
|
||||
func (c dbCataloger) catalog(resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
func (c dbCataloger) catalog(ctx context.Context, resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
dbLocs, err := resolver.FilesByGlob("**/nix/var/nix/db/db.sqlite")
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to find Nix database: %w", err)
|
||||
@@ -65,7 +66,7 @@ func (c dbCataloger) catalog(resolver file.Resolver) ([]pkg.Package, []artifact.
|
||||
continue
|
||||
}
|
||||
|
||||
newPkgs, newRelationships, err := parser(c.config, dbLoc, resolver, c.catalogerName)
|
||||
newPkgs, newRelationships, err := parser(ctx, c.config, dbLoc, resolver, c.catalogerName)
|
||||
if err != nil {
|
||||
errs = unknown.Append(errs, dbLoc.Coordinates, err)
|
||||
continue
|
||||
@@ -92,13 +93,13 @@ func (c dbCataloger) selectDBParser(dbLocation file.Location, resolver file.Reso
|
||||
return c.schemaProcessor[defaultSchema], 0
|
||||
}
|
||||
|
||||
contents, err := io.ReadAll(schemaContents)
|
||||
if err != nil {
|
||||
scanner := bufio.NewScanner(schemaContents)
|
||||
if !scanner.Scan() {
|
||||
log.WithFields("path", loc.RealPath).Tracef("failed to read Nix database schema file, assuming %d", defaultSchema)
|
||||
return c.schemaProcessor[defaultSchema], 0
|
||||
}
|
||||
|
||||
schema, err := strconv.Atoi(strings.TrimSpace(string(contents)))
|
||||
schema, err := strconv.Atoi(strings.TrimSpace(scanner.Text()))
|
||||
if err != nil {
|
||||
log.WithFields("path", loc.RealPath).Tracef("failed to parse Nix database schema file, assuming %d", defaultSchema)
|
||||
return c.schemaProcessor[defaultSchema], 0
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package nix
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -8,6 +9,7 @@ import (
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/tmpdir"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
@@ -15,26 +17,33 @@ import (
|
||||
|
||||
var _ dbProcessor = processV10DB
|
||||
|
||||
func processV10DB(config Config, dbLocation file.Location, resolver file.Resolver, catalogerName string) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
func processV10DB(ctx context.Context, config Config, dbLocation file.Location, resolver file.Resolver, catalogerName string) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
dbContents, err := resolver.FileContentsByLocation(dbLocation)
|
||||
defer internal.CloseAndLogError(dbContents, dbLocation.RealPath)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to read Nix database: %w", err)
|
||||
}
|
||||
|
||||
tempDB, err := createTempDB(dbContents)
|
||||
td := tmpdir.FromContext(ctx)
|
||||
if td == nil {
|
||||
return nil, nil, fmt.Errorf("no temp dir factory in context")
|
||||
}
|
||||
tempDB, cleanupDB, err := createTempDB(td, dbContents)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to create temporary database: %w", err)
|
||||
}
|
||||
defer os.RemoveAll(tempDB.Name())
|
||||
// defer order is LIFO: cleanupDB (remove file) must run after db.Close and tempDB.Close
|
||||
defer cleanupDB()
|
||||
|
||||
// close order is LIFO: db.Close() (SQLite conn) → tempDB.Close() (file handle) → cleanupDB (remove file)
|
||||
defer tempDB.Close()
|
||||
|
||||
db, err := sql.Open("sqlite", tempDB.Name())
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to open database: %w", err)
|
||||
}
|
||||
|
||||
db.SetConnMaxLifetime(0)
|
||||
defer db.Close()
|
||||
db.SetConnMaxLifetime(0)
|
||||
|
||||
packageEntries, err := extractV10DBPackages(config, db, dbLocation, resolver)
|
||||
if err != nil {
|
||||
@@ -232,18 +241,20 @@ func finalizeV10DBResults(db *sql.DB, packageEntries map[int]*dbPackageEntry, ca
|
||||
return pkgs, relationships, nil
|
||||
}
|
||||
|
||||
func createTempDB(content io.ReadCloser) (*os.File, error) {
|
||||
tempFile, err := os.CreateTemp("", "nix-db-*.sqlite")
|
||||
func createTempDB(td *tmpdir.TempDir, content io.ReadCloser) (*os.File, func(), error) {
|
||||
noop := func() {}
|
||||
|
||||
tempFile, cleanup, err := td.NewFile("nix-db-*.sqlite") //nolint:gocritic // cleanup is returned to caller, not deferred here
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, noop, err
|
||||
}
|
||||
|
||||
_, err = io.Copy(tempFile, content)
|
||||
if err != nil {
|
||||
tempFile.Close()
|
||||
os.Remove(tempFile.Name())
|
||||
return nil, err
|
||||
cleanup()
|
||||
return nil, noop, err
|
||||
}
|
||||
|
||||
return tempFile, nil
|
||||
return tempFile, cleanup, nil
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ func parseOpamPackage(ctx context.Context, _ file.Resolver, _ *generic.Environme
|
||||
homepageRe := regexp.MustCompile(`(?m)homepage:\s*"(?P<url>[^"]+)"`)
|
||||
urlRe := regexp.MustCompile(`(?m)url\s*{(?P<url>[^}]+)}`)
|
||||
|
||||
data, err := io.ReadAll(reader)
|
||||
data, err := io.ReadAll(reader) //nolint:gocritic // regex matching requires full buffer
|
||||
if err != nil {
|
||||
log.WithFields("error", err).Trace("unable to read opam package")
|
||||
return nil, nil, nil
|
||||
|
||||
@@ -58,7 +58,7 @@ func parsePear(ctx context.Context, _ file.Resolver, _ *generic.Environment, rea
|
||||
|
||||
// parsePeclPearSerialized is a parser function for Pear metadata contents, returning "Default" php packages discovered.
|
||||
func parsePeclPearSerialized(reader file.LocationReadCloser) (*peclPearData, error) {
|
||||
data, err := io.ReadAll(reader)
|
||||
data, err := io.ReadAll(reader) //nolint:gocritic // phpserialize requires []byte
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read file: %w", err)
|
||||
|
||||
@@ -473,7 +473,7 @@ func Test_PackageCataloger_IgnorePackage(t *testing.T) {
|
||||
t.Run(test.MetadataFixture, func(t *testing.T) {
|
||||
resolver := file.NewMockResolverForPaths(test.MetadataFixture)
|
||||
|
||||
actual, _, err := NewInstalledPackageCataloger().Catalog(pkgtest.Context(), resolver)
|
||||
actual, _, err := NewInstalledPackageCataloger().Catalog(pkgtest.Context(t), resolver)
|
||||
require.NoError(t, err)
|
||||
|
||||
if len(actual) != 0 {
|
||||
|
||||
@@ -4,10 +4,8 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/anchore/syft/internal/cache"
|
||||
@@ -100,13 +98,6 @@ func getLicenseFromPypiRegistry(baseURL, packageName, version string) (string, e
|
||||
return "", fmt.Errorf("unable to get package from pypi registry")
|
||||
}
|
||||
|
||||
bytes, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to parse package from pypi registry: %w", err)
|
||||
}
|
||||
|
||||
dec := json.NewDecoder(strings.NewReader(string(bytes)))
|
||||
|
||||
// Read "license" from the response
|
||||
var pypiResponse struct {
|
||||
Info struct {
|
||||
@@ -115,7 +106,7 @@ func getLicenseFromPypiRegistry(baseURL, packageName, version string) (string, e
|
||||
} `json:"info"`
|
||||
}
|
||||
|
||||
if err := dec.Decode(&pypiResponse); err != nil {
|
||||
if err := json.NewDecoder(resp.Body).Decode(&pypiResponse); err != nil {
|
||||
return "", fmt.Errorf("unable to parse license from pypi registry: %w", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -143,7 +143,7 @@ func (ulp uvLockParser) uvLockPackages(ctx context.Context, reader file.Location
|
||||
var parsedLockFileVersion uvLockFileVersion
|
||||
|
||||
// we cannot use the reader twice, so we read the contents first --uv.lock files tend to be small enough
|
||||
contents, err := io.ReadAll(reader)
|
||||
contents, err := io.ReadAll(reader) //nolint:gocritic // multi-pass parse requires []byte
|
||||
if err != nil {
|
||||
return nil, unknown.New(reader.Location, fmt.Errorf("failed to read uv lock file: %w", err))
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -174,13 +173,8 @@ func fetchDirectURLData(resolver file.Resolver, metadataLocation file.Location)
|
||||
}
|
||||
defer internal.CloseAndLogError(directURLContents, directURLLocation.AccessPath)
|
||||
|
||||
buffer, err := io.ReadAll(directURLContents)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var directURLJson directURLOrigin
|
||||
if err := json.Unmarshal(buffer, &directURLJson); err != nil {
|
||||
if err := json.NewDecoder(directURLContents).Decode(&directURLJson); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
|
||||
@@ -5,11 +5,11 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
rpmdb "github.com/anchore/go-rpmdb/pkg"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/tmpdir"
|
||||
"github.com/anchore/syft/internal/unknown"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
@@ -22,21 +22,16 @@ import (
|
||||
//
|
||||
//nolint:funlen
|
||||
func parseRpmDB(ctx context.Context, resolver file.Resolver, env *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
f, err := os.CreateTemp("", "rpmdb")
|
||||
td := tmpdir.FromContext(ctx)
|
||||
if td == nil {
|
||||
return nil, nil, fmt.Errorf("no temp dir factory in context")
|
||||
}
|
||||
f, cleanup, err := td.NewFile("rpmdb-*")
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to create temp rpmdb file: %w", err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err = f.Close()
|
||||
if err != nil {
|
||||
log.Errorf("failed to close temp rpmdb file: %+v", err)
|
||||
}
|
||||
err = os.Remove(f.Name())
|
||||
if err != nil {
|
||||
log.Errorf("failed to remove temp rpmdb file: %+v", err)
|
||||
}
|
||||
}()
|
||||
defer cleanup()
|
||||
defer f.Close()
|
||||
|
||||
_, err = io.Copy(f, reader)
|
||||
if err != nil {
|
||||
|
||||
@@ -85,6 +85,6 @@ func adaptToReadSeeker(reader io.Reader) (io.ReadSeeker, error) {
|
||||
log.Debug("SBOM cataloger reader is not a ReadSeeker, reading entire SBOM into memory")
|
||||
|
||||
var buff bytes.Buffer
|
||||
_, err := io.Copy(&buff, reader)
|
||||
_, err := io.Copy(&buff, reader) //nolint:gocritic // buffering to ReadSeeker
|
||||
return bytes.NewReader(buff.Bytes()), err
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package swift
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"go.yaml.in/yaml/v3"
|
||||
@@ -28,12 +27,8 @@ type podfileLock struct {
|
||||
|
||||
// parsePodfileLock is a parser function for Podfile.lock contents, returning all cocoapods pods discovered.
|
||||
func parsePodfileLock(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
bytes, err := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to read file: %w", err)
|
||||
}
|
||||
var podfile podfileLock
|
||||
if err = yaml.Unmarshal(bytes, &podfile); err != nil {
|
||||
if err := yaml.NewDecoder(reader).Decode(&podfile); err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to parse yaml: %w", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ func parsePackPackage(ctx context.Context, resolver file.Resolver, _ *generic.En
|
||||
homeRe := regexp.MustCompile(`home\(\s*'([^']+)'\s*\)`)
|
||||
authorRe := regexp.MustCompile(`(author|packager)\(\s*'([^']+)'\s*(?:,\s*'([^']+)'\s*)?\)`)
|
||||
|
||||
data, err := io.ReadAll(reader)
|
||||
data, err := io.ReadAll(reader) //nolint:gocritic // regex matching requires full buffer
|
||||
if err != nil {
|
||||
log.WithFields("error", err).Trace("unable to parse Rockspec app")
|
||||
return nil, nil, nil
|
||||
|
||||
@@ -20,7 +20,7 @@ type terraformLockFile struct {
|
||||
func parseTerraformLock(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
var lockFile terraformLockFile
|
||||
|
||||
contents, err := io.ReadAll(reader)
|
||||
contents, err := io.ReadAll(reader) //nolint:gocritic // hclsimple.Decode requires []byte
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to read terraform lock file: %w", err)
|
||||
}
|
||||
|
||||
+1
-1
@@ -411,7 +411,7 @@ func (b *licenseBuilder) licenseFromContentHash(content string) License {
|
||||
}
|
||||
|
||||
func contentFromReader(r io.Reader) (string, error) {
|
||||
bytes, err := io.ReadAll(r)
|
||||
bytes, err := io.ReadAll(r) //nolint:gocritic // reading license content for storage
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package snapsource
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
@@ -215,13 +214,8 @@ func (c *snapcraftClient) GetSnapDownloadURL(id snapIdentity) (string, error) {
|
||||
return "", fmt.Errorf("API request failed with status code %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read response body: %w", err)
|
||||
}
|
||||
|
||||
var info snapcraftInfo
|
||||
if err := json.Unmarshal(body, &info); err != nil {
|
||||
if err := json.NewDecoder(resp.Body).Decode(&info); err != nil {
|
||||
return "", fmt.Errorf("failed to parse JSON response: %w", err)
|
||||
}
|
||||
|
||||
@@ -259,13 +253,8 @@ func (c *snapcraftClient) CheckSnapExists(snapName string) (bool, string, error)
|
||||
return false, "", fmt.Errorf("find API request failed with status code %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return false, "", fmt.Errorf("failed to read find response body: %w", err)
|
||||
}
|
||||
|
||||
var findResp snapFindResponse
|
||||
if err := json.Unmarshal(body, &findResp); err != nil {
|
||||
if err := json.NewDecoder(resp.Body).Decode(&findResp); err != nil {
|
||||
return false, "", fmt.Errorf("failed to parse find JSON response: %w", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,50 @@ func isPtr(ctx *dsl.VarFilterContext) bool {
|
||||
return strings.HasPrefix(ctx.Type.String(), "*") || strings.HasPrefix(ctx.Type.Underlying().String(), "*")
|
||||
}
|
||||
|
||||
// nolint:unused
|
||||
func noUnboundedReads(m dsl.Matcher) {
|
||||
// flag io.ReadAll where the argument is not already wrapped in io.LimitReader
|
||||
m.Match(`io.ReadAll($reader)`).
|
||||
Where(!m["reader"].Text.Matches(`(?i)LimitReader|LimitedReader`)).
|
||||
Report("do not use unbounded io.ReadAll; wrap the reader with io.LimitReader or use a streaming parser")
|
||||
|
||||
// flag io.Copy only when the destination is an in-memory buffer
|
||||
// io.Copy to files, hash writers, encoders, etc. is streaming and safe
|
||||
m.Match(`io.Copy($dst, $src)`).
|
||||
Where((m["dst"].Type.Is(`*bytes.Buffer`) || m["dst"].Type.Is(`*strings.Builder`)) && !m["src"].Text.Matches(`(?i)LimitReader|LimitedReader`)).
|
||||
Report("do not use unbounded io.Copy to in-memory buffer; wrap the source reader with io.LimitReader")
|
||||
}
|
||||
|
||||
// nolint:unused
|
||||
func noDirectTempFiles(m dsl.Matcher) {
|
||||
// catalogers must use tmpdir.FromContext(ctx) instead of creating temp files/dirs directly,
|
||||
// so that all temp storage is centrally managed and cleaned up
|
||||
m.Match(
|
||||
`os.CreateTemp($*_)`,
|
||||
`os.MkdirTemp($*_)`,
|
||||
).
|
||||
Where(m.File().PkgPath.Matches(`/cataloger/`)).
|
||||
Report("do not use os.CreateTemp/os.MkdirTemp in catalogers; use tmpdir.FromContext(ctx) instead")
|
||||
}
|
||||
|
||||
// nolint:unused
|
||||
func tmpCleanupDeferred(m dsl.Matcher) {
|
||||
// ensure the cleanup function returned by NewFile/NewChild is deferred, not discarded
|
||||
m.Match(
|
||||
`$_, $cleanup, $err := $x.NewFile($*_); if $*_ { $*_ }; $next`,
|
||||
`$_, $cleanup, $err = $x.NewFile($*_); if $*_ { $*_ }; $next`,
|
||||
).
|
||||
Where(!m["next"].Text.Matches(`^defer `)).
|
||||
Report("defer the cleanup function returned by NewFile immediately after the error check")
|
||||
|
||||
m.Match(
|
||||
`$_, $cleanup, $err := $x.NewChild($*_); if $*_ { $*_ }; $next`,
|
||||
`$_, $cleanup, $err = $x.NewChild($*_); if $*_ { $*_ }; $next`,
|
||||
).
|
||||
Where(!m["next"].Text.Matches(`^defer `)).
|
||||
Report("defer the cleanup function returned by NewChild immediately after the error check")
|
||||
}
|
||||
|
||||
// nolint:unused
|
||||
func packagesInRelationshipsAsValues(m dsl.Matcher) {
|
||||
m.Import("github.com/anchore/syft/syft/artifact")
|
||||
|
||||
Reference in New Issue
Block a user