Compare commits

..
Author SHA1 Message Date
Alex Goodman 5ea14d6e8b [wip] prototype
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-03-11 10:00:29 -04:00
298 changed files with 4594 additions and 4829 deletions
+16 -23
View File
@@ -1,13 +1,8 @@
# only pull in version updates that were released more than a week ago (low-pass filter for quickly-retracted releases)
cooldown: 7d
tools:
## internal tools ############################################################################
# we want to use a pinned version of binny to manage the toolchain (so binny manages itself!)
- name: binny
version:
want: v0.13.0
want: v0.11.3
method: github-release
with:
repo: anchore/binny
@@ -15,7 +10,7 @@ tools:
# used to produce SBOMs during release
- name: syft
version:
want: v1.42.3
want: latest
method: github-release
with:
repo: anchore/syft
@@ -23,25 +18,15 @@ tools:
# used to sign mac binaries at release
- name: quill
version:
want: v0.7.1
want: v0.5.1
method: github-release
with:
repo: anchore/quill
# used at release to generate the changelog
- name: chronicle
version:
want: v0.8.0
method: github-release
with:
repo: anchore/chronicle
## external tools ############################################################################
# used for linting
- name: golangci-lint
version:
want: v2.11.4
want: v2.11.2
method: github-release
with:
repo: golangci/golangci-lint
@@ -73,7 +58,7 @@ tools:
# used to release all artifacts
- name: goreleaser
version:
want: v2.15.2
want: v2.14.2
method: github-release
with:
repo: goreleaser/goreleaser
@@ -86,6 +71,14 @@ tools:
with:
repo: rinchsan/gosimports
# used at release to generate the changelog
- name: chronicle
version:
want: v0.8.0
method: github-release
with:
repo: anchore/chronicle
# used during static analysis for license compliance
- name: bouncer
version:
@@ -105,7 +98,7 @@ tools:
# used for triggering a release
- name: gh
version:
want: v2.89.0
want: v2.87.3
method: github-release
with:
repo: cli/cli
@@ -113,7 +106,7 @@ tools:
# used to upload test fixture cache
- name: oras
version:
want: v1.3.1
want: v1.3.0
method: github-release
with:
repo: oras-project/oras
@@ -121,7 +114,7 @@ tools:
# used to upload test fixture cache
- name: yq
version:
want: v4.52.5
want: v4.52.4
method: github-release
with:
repo: mikefarah/yq
+12 -4
View File
@@ -1,10 +1,11 @@
name: "Bootstrap"
description: "Bootstrap all tools and dependencies"
inputs:
go-version:
description: "Go version to install"
required: true
default: "1.26.2"
default: "1.26.x"
go-dependencies:
description: "Download go dependencies"
required: true
@@ -23,22 +24,25 @@ inputs:
bootstrap-apt-packages:
description: "Space delimited list of tools to install via apt"
default: "libxml2-utils"
runs:
using: "composite"
steps:
# note: go mod and build is automatically cached on default with v4+
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
if: inputs.go-version != ''
with:
go-version: ${{ inputs.go-version }}
check-latest: true
- name: Restore tool cache
if: inputs.tools == 'true'
id: tool-cache
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
with:
path: ${{ github.workspace }}/.tool
key: ${{ inputs.cache-key-prefix }}-${{ runner.os }}-tool-${{ hashFiles('.binny.yaml') }}
- name: Install project tools
shell: bash
if: inputs.tools == 'true'
@@ -46,10 +50,12 @@ runs:
make tools
.tool/binny list
.tool/binny check
- name: Install go dependencies
if: inputs.go-dependencies == 'true'
shell: bash
run: make ci-bootstrap-go
- name: Install apt packages
if: inputs.bootstrap-apt-packages != ''
shell: bash
@@ -58,12 +64,14 @@ runs:
run: |
IFS=' ' read -ra packages <<< "$APT_PACKAGES"
DEBIAN_FRONTEND=noninteractive sudo apt update && sudo -E apt install -y "${packages[@]}"
- name: Restore ORAS cache from github actions
if: inputs.download-test-fixture-cache == 'true'
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
with:
path: ${{ github.workspace }}/.tmp/oras-cache
key: ${{ inputs.cache-key-prefix }}-oras-cache
- name: Download test fixture cache
if: inputs.download-test-fixture-cache == 'true'
shell: bash
+1 -6
View File
@@ -23,11 +23,6 @@ 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
@@ -40,7 +35,7 @@ updates:
- package-ecosystem: "github-actions"
directories:
- "/"
- "/.github/actions/*"
- "/.github/actions/bootstrap"
cooldown:
default-days: 7
schedule:
+76
View File
@@ -0,0 +1,76 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
name: "CodeQL Security Scan"
on:
push:
branches:
# only run when there are pushes to the main branch (not on PRs)
- main
schedule:
- cron: '0 0 * * 3'
permissions:
contents: read
jobs:
analyze:
name: Analyze
runs-on: ubuntu-22.04-4core-16gb
if: github.repository == 'anchore/syft' # only run for main repo
permissions:
security-events: write
strategy:
fail-fast: false
matrix:
# Override automatic language detection by changing the below list
# Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
language: ['go', 'python']
# Learn more...
# https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
with:
persist-credentials: false
- name: Install Go
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 #v6.3.0
with:
go-version-file: go.mod
check-latest: true
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# 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
# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3
-21
View File
@@ -1,21 +0,0 @@
name: "CodeQL"
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
schedule:
- cron: '38 11 * * 3'
permissions: {}
jobs:
analyze:
name: Analyze
uses: anchore/workflows/.github/workflows/codeql.yaml@e8cee3a5916cebb68cda68b54c180f43394c1910 # v0.5.0
permissions:
security-events: write
packages: read
actions: read
contents: read
@@ -0,0 +1,10 @@
name: Dependabot Automation
on:
pull_request:
permissions:
pull-requests: write
jobs:
run:
uses: anchore/workflows/.github/workflows/dependabot-automation.yaml@main
@@ -0,0 +1,60 @@
name: "Detect schema changes"
on:
# IMPORTANT! This workflow is triggered by the `pull_request_target` event
# which means that forked PRs will run with access secrets from the repo
# it's forked from (the "target" repo).
#
# For this reason we only NEVER checkout the code from the pull request
# (e.g. "ref: ${{ github.event.pull_request.head.sha }}") to prevent
# accidentally running potentially untrusted code.
#
# By default the checkout will be:
# - GITHUB_SHA: Last commit on the PR base branch
# - GITHUB_REF: PR base branch
#
# ...unlike a typical PR where:
# - GITHUB_SHA: Last merge commit on the GITHUB_REF branch
# - GITHUB_REF: PR merge branch refs/pull/:prNumber/merge
pull_request_target:
env:
# note: this is used within hashFiles() so must be within the GITHUB_WORKSPACE path (or will silently fail)
CI_COMMENT_FILE: .tmp/labeler-comment.txt
# needs to be any string to uniquely identify the comment on a PR across multiple runs
COMMENT_HEADER: "label-commentary"
jobs:
label:
name: "Label changes"
runs-on: ubuntu-22.04
permissions:
contents: read
pull-requests: write
issues: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
with:
persist-credentials: false
- run: python .github/scripts/labeler.py
env:
# note: this token has write access to the repo
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_PR_NUMBER: ${{ github.event.number }}
- name: Delete existing comment
if: ${{ hashFiles( env.CI_COMMENT_FILE ) == '' }}
uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 #v2.9.4
with:
header: ${{ env.COMMENT_HEADER }}
hide: true
hide_classify: "OUTDATED"
- name: Add comment
if: ${{ hashFiles( env.CI_COMMENT_FILE ) != '' }}
uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 #v2.9.4
with:
header: ${{ env.COMMENT_HEADER }}
path: ${{ env.CI_COMMENT_FILE }}
@@ -0,0 +1,18 @@
name: Add to OSS board
permissions:
contents: read
on:
issues:
types:
- opened
- reopened
- transferred
- labeled
jobs:
run:
uses: "anchore/workflows/.github/workflows/oss-project-board-add.yaml@main"
secrets:
token: ${{ secrets.OSS_PROJECT_GH_TOKEN }}
+111 -26
View File
@@ -1,11 +1,7 @@
name: "Release"
permissions: {}
# there should never be two releases in progress at the same time
concurrency:
group: release
cancel-in-progress: false
permissions:
contents: read
on:
workflow_dispatch:
@@ -23,26 +19,115 @@ on:
- "install-script-only"
jobs:
version-available:
quality-gate:
environment: release
if: ${{ github.event.inputs.phase == 'all' }}
uses: anchore/workflows/.github/workflows/check-version-available.yaml@8b2b1caf40e03933c6807e03b99e883e2ceb5ac8 # v0.4.0
with:
version: ${{ github.event.inputs.version }}
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
with:
persist-credentials: false
check-gate:
if: ${{ github.event.inputs.phase == 'all' }}
permissions:
checks: read # required for getting the status of specific check names
uses: anchore/workflows/.github/workflows/check-gate.yaml@8b2b1caf40e03933c6807e03b99e883e2ceb5ac8 # v0.4.0
with:
# these are checks that should be run on pull-request and merges to main.
# we do NOT want to kick off a release if these have not been verified on main.
# Please see the validations.yaml workflow for the names that should be used here.
checks: '["Acceptance tests (Linux)", "Acceptance tests (Mac)", "Build snapshot artifacts", "CLI tests (Linux)", "Integration tests", "Static analysis", "Unit tests"]'
- name: Bootstrap environment
uses: ./.github/actions/bootstrap
- name: Validate Apple notarization credentials
run: .tool/quill submission list
env:
QUILL_NOTARY_ISSUER: ${{ secrets.APPLE_NOTARY_ISSUER }}
QUILL_NOTARY_KEY_ID: ${{ secrets.APPLE_NOTARY_KEY_ID }}
QUILL_NOTARY_KEY: ${{ secrets.APPLE_NOTARY_KEY }}
- name: Check if running on main
if: github.ref != 'refs/heads/main'
# we are using the following flag when running `cosign blob-verify` for checksum signature verification:
# --certificate-identity-regexp "https://github.com/anchore/.github/workflows/release.yaml@refs/heads/main"
# if we are not on the main branch, the signature will not be verifiable since the suffix requires the main branch
# at the time of when the OIDC token was issued on the Github Actions runner.
run: echo "This can only be run on the main branch otherwise releases produced will not be verifiable with cosign" && exit 1
- name: Check if tag already exists
# note: this will fail if the tag already exists
run: |
[[ "$VERSION" == v* ]] || (echo "version '$VERSION' does not have a 'v' prefix" && exit 1)
git tag "$VERSION"
env:
VERSION: ${{ github.event.inputs.version }}
- name: Check static analysis results
uses: fountainhead/action-wait-for-check@5a908a24814494009c4bb27c242ea38c93c593be # v1.2.0
id: static-analysis
with:
token: ${{ secrets.GITHUB_TOKEN }}
# This check name is defined as the github action job name (in .github/workflows/testing.yaml)
checkName: "Static analysis"
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Check unit test results
uses: fountainhead/action-wait-for-check@5a908a24814494009c4bb27c242ea38c93c593be # v1.2.0
id: unit
with:
token: ${{ secrets.GITHUB_TOKEN }}
# This check name is defined as the github action job name (in .github/workflows/testing.yaml)
checkName: "Unit tests"
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Check integration test results
uses: fountainhead/action-wait-for-check@5a908a24814494009c4bb27c242ea38c93c593be # v1.2.0
id: integration
with:
token: ${{ secrets.GITHUB_TOKEN }}
# This check name is defined as the github action job name (in .github/workflows/testing.yaml)
checkName: "Integration tests"
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Check acceptance test results (linux)
uses: fountainhead/action-wait-for-check@5a908a24814494009c4bb27c242ea38c93c593be # v1.2.0
id: acceptance-linux
with:
token: ${{ secrets.GITHUB_TOKEN }}
# This check name is defined as the github action job name (in .github/workflows/testing.yaml)
checkName: "Acceptance tests (Linux)"
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Check acceptance test results (mac)
uses: fountainhead/action-wait-for-check@5a908a24814494009c4bb27c242ea38c93c593be # v1.2.0
id: acceptance-mac
with:
token: ${{ secrets.GITHUB_TOKEN }}
# This check name is defined as the github action job name (in .github/workflows/testing.yaml)
checkName: "Acceptance tests (Mac)"
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Check cli test results (linux)
uses: fountainhead/action-wait-for-check@5a908a24814494009c4bb27c242ea38c93c593be # v1.2.0
id: cli-linux
with:
token: ${{ secrets.GITHUB_TOKEN }}
# This check name is defined as the github action job name (in .github/workflows/testing.yaml)
checkName: "CLI tests (Linux)"
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Quality gate
if: steps.static-analysis.outputs.conclusion != 'success' || steps.unit.outputs.conclusion != 'success' || steps.integration.outputs.conclusion != 'success' || steps.cli-linux.outputs.conclusion != 'success' || steps.acceptance-linux.outputs.conclusion != 'success' || steps.acceptance-mac.outputs.conclusion != 'success'
env:
STATIC_ANALYSIS_STATUS: ${{ steps.static-analysis.conclusion }}
UNIT_TEST_STATUS: ${{ steps.unit.outputs.conclusion }}
INTEGRATION_TEST_STATUS: ${{ steps.integration.outputs.conclusion }}
ACCEPTANCE_LINUX_STATUS: ${{ steps.acceptance-linux.outputs.conclusion }}
ACCEPTANCE_MAC_STATUS: ${{ steps.acceptance-mac.outputs.conclusion }}
CLI_LINUX_STATUS: ${{ steps.cli-linux.outputs.conclusion }}
run: |
echo "Static Analysis Status: $STATIC_ANALYSIS_STATUS"
echo "Unit Test Status: $UNIT_TEST_STATUS"
echo "Integration Test Status: $INTEGRATION_TEST_STATUS"
echo "Acceptance Test (Linux) Status: $ACCEPTANCE_LINUX_STATUS"
echo "Acceptance Test (Mac) Status: $ACCEPTANCE_MAC_STATUS"
echo "CLI Test (Linux) Status: $CLI_LINUX_STATUS"
false
release:
needs: [ check-gate, version-available ]
needs: [ quality-gate ]
if: ${{ github.event.inputs.phase == 'all' }}
# runs-on.com: compute instances for parallel builds
# spot disabled: reliability for build workflows (used for releases too)
@@ -65,13 +150,13 @@ jobs:
uses: ./.github/actions/bootstrap
- name: Login to Docker Hub
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 #v4.1.0
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 #v3.7.0
with:
username: ${{ secrets.ANCHOREOSSWRITE_DH_USERNAME }}
password: ${{ secrets.ANCHOREOSSWRITE_DH_PAT }}
- name: Login to GitHub Container Registry
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 #v4.1.0
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 #v3.7.0
with:
registry: ghcr.io
username: ${{ github.actor }}
@@ -101,14 +186,14 @@ jobs:
# for updating brew formula in anchore/homebrew-syft
GITHUB_BREW_TOKEN: ${{ secrets.ANCHOREOPS_GITHUB_OSS_WRITE_TOKEN }}
- uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 #v0.24.0
- uses: anchore/sbom-action@17ae1740179002c89186b61233e0f892c3118b11 #v0.23.0
continue-on-error: true
with:
file: go.mod
artifact-name: sbom.spdx.json
- name: Notify Slack of new release
uses: slackapi/slack-github-action@45a88b9581bfab2566dc881e2cd66d334e621e2c #v3.0.3
uses: slackapi/slack-github-action@91efab103c0de0a537f72a35f6b8cda0ee76bf0a #v2.1.1
continue-on-error: true
with:
webhook: ${{ secrets.SLACK_TOOLBOX_WEBHOOK_URL }}
@@ -0,0 +1,15 @@
name: "Manage Awaiting Response Label"
on:
issue_comment:
types: [created]
jobs:
run:
permissions:
contents: read
issues: write
pull-requests: write
uses: "anchore/workflows/.github/workflows/remove-awaiting-response-label.yaml@main"
secrets:
token: ${{ secrets.OSS_PROJECT_GH_TOKEN }}
@@ -0,0 +1,48 @@
name: "Test fixture cache: publish"
on:
workflow_dispatch:
schedule:
# run nightly at 4AM UTC
- cron: "0 4 * * *"
permissions:
contents: read
jobs:
Publish:
name: "Publish test fixture image cache"
# runs-on.com: general purpose instances for building test fixture cache
# spot disabled: can run up to an hour, avoid interruptions
# s3-cache: faster actions cache
# family: note that m*d instances have local nvme storage which is beneficial for building large test fixture images
runs-on: "runs-on=${{ github.run_id }}/cpu=8+16/ram=32+64/family=m5d+m5ad+m5dn+m6gd+m6id+m6idn+m7gd+m8gd/spot=false/extras=s3-cache"
if: github.repository == 'anchore/syft' # only run for main repo
permissions:
packages: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
with:
persist-credentials: false
- name: Bootstrap environment
uses: ./.github/actions/bootstrap
with:
# we want to rebuild the cache with no previous state
download-test-fixture-cache: false
- name: Run all tests
run: |
make test
env:
# we want to rebuild the cache with no previous state
DOWNLOAD_TEST_FIXTURE_CACHE: "false"
- name: Login to GitHub Container Registry (ORAS)
run: echo "${{ secrets.GITHUB_TOKEN }}" | .tool/oras login ghcr.io -u "$ACTOR" --password-stdin
env:
ACTOR: ${{ github.actor }}
- name: Publish test fixture cache
run: make upload-test-fixture-cache
@@ -0,0 +1,51 @@
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 }}
@@ -0,0 +1,67 @@
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 }}
@@ -0,0 +1,84 @@
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' }}
@@ -0,0 +1,64 @@
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' }}
@@ -1,8 +1,10 @@
name: "Validate GitHub Actions"
on:
workflow_dispatch:
pull_request:
paths:
- '.github/workflows/**'
- '.github/actions/**'
push:
branches:
- main
@@ -10,7 +12,8 @@ on:
- '.github/workflows/**'
- '.github/actions/**'
permissions: {}
permissions:
contents: read
jobs:
zizmor:
@@ -25,8 +28,9 @@ jobs:
persist-credentials: false
- name: "Run zizmor"
uses: zizmorcore/zizmor-action@b1d7e1fb5de872772f31590499237e7cce841e8e # v0.5.3
uses: zizmorcore/zizmor-action@0dce2577a4760a2749d8cfb7a84b7d5585ebcb7d # v0.5.0
with:
# there is a pass/fail gate as a repo ruleset (if there is no ruleset configured then the action will pass by default)
advanced-security: true
config: .github/zizmor.yml
# Disable SARIF upload so the step is a simple pass/fail gate
advanced-security: false
inputs: .github
+10 -23
View File
@@ -13,7 +13,8 @@ on:
branches:
- main
permissions: {}
permissions:
contents: read
jobs:
Static-Analysis:
@@ -24,8 +25,6 @@ jobs:
# s3-cache: faster actions cache
# tmpfs: faster io-intensive workflows
runs-on: &test-runner "runs-on=${{ github.run_id }}/cpu=4+8/ram=32+128/family=r5+r6+r7+r8+m4+m5+m6+m7+m8/spot=price-capacity-optimized/extras=s3-cache+tmpfs"
permissions:
contents: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
with:
@@ -46,8 +45,6 @@ jobs:
# Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline
name: "Unit tests"
runs-on: *test-runner
permissions:
contents: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
with:
@@ -68,8 +65,6 @@ jobs:
# Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline
name: "Integration tests"
runs-on: *test-runner
permissions:
contents: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
with:
@@ -94,11 +89,9 @@ jobs:
# s3-cache: faster actions cache
# tmpfs: faster io-intensive workflows
runs-on: "runs-on=${{ github.run_id }}/cpu=16+32/ram=32+128/family=c5+c6+c7+c8/spot=false/extras=s3-cache+tmpfs"
permissions:
contents: read
steps:
# required for magic-cache from runs-on to function with artifact upload/download (see https://runs-on.com/caching/magic-cache/#actionsupload-artifact-compatibility)
- uses: runs-on/action@742bf56072eb4845a0f94b3394673e4903c90ff0 # v2.1.0
- uses: runs-on/action@cd2b598b0515d39d78c38a02d529db87d2196d1e # v2.0.3
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
with:
@@ -116,7 +109,7 @@ jobs:
run: make snapshot-smoke-test
- name: Upload snapshot artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a #v7.0.1
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f #v7.0.0
with:
name: snapshot
path: snapshot/
@@ -127,11 +120,9 @@ jobs:
name: "Acceptance tests (Linux)"
needs: [Build-Snapshot-Artifacts]
runs-on: *test-runner
permissions:
contents: read
steps:
# required for magic-cache from runs-on to function with artifact upload/download (see https://runs-on.com/caching/magic-cache/#actionsupload-artifact-compatibility)
- uses: runs-on/action@742bf56072eb4845a0f94b3394673e4903c90ff0 # v2.1.0
- uses: runs-on/action@cd2b598b0515d39d78c38a02d529db87d2196d1e # v2.0.3
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
with:
@@ -143,7 +134,7 @@ jobs:
download-test-fixture-cache: true
- name: Download snapshot artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c #v8.0.1
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 #v8.0.0
with:
name: snapshot
path: snapshot
@@ -171,11 +162,9 @@ jobs:
needs: [Build-Snapshot-Artifacts]
# note: macos runners aren't supported yet for runs-on managed runners.
runs-on: macos-latest
permissions:
contents: read
steps:
- name: Install Cosign
uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4.1.1
uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
with:
@@ -189,7 +178,7 @@ jobs:
download-test-fixture-cache: true
- name: Download snapshot artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c #v8.0.1
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 #v8.0.0
with:
name: snapshot
path: snapshot
@@ -208,11 +197,9 @@ jobs:
name: "CLI tests (Linux)"
needs: [Build-Snapshot-Artifacts]
runs-on: *test-runner
permissions:
contents: read
steps:
# required for magic-cache from runs-on to function with artifact upload/download (see https://runs-on.com/caching/magic-cache/#actionsupload-artifact-compatibility)
- uses: runs-on/action@742bf56072eb4845a0f94b3394673e4903c90ff0 # v2.1.0
- uses: runs-on/action@cd2b598b0515d39d78c38a02d529db87d2196d1e # v2.0.3
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
with:
@@ -224,7 +211,7 @@ jobs:
download-test-fixture-cache: true
- name: Download snapshot artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c #v8.0.1
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 #v8.0.0
with:
name: snapshot
path: snapshot
+1 -55
View File
@@ -14,7 +14,7 @@ builds:
dir: ./cmd/syft
binary: syft
goos: [linux]
goarch: [amd64, arm64, ppc64le, riscv64, s390x]
goarch: [amd64, arm64, ppc64le, s390x]
mod_timestamp: &build-timestamp '{{ .CommitTimestamp }}'
ldflags: &build-ldflags |
-w
@@ -114,20 +114,6 @@ dockers:
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
- image_templates:
- anchore/syft:{{.Tag}}-riscv64
- ghcr.io/anchore/syft:{{.Tag}}-riscv64
goarch: riscv64
dockerfile: Dockerfile
use: buildx
build_flag_templates:
- "--platform=linux/riscv64"
- "--build-arg=DEBIAN_VERSION=13"
- "--build-arg=BUILD_DATE={{.Date}}"
- "--build-arg=BUILD_VERSION={{.Version}}"
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
- image_templates:
- anchore/syft:{{.Tag}}-s390x
- ghcr.io/anchore/syft:{{.Tag}}-s390x
@@ -181,20 +167,6 @@ dockers:
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
- image_templates:
- anchore/syft:{{.Tag}}-nonroot-riscv64
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-riscv64
goarch: riscv64
dockerfile: Dockerfile.nonroot
use: buildx
build_flag_templates:
- "--platform=linux/riscv64"
- "--build-arg=DEBIAN_VERSION=13"
- "--build-arg=BUILD_DATE={{.Date}}"
- "--build-arg=BUILD_VERSION={{.Version}}"
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
- image_templates:
- anchore/syft:{{.Tag}}-nonroot-s390x
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-s390x
@@ -248,20 +220,6 @@ dockers:
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
- image_templates:
- anchore/syft:{{.Tag}}-debug-riscv64
- ghcr.io/anchore/syft:{{.Tag}}-debug-riscv64
goarch: riscv64
dockerfile: Dockerfile.debug
use: buildx
build_flag_templates:
- "--platform=linux/riscv64"
- "--build-arg=DEBIAN_VERSION=13"
- "--build-arg=BUILD_DATE={{.Date}}"
- "--build-arg=BUILD_VERSION={{.Version}}"
- "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}"
- image_templates:
- anchore/syft:{{.Tag}}-debug-s390x
- ghcr.io/anchore/syft:{{.Tag}}-debug-s390x
@@ -281,7 +239,6 @@ docker_manifests:
- anchore/syft:{{.Tag}}-amd64
- anchore/syft:{{.Tag}}-arm64v8
- anchore/syft:{{.Tag}}-ppc64le
- anchore/syft:{{.Tag}}-riscv64
- anchore/syft:{{.Tag}}-s390x
- name_template: ghcr.io/anchore/syft:latest
@@ -289,7 +246,6 @@ docker_manifests:
- ghcr.io/anchore/syft:{{.Tag}}-amd64
- ghcr.io/anchore/syft:{{.Tag}}-arm64v8
- ghcr.io/anchore/syft:{{.Tag}}-ppc64le
- ghcr.io/anchore/syft:{{.Tag}}-riscv64
- ghcr.io/anchore/syft:{{.Tag}}-s390x
- name_template: anchore/syft:{{.Tag}}
@@ -297,7 +253,6 @@ docker_manifests:
- anchore/syft:{{.Tag}}-amd64
- anchore/syft:{{.Tag}}-arm64v8
- anchore/syft:{{.Tag}}-ppc64le
- anchore/syft:{{.Tag}}-riscv64
- anchore/syft:{{.Tag}}-s390x
- name_template: ghcr.io/anchore/syft:{{.Tag}}
@@ -305,7 +260,6 @@ docker_manifests:
- ghcr.io/anchore/syft:{{.Tag}}-amd64
- ghcr.io/anchore/syft:{{.Tag}}-arm64v8
- ghcr.io/anchore/syft:{{.Tag}}-ppc64le
- ghcr.io/anchore/syft:{{.Tag}}-riscv64
- ghcr.io/anchore/syft:{{.Tag}}-s390x
# nonroot images...
@@ -314,7 +268,6 @@ docker_manifests:
- anchore/syft:{{.Tag}}-nonroot-amd64
- anchore/syft:{{.Tag}}-nonroot-arm64v8
- anchore/syft:{{.Tag}}-nonroot-ppc64le
- anchore/syft:{{.Tag}}-nonroot-riscv64
- anchore/syft:{{.Tag}}-nonroot-s390x
- name_template: ghcr.io/anchore/syft:nonroot
@@ -322,7 +275,6 @@ docker_manifests:
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-amd64
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-arm64v8
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-ppc64le
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-riscv64
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-s390x
- name_template: anchore/syft:{{.Tag}}-nonroot
@@ -330,7 +282,6 @@ docker_manifests:
- anchore/syft:{{.Tag}}-nonroot-amd64
- anchore/syft:{{.Tag}}-nonroot-arm64v8
- anchore/syft:{{.Tag}}-nonroot-ppc64le
- anchore/syft:{{.Tag}}-nonroot-riscv64
- anchore/syft:{{.Tag}}-nonroot-s390x
- name_template: ghcr.io/anchore/syft:{{.Tag}}-nonroot
@@ -338,7 +289,6 @@ docker_manifests:
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-amd64
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-arm64v8
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-ppc64le
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-riscv64
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-s390x
# debug images...
@@ -347,7 +297,6 @@ docker_manifests:
- anchore/syft:{{.Tag}}-debug-amd64
- anchore/syft:{{.Tag}}-debug-arm64v8
- anchore/syft:{{.Tag}}-debug-ppc64le
- anchore/syft:{{.Tag}}-debug-riscv64
- anchore/syft:{{.Tag}}-debug-s390x
- name_template: ghcr.io/anchore/syft:debug
@@ -355,7 +304,6 @@ docker_manifests:
- ghcr.io/anchore/syft:{{.Tag}}-debug-amd64
- ghcr.io/anchore/syft:{{.Tag}}-debug-arm64v8
- ghcr.io/anchore/syft:{{.Tag}}-debug-ppc64le
- ghcr.io/anchore/syft:{{.Tag}}-debug-riscv64
- ghcr.io/anchore/syft:{{.Tag}}-debug-s390x
- name_template: anchore/syft:{{.Tag}}-debug
@@ -363,7 +311,6 @@ docker_manifests:
- anchore/syft:{{.Tag}}-debug-amd64
- anchore/syft:{{.Tag}}-debug-arm64v8
- anchore/syft:{{.Tag}}-debug-ppc64le
- anchore/syft:{{.Tag}}-debug-riscv64
- anchore/syft:{{.Tag}}-debug-s390x
- name_template: ghcr.io/anchore/syft:{{.Tag}}-debug
@@ -371,7 +318,6 @@ docker_manifests:
- ghcr.io/anchore/syft:{{.Tag}}-debug-amd64
- ghcr.io/anchore/syft:{{.Tag}}-debug-arm64v8
- ghcr.io/anchore/syft:{{.Tag}}-debug-ppc64le
- ghcr.io/anchore/syft:{{.Tag}}-debug-riscv64
- ghcr.io/anchore/syft:{{.Tag}}-debug-s390x
sboms:
+1 -2
View File
@@ -1,5 +1,4 @@
ARG DEBIAN_VERSION=12
FROM gcr.io/distroless/static-debian${DEBIAN_VERSION}:latest AS build
FROM gcr.io/distroless/static-debian12:latest AS build
FROM scratch
# needed for version check HTTPS request
+1 -2
View File
@@ -1,5 +1,4 @@
ARG DEBIAN_VERSION=12
FROM gcr.io/distroless/static-debian${DEBIAN_VERSION}:debug-nonroot
FROM gcr.io/distroless/static-debian12:debug-nonroot
# create the /tmp dir, which is needed for image content cache
WORKDIR /tmp
+1 -2
View File
@@ -1,5 +1,4 @@
ARG DEBIAN_VERSION=12
FROM gcr.io/distroless/static-debian${DEBIAN_VERSION}:nonroot
FROM gcr.io/distroless/static-debian12:nonroot
# create the /tmp dir, which is needed for image content cache
WORKDIR /tmp
-20
View File
@@ -14,23 +14,3 @@ affected versions, and, if known, mitigations for the issue.
All support will be made on a best effort basis, so please indicate the "urgency level" of the vulnerability as Critical, High, Medium or Low.
For more details, see our [security policy documentation](https://oss.anchore.com/docs/contributing/security/).
## Trust Boundary
Syft is a tool to scan content and product an SBOM. Syft is not a tool designed to scan malicious content. Detecting and properly reporting on purposely malicious artifacts is outside the scope of Syft's expected operating environment.
There are many possible ways for malicious content to cause Syft to become confused or fail to include results in an SBOM. We do not consider this to be a security vulnerability.
**Examples**
- Removing or altering a package lock file
- Removing or altering an RPM or DEB database
- A malicious archive that Syft will skip but the runtime may not
- Self modifying systems that change state when running
We consider the security trust boundary for Syft to be anything that causes problems for the overall system running Syft, or Syft operating in a way that is dangerous to itself, the system, or the operator.
**Examples**
- Filling up temp space permanently
- Syft executing arbitrary code when scanning an artifact
- Syft leaking secrets from the environment or configuration files into logs or SBOMs
- Syft operating outside of the expected artifact or directory (directory traversal)
+2 -3
View File
@@ -10,8 +10,7 @@ vars:
# v1: when fixtures were located at test-fixtures dirs
# v2: migration to testdata dirs
CACHE_REPO: oss-cache
CACHE_IMAGE: ghcr.io/{{ .OWNER }}/{{ .CACHE_REPO }}/syft-test-fixture-cache:v2
CACHE_IMAGE: ghcr.io/{{ .OWNER }}/{{ .PROJECT }}/test-fixture-cache:v2
# static file dirs
TOOL_DIR: .tool
@@ -425,7 +424,7 @@ tasks:
done
oras_command+=" {{ .CACHE_PATHS_FILE }}"
oras_command+=" --annotation org.opencontainers.image.source=https://github.com/{{ .OWNER }}/{{ .CACHE_REPO }}"
oras_command+=" --annotation org.opencontainers.image.source=https://github.com/{{ .OWNER }}/{{ .PROJECT }}"
oras_command+=" --annotation fingerprint=$(cat {{ .CACHE_PATHS_FILE }} | {{ .YQ }} -r '.digest')"
echo "Executing: $oras_command"
+1
View File
@@ -48,6 +48,7 @@ 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 -1
View File
@@ -59,7 +59,7 @@ func Attest(app clio.Application) *cobra.Command {
Use: "attest --output [FORMAT] <IMAGE>",
Short: "Generate an SBOM as an attestation for the given [SOURCE] container image",
Long: "Generate a packaged-based Software Bill Of Materials (SBOM) from a container image as the predicate of an in-toto attestation that will be uploaded to the image registry",
Example: internal.Tprintf(attestHelp, map[string]any{
Example: internal.Tprintf(attestHelp, map[string]interface{}{
"appName": id.Name,
"command": "attest",
}),
+9 -5
View File
@@ -5,7 +5,6 @@ import (
"encoding/json"
"fmt"
"os"
"slices"
"sort"
"strings"
@@ -249,7 +248,12 @@ func renderCatalogerInfoJSON(doc *capabilities.Document, catalogers []capabiliti
// isDeprecatedCataloger checks if a cataloger is deprecated based on its selectors
func isDeprecatedCataloger(selectors []string) bool {
return slices.Contains(selectors, "deprecated")
for _, selector := range selectors {
if selector == "deprecated" {
return true
}
}
return false
}
// convertDetectorPackages converts detector package info to the JSON output format
@@ -448,7 +452,7 @@ func extractArrayCapability(caps capabilities.CapabilitySet, name string) string
if len(v) > 0 {
return strings.Join(v, ", ")
}
case []any:
case []interface{}:
if len(v) > 0 {
strs := make([]string, 0, len(v))
for _, item := range v {
@@ -471,7 +475,7 @@ func extractNodesCapability(caps capabilities.CapabilitySet) string {
switch v := cap.Default.(type) {
case []string:
return formatDepthStringArray(v)
case []any:
case []interface{}:
return formatDepthInterfaceArray(v)
}
return noStyle.Render("·")
@@ -492,7 +496,7 @@ func formatDepthStringArray(v []string) string {
}
// formatDepthInterfaceArray formats a []interface{} dependency depth value
func formatDepthInterfaceArray(v []any) string {
func formatDepthInterfaceArray(v []interface{}) string {
if len(v) == 0 {
return noStyle.Render("·")
}
+1 -1
View File
@@ -40,7 +40,7 @@ func Convert(app clio.Application) *cobra.Command {
Use: "convert [SOURCE-SBOM] -o [FORMAT]",
Short: "Convert between SBOM formats",
Long: "[Experimental] Convert SBOM files to, and from, SPDX, CycloneDX and Syft's format. For more info about data loss between formats see https://github.com/anchore/syft/wiki/format-conversion",
Example: internal.Tprintf(convertExample, map[string]any{
Example: internal.Tprintf(convertExample, map[string]interface{}{
"appName": id.Name,
"command": "convert",
}),
+1 -1
View File
@@ -18,7 +18,7 @@ func Packages(app clio.Application, scanCmd *cobra.Command) *cobra.Command {
Short: scanCmd.Short,
Long: scanCmd.Long,
Args: scanCmd.Args,
Example: internal.Tprintf(scanHelp, map[string]any{
Example: internal.Tprintf(scanHelp, map[string]interface{}{
"appName": id.Name,
"command": "packages",
}),
+6 -6
View File
@@ -29,7 +29,7 @@ func Test_filterExpressionErrors_expressionErrorsHelp(t *testing.T) {
{
name: "single non-expression error is retained",
err: errors.New("foo"),
wantErr: func(t assert.TestingT, err error, i ...any) bool {
wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
return assert.Equal(t, "foo", err.Error())
},
wantHelp: "",
@@ -42,7 +42,7 @@ func Test_filterExpressionErrors_expressionErrorsHelp(t *testing.T) {
err = multierror.Append(err, errors.New("bar"))
return err
}(),
wantErr: func(t assert.TestingT, err error, i ...any) bool {
wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
// note: this is the default formatting from the hashicorp multierror object
expected := `2 errors occurred:
* foo
@@ -64,7 +64,7 @@ func Test_filterExpressionErrors_expressionErrorsHelp(t *testing.T) {
err = multierror.Append(err, errors.New("last"))
return err
}(),
wantErr: func(t assert.TestingT, err error, i ...any) bool {
wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
expected := `5 errors occurred:
* foo
* invalid expression: "foo": tags are not allowed with this operation (must use exact names)
@@ -103,7 +103,7 @@ func Test_filterExpressionErrors_expressionErrorsHelp(t *testing.T) {
return fmt.Errorf("top: %w", fmt.Errorf("middle: %w", err))
}(),
wantErr: func(t assert.TestingT, err error, i ...any) bool {
wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
expected := `top: middle: 4 errors occurred:
* foo: bar: last
* invalid expression: "foo": tags are not allowed with this operation (must use exact names)
@@ -142,7 +142,7 @@ func Test_filterExpressionErrors_expressionErrorsHelp(t *testing.T) {
// note we wrap the top error in a chain
return fmt.Errorf("top: %w", fmt.Errorf("middle: %w", err))
}(),
wantErr: func(t assert.TestingT, err error, i ...any) bool {
wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
expected := `top: middle: 4 errors occurred:
* foo: bar: last
* invalid expression: "foo": tags are not allowed with this operation (must use exact names)
@@ -178,7 +178,7 @@ func Test_filterExpressionErrors_expressionErrorsHelp(t *testing.T) {
return err
}(),
wantErr: func(t assert.TestingT, err error, i ...any) bool {
wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
// note: the errors are removed and the help text shows the enriched error help
expected := `2 errors occurred:
* invalid expression: "foo": tags are not allowed with this operation (must use exact names)
+1 -1
View File
@@ -92,7 +92,7 @@ func Scan(app clio.Application) *cobra.Command {
Use: "scan [SOURCE]",
Short: "Generate an SBOM",
Long: "Generate a packaged-based Software Bill Of Materials (SBOM) from container images and filesystems",
Example: internal.Tprintf(scanHelp, map[string]any{
Example: internal.Tprintf(scanHelp, map[string]interface{}{
"appName": id.Name,
"command": "scan",
}),
+1 -1
View File
@@ -60,7 +60,7 @@ func Test_scanOptions_validateLegacyOptionsNotUsed(t *testing.T) {
}
func assertErrorContains(contains string) assert.ErrorAssertionFunc {
return func(t assert.TestingT, err error, i ...any) bool {
return func(t assert.TestingT, err error, i ...interface{}) bool {
return assert.ErrorContains(t, err, contains, i...)
}
}
+264
View File
@@ -0,0 +1,264 @@
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
}
+119
View File
@@ -0,0 +1,119 @@
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")
}
+1 -1
View 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(io.LimitReader(resp.Body, 500))
versionBytes, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read latest version: %w", err)
}
+4 -3
View File
@@ -2,7 +2,6 @@ package options
import (
"fmt"
"slices"
"sort"
"strings"
@@ -314,8 +313,10 @@ func enrichmentEnabled(enrichDirectives []string, features ...string) *bool {
directive = directive[1:]
enable = false
}
if slices.Contains(features, directive) {
return &enable
for _, feature := range features {
if directive == feature {
return &enable
}
}
}
return nil
+1 -1
View File
@@ -32,7 +32,7 @@ func Test_MakeSBOMWriter(t *testing.T) {
{
name: "unknown format",
outputs: []string{"unknown"},
wantErr: func(t assert.TestingT, err error, bla ...any) bool {
wantErr: func(t assert.TestingT, err error, bla ...interface{}) bool {
return assert.ErrorContains(t, err, `unsupported output format "unknown", supported formats are:`)
},
},
+307
View File
@@ -0,0 +1,307 @@
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)
}
+499
View File
@@ -0,0 +1,499 @@
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)
})
}
}
+92
View File
@@ -0,0 +1,92 @@
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
}
+124
View File
@@ -0,0 +1,124 @@
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)
}
})
}
}
@@ -0,0 +1,158 @@
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
}
+314
View File
@@ -0,0 +1,314 @@
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
}
+606
View File
@@ -0,0 +1,606 @@
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)
})
}
}
@@ -2,7 +2,6 @@ package integration
import (
"context"
"slices"
"strings"
"testing"
@@ -195,8 +194,11 @@ func assertPackages(t *testing.T, sbom sbom.SBOM, test testCase, observedLanguag
}
var foundLang bool
if slices.Contains(strings.Split(test.pkgLanguage.String(), ","), actualPkg.Language.String()) {
foundLang = true
for _, lang := range strings.Split(test.pkgLanguage.String(), ",") {
if actualPkg.Language.String() == lang {
foundLang = true
break
}
}
if !foundLang {
t.Errorf("bad language (pkg=%+v): %+v", actualPkg.Name, actualPkg.Language)
@@ -329,7 +329,7 @@ func packageCatalogerExports(t *testing.T) map[string]exportTokenSet {
}
exportsPerPackage[pkg].Add(exportToken{
Name: decl.Name.Name,
Type: reflect.TypeFor[*ast.FuncType]().String(),
Type: reflect.TypeOf(decl.Type).String(),
SignatureSize: len(decl.Type.Params.List),
ReturnTypeNames: returnTypes,
})
+12 -12
View File
@@ -36,33 +36,33 @@ func Test_logWriter(t *testing.T) {
type bufferLogger struct{ values []any }
func (l *bufferLogger) Tracef(_ string, _ ...any) {}
func (l *bufferLogger) Tracef(_ string, _ ...interface{}) {}
func (l *bufferLogger) Debugf(_ string, _ ...any) {}
func (l *bufferLogger) Debugf(_ string, _ ...interface{}) {}
func (l *bufferLogger) Infof(_ string, _ ...any) {}
func (l *bufferLogger) Infof(_ string, _ ...interface{}) {}
func (l *bufferLogger) Warnf(_ string, _ ...any) {}
func (l *bufferLogger) Warnf(_ string, _ ...interface{}) {}
func (l *bufferLogger) Errorf(_ string, _ ...any) {}
func (l *bufferLogger) Errorf(_ string, _ ...interface{}) {}
func (l *bufferLogger) Trace(vals ...any) {
func (l *bufferLogger) Trace(vals ...interface{}) {
l.values = append(l.values, vals...)
}
func (l *bufferLogger) Debug(_ ...any) {}
func (l *bufferLogger) Debug(_ ...interface{}) {}
func (l *bufferLogger) Info(_ ...any) {}
func (l *bufferLogger) Info(_ ...interface{}) {}
func (l *bufferLogger) Warn(vals ...any) {
func (l *bufferLogger) Warn(vals ...interface{}) {
l.values = append(l.values, vals...)
}
func (l *bufferLogger) Error(_ ...any) {}
func (l *bufferLogger) Error(_ ...interface{}) {}
func (l *bufferLogger) WithFields(_ ...any) logger.MessageLogger { return l }
func (l *bufferLogger) WithFields(_ ...interface{}) logger.MessageLogger { return l }
func (l *bufferLogger) Nested(_ ...any) logger.Logger { return l }
func (l *bufferLogger) Nested(_ ...interface{}) logger.Logger { return l }
func (l *bufferLogger) SetOutput(_ io.Writer) {}
@@ -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) //nolint:gocritic // example code
version, err := io.ReadAll(reader)
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) //nolint:gocritic // example code
content, err := io.ReadAll(reader)
if err != nil {
return nil, nil, fmt.Errorf("unable to read apk key content at %s: %w", location.RealPath, err)
}
+128 -118
View File
@@ -1,6 +1,6 @@
module github.com/anchore/syft
go 1.25.8
go 1.25.6
require (
github.com/BurntSushi/toml v1.6.0
@@ -11,24 +11,22 @@ require (
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.2.0
github.com/anchore/clio v0.1.0
github.com/anchore/fangs v0.1.0
github.com/anchore/go-collections v0.1.0
github.com/anchore/go-homedir v0.1.0
github.com/anchore/go-logger v0.1.0
github.com/anchore/go-macholibre v0.1.0
github.com/anchore/go-rpmdb v0.1.0
github.com/anchore/go-sync v0.1.0
github.com/anchore/bubbly v0.0.0-20231115134915-def0aba654a9
github.com/anchore/clio v0.0.0-20250319180342-2cfe4b0cb716
github.com/anchore/fangs v0.0.0-20250319222917-446a1e748ec2
github.com/anchore/go-collections v0.0.0-20251016125210-a3c352120e8c
github.com/anchore/go-homedir v0.0.0-20250319154043-c29668562e4d
github.com/anchore/go-logger v0.0.0-20250318195838-07ae343dd722
github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb
github.com/anchore/go-rpmdb v0.0.0-20250516171929-f77691e1faec
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.2.0
github.com/anchore/stereoscope v0.1.23
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be
github.com/anchore/packageurl-go v0.1.1-0.20250220190351-d62adb6e1115
github.com/anchore/stereoscope v0.1.21
github.com/aquasecurity/go-pep440-version v0.0.1
github.com/bitnami/go-version v0.0.0-20250131085805-b1f57a8634ef
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb
github.com/bmatcuk/doublestar/v4 v4.10.0
github.com/cespare/xxhash/v2 v2.3.0
github.com/charmbracelet/bubbles v1.0.0
github.com/charmbracelet/bubbletea v1.3.10
github.com/charmbracelet/lipgloss v1.1.0
@@ -40,21 +38,19 @@ require (
github.com/elliotchance/phpserialize v1.4.0
github.com/facebookincubator/nvdtools v0.1.5
github.com/github/go-spdx/v2 v2.4.0
github.com/gkampitakis/go-snaps v0.5.21
github.com/go-git/go-billy/v5 v5.9.0
github.com/go-git/go-git/v5 v5.19.0
github.com/gkampitakis/go-snaps v0.5.20
github.com/go-git/go-billy/v5 v5.8.0
github.com/go-git/go-git/v5 v5.17.0
github.com/go-test/deep v1.1.1
github.com/go-viper/mapstructure/v2 v2.5.0
github.com/goccy/go-yaml v1.19.2
github.com/gohugoio/hashstructure v0.6.0
github.com/google/go-cmp v0.7.0
github.com/google/go-containerregistry v0.21.5
github.com/google/go-containerregistry v0.21.1
github.com/google/licensecheck v0.3.1
github.com/google/uuid v1.6.0
github.com/gookit/color v1.6.0
github.com/gpustack/gguf-parser-go v0.24.0
github.com/hashicorp/go-cleanhttp v0.5.2
github.com/hashicorp/go-getter v1.8.6
github.com/hashicorp/go-getter v1.8.4
github.com/hashicorp/go-multierror v1.1.1
github.com/hashicorp/hcl/v2 v2.24.0
github.com/iancoleman/strcase v0.3.0
@@ -62,12 +58,11 @@ require (
github.com/jedib0t/go-pretty/v6 v6.7.8
github.com/jinzhu/copier v0.4.0
github.com/kastenhq/goversion v0.0.0-20230811215019-93b2f8823953
github.com/klauspost/compress v1.18.5
github.com/magiconair/properties v1.8.10
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.4
github.com/olekukonko/tablewriter v1.1.3
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
@@ -83,111 +78,72 @@ require (
github.com/spf13/afero v1.15.0
github.com/spf13/cobra v1.10.2
github.com/stretchr/testify v1.11.1
github.com/ulikunitz/xz v0.5.15
github.com/vbatts/go-mtree v0.7.0
github.com/vifraa/gopom v1.0.0
github.com/wagoodman/go-partybus v0.0.0-20230516145632-8ccac152c651
github.com/wagoodman/go-progress v0.0.0-20260303201901-10176f79b2c0
github.com/wk8/go-ordered-map/v2 v2.1.8
github.com/xeipuuv/gojsonschema v1.2.0
github.com/zyedidia/generic v1.2.2-0.20230320175451-4410d2372cb1
go.uber.org/goleak v1.3.0
go.yaml.in/yaml/v3 v3.0.4
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f
golang.org/x/mod v0.35.0
golang.org/x/net v0.53.0
golang.org/x/time v0.15.0
golang.org/x/tools v0.44.0
gopkg.in/yaml.v3 v3.0.1
modernc.org/sqlite v1.46.2
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546
golang.org/x/mod v0.33.0
golang.org/x/net v0.51.0
modernc.org/sqlite v1.46.1
)
require (
cel.dev/expr v0.25.1 // indirect
cloud.google.com/go v0.123.0 // indirect
cloud.google.com/go/auth v0.18.2 // indirect
cloud.google.com/go/auth v0.17.0 // 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/monitoring v1.24.3 // indirect
cloud.google.com/go/storage v1.61.3 // indirect
cyphar.com/go-pathrs v0.2.1 // indirect
cloud.google.com/go/storage v1.58.0 // indirect
dario.cat/mergo v1.0.2 // indirect
github.com/DataDog/zstd v1.5.5 // 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/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.4.0 // indirect
github.com/ProtonMail/go-crypto v1.3.0 // indirect
github.com/STARRY-S/zip v0.2.3 // indirect
github.com/agext/levenshtein v1.2.1 // indirect
github.com/agext/levenshtein v1.2.1 // indirect; indirectt
github.com/anchore/go-lzo v0.1.0 // indirect
github.com/anchore/go-struct-converter v0.1.0 // indirect
github.com/andybalholm/brotli v1.2.0 // indirect
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/aquasecurity/go-version v0.0.1 // indirect
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aws/aws-sdk-go-v2 v1.41.5 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 // indirect
github.com/aws/aws-sdk-go-v2/config v1.32.12 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.19.12 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21 // indirect
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3 // indirect
github.com/aws/aws-sdk-go-v2/service/signin v1.0.8 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.30.13 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.41.9 // indirect
github.com/aws/smithy-go v1.24.2 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/becheran/wildmatch-go v1.0.0 // indirect
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
github.com/bodgit/plumbing v1.3.0 // indirect
github.com/bodgit/sevenzip v1.6.1 // indirect
github.com/bodgit/windows v1.0.1 // indirect
github.com/buger/jsonparser v1.1.2 // indirect
github.com/charmbracelet/colorprofile v0.4.1 // indirect
github.com/charmbracelet/harmonica v0.2.0 // indirect
github.com/charmbracelet/x/ansi v0.11.6 // indirect
github.com/charmbracelet/x/cellbuf v0.0.15 // indirect
github.com/charmbracelet/x/term v0.2.2 // indirect
github.com/clipperhouse/displaywidth v0.10.0 // indirect
github.com/clipperhouse/uax29/v2 v2.6.0 // indirect
github.com/cloudflare/circl v1.6.3 // 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/api v1.10.0 // indirect
github.com/containerd/containerd/v2 v2.2.2 // indirect
github.com/containerd/continuity v0.4.5 // indirect
github.com/containerd/errdefs v1.0.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/containerd/fifo v1.1.0 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/containerd/platforms v1.0.0-rc.4 // indirect
github.com/containerd/plugin v1.0.0 // indirect
github.com/containerd/platforms v1.0.0-rc.2 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.18.2 // indirect
github.com/containerd/ttrpc v1.2.7 // indirect
github.com/containerd/typeurl/v2 v2.2.3 // indirect
github.com/cyphar/filepath-securejoin v0.6.1 // 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.4.0+incompatible // indirect
github.com/docker/cli v29.2.1+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
github.com/docker/go-units v0.5.0 // indirect
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/envoyproxy/go-control-plane/envoy v1.36.0 // indirect
github.com/envoyproxy/protoc-gen-validate v1.3.0 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/felixge/fgprof v0.9.5 // indirect
@@ -196,32 +152,28 @@ require (
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
github.com/gkampitakis/ciinfo v0.3.2 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-jose/go-jose/v4 v4.1.4 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-restruct/restruct v1.2.0-alpha // indirect
github.com/goccy/go-yaml v1.19.2
github.com/gogo/protobuf v1.3.2 // indirect
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.14 // indirect
github.com/googleapis/gax-go/v2 v2.17.0 // indirect
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.72 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect
github.com/googleapis/gax-go/v2 v2.15.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
github.com/henvic/httpretty v0.1.4 // indirect
github.com/huandu/xstrings v1.5.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/klauspost/compress v1.18.4 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/maruel/natural v1.1.1 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
@@ -236,58 +188,48 @@ require (
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/locker v1.0.1 // indirect
github.com/moby/moby/api v1.54.1 // indirect
github.com/moby/moby/client v0.4.0 // indirect
github.com/moby/sys/sequential v0.6.0 // indirect
github.com/moby/sys/signal v0.7.1 // indirect
github.com/moby/sys/user v0.4.0 // indirect
github.com/moby/sys/userns v0.1.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/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/termenv v0.16.0 // indirect
github.com/ncruces/go-strftime v1.0.0 // 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.2.0 // indirect
github.com/olekukonko/ll v0.1.6 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/opencontainers/runtime-spec v1.3.0 // indirect
github.com/opencontainers/selinux v1.13.1 // indirect
github.com/pborman/indent v1.2.1 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pierrec/lz4/v4 v4.1.22 // indirect
github.com/pjbgf/sha1cd v0.6.0 // indirect
github.com/pjbgf/sha1cd v0.3.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pkg/profile v1.7.0 // indirect
github.com/pkg/xattr v0.4.9 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/sagikazarmark/locafero v0.11.0 // indirect
github.com/sagikazarmark/locafero v0.7.0 // indirect
github.com/sahilm/fuzzy v0.1.1 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/sirupsen/logrus v1.9.4 // indirect
github.com/skeema/knownhosts v1.3.1 // indirect
github.com/smallnest/ringbuffer v0.0.0-20241116012123-461381446e3d // indirect
github.com/sorairolake/lzip-go v0.3.8 // indirect
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
github.com/spf13/cast v1.10.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/cast v1.7.1 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/spf13/viper v1.21.0 // indirect
github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect
github.com/spf13/viper v1.20.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/sylabs/sif/v2 v2.24.0 // indirect
github.com/sylabs/sif/v2 v2.23.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
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/sjson v1.2.5 // indirect
github.com/ulikunitz/xz v0.5.15
github.com/vbatts/tar-split v0.12.2 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
@@ -297,35 +239,103 @@ require (
github.com/zclconf/go-cty v1.16.3 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.39.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
go.opentelemetry.io/otel v1.43.0 // indirect
go.opentelemetry.io/otel/metric v1.43.0 // indirect
go.opentelemetry.io/otel/sdk v1.43.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect
go.opentelemetry.io/otel/trace v1.43.0 // indirect
go.opentelemetry.io/otel v1.40.0 // indirect
go.opentelemetry.io/otel/metric v1.40.0 // indirect
go.opentelemetry.io/otel/trace v1.40.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
golang.org/x/crypto v0.50.0 // indirect
golang.org/x/oauth2 v0.36.0 // indirect
golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.43.0 // indirect
golang.org/x/term v0.42.0 // indirect
golang.org/x/text v0.36.0 // 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/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
gonum.org/v1/gonum v0.16.0 // indirect
google.golang.org/api v0.271.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-20260226221140-a57be14db171 // indirect
google.golang.org/grpc v1.79.3 // 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/protobuf v1.36.11 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
modernc.org/libc v1.70.0 // indirect
gopkg.in/yaml.v3 v3.0.1
modernc.org/libc v1.67.6 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
)
require (
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
github.com/wk8/go-ordered-map/v2 v2.1.8
)
require (
cel.dev/expr v0.24.0 // indirect
cloud.google.com/go/monitoring v1.24.2 // 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/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
github.com/aws/aws-sdk-go-v2/credentials v1.19.10 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.18 // indirect
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/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/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/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/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/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/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/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
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
)
retract (
v1.25.0 // published with a replace directive (confusing for API users)
v0.53.2
+216 -208
View File
@@ -1,5 +1,5 @@
cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4=
cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=
cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
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.18.2 h1:+Nbt5Ev0xEqxlNjd6c+yYUeosQ5TtEUaNcN/3FozlaM=
cloud.google.com/go/auth v0.18.2/go.mod h1:xD+oY7gcahcu7G2SG2DsBerfFxgPAJz17zz2joOFF3M=
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/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.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/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/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.61.3 h1:VS//ZfBuPGDvakfD9xyPW1RGF1Vy3BWUoVZXgW1KMOg=
cloud.google.com/go/storage v1.61.3/go.mod h1:JtqK8BBB7TWv0HVGHubtUdzYYrakOQIsMLffZ2Z/HWk=
cloud.google.com/go/trace v1.11.7 h1:kDNDX8JkaAG3R2nq1lIdkb7FCSi1rCmsEtKVsty7p+U=
cloud.google.com/go/trace v1.11.7/go.mod h1:TNn9d5V3fQVf6s4SCveVMIBS2LJUqo73GACmq/Tky0s=
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=
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.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/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/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.4.0 h1:Zq/pbM3F5DFgJiMouxEdSVY44MVoQNEKp5d5QxIQceQ=
github.com/ProtonMail/go-crypto v1.4.0/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo=
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/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=
@@ -122,34 +122,34 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/anchore/bubbly v0.2.0 h1:VE6nLlWsYV+bTOqqCzS46NirrVmvHah3UQDtFcjzgHU=
github.com/anchore/bubbly v0.2.0/go.mod h1:zRg1mfRCCe4vq1t9Ibu24YurErmnp9LAVCbpnpsb1pQ=
github.com/anchore/clio v0.1.0 h1:lrlSgD38V0T6TLjOG5Kn3miEXCQyYr6ZlwTSnW1WIJg=
github.com/anchore/clio v0.1.0/go.mod h1:z4l6oHaU8x/yQ3BnES4Ekh4eLqFSYlj1jQpk7waPkJs=
github.com/anchore/fangs v0.1.0 h1:EidxQN2XDRWn8q2n9pCVZXrOEnvpV1CuPLiWMyWG/vE=
github.com/anchore/fangs v0.1.0/go.mod h1:5YocMj6a0MC+KiBXBP0/ocOfc54DvlsOcpkAxmPWgx0=
github.com/anchore/go-collections v0.1.0 h1:vh2LE/j6HLKC27pENkas+J+h4BK4nOte9b7pDjlhY9w=
github.com/anchore/go-collections v0.1.0/go.mod h1:1aiktV46ATCkuVg0O573ZrH56BUawTECPETbZyBcqT8=
github.com/anchore/go-homedir v0.1.0 h1:WP+lNLOz2og8hzCkY7IccyFNInVKsrjogzV19G5G8p0=
github.com/anchore/go-homedir v0.1.0/go.mod h1:9B0DGhbmMAJVGGEbrlb+PkORM5eDFCEOtZ3xQ22qaLA=
github.com/anchore/go-logger v0.1.0 h1:3pE9Sli4SE1I/mtV+2pZXzim+aWj83BRaneVXeMgD3A=
github.com/anchore/go-logger v0.1.0/go.mod h1:0l63X5VyHqfWQwsNaTmkgXErsfohze3NAFCXFdhSjdQ=
github.com/anchore/bubbly v0.0.0-20231115134915-def0aba654a9 h1:p0ZIe0htYOX284Y4axJaGBvXHU0VCCzLN5Wf5XbKStU=
github.com/anchore/bubbly v0.0.0-20231115134915-def0aba654a9/go.mod h1:3ZsFB9tzW3vl4gEiUeuSOMDnwroWxIxJelOOHUp8dSw=
github.com/anchore/clio v0.0.0-20250319180342-2cfe4b0cb716 h1:2sIdYJlQESEnyk3Y0WD2vXWW5eD2iMz9Ev8fj1Z8LNA=
github.com/anchore/clio v0.0.0-20250319180342-2cfe4b0cb716/go.mod h1:Utb9i4kwiCWvqAIxZaJeMIXFO9uOgQXlvH2BfbfO/zI=
github.com/anchore/fangs v0.0.0-20250319222917-446a1e748ec2 h1:GC2QaO0YsmjpsZ4rtVKv9DnproIxqqn+qkskpc+i8MA=
github.com/anchore/fangs v0.0.0-20250319222917-446a1e748ec2/go.mod h1:XUbUECwVKuD3qYRUj+QZIOHjyyXua2gFmVjKA40iHXA=
github.com/anchore/go-collections v0.0.0-20251016125210-a3c352120e8c h1:eoJXyC0n7DZ4YvySG/ETdYkTar2Due7eH+UmLK6FbrA=
github.com/anchore/go-collections v0.0.0-20251016125210-a3c352120e8c/go.mod h1:1aiktV46ATCkuVg0O573ZrH56BUawTECPETbZyBcqT8=
github.com/anchore/go-homedir v0.0.0-20250319154043-c29668562e4d h1:gT69osH9AsdpOfqxbRwtxcNnSZ1zg4aKy2BevO3ZBdc=
github.com/anchore/go-homedir v0.0.0-20250319154043-c29668562e4d/go.mod h1:PhSnuFYknwPZkOWKB1jXBNToChBA+l0FjwOxtViIc50=
github.com/anchore/go-logger v0.0.0-20250318195838-07ae343dd722 h1:2SqmFgE7h+Ql4VyBzhjLkRF/3gDrcpUBj8LjvvO6OOM=
github.com/anchore/go-logger v0.0.0-20250318195838-07ae343dd722/go.mod h1:oFuE8YuTCM+spgMXhePGzk3asS94yO9biUfDzVTFqNw=
github.com/anchore/go-lzo v0.1.0 h1:NgAacnzqPeGH49Ky19QKLBZEuFRqtTG9cdaucc3Vncs=
github.com/anchore/go-lzo v0.1.0/go.mod h1:3kLx0bve2oN1iDwgM1U5zGku1Tfbdb0No5qp1eL1fIk=
github.com/anchore/go-macholibre v0.1.0 h1:qHbdusBZNcZM/uuKf1Psa9xxAFSoyRTps8GW9gpJgsg=
github.com/anchore/go-macholibre v0.1.0/go.mod h1:eu0gbwaZ+ocVFJLePdmPPDKU8MboV1MKsUCr36Ckd5s=
github.com/anchore/go-rpmdb v0.1.0 h1:Q8dc208/HYzCqhx0L1zurfm1UPil24hlo9NjkdFmLdE=
github.com/anchore/go-rpmdb v0.1.0/go.mod h1:eQVa6QFGzKy0qMcnW2pez0XBczvgwSjw9vA23qifEyU=
github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb h1:iDMnx6LIjtjZ46C0akqveX83WFzhpTD3eqOthawb5vU=
github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb/go.mod h1:DmTY2Mfcv38hsHbG78xMiTDdxFtkHpgYNVDPsF2TgHk=
github.com/anchore/go-rpmdb v0.0.0-20250516171929-f77691e1faec h1:SjjPMOXTzpuU1ZME4XeoHyek+dry3/C7I8gzaCo02eg=
github.com/anchore/go-rpmdb v0.0.0-20250516171929-f77691e1faec/go.mod h1:eQVa6QFGzKy0qMcnW2pez0XBczvgwSjw9vA23qifEyU=
github.com/anchore/go-struct-converter v0.1.0 h1:2rDRssAl6mgKBSLNiVCMADgZRhoqtw9dedlWa0OhD30=
github.com/anchore/go-struct-converter v0.1.0/go.mod h1:rYqSE9HbjzpHTI74vwPvae4ZVYZd1lue2ta6xHPdblA=
github.com/anchore/go-sync v0.1.0 h1:1TEZM7jISrvtoBMOF79xP0caQKASAtgW1yKqc0EjyZg=
github.com/anchore/go-sync v0.1.0/go.mod h1:Iposeub0kHipoTei1icj4Tys0SJN+cCdxEnkS7bZUFs=
github.com/anchore/go-sync v0.0.0-20250326131806-4eda43a485b6 h1:Ha+LSCVuXYSYGi7wIkJK6G8g6jI3LH7y6LbyEVyp4Io=
github.com/anchore/go-sync v0.0.0-20250326131806-4eda43a485b6/go.mod h1:+9oM3XUy8iea/vWj9FhZ9bQGUBN8JpPxxJm5Wbcx9XM=
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b h1:e1bmaoJfZVsCYMrIZBpFxwV26CbsuoEh5muXD5I1Ods=
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b/go.mod h1:Bkc+JYWjMCF8OyZ340IMSIi2Ebf3uwByOk6ho4wne1E=
github.com/anchore/packageurl-go v0.2.0 h1:CkrM4RMUwrEGAiE1OVlxaZNzWj0TuHRey7o4T/EAErk=
github.com/anchore/packageurl-go v0.2.0/go.mod h1:2JCgOQMIsqZ7TmliXG4PnUthPJAKE3mWQbsW2XHjAOE=
github.com/anchore/stereoscope v0.1.23 h1:q9i3CtbicTuSlcCnA+5pfoT9WDCEoSqvXDfHMH1hyWo=
github.com/anchore/stereoscope v0.1.23/go.mod h1:JLnun49fkLkuv3ebU0ROvFl/0JiRmNmUtCzc6y4ollo=
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/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=
@@ -171,44 +171,44 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aws/aws-sdk-go-v2 v1.41.5 h1:dj5kopbwUsVUVFgO4Fi5BIT3t4WyqIDjGKCangnV/yY=
github.com/aws/aws-sdk-go-v2 v1.41.5/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 h1:eBMB84YGghSocM7PsjmmPffTa+1FBUeNvGvFou6V/4o=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8/go.mod h1:lyw7GFp3qENLh7kwzf7iMzAxDn+NzjXEAGjKS2UOKqI=
github.com/aws/aws-sdk-go-v2/config v1.32.12 h1:O3csC7HUGn2895eNrLytOJQdoL2xyJy0iYXhoZ1OmP0=
github.com/aws/aws-sdk-go-v2/config v1.32.12/go.mod h1:96zTvoOFR4FURjI+/5wY1vc1ABceROO4lWgWJuxgy0g=
github.com/aws/aws-sdk-go-v2/credentials v1.19.12 h1:oqtA6v+y5fZg//tcTWahyN9PEn5eDU/Wpvc2+kJ4aY8=
github.com/aws/aws-sdk-go-v2/credentials v1.19.12/go.mod h1:U3R1RtSHx6NB0DvEQFGyf/0sbrpJrluENHdPy1j/3TE=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20 h1:zOgq3uezl5nznfoK3ODuqbhVg1JzAGDUhXOsU0IDCAo=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20/go.mod h1:z/MVwUARehy6GAg/yQ1GO2IMl0k++cu1ohP9zo887wE=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 h1:Rgg6wvjjtX8bNHcvi9OnXWwcE0a2vGpbwmtICOsvcf4=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21/go.mod h1:A/kJFst/nm//cyqonihbdpQZwiUhhzpqTsdbhDdRF9c=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 h1:PEgGVtPoB6NTpPrBgqSE5hE/o47Ij9qk/SEZFbUOe9A=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21/go.mod h1:p+hz+PRAYlY3zcpJhPwXlLC4C+kqn70WIHwnzAfs6ps=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 h1:qYQ4pzQ2Oz6WpQ8T3HvGHnZydA72MnLuFK9tJwmrbHw=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6/go.mod h1:O3h0IK87yXci+kg6flUKzJnWeziQUKciKrLjcatSNcY=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22 h1:rWyie/PxDRIdhNf4DzRk0lvjVOqFJuNnO8WwaIRVxzQ=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22/go.mod h1:zd/JsJ4P7oGfUhXn1VyLqaRZwPmZwg44Jf2dS84Dm3Y=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 h1:5EniKhLZe4xzL7a+fU3C2tfUN4nWIqlLesfrjkuPFTY=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13 h1:JRaIgADQS/U6uXDqlPiefP32yXTda7Kqfx+LgspooZM=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13/go.mod h1:CEuVn5WqOMilYl+tbccq8+N2ieCy0gVn3OtRb0vBNNM=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 h1:c31//R3xgIJMSC8S6hEVq+38DcvUlgFY0FM6mSI5oto=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21/go.mod h1:r6+pf23ouCB718FUxaqzZdbpYFyDtehyZcmP5KL9FkA=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21 h1:ZlvrNcHSFFWURB8avufQq9gFsheUgjVD9536obIknfM=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21/go.mod h1:cv3TNhVrssKR0O/xxLJVRfd2oazSnZnkUeTf6ctUwfQ=
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3 h1:HwxWTbTrIHm5qY+CAEur0s/figc3qwvLWsNkF4RPToo=
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3/go.mod h1:uoA43SdFwacedBfSgfFSjjCvYe8aYBS7EnU5GZ/YKMM=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.8 h1:0GFOLzEbOyZABS3PhYfBIx2rNBACYcKty+XGkTgw1ow=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.8/go.mod h1:LXypKvk85AROkKhOG6/YEcHFPoX+prKTowKnVdcaIxE=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.13 h1:kiIDLZ005EcKomYYITtfsjn7dtOwHDOFy7IbPXKek2o=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.13/go.mod h1:2h/xGEowcW/g38g06g3KpRWDlT+OTfxxI0o1KqayAB8=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17 h1:jzKAXIlhZhJbnYwHbvUQZEB8KfgAEuG0dc08Bkda7NU=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17/go.mod h1:Al9fFsXjv4KfbzQHGe6V4NZSZQXecFcvaIF4e70FoRA=
github.com/aws/aws-sdk-go-v2/service/sts v1.41.9 h1:Cng+OOwCHmFljXIxpEVXAGMnBia8MSU6Ch5i9PgBkcU=
github.com/aws/aws-sdk-go-v2/service/sts v1.41.9/go.mod h1:LrlIndBDdjA/EeXeyNBle+gyCwTlizzW5ycgWnvIxkk=
github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng=
github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
github.com/aws/aws-sdk-go-v2 v1.41.2 h1:LuT2rzqNQsauaGkPK/7813XxcZ3o3yePY0Iy891T2ls=
github.com/aws/aws-sdk-go-v2 v1.41.2/go.mod h1:IvvlAZQXvTXznUPfRVfryiG1fbzE2NGK6m9u39YQ+S4=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 h1:489krEF9xIGkOaaX3CE/Be2uWjiXrkCH6gUX+bZA/BU=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4/go.mod h1:IOAPF6oT9KCsceNTvvYMNHy0+kMF8akOjeDvPENWxp4=
github.com/aws/aws-sdk-go-v2/config v1.32.10 h1:9DMthfO6XWZYLfzZglAgW5Fyou2nRI5CuV44sTedKBI=
github.com/aws/aws-sdk-go-v2/config v1.32.10/go.mod h1:2rUIOnA2JaiqYmSKYmRJlcMWy6qTj1vuRFscppSBMcw=
github.com/aws/aws-sdk-go-v2/credentials v1.19.10 h1:EEhmEUFCE1Yhl7vDhNOI5OCL/iKMdkkYFTRpZXNw7m8=
github.com/aws/aws-sdk-go-v2/credentials v1.19.10/go.mod h1:RnnlFCAlxQCkN2Q379B67USkBMu1PipEEiibzYN5UTE=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.18 h1:Ii4s+Sq3yDfaMLpjrJsqD6SmG/Wq/P5L/hw2qa78UAY=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.18/go.mod h1:6x81qnY++ovptLE6nWQeWrpXxbnlIex+4H4eYYGcqfc=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.18 h1:F43zk1vemYIqPAwhjTjYIz0irU2EY7sOb/F5eJ3HuyM=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.18/go.mod h1:w1jdlZXrGKaJcNoL+Nnrj+k5wlpGXqnNrKoP22HvAug=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.18 h1:xCeWVjj0ki0l3nruoyP2slHsGArMxeiiaoPN5QZH6YQ=
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/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/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/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=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.11/go.mod h1:0DO9B5EUJQlIDif+XJRWCljZRKsAFKh3gpFz7UnDtOo=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.15 h1:edCcNp9eGIUDUCrzoCu1jWAXLGFIizeqkdkKgRlJwWc=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.15/go.mod h1:lyRQKED9xWfgkYC/wmmYfv7iVIM68Z5OQ88ZdcV1QbU=
github.com/aws/aws-sdk-go-v2/service/sts v1.41.7 h1:NITQpgo9A5NrDZ57uOWj+abvXSb83BbyggcUBVksN7c=
github.com/aws/aws-sdk-go-v2/service/sts v1.41.7/go.mod h1:sks5UWBhEuWYDPdwlnRFn1w7xWdH29Jcpe+/PJQefEs=
github.com/aws/smithy-go v1.24.1 h1:VbyeNfmYkWoxMVpGUAbQumkODcYmfMRfZ8yQiH30SK0=
github.com/aws/smithy-go v1.24.1/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3vj1nolY=
@@ -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.2 h1:frqHqw7otoVbk5M8LlE/L7HTnIq2v9RX6EJ48i9AxJk=
github.com/buger/jsonparser v1.1.2/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
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/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,10 +276,12 @@ 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.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/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/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=
@@ -292,14 +294,14 @@ 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-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/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/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=
github.com/containerd/containerd/api v1.10.0/go.mod h1:NBm1OAk8ZL+LG8R0ceObGxT5hbUYj7CzTmR3xh0DlMM=
github.com/containerd/containerd/v2 v2.2.2 h1:mjVQdtfryzT7lOqs5EYUFZm8ioPVjOpkSoG1GJPxEMY=
github.com/containerd/containerd/v2 v2.2.2/go.mod h1:5Jhevmv6/2J+Iu/A2xXAdUIdI5Ah/hfyO7okJ4AFIdY=
github.com/containerd/containerd/v2 v2.2.1 h1:TpyxcY4AL5A+07dxETevunVS5zxqzuq7ZqJXknM11yk=
github.com/containerd/containerd/v2 v2.2.1/go.mod h1:NR70yW1iDxe84F2iFWbR9xfAN0N2F0NcjTi1OVth4nU=
github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4=
github.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE=
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
@@ -310,8 +312,8 @@ github.com/containerd/fifo v1.1.0 h1:4I2mbh5stb1u6ycIABlBw9zgtlK8viPI9QkQNRQEEmY
github.com/containerd/fifo v1.1.0/go.mod h1:bmC4NWMbXlt2EZ0Hc7Fx7QzTFxgPID13eH0Qu+MAb2o=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/containerd/platforms v1.0.0-rc.4 h1:M42JrUT4zfZTqtkUwkr0GzmUWbfyO5VO0Q5b3op97T4=
github.com/containerd/platforms v1.0.0-rc.4/go.mod h1:lKlMXyLybmBedS/JJm11uDofzI8L2v0J2ZbYvNsbq1A=
github.com/containerd/platforms v1.0.0-rc.2 h1:0SPgaNZPVWGEi4grZdV8VRYQn78y+nm6acgLGv/QzE4=
github.com/containerd/platforms v1.0.0-rc.2/go.mod h1:J71L7B+aiM5SdIEqmd9wp6THLVRzJGXfNuWCZCllLA4=
github.com/containerd/plugin v1.0.0 h1:c8Kf1TNl6+e2TtMHZt+39yAPDbouRH9WAToRjex483Y=
github.com/containerd/plugin v1.0.0/go.mod h1:hQfJe5nmWfImiqT1q8Si3jLv3ynMUIBB47bQ+KexvO8=
github.com/containerd/stargz-snapshotter/estargz v0.18.2 h1:yXkZFYIzz3eoLwlTUZKz2iQ4MrckBxJjkmD16ynUTrw=
@@ -325,8 +327,8 @@ github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE=
github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=
github.com/cyphar/filepath-securejoin v0.6.0 h1:BtGB77njd6SVO6VztOHfPxKitJvd/VPT+OFBFMOi1Is=
github.com/cyphar/filepath-securejoin v0.6.0/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=
github.com/dave/jennifer v1.7.1 h1:B4jJJDHelWcDhlRQxWeo0Npa/pYKBLrirAQoTN45txo=
github.com/dave/jennifer v1.7.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc=
github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -343,8 +345,10 @@ 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.4.0+incompatible h1:+IjXULMetlvWJiuSI0Nbor36lcJ5BTcVpUmB21KBoVM=
github.com/docker/cli v29.4.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
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/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=
github.com/docker/docker-credential-helpers v0.9.5/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c=
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
@@ -373,16 +377,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.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 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/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.3.0 h1:TvGH1wof4H33rezVKWSpqKz5NXWg5VPuZ0uONDT6eb4=
github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA=
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/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=
@@ -412,25 +416,25 @@ github.com/github/go-spdx/v2 v2.4.0 h1:+4IwVwJJbm3rzvrQ6P1nI9BDMcy3la4RchRy5uehV
github.com/github/go-spdx/v2 v2.4.0/go.mod h1:/5rwgS0txhGtRdUZwc02bTglzg6HK3FfuEbECKlK2Sg=
github.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs=
github.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo=
github.com/gkampitakis/go-snaps v0.5.21 h1:SvhSFeZviQXwlT+dnGyAIATVehkhqRVW6qfQZhCZH+Y=
github.com/gkampitakis/go-snaps v0.5.21/go.mod h1:gC3YqxQTPyIXvQrw/Vpt3a8VqR1MO8sVpZFWN4DGwNs=
github.com/gkampitakis/go-snaps v0.5.20 h1:FGKonEeQPJ12t7RQj6cTPa881fl5c8HYarMLv5vP7sg=
github.com/gkampitakis/go-snaps v0.5.20/go.mod h1:gC3YqxQTPyIXvQrw/Vpt3a8VqR1MO8sVpZFWN4DGwNs=
github.com/glebarez/go-sqlite v1.20.3 h1:89BkqGOXR9oRmG58ZrzgoY/Fhy5x0M+/WV48U5zVrZ4=
github.com/glebarez/go-sqlite v1.20.3/go.mod h1:u3N6D/wftiAzIOJtZl6BmedqxmmkDfH3q+ihjqxC9u0=
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
github.com/go-git/go-billy/v5 v5.9.0 h1:jItGXszUDRtR/AlferWPTMN4j38BQ88XnXKbilmmBPA=
github.com/go-git/go-billy/v5 v5.9.0/go.mod h1:jCnQMLj9eUgGU7+ludSTYoZL/GGmii14RxKFj7ROgHw=
github.com/go-git/go-billy/v5 v5.8.0 h1:I8hjc3LbBlXTtVuFNJuwYuMiHvQJDq1AT6u4DwDzZG0=
github.com/go-git/go-billy/v5 v5.8.0/go.mod h1:RpvI/rw4Vr5QA+Z60c6d6LXH0rYJo0uD5SqfmrrheCY=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
github.com/go-git/go-git/v5 v5.19.0 h1:+WkVUQZSy/F1Gb13udrMKjIM2PrzsNfDKFSfo5tkMtc=
github.com/go-git/go-git/v5 v5.19.0/go.mod h1:Pb1v0c7/g8aGQJwx9Us09W85yGoyvSwuhEGMH7zjDKQ=
github.com/go-git/go-git/v5 v5.17.0 h1:AbyI4xf+7DsjINHMu35quAh4wJygKBKBuXVjV/pxesM=
github.com/go-git/go-git/v5 v5.17.0/go.mod h1:f82C4YiLx+Lhi8eHxltLeGC5uBTXSFa6PC5WW9o4SjI=
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.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
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-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=
@@ -511,8 +515,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.5 h1:KTJG9Pn/jC0VdZR6ctV3/jcN+q6/Iqlx0sTVz3ywZlM=
github.com/google/go-containerregistry v0.21.5/go.mod h1:ySvMuiWg+dOsRW0Hw8GYwfMwBlNRTmpYBFJPlkco5zU=
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/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=
@@ -547,14 +551,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.14 h1:yh8ncqsbUY4shRD5dA6RlzjJaT4hi3kII+zYw8wmLb8=
github.com/googleapis/enterprise-certificate-proxy v0.3.14/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg=
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/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.17.0 h1:RksgfBpxqff0EZkDWYuz9q/uWsTVz+kf43LsZ1J6SMc=
github.com/googleapis/gax-go/v2 v2.17.0/go.mod h1:mzaqghpQp4JDh3HvADwrat+6M3MOIDp5YKHhb9PAgDY=
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/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=
@@ -563,8 +567,8 @@ github.com/gookit/color v1.6.0/go.mod h1:9ACFc7/1IpHGBW8RwuDm/0YEnhg3dwwXpoMsmty
github.com/gpustack/gguf-parser-go v0.24.0 h1:tdJceXYp9e5RhE9RwVYIuUpir72Jz2D68NEtDXkKCKc=
github.com/gpustack/gguf-parser-go v0.24.0/go.mod h1:y4TwTtDqFWTK+xvprOjRUh+dowgU2TKCX37vRKvGiZ0=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.72 h1:vTCWu1wbdYo7PEZFem/rlr01+Un+wwVmI7wiegFdRLk=
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.72/go.mod h1:Vn+BBgKQHVQYdVQ4NZDICE1Brb+JfaONyDHr3q07oQc=
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.70 h1:0HADrxxqaQkGycO1JoUUA+B4FnIkuo8d2bz/hSaTFFQ=
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.70/go.mod h1:fm2FdDCzJdtbXF7WKAMvBb5NEPouXPHFbGNYs9ShFns=
github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M=
github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@@ -574,8 +578,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.6 h1:9sQboWULaydVphxc4S64oAI4YqpuCk7nPmvbk131ebY=
github.com/hashicorp/go-getter v1.8.6/go.mod h1:nVH12eOV2P58dIiL3rsU6Fh3wLeJEKBOJzhMmzlSWoo=
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-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=
@@ -646,11 +650,9 @@ github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=
github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@@ -725,10 +727,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.54.1 h1:TqVzuJkOLsgLDDwNLmYqACUuTehOHRGKiPhvH8V3Nn4=
github.com/moby/moby/api v1.54.1/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs=
github.com/moby/moby/client v0.4.0 h1:S+2XegzHQrrvTCvF6s5HFzcrywWQmuVnhOXe2kiWjIw=
github.com/moby/moby/client v0.4.0/go.mod h1:QWPbvWchQbxBNdaLSpoKpCdf5E+WxFAgNHogCWDoa7g=
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/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=
@@ -764,12 +766,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.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/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/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=
@@ -792,8 +794,8 @@ github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pjbgf/sha1cd v0.6.0 h1:3WJ8Wz8gvDz29quX1OcEmkAlUg9diU4GxJHqs0/XiwU=
github.com/pjbgf/sha1cd v0.6.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM=
github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -843,8 +845,8 @@ github.com/rust-secure-code/go-rustaudit v0.0.0-20250226111315-e20ec32e963c/go.m
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig=
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo=
github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k=
github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA=
github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA=
@@ -875,8 +877,8 @@ github.com/smallnest/ringbuffer v0.0.0-20241116012123-461381446e3d h1:3VwvTjiRPA
github.com/smallnest/ringbuffer v0.0.0-20241116012123-461381446e3d/go.mod h1:tAG61zBM1DYRaGIPloumExGvScf08oHuo0kFoOqdbT0=
github.com/sorairolake/lzip-go v0.3.8 h1:j5Q2313INdTA80ureWYRhX+1K78mUXfMoPZCw/ivWik=
github.com/sorairolake/lzip-go v0.3.8/go.mod h1:JcBqGMV0frlxwrsE9sMWXDjqn3EeVf0/54YPsw66qkU=
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spdx/gordf v0.0.0-20201111095634-7098f93598fb h1:bLo8hvc8XFm9J47r690TUKBzcjSWdJDxmjXJZ+/f92U=
github.com/spdx/gordf v0.0.0-20201111095634-7098f93598fb/go.mod h1:uKWaldnbMnjsSAXRurWqqrdyZen1R7kxl8TkmWk2OyM=
@@ -887,8 +889,8 @@ github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
@@ -898,10 +900,10 @@ github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM=
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
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/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/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=
@@ -923,8 +925,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.24.0 h1:1wB5uMDUQYjk8AckTySaDcP9YnpMb1LyDRr1Jt9A10w=
github.com/sylabs/sif/v2 v2.24.0/go.mod h1:DbXWqWZ1hdLSU+K9ipdds5AmZeHWsyxCOj/oQakBa88=
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/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=
@@ -982,6 +984,8 @@ 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=
@@ -998,29 +1002,33 @@ 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.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/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/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.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.40.0 h1:ZrPRak/kS4xI3AVXy8F7pipuDXmDsrO8Lg+yQjBLjw0=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.40.0/go.mod h1:3y6kQCWztq6hyW8Z9YxQDDm0Je9AJoFar2G0yDcmhRk=
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
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/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=
go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE=
go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw=
go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg=
go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
@@ -1038,8 +1046,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.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
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=
@@ -1050,8 +1058,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM=
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80=
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY=
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -1079,8 +1087,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.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
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=
@@ -1125,8 +1133,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.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
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/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=
@@ -1144,8 +1152,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.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=
golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
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=
@@ -1158,8 +1166,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.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
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/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=
@@ -1234,13 +1242,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.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
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/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.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY=
golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY=
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
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=
@@ -1251,13 +1259,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.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
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/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.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
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/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=
@@ -1313,8 +1321,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.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
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/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=
@@ -1355,8 +1363,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.271.0 h1:cIPN4qcUc61jlh7oXu6pwOQqbJW2GqYh5PS6rB2C/JY=
google.golang.org/api v0.271.0/go.mod h1:CGT29bhwkbF+i11qkRUJb2KMKqcJ1hdFceEIRd9u64Q=
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/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=
@@ -1427,12 +1435,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-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-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
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/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=
@@ -1460,8 +1468,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.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
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/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=
@@ -1510,18 +1518,18 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis=
modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/ccgo/v4 v4.32.0 h1:hjG66bI/kqIPX1b2yT6fr/jt+QedtP2fqojG2VrFuVw=
modernc.org/ccgo/v4 v4.32.0/go.mod h1:6F08EBCx5uQc38kMGl+0Nm0oWczoo1c7cgpzEry7Uc0=
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
modernc.org/ccgo/v4 v4.30.1 h1:4r4U1J6Fhj98NKfSjnPUN7Ze2c6MnAdL0hWw6+LrJpc=
modernc.org/ccgo/v4 v4.30.1/go.mod h1:bIOeI1JL54Utlxn+LwrFyjCx2n2RDiYEaJVSrgdrRfM=
modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA=
modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo=
modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
modernc.org/gc/v3 v3.1.1 h1:k8T3gkXWY9sEiytKhcgyiZ2L0DTyCQ/nvX+LoCljoRE=
modernc.org/gc/v3 v3.1.1/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
modernc.org/libc v1.70.0 h1:U58NawXqXbgpZ/dcdS9kMshu08aiA6b7gusEusqzNkw=
modernc.org/libc v1.70.0/go.mod h1:OVmxFGP1CI/Z4L3E0Q3Mf1PDE0BucwMkcXjjLntvHJo=
modernc.org/libc v1.67.6 h1:eVOQvpModVLKOdT+LvBPjdQqfrZq+pC39BygcT+E7OI=
modernc.org/libc v1.67.6/go.mod h1:JAhxUVlolfYDErnwiqaLvUqc8nfb2r6S6slAgZOnaiE=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
@@ -1530,8 +1538,8 @@ modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.46.2 h1:gkXQ6R0+AjxFC/fTDaeIVLbNLNrRoOK7YYVz5BKhTcE=
modernc.org/sqlite v1.46.2/go.mod h1:hWjRO6Tj/5Ik8ieqxQybiEOUXy0NJFNp2tpvVpKlvig=
modernc.org/sqlite v1.46.1 h1:eFJ2ShBLIEnUWlLy12raN0Z1plqmFX9Qe3rjQTKt6sU=
modernc.org/sqlite v1.46.1/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
-1
View File
@@ -154,7 +154,6 @@ uname_arch_check() (
mipsle) return 0 ;;
mips64) return 0 ;;
mips64le) return 0 ;;
riscv64) return 0 ;;
s390x) return 0 ;;
amd64p32) return 0 ;;
esac
+5 -5
View File
@@ -5,8 +5,8 @@ import "reflect"
// EvaluateCapabilities evaluates a capability set against a given configuration
// and returns the effective capability values as a flat map.
// Example: {"license": false, "dependency.depth": ["direct", "indirect"]}
func EvaluateCapabilities(caps CapabilitySet, config map[string]any) map[string]any {
result := make(map[string]any)
func EvaluateCapabilities(caps CapabilitySet, config map[string]interface{}) map[string]interface{} {
result := make(map[string]interface{})
for _, capField := range caps {
result[capField.Name] = EvaluateField(capField, config)
}
@@ -16,7 +16,7 @@ func EvaluateCapabilities(caps CapabilitySet, config map[string]any) map[string]
// EvaluateField evaluates a single capability field against a configuration.
// Conditions are evaluated in order, and the first matching condition's value is returned.
// If no conditions match, the default value is returned.
func EvaluateField(capField CapabilityField, config map[string]any) any {
func EvaluateField(capField CapabilityField, config map[string]interface{}) interface{} {
// check conditions in order (first match wins)
for _, cond := range capField.Conditions {
if ConditionMatches(cond.When, config) {
@@ -30,7 +30,7 @@ func EvaluateField(capField CapabilityField, config map[string]any) any {
// ConditionMatches checks if a condition's when clause matches the given configuration.
// All fields in the when clause must match the config (AND logic).
// Returns true if all key-value pairs in when match the config.
func ConditionMatches(when map[string]any, config map[string]any) bool {
func ConditionMatches(when map[string]interface{}, config map[string]interface{}) bool {
// all fields in when must match config (AND logic)
for key, expectedValue := range when {
actualValue, exists := config[key]
@@ -46,7 +46,7 @@ func ConditionMatches(when map[string]any, config map[string]any) bool {
// valuesEqual compares two values for equality, handling different types appropriately.
// Uses reflect.DeepEqual for complex types like slices and maps.
func valuesEqual(a, b any) bool {
func valuesEqual(a, b interface{}) bool {
// handle nil cases
if a == nil && b == nil {
return true
+52 -52
View File
@@ -10,8 +10,8 @@ import (
func Test_valuesEqual(t *testing.T) {
tests := []struct {
name string
a any
b any
a interface{}
b interface{}
want bool
}{
{
@@ -123,47 +123,47 @@ func Test_valuesEqual(t *testing.T) {
func TestConditionMatches(t *testing.T) {
tests := []struct {
name string
when map[string]any
config map[string]any
when map[string]interface{}
config map[string]interface{}
want bool
}{
{
name: "empty when clause matches anything",
when: map[string]any{},
config: map[string]any{"key": "value"},
when: map[string]interface{}{},
config: map[string]interface{}{"key": "value"},
want: true,
},
{
name: "empty when clause with empty config",
when: map[string]any{},
config: map[string]any{},
when: map[string]interface{}{},
config: map[string]interface{}{},
want: true,
},
{
name: "single key match",
when: map[string]any{"SearchLocalModCacheLicenses": true},
config: map[string]any{"SearchLocalModCacheLicenses": true},
when: map[string]interface{}{"SearchLocalModCacheLicenses": true},
config: map[string]interface{}{"SearchLocalModCacheLicenses": true},
want: true,
},
{
name: "single key mismatch",
when: map[string]any{"SearchLocalModCacheLicenses": true},
config: map[string]any{"SearchLocalModCacheLicenses": false},
when: map[string]interface{}{"SearchLocalModCacheLicenses": true},
config: map[string]interface{}{"SearchLocalModCacheLicenses": false},
want: false,
},
{
name: "key missing from config",
when: map[string]any{"SearchLocalModCacheLicenses": true},
config: map[string]any{},
when: map[string]interface{}{"SearchLocalModCacheLicenses": true},
config: map[string]interface{}{},
want: false,
},
{
name: "multiple keys all match",
when: map[string]any{
when: map[string]interface{}{
"SearchLocalModCacheLicenses": true,
"UseNetwork": true,
},
config: map[string]any{
config: map[string]interface{}{
"SearchLocalModCacheLicenses": true,
"UseNetwork": true,
"ExtraKey": "ignored",
@@ -172,11 +172,11 @@ func TestConditionMatches(t *testing.T) {
},
{
name: "multiple keys one mismatch",
when: map[string]any{
when: map[string]interface{}{
"SearchLocalModCacheLicenses": true,
"UseNetwork": true,
},
config: map[string]any{
config: map[string]interface{}{
"SearchLocalModCacheLicenses": true,
"UseNetwork": false,
},
@@ -184,31 +184,31 @@ func TestConditionMatches(t *testing.T) {
},
{
name: "multiple keys one missing",
when: map[string]any{
when: map[string]interface{}{
"SearchLocalModCacheLicenses": true,
"UseNetwork": true,
},
config: map[string]any{
config: map[string]interface{}{
"SearchLocalModCacheLicenses": true,
},
want: false,
},
{
name: "string value match",
when: map[string]any{"mode": "fast"},
config: map[string]any{"mode": "fast"},
when: map[string]interface{}{"mode": "fast"},
config: map[string]interface{}{"mode": "fast"},
want: true,
},
{
name: "slice value match",
when: map[string]any{"formats": []string{"json", "yaml"}},
config: map[string]any{"formats": []string{"json", "yaml"}},
when: map[string]interface{}{"formats": []string{"json", "yaml"}},
config: map[string]interface{}{"formats": []string{"json", "yaml"}},
want: true,
},
{
name: "slice value mismatch",
when: map[string]any{"formats": []string{"json", "yaml"}},
config: map[string]any{"formats": []string{"json", "xml"}},
when: map[string]interface{}{"formats": []string{"json", "yaml"}},
config: map[string]interface{}{"formats": []string{"json", "xml"}},
want: false,
},
}
@@ -225,8 +225,8 @@ func TestEvaluateField(t *testing.T) {
tests := []struct {
name string
capField CapabilityField
config map[string]any
want any
config map[string]interface{}
want interface{}
}{
{
name: "no conditions returns default",
@@ -235,7 +235,7 @@ func TestEvaluateField(t *testing.T) {
Default: false,
Conditions: nil,
},
config: map[string]any{},
config: map[string]interface{}{},
want: false,
},
{
@@ -245,7 +245,7 @@ func TestEvaluateField(t *testing.T) {
Default: false,
Conditions: []CapabilityCondition{},
},
config: map[string]any{},
config: map[string]interface{}{},
want: false,
},
{
@@ -260,7 +260,7 @@ func TestEvaluateField(t *testing.T) {
},
},
},
config: map[string]any{"SearchLocalModCacheLicenses": true},
config: map[string]interface{}{"SearchLocalModCacheLicenses": true},
want: true,
},
{
@@ -275,7 +275,7 @@ func TestEvaluateField(t *testing.T) {
},
},
},
config: map[string]any{"SearchLocalModCacheLicenses": false},
config: map[string]interface{}{"SearchLocalModCacheLicenses": false},
want: false,
},
{
@@ -294,7 +294,7 @@ func TestEvaluateField(t *testing.T) {
},
},
},
config: map[string]any{
config: map[string]interface{}{
"SearchLocalModCacheLicenses": true,
"SearchRemoteLicenses": true,
},
@@ -316,7 +316,7 @@ func TestEvaluateField(t *testing.T) {
},
},
},
config: map[string]any{
config: map[string]interface{}{
"SearchLocalModCacheLicenses": false,
"SearchRemoteLicenses": true,
},
@@ -338,7 +338,7 @@ func TestEvaluateField(t *testing.T) {
},
},
},
config: map[string]any{
config: map[string]interface{}{
"SearchLocalModCacheLicenses": false,
"SearchRemoteLicenses": false,
},
@@ -351,7 +351,7 @@ func TestEvaluateField(t *testing.T) {
Default: []string{"direct", "indirect"},
Conditions: nil,
},
config: map[string]any{},
config: map[string]interface{}{},
want: []string{"direct", "indirect"},
},
{
@@ -369,7 +369,7 @@ func TestEvaluateField(t *testing.T) {
},
},
},
config: map[string]any{
config: map[string]interface{}{
"EnableFeatureA": true,
"EnableFeatureB": true,
},
@@ -390,7 +390,7 @@ func TestEvaluateField(t *testing.T) {
},
},
},
config: map[string]any{
config: map[string]interface{}{
"EnableFeatureA": true,
"EnableFeatureB": false,
},
@@ -412,14 +412,14 @@ func TestEvaluateCapabilities(t *testing.T) {
tests := []struct {
name string
caps CapabilitySet
config map[string]any
want map[string]any
config map[string]interface{}
want map[string]interface{}
}{
{
name: "empty capability set",
caps: CapabilitySet{},
config: map[string]any{},
want: map[string]any{},
config: map[string]interface{}{},
want: map[string]interface{}{},
},
{
name: "single capability no conditions",
@@ -429,8 +429,8 @@ func TestEvaluateCapabilities(t *testing.T) {
Default: false,
},
},
config: map[string]any{},
want: map[string]any{
config: map[string]interface{}{},
want: map[string]interface{}{
"license": false,
},
},
@@ -448,8 +448,8 @@ func TestEvaluateCapabilities(t *testing.T) {
},
},
},
config: map[string]any{"SearchLocalModCacheLicenses": true},
want: map[string]any{
config: map[string]interface{}{"SearchLocalModCacheLicenses": true},
want: map[string]interface{}{
"license": true,
},
},
@@ -475,8 +475,8 @@ func TestEvaluateCapabilities(t *testing.T) {
Default: "flat",
},
},
config: map[string]any{"SearchLocalModCacheLicenses": true},
want: map[string]any{
config: map[string]interface{}{"SearchLocalModCacheLicenses": true},
want: map[string]interface{}{
"license": true,
"dependency.depth": []string{"direct", "indirect"},
"dependency.edges": "flat",
@@ -512,11 +512,11 @@ func TestEvaluateCapabilities(t *testing.T) {
Default: false,
},
},
config: map[string]any{
config: map[string]interface{}{
"SearchLocalModCacheLicenses": false,
"SearchRemoteLicenses": true,
},
want: map[string]any{
want: map[string]interface{}{
"license": true,
"dependency.depth": []string{"direct", "indirect"},
"dependency.edges": "flat",
@@ -526,10 +526,10 @@ func TestEvaluateCapabilities(t *testing.T) {
{
name: "nil capability set",
caps: nil,
config: map[string]any{
config: map[string]interface{}{
"anything": true,
},
want: map[string]any{},
want: map[string]interface{}{},
},
}
@@ -15,9 +15,9 @@ import (
// AppConfigField represents an application-level configuration field for catalogers
type AppConfigField struct {
Key string // e.g., "golang.search-local-mod-cache-licenses"
Description string // extracted from DescribeFields() method
DefaultValue any // extracted from Default*() functions
Key string // e.g., "golang.search-local-mod-cache-licenses"
Description string // extracted from DescribeFields() method
DefaultValue interface{} // extracted from Default*() functions
}
// extractEcosystemConfigFieldsFromCatalog parses catalog.go and extracts the ecosystem-specific
@@ -390,7 +390,7 @@ func extractDescriptionsFromDescribeFields(f *ast.File) map[string]string {
}
// extractNestedAppConfigs handles nested config structs like golang.MainModuleVersion
func extractNestedAppConfigs(f *ast.File, parentKey, parentFieldName string, fieldType ast.Expr, descriptions map[string]string, defaults map[string]any) []AppConfigField {
func extractNestedAppConfigs(f *ast.File, parentKey, parentFieldName string, fieldType ast.Expr, descriptions map[string]string, defaults map[string]interface{}) []AppConfigField {
var configs []AppConfigField
// find the nested struct type
@@ -449,8 +449,8 @@ func extractNestedAppConfigs(f *ast.File, parentKey, parentFieldName string, fie
description := descriptions[nestedPath]
// try to get default value from nested defaults
var defaultValue any
if nestedDefaults, ok := defaults[parentFieldName].(map[string]any); ok {
var defaultValue interface{}
if nestedDefaults, ok := defaults[parentFieldName].(map[string]interface{}); ok {
defaultValue = nestedDefaults[fieldName]
}
@@ -465,8 +465,8 @@ func extractNestedAppConfigs(f *ast.File, parentKey, parentFieldName string, fie
}
// extractAppDefaultValues extracts default values from the default*Config function
func extractAppDefaultValues(f *ast.File) map[string]any {
defaults := make(map[string]any)
func extractAppDefaultValues(f *ast.File) map[string]interface{} {
defaults := make(map[string]interface{})
for _, decl := range f.Decls {
funcDecl, ok := decl.(*ast.FuncDecl)
@@ -518,7 +518,7 @@ func extractAppDefaultValues(f *ast.File) map[string]any {
}
// extractAppValue extracts a Go value from an AST expression
func extractAppValue(expr ast.Expr) any {
func extractAppValue(expr ast.Expr) interface{} {
switch v := expr.(type) {
case *ast.BasicLit:
// string, int, bool literals
@@ -543,7 +543,7 @@ func extractAppValue(expr ast.Expr) any {
}
case *ast.CompositeLit:
// nested struct literal
nested := make(map[string]any)
nested := make(map[string]interface{})
for _, elt := range v.Elts {
kvExpr, ok := elt.(*ast.KeyValueExpr)
if !ok {
@@ -340,7 +340,7 @@ func TestExtractAppValue(t *testing.T) {
tests := []struct {
name string
src string
want any
want interface{}
}{
{
name: "string literal",
@@ -406,7 +406,7 @@ func TestExtractAppValue_NestedStruct(t *testing.T) {
got := extractAppValue(compositeLit)
// verify it's a map with the expected values
gotMap, ok := got.(map[string]any)
gotMap, ok := got.(map[string]interface{})
require.True(t, ok)
require.Equal(t, "value", gotMap["Field1"])
require.Equal(t, true, gotMap["Field2"])
@@ -567,8 +567,8 @@ func resolveImportedConstant(pkgName, constName string, ctx *parseContext) strin
// resolveImportPath converts an import path to a file system path
func resolveImportPath(importPath, repoRoot string) string {
// for github.com/anchore/syft/... imports, convert to repo-relative path
if after, ok := strings.CutPrefix(importPath, "github.com/anchore/syft/"); ok {
relPath := after
if strings.HasPrefix(importPath, "github.com/anchore/syft/") {
relPath := strings.TrimPrefix(importPath, "github.com/anchore/syft/")
return filepath.Join(repoRoot, relPath)
}
+2 -2
View File
@@ -214,7 +214,7 @@ func mapCatalogerToEcosystem(cat capabilities.CatalogerEntry) string {
}
// updateNodeTreeEcosystem updates an existing ecosystem YAML node tree
func updateNodeTreeEcosystem(rootNode *yaml.Node, doc any) error {
func updateNodeTreeEcosystem(rootNode *yaml.Node, doc interface{}) error {
var newNode yaml.Node
if err := newNode.Encode(doc); err != nil {
return err
@@ -242,7 +242,7 @@ func updateNodeTreeEcosystem(rootNode *yaml.Node, doc any) error {
}
// updateNodeTreeAppConfig updates appconfig YAML node tree
func updateNodeTreeAppConfig(rootNode *yaml.Node, doc any) error {
func updateNodeTreeAppConfig(rootNode *yaml.Node, doc interface{}) error {
return updateNodeTreeEcosystem(rootNode, doc)
}
@@ -96,20 +96,6 @@ var binaryClassifierOverrides = map[string][]binaryClassifierOverride{
CPEs: singleCPE("cpe:2.3:a:oracle:jdk:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
},
},
"openssl-binary": {
{
Class: "openssl-binary-aws-lc",
Package: "aws-lc",
PURL: mustPURL("pkg:generic/aws-lc@version"),
CPEs: singleCPE("cpe:2.3:a:amazon:aws_libcrypto:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
},
{
Class: "openssl-binary",
Package: "openssl",
PURL: mustPURL("pkg:generic/openssl@version"),
CPEs: singleCPE("cpe:2.3:a:openssl:openssl:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
},
},
}
func mustPURL(purl string) string {
@@ -30,7 +30,7 @@ const requireParserObservations = false
// metadataTypeCoverageExceptions lists metadata types that are allowed to not be represented in any cataloger
var metadataTypeCoverageExceptions = strset.New(
reflect.TypeFor[pkg.MicrosoftKbPatch]().Name(),
reflect.TypeOf(pkg.MicrosoftKbPatch{}).Name(),
)
// packageTypeCoverageExceptions lists package types that are allowed to not be represented in any cataloger
@@ -408,7 +408,7 @@ func TestCapabilityValueTypes(t *testing.T) {
}
// validateCapabilityValueType checks if a value matches the expected type for a capability field
func validateCapabilityValueType(fieldPath string, value any) error {
func validateCapabilityValueType(fieldPath string, value interface{}) error {
if value == nil {
return nil // nil is acceptable
}
@@ -428,7 +428,7 @@ func validateCapabilityValueType(fieldPath string, value any) error {
switch v := value.(type) {
case []string:
// ok
case []any:
case []interface{}:
// check each element is a string
for i, elem := range v {
if _, ok := elem.(string); !ok {
@@ -829,6 +829,7 @@ func TestCapabilityEvidenceFieldReferences(t *testing.T) {
// validate each evidence reference
for _, ref := range allReferences {
ref := ref // capture for subtest
// create test name
testName := ref.catalogerName
@@ -988,6 +989,7 @@ func validateCapabilitiesFilled(t *testing.T, catalogers []capabilities.Cataloge
checkCompletenessTestsEnabled(t)
for _, c := range catalogers {
c := c // capture loop variable for subtest
t.Run(c.Name, func(t *testing.T) {
if c.Type == "generic" {
@@ -995,6 +997,7 @@ func validateCapabilitiesFilled(t *testing.T, catalogers []capabilities.Cataloge
require.NotEmpty(t, c.Parsers, "generic cataloger must have at least one parser")
for _, p := range c.Parsers {
p := p // capture loop variable for subtest
t.Run(p.ParserFunction, func(t *testing.T) {
require.NotEmpty(t, p.Capabilities, "parser must have at least one capability field defined")
@@ -1117,6 +1120,7 @@ func TestCatalogerStructure(t *testing.T) {
require.NoError(t, err)
for _, c := range catalogerEntries {
c := c // capture loop variable for subtest
t.Run(c.Name, func(t *testing.T) {
// ecosystem must always be set (it's MANUAL)
+1 -13
View File
@@ -12,20 +12,8 @@ const perFileReadLimit = 2 * GB
// protect against decompression bomb attacks.
func safeCopy(writer io.Writer, reader io.Reader) error {
numBytes, err := io.Copy(writer, io.LimitReader(reader, perFileReadLimit))
if numBytes >= perFileReadLimit {
if numBytes >= perFileReadLimit || errors.Is(err, io.EOF) {
return fmt.Errorf("zip read limit hit (potential decompression bomb attack)")
}
// Propagate decompression / read errors up to the caller. io.Copy
// on the happy path returns (n, nil); the only way err is non-nil
// here is that the underlying reader surfaced a real failure
// ("flate: corrupt input before offset X" on a mangled ZIP entry,
// a network-backed reader erroring mid-stream, etc.). The previous
// implementation dropped that error and the caller stored a
// partial / empty buffer as a "successful" extract, which silently
// downgraded Java cataloger output and caused SBOM scanners to
// miss known CVEs (#4806).
if err != nil && !errors.Is(err, io.EOF) {
return fmt.Errorf("failed to read archive entry: %w", err)
}
return nil
}
-64
View File
@@ -1,64 +0,0 @@
package file
import (
"bytes"
"errors"
"io"
"strings"
"testing"
)
// errReader returns a deterministic non-EOF error after emitting some
// bytes, mimicking what compress/flate does when it hits a corrupt
// stream mid-entry.
type errReader struct {
data []byte
err error
off int
}
func (r *errReader) Read(p []byte) (int, error) {
if r.off >= len(r.data) {
return 0, r.err
}
n := copy(p, r.data[r.off:])
r.off += n
return n, nil
}
func TestSafeCopy(t *testing.T) {
t.Run("clean copy returns nil", func(t *testing.T) {
var buf bytes.Buffer
if err := safeCopy(&buf, strings.NewReader("hello")); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got := buf.String(); got != "hello" {
t.Fatalf("unexpected buffer contents: %q", got)
}
})
t.Run("propagates decompression error", func(t *testing.T) {
// #4806: safeCopy used to drop non-EOF errors, so the caller
// would persist a partial buffer as a successful extract and
// downstream catalogers silently read empty manifests.
sentinel := errors.New("flate: corrupt input before offset 42")
var buf bytes.Buffer
err := safeCopy(&buf, &errReader{data: []byte("partial"), err: sentinel})
if err == nil {
t.Fatalf("expected error to be returned, got nil")
}
if !errors.Is(err, sentinel) {
t.Fatalf("error does not wrap sentinel: %v", err)
}
})
t.Run("EOF is not treated as an error", func(t *testing.T) {
// The old code had a dead io.EOF branch that labelled clean
// reads as decompression bombs; keep the happy path clean.
var buf bytes.Buffer
err := safeCopy(&buf, io.LimitReader(strings.NewReader("abc"), 3))
if err != nil {
t.Fatalf("unexpected error on clean copy: %v", err)
}
})
}
+2 -2
View File
@@ -151,11 +151,11 @@ func TestGetter_GetToDir_CertConcerns(t *testing.T) {
}
}
func assertUnknownAuthorityError(t assert.TestingT, err error, _ ...any) bool {
func assertUnknownAuthorityError(t assert.TestingT, err error, _ ...interface{}) bool {
return assert.ErrorAs(t, err, &x509.UnknownAuthorityError{})
}
func assertErrNonArchiveSource(t assert.TestingT, err error, _ ...any) bool {
func assertErrNonArchiveSource(t assert.TestingT, err error, _ ...interface{}) bool {
return assert.ErrorIs(t, err, ErrNonArchiveSource)
}
+1
View File
@@ -1,4 +1,5 @@
//go:build !windows
// +build !windows
package file
+3 -2
View File
@@ -1,4 +1,5 @@
//go:build !windows
// +build !windows
package file
@@ -241,8 +242,8 @@ func assertZipSourceFixtureContents(t testing.TB, actual map[string]string, expe
}
// looks like there isn't a helper for this yet? https://github.com/stretchr/testify/issues/497
func assertErrorAs(expectedErr any) assert.ErrorAssertionFunc {
return func(t assert.TestingT, actualErr error, i ...any) bool {
func assertErrorAs(expectedErr interface{}) assert.ErrorAssertionFunc {
return func(t assert.TestingT, actualErr error, i ...interface{}) bool {
return errors.As(actualErr, &expectedErr)
}
}
+2 -2
View File
@@ -143,7 +143,7 @@ func build() *jsonschema.Schema {
// srcMetadataContainer := assembleTypeContainer(sourcemetadata.AllTypes())
// srcMetadataContainerType := reflect.TypeOf(srcMetadataContainer)
documentSchema := reflector.ReflectFromType(reflect.TypeFor[*syftJsonModel.Document]())
documentSchema := reflector.ReflectFromType(reflect.TypeOf(&syftJsonModel.Document{}))
pkgMetadataSchema := reflector.ReflectFromType(reflect.TypeOf(pkgMetadataContainer))
// srcMetadataSchema := reflector.ReflectFromType(reflect.TypeOf(srcMetadataContainer))
@@ -215,7 +215,7 @@ func write(schema []byte) {
panic(err)
}
existingSchemaBytes, err := io.ReadAll(existingFh) //nolint:gocritic // offline code generator
existingSchemaBytes, err := io.ReadAll(existingFh)
if err != nil {
panic(err)
}
+1 -1
View File
@@ -10,7 +10,7 @@ func (s *scanner) FindEvidence(_ context.Context, reader io.Reader) (evidence []
return nil, nil, nil
}
content, err = io.ReadAll(reader) //nolint:gocritic // license scanner requires full content
content, err = io.ReadAll(reader)
if err != nil {
return nil, nil, err
}
+12 -12
View File
@@ -30,61 +30,61 @@ func Get() logger.Logger {
}
// Errorf takes a formatted template string and template arguments for the error logging level.
func Errorf(format string, args ...any) {
func Errorf(format string, args ...interface{}) {
log.Errorf(format, args...)
}
// Error logs the given arguments at the error logging level.
func Error(args ...any) {
func Error(args ...interface{}) {
log.Error(args...)
}
// Warnf takes a formatted template string and template arguments for the warning logging level.
func Warnf(format string, args ...any) {
func Warnf(format string, args ...interface{}) {
log.Warnf(format, args...)
}
// Warn logs the given arguments at the warning logging level.
func Warn(args ...any) {
func Warn(args ...interface{}) {
log.Warn(args...)
}
// Infof takes a formatted template string and template arguments for the info logging level.
func Infof(format string, args ...any) {
func Infof(format string, args ...interface{}) {
log.Infof(format, args...)
}
// Info logs the given arguments at the info logging level.
func Info(args ...any) {
func Info(args ...interface{}) {
log.Info(args...)
}
// Debugf takes a formatted template string and template arguments for the debug logging level.
func Debugf(format string, args ...any) {
func Debugf(format string, args ...interface{}) {
log.Debugf(format, args...)
}
// Debug logs the given arguments at the debug logging level.
func Debug(args ...any) {
func Debug(args ...interface{}) {
log.Debug(args...)
}
// Tracef takes a formatted template string and template arguments for the trace logging level.
func Tracef(format string, args ...any) {
func Tracef(format string, args ...interface{}) {
log.Tracef(format, args...)
}
// Trace logs the given arguments at the trace logging level.
func Trace(args ...any) {
func Trace(args ...interface{}) {
log.Trace(args...)
}
// WithFields returns a message logger with multiple key-value fields.
func WithFields(fields ...any) logger.MessageLogger {
func WithFields(fields ...interface{}) logger.MessageLogger {
return log.WithFields(fields...)
}
// Nested returns a new logger with hard coded key-value pairs
func Nested(fields ...any) logger.Logger {
func Nested(fields ...interface{}) logger.Logger {
return log.Nested(fields...)
}
+37 -37
View File
@@ -40,28 +40,28 @@ func TestReflectTypeFromJSONName(t *testing.T) {
{
name: "exact match on ID",
lookup: "rust-cargo-lock-entry",
wantRecord: reflect.TypeFor[pkg.RustCargoLockEntry](),
wantRecord: reflect.TypeOf(pkg.RustCargoLockEntry{}),
},
{
name: "exact match on former name",
lookup: "RustCargoPackageMetadata",
wantRecord: reflect.TypeFor[pkg.RustCargoLockEntry](),
wantRecord: reflect.TypeOf(pkg.RustCargoLockEntry{}),
},
{
name: "case insensitive on ID",
lookup: "RUST-CARGO-lock-entrY",
wantRecord: reflect.TypeFor[pkg.RustCargoLockEntry](),
wantRecord: reflect.TypeOf(pkg.RustCargoLockEntry{}),
},
{
name: "case insensitive on alias",
lookup: "rusTcArgopacKagEmEtadATa",
wantRecord: reflect.TypeFor[pkg.RustCargoLockEntry](),
wantRecord: reflect.TypeOf(pkg.RustCargoLockEntry{}),
},
{
name: "consistent override",
// there are two correct answers for this -- we should always get the same answer.
lookup: "HackageMetadataType",
wantRecord: reflect.TypeFor[pkg.HackageStackYamlLockEntry](),
wantRecord: reflect.TypeOf(pkg.HackageStackYamlLockEntry{}),
},
}
for _, tt := range tests {
@@ -83,142 +83,142 @@ func TestReflectTypeFromJSONName_LegacyValues(t *testing.T) {
{
name: "map pkg.AlpmDBEntry struct type",
input: "AlpmMetadata",
expected: reflect.TypeFor[pkg.AlpmDBEntry](),
expected: reflect.TypeOf(pkg.AlpmDBEntry{}),
},
{
name: "map pkg.ApkDBEntry struct type",
input: "ApkMetadata",
expected: reflect.TypeFor[pkg.ApkDBEntry](),
expected: reflect.TypeOf(pkg.ApkDBEntry{}),
},
{
name: "map pkg.BinarySignature struct type",
input: "BinaryMetadata",
expected: reflect.TypeFor[pkg.BinarySignature](),
expected: reflect.TypeOf(pkg.BinarySignature{}),
},
{
name: "map pkg.CocoaPodfileLockEntry struct type",
input: "CocoapodsMetadataType",
expected: reflect.TypeFor[pkg.CocoaPodfileLockEntry](),
expected: reflect.TypeOf(pkg.CocoaPodfileLockEntry{}),
},
{
name: "map pkg.ConanLockEntry struct type",
input: "ConanLockMetadataType",
expected: reflect.TypeFor[pkg.ConanV1LockEntry](),
expected: reflect.TypeOf(pkg.ConanV1LockEntry{}),
},
{
name: "map pkg.ConanfileEntry struct type",
input: "ConanMetadataType",
expected: reflect.TypeFor[pkg.ConanfileEntry](),
expected: reflect.TypeOf(pkg.ConanfileEntry{}),
},
{
name: "map pkg.DartPubspecLockEntry struct type",
input: "DartPubMetadata",
expected: reflect.TypeFor[pkg.DartPubspecLockEntry](),
expected: reflect.TypeOf(pkg.DartPubspecLockEntry{}),
},
{
name: "map pkg.DotnetDepsEntry struct type",
input: "DotnetDepsMetadata",
expected: reflect.TypeFor[pkg.DotnetDepsEntry](),
expected: reflect.TypeOf(pkg.DotnetDepsEntry{}),
},
{
name: "map pkg.DpkgDBEntry struct type",
input: "DpkgMetadata",
expected: reflect.TypeFor[pkg.DpkgDBEntry](),
expected: reflect.TypeOf(pkg.DpkgDBEntry{}),
},
{
name: "map pkg.RubyGemspec struct type",
input: "GemMetadata",
expected: reflect.TypeFor[pkg.RubyGemspec](),
expected: reflect.TypeOf(pkg.RubyGemspec{}),
},
{
name: "map pkg.GolangBinaryBuildinfoEntry struct type",
input: "GolangBinMetadata",
expected: reflect.TypeFor[pkg.GolangBinaryBuildinfoEntry](),
expected: reflect.TypeOf(pkg.GolangBinaryBuildinfoEntry{}),
},
{
name: "map pkg.GolangModuleEntry struct type",
input: "GolangModMetadata",
expected: reflect.TypeFor[pkg.GolangModuleEntry](),
expected: reflect.TypeOf(pkg.GolangModuleEntry{}),
},
{
name: "map pkg.JavaArchive struct type",
input: "JavaMetadata",
expected: reflect.TypeFor[pkg.JavaArchive](),
expected: reflect.TypeOf(pkg.JavaArchive{}),
},
{
name: "map pkg.MicrosoftKbPatch struct type",
input: "KbPatchMetadata",
expected: reflect.TypeFor[pkg.MicrosoftKbPatch](),
expected: reflect.TypeOf(pkg.MicrosoftKbPatch{}),
},
{
name: "map pkg.LinuxKernel struct type",
input: "LinuxKernel",
expected: reflect.TypeFor[pkg.LinuxKernel](),
expected: reflect.TypeOf(pkg.LinuxKernel{}),
},
{
name: "map pkg.LinuxKernelModule struct type",
input: "LinuxKernelModule",
expected: reflect.TypeFor[pkg.LinuxKernelModule](),
expected: reflect.TypeOf(pkg.LinuxKernelModule{}),
},
{
name: "map pkg.ElixirMixLockEntry struct type",
input: "MixLockMetadataType",
expected: reflect.TypeFor[pkg.ElixirMixLockEntry](),
expected: reflect.TypeOf(pkg.ElixirMixLockEntry{}),
},
{
name: "map pkg.NixStoreEntry struct type",
input: "NixStoreMetadata",
expected: reflect.TypeFor[pkg.NixStoreEntry](),
expected: reflect.TypeOf(pkg.NixStoreEntry{}),
},
{
name: "map pkg.NpmPackage struct type",
input: "NpmPackageJsonMetadata",
expected: reflect.TypeFor[pkg.NpmPackage](),
expected: reflect.TypeOf(pkg.NpmPackage{}),
},
{
name: "map pkg.NpmPackageLockEntry struct type",
input: "NpmPackageLockJsonMetadata",
expected: reflect.TypeFor[pkg.NpmPackageLockEntry](),
expected: reflect.TypeOf(pkg.NpmPackageLockEntry{}),
},
{
name: "map pkg.PortageEntry struct type",
input: "PortageMetadata",
expected: reflect.TypeFor[pkg.PortageEntry](),
expected: reflect.TypeOf(pkg.PortageEntry{}),
},
{
name: "map pkg.PythonPackage struct type",
input: "PythonPackageMetadata",
expected: reflect.TypeFor[pkg.PythonPackage](),
expected: reflect.TypeOf(pkg.PythonPackage{}),
},
{
name: "map pkg.PythonPipfileLockEntry struct type",
input: "PythonPipfileLockMetadata",
expected: reflect.TypeFor[pkg.PythonPipfileLockEntry](),
expected: reflect.TypeOf(pkg.PythonPipfileLockEntry{}),
},
{
name: "map pkg.PythonRequirementsEntry struct type",
input: "PythonRequirementsMetadata",
expected: reflect.TypeFor[pkg.PythonRequirementsEntry](),
expected: reflect.TypeOf(pkg.PythonRequirementsEntry{}),
},
{
name: "map pkg.PhpPeclEntry struct type",
input: "PhpPeclMetadata",
expected: reflect.TypeFor[pkg.PhpPeclEntry](),
expected: reflect.TypeOf(pkg.PhpPeclEntry{}),
},
{
name: "map pkg.ErlangRebarLockEntry struct type",
input: "RebarLockMetadataType",
expected: reflect.TypeFor[pkg.ErlangRebarLockEntry](),
expected: reflect.TypeOf(pkg.ErlangRebarLockEntry{}),
},
{
name: "map pkg.RDescription struct type",
input: "RDescriptionFileMetadataType",
expected: reflect.TypeFor[pkg.RDescription](),
expected: reflect.TypeOf(pkg.RDescription{}),
},
{
name: "map pkg.RpmDBEntry struct type",
input: "RpmdbMetadata",
expected: reflect.TypeFor[pkg.RpmDBEntry](),
expected: reflect.TypeOf(pkg.RpmDBEntry{}),
},
// these cases are 1:many
{
@@ -228,28 +228,28 @@ func TestReflectTypeFromJSONName_LegacyValues(t *testing.T) {
// from a data-shape perspective either would be equally correct
// however, the RPMDBMetadata has been around longer and may have been more widely used
// so we'll map to that type for backwards compatibility.
expected: reflect.TypeFor[pkg.RpmDBEntry](),
expected: reflect.TypeOf(pkg.RpmDBEntry{}),
},
{
name: "map pkg.HackageStackYamlLockEntry struct type - overlap with HackageStack*Metadata",
input: "HackageMetadataType",
// this used to be shared as a use case for both HackageStackYamlLockEntry and HackageStackYamlEntry
// but the HackageStackYamlLockEntry maps most closely to the original data shape.
expected: reflect.TypeFor[pkg.HackageStackYamlLockEntry](),
expected: reflect.TypeOf(pkg.HackageStackYamlLockEntry{}),
},
{
name: "map pkg.PhpComposerLockEntry struct type",
input: "PhpComposerJsonMetadata",
// this used to be shared as a use case for both PhpComposerLockEntry and PhpComposerInstalledEntry
// neither of these is more correct over the other. These parsers were also introduced at the same time.
expected: reflect.TypeFor[pkg.PhpComposerLockEntry](),
expected: reflect.TypeOf(pkg.PhpComposerLockEntry{}),
},
{
name: "map pkg.RustCargoLockEntry struct type",
input: "RustCargoPackageMetadata",
// this used to be shared as a use case for both RustCargoLockEntry and RustBinaryAuditEntry
// neither of these is more correct over the other.
expected: reflect.TypeFor[pkg.RustCargoLockEntry](),
expected: reflect.TypeOf(pkg.RustCargoLockEntry{}),
},
}
+4 -1
View File
@@ -92,7 +92,10 @@ func processReaderInChunks(rdr io.Reader, chunkSize int, handler func(data []byt
lastRead := 0
for {
offset := min(lastRead, half)
offset := half
if lastRead < half {
offset = lastRead
}
start := half - offset
if lastRead > 0 {
copy(buf[start:], buf[half+offset:half+lastRead])
@@ -26,9 +26,9 @@ var (
pkg.BitnamiPkg,
}
binaryMetadataTypes = []string{
reflect.TypeFor[pkg.ELFBinaryPackageNoteJSONPayload]().Name(),
reflect.TypeFor[pkg.BinarySignature]().Name(),
reflect.TypeFor[pkg.JavaVMInstallation]().Name(),
reflect.TypeOf(pkg.ELFBinaryPackageNoteJSONPayload{}).Name(),
reflect.TypeOf(pkg.BinarySignature{}).Name(),
reflect.TypeOf(pkg.JavaVMInstallation{}).Name(),
}
)
@@ -15,6 +15,7 @@ func TestExcludeByFileOwnershipOverlap(t *testing.T) {
packageC := pkg.Package{Name: "package-c", Type: pkg.BinaryPkg, Metadata: pkg.ELFBinaryPackageNoteJSONPayload{Type: "rpm"}}
packageD := pkg.Package{Name: "package-d", Type: pkg.BitnamiPkg}
for _, p := range []*pkg.Package{&packageA, &packageB, &packageC, &packageD} {
p := p
p.SetID()
}
+5 -5
View File
@@ -8,11 +8,11 @@ import (
)
var jsonNameFromType = map[reflect.Type][]string{
reflect.TypeFor[source.DirectoryMetadata](): {"directory", "dir"},
reflect.TypeFor[source.FileMetadata](): {"file"},
reflect.TypeFor[source.ImageMetadata](): {"image"},
reflect.TypeFor[source.SnapMetadata](): {"snap"},
reflect.TypeFor[source.OCIModelMetadata](): {"oci-model"},
reflect.TypeOf(source.DirectoryMetadata{}): {"directory", "dir"},
reflect.TypeOf(source.FileMetadata{}): {"file"},
reflect.TypeOf(source.ImageMetadata{}): {"image"},
reflect.TypeOf(source.SnapMetadata{}): {"snap"},
reflect.TypeOf(source.OCIModelMetadata{}): {"oci-model"},
}
func AllTypeNames() []string {
+7 -5
View File
@@ -1,9 +1,6 @@
package internal
import (
"slices"
"strings"
)
import "strings"
// HasAnyOfPrefixes returns an indication if the given string has any of the given prefixes.
func HasAnyOfPrefixes(input string, prefixes ...string) bool {
@@ -35,7 +32,12 @@ func TruncateMiddleEllipsis(input string, maxLen int) string {
}
func StringInSlice(a string, list []string) bool {
return slices.Contains(list, a)
for _, b := range list {
if b == a {
return true
}
}
return false
}
func SplitAny(s string, seps string) []string {
+2 -4
View File
@@ -31,7 +31,7 @@ func UpdateGoldenFileContents(t *testing.T, contents []byte) {
t.Log(dangerText("!!! UPDATING GOLDEN FILE !!!"), goldenFilePath)
err := os.WriteFile(goldenFilePath, contents, 0o600)
err := os.WriteFile(goldenFilePath, contents, 0600)
if err != nil {
t.Fatalf("could not update golden file (%s): %+v", goldenFilePath, err)
}
@@ -50,9 +50,7 @@ func GetGoldenFileContents(t *testing.T) []byte {
}
defer f.Close()
// 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
bytes, err := io.ReadAll(f)
if err != nil {
t.Fatalf("could not read file (%s): %+v", goldenPath, err)
}
-121
View File
@@ -1,121 +0,0 @@
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
}
-295
View File
@@ -1,295 +0,0 @@
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 := range goroutines {
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 range goroutines {
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 range 10 {
_, 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))
}
+1 -1
View File
@@ -6,7 +6,7 @@ import (
)
// Tprintf renders a string from a given template string and field values
func Tprintf(tmpl string, data map[string]any) string {
func Tprintf(tmpl string, data map[string]interface{}) string {
t := template.Must(template.New("").Parse(tmpl))
buf := &bytes.Buffer{}
if err := t.Execute(buf, data); err != nil {
+6 -2
View File
@@ -3,7 +3,6 @@ package unknown
import (
"errors"
"fmt"
"slices"
"strings"
"github.com/anchore/syft/internal/log"
@@ -156,7 +155,12 @@ func containsErr(out []error, err error) bool {
log.Tracef("error comparing errors: %v", err)
}
}()
return slices.Contains(out, err)
for _, e := range out {
if e == err {
return true
}
}
return false
}
// visitErrors visits every wrapped error. the returned error replaces the provided error, null errors are omitted from
-1
View File
@@ -5132,7 +5132,6 @@
"armv9-a",
"armv9-m",
"s390x",
"riscv64",
"ppc64",
"ppc64le",
"other",
-1
View File
@@ -6471,7 +6471,6 @@ limitations under the License.
<xs:enumeration value="armv9-a"/>
<xs:enumeration value="armv9-m"/>
<xs:enumeration value="s390x"/>
<xs:enumeration value="riscv64"/>
<xs:enumeration value="ppc64"/>
<xs:enumeration value="ppc64le"/>
<xs:enumeration value="other"/>
+1 -1
View File
@@ -13,7 +13,7 @@ type Identifiable interface {
ID() ID
}
func IDByHash(obj any) (ID, error) {
func IDByHash(obj interface{}) (ID, error) {
f, err := hashstructure.Hash(obj, &hashstructure.HashOptions{
ZeroNil: true,
SlicesAsSets: true,
+1 -1
View File
@@ -38,5 +38,5 @@ type Relationship struct {
From Identifiable
To Identifiable
Type RelationshipType
Data any
Data interface{}
}
+3 -3
View File
@@ -41,7 +41,7 @@ func (cfg configurationAuditTrail) MarshalJSON() ([]byte, error) {
return nil, err
}
var dataMap map[string]any
var dataMap map[string]interface{}
if err := json.Unmarshal(initialJSON, &dataMap); err != nil {
return nil, err
}
@@ -55,13 +55,13 @@ func (cfg configurationAuditTrail) MarshalJSON() ([]byte, error) {
}
// marshalSorted recursively marshals a map with sorted keys
func marshalSorted(m any) ([]byte, error) {
func marshalSorted(m interface{}) ([]byte, error) {
if reflect.TypeOf(m).Kind() != reflect.Map {
return json.Marshal(m)
}
val := reflect.ValueOf(m)
sortedMap := make(map[string]any)
sortedMap := make(map[string]interface{})
for _, key := range val.MapKeys() {
value := val.MapIndex(key).Interface()
+2 -2
View File
@@ -33,7 +33,7 @@ func Test_configurationAuditTrail_StructTags(t *testing.T) {
}
func getJSONTags(t *testing.T, v any) []string {
func getJSONTags(t *testing.T, v interface{}) []string {
var tags []string
err := collectJSONTags(t, reflect.ValueOf(v), &tags, "", "")
require.NoError(t, err)
@@ -142,7 +142,7 @@ func Test_collectJSONTags(t *testing.T) {
tests := []struct {
name string
v any
v interface{}
want []string
wantErr require.ErrorAssertionFunc
}{
+1 -23
View File
@@ -12,10 +12,8 @@ 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"
@@ -26,8 +24,6 @@ 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()
@@ -69,25 +65,12 @@ func CreateSBOM(ctx context.Context, src source.Source, cfg *CreateSBOMConfig) (
},
}
// 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.
// setup everything we need in context: license scanner, executors, 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()
@@ -112,11 +95,6 @@ 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) {
+1 -1
View File
@@ -34,7 +34,7 @@ type CreateSBOMConfig struct {
// audit what tool is being used to generate the SBOM
ToolName string
ToolVersion string
ToolConfiguration any
ToolConfiguration interface{}
packageTaskFactories task.Factories
packageCatalogerReferences []pkgcataloging.CatalogerReference
+2 -2
View File
@@ -17,14 +17,14 @@ import (
type ErrBadPayload struct {
Type partybus.EventType
Field string
Value any
Value interface{}
}
func (e *ErrBadPayload) Error() string {
return fmt.Sprintf("event='%s' has bad event payload field=%q: %q", string(e.Type), e.Field, e.Value)
}
func newPayloadErr(t partybus.EventType, field string, value any) error {
func newPayloadErr(t partybus.EventType, field string, value interface{}) error {
return &ErrBadPayload{
Type: t,
Field: field,
+2 -2
View File
@@ -22,7 +22,7 @@ tools-check:
# docker buildx build --platform linux/amd64 -t $(TOOL_IMAGE) .
tools:
@(docker image inspect $(TOOL_IMAGE) > /dev/null 2>&1 && make tools-check) || (docker build -t $(TOOL_IMAGE) . && sha256sum Dockerfile > Dockerfile.sha256)
@(docker inspect $(TOOL_IMAGE) > /dev/null && 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)
@python3 ./differ expected_verify $(VERIFY_FILE)
@python ./differ expected_verify $(VERIFY_FILE)
debug:
docker run -i --rm -v $(shell pwd):/mount -w /mount/project $(TOOL_IMAGE) bash
@@ -1,9 +1,10 @@
# syntax=docker/dockerfile:1
ARG OSXCROSS_VERSION=13.1
FROM --platform=linux/amd64 crazymax/osxcross:${OSXCROSS_VERSION}-ubuntu AS osxcross
ARG BUILDPLATFORM=linux/amd64
FROM --platform=$BUILDPLATFORM crazymax/osxcross:${OSXCROSS_VERSION}-ubuntu AS osxcross
FROM --platform=linux/amd64 ubuntu:22.04
FROM --platform=$BUILDPLATFORM 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 image inspect $(TOOL_IMAGE) > /dev/null 2>&1 && make tools-check) || (docker build --platform linux/amd64 -t $(TOOL_IMAGE) . && sha256sum Dockerfile > Dockerfile.sha256)
@(docker inspect $(TOOL_IMAGE) > /dev/null && make tools-check) || (docker build -t $(TOOL_IMAGE) . && sha256sum Dockerfile > Dockerfile.sha256)
build: tools
@mkdir -p $(BIN)
+1 -1
View File
@@ -26,5 +26,5 @@ type SearchResult struct {
}
func (s SearchResult) String() string {
return fmt.Sprintf("SearchResult(classification=%q seek=%d length=%d)", s.Classification, s.SeekPosition, s.Length)
return fmt.Sprintf("SearchResult(classification=%q seek=%q length=%q)", s.Classification, s.SeekPosition, s.Length)
}
@@ -310,9 +310,9 @@ func toRootPackage(s source.Description) *spdx.Package {
func toSPDXID(identifiable artifact.Identifiable) spdx.ElementID {
id := string(identifiable.ID())
if after, ok := strings.CutPrefix(id, "SPDXRef-"); ok {
if strings.HasPrefix(id, "SPDXRef-") {
// this is already an SPDX ID, no need to change it (except for the prefix)
return spdx.ElementID(helpers.SanitizeElementID(after))
return spdx.ElementID(helpers.SanitizeElementID(strings.TrimPrefix(id, "SPDXRef-")))
}
maxLen := 40
switch it := identifiable.(type) {
@@ -479,7 +479,7 @@ func toSyftLocation(f *spdx.File) file.Location {
return l
}
func requireAndTrimPrefix(val any, prefix string) string {
func requireAndTrimPrefix(val interface{}, prefix string) string {
if v, ok := val.(string); ok {
if i := strings.Index(v, prefix); i == 0 {
return strings.Replace(v, prefix, "", 1)
@@ -119,7 +119,7 @@ func Test_extractMetadata(t *testing.T) {
oneTwoThreeFour := 1234
tests := []struct {
pkg spdx.Package
meta any
meta interface{}
}{
{
pkg: spdx.Package{
@@ -73,6 +73,6 @@ type DependencyGraph map[string]DependencyNode
type ISO8601Date = string
type Scalar any // should be: null | boolean | string | number
type Scalar interface{} // should be: null | boolean | string | number
type Metadata map[string]Scalar
+2 -2
View File
@@ -151,13 +151,13 @@ var epochPrefix = regexp.MustCompile(`^\d+:`)
// nameFromPurl returns the syft package name of the package from the purl. If the purl includes a namespace,
// the name is prefixed as appropriate based on the PURL type
func nameFromPurl(purl packageurl.PackageURL) string {
if !NameExcludesPurlNamespace(purl.Type) && purl.Namespace != "" {
if !nameExcludesPurlNamespace(purl.Type) && purl.Namespace != "" {
return fmt.Sprintf("%s/%s", purl.Namespace, purl.Name)
}
return purl.Name
}
func NameExcludesPurlNamespace(purlType string) bool {
func nameExcludesPurlNamespace(purlType string) bool {
switch purlType {
case packageurl.TypeAlpine,
packageurl.TypeAlpm,
@@ -31,7 +31,7 @@ func encodeAuthor(p pkg.Package) string {
return ""
}
func decodeAuthor(author string, metadata any) {
func decodeAuthor(author string, metadata interface{}) {
switch meta := metadata.(type) {
case *pkg.NpmPackage:
meta.Author = author
@@ -165,7 +165,6 @@ func getPURL(c *cyclonedx.Component, ty pkg.Type) string {
return purl.ToString()
}
//nolint:gocognit
func setPackageName(p *pkg.Package, c *cyclonedx.Component) {
name := c.Name
if c.Group != "" {
@@ -196,16 +195,14 @@ func setPackageName(p *pkg.Package, c *cyclonedx.Component) {
}
}
default:
if !internal.NameExcludesPurlNamespace(p.Type.PackageURLType()) {
name = fmt.Sprintf("%s/%s", c.Group, name)
}
name = fmt.Sprintf("%s/%s", c.Group, name)
}
}
p.Name = name
}
func decodeLocations(vals map[string]string) file.LocationSet {
v := Decode(reflect.TypeFor[[]file.Location](), vals, "syft:location", CycloneDXFields)
v := Decode(reflect.TypeOf([]file.Location{}), vals, "syft:location", CycloneDXFields)
out, ok := v.([]file.Location)
if !ok {
out = nil
@@ -213,7 +210,7 @@ func decodeLocations(vals map[string]string) file.LocationSet {
return file.NewLocationSet(out...)
}
func decodePackageMetadata(vals map[string]string, c *cyclonedx.Component, typeName string) any {
func decodePackageMetadata(vals map[string]string, c *cyclonedx.Component, typeName string) interface{} {
if typeName != "" && c.Properties != nil {
metadataType := packagemetadata.ReflectTypeFromJSONName(typeName)
if metadataType == nil {
@@ -384,65 +384,6 @@ func Test_decodeComponent(t *testing.T) {
}
}
func Test_setPackageName(t *testing.T) {
tests := []struct {
name string
pkg pkg.Package
comp cyclonedx.Component
wantName string
}{
{
name: "debian group excluded from name",
pkg: pkg.Package{Type: pkg.DebPkg},
comp: cyclonedx.Component{Name: "wget", Group: "debian"},
wantName: "wget",
},
{
name: "rpm group excluded from name",
pkg: pkg.Package{Type: pkg.RpmPkg},
comp: cyclonedx.Component{Name: "acl", Group: "centos"},
wantName: "acl",
},
{
name: "apk group excluded from name",
pkg: pkg.Package{Type: pkg.ApkPkg},
comp: cyclonedx.Component{Name: "musl", Group: "alpine"},
wantName: "musl",
},
{
name: "npm group included in name",
pkg: pkg.Package{Type: pkg.NpmPkg},
comp: cyclonedx.Component{Name: "node", Group: "@types"},
wantName: "@types/node",
},
{
name: "go module group included in name",
pkg: pkg.Package{Type: pkg.GoModulePkg},
comp: cyclonedx.Component{Name: "net", Group: "golang.org/x"},
wantName: "golang.org/x/net",
},
{
name: "no group leaves name unchanged",
pkg: pkg.Package{Type: pkg.DebPkg},
comp: cyclonedx.Component{Name: "wget"},
wantName: "wget",
},
{
name: "java group stored in metadata not name",
pkg: pkg.Package{Type: pkg.JavaPkg},
comp: cyclonedx.Component{Name: "log4j", Group: "org.apache.logging.log4j"},
wantName: "log4j",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p := tt.pkg
setPackageName(&p, &tt.comp)
assert.Equal(t, tt.wantName, p.Name)
})
}
}
func TestGetPURL(t *testing.T) {
tests := []struct {
name string
@@ -26,7 +26,7 @@ func ToSyftModel(bom *cyclonedx.BOM) (*sbom.SBOM, error) {
Descriptor: extractDescriptor(bom.Metadata),
}
idMap := make(map[string]any)
idMap := make(map[string]interface{})
if err := collectBomPackages(bom, s, idMap); err != nil {
return nil, err
@@ -37,7 +37,7 @@ func ToSyftModel(bom *cyclonedx.BOM) (*sbom.SBOM, error) {
return s, nil
}
func collectBomPackages(bom *cyclonedx.BOM, s *sbom.SBOM, idMap map[string]any) error {
func collectBomPackages(bom *cyclonedx.BOM, s *sbom.SBOM, idMap map[string]interface{}) error {
componentsPresent := false
if bom.Components != nil {
for i := range *bom.Components {
@@ -58,7 +58,7 @@ func collectBomPackages(bom *cyclonedx.BOM, s *sbom.SBOM, idMap map[string]any)
return nil
}
func collectPackages(component *cyclonedx.Component, s *sbom.SBOM, idMap map[string]any) {
func collectPackages(component *cyclonedx.Component, s *sbom.SBOM, idMap map[string]interface{}) {
switch component.Type {
case cyclonedx.ComponentTypeOS:
case cyclonedx.ComponentTypeContainer:
@@ -168,7 +168,7 @@ func getPropertyValue(component *cyclonedx.Component, name string) string {
return ""
}
func collectRelationships(bom *cyclonedx.BOM, s *sbom.SBOM, idMap map[string]any) {
func collectRelationships(bom *cyclonedx.BOM, s *sbom.SBOM, idMap map[string]interface{}) {
if bom.Dependencies == nil {
return
}
@@ -14,7 +14,7 @@ func encodeDescription(p pkg.Package) string {
return ""
}
func decodeDescription(description string, metadata any) {
func decodeDescription(description string, metadata interface{}) {
switch meta := metadata.(type) {
case *pkg.ApkDBEntry:
meta.Description = description

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