mirror of
https://github.com/anchore/syft.git
synced 2026-10-12 14:17:20 +02:00
Compare commits
101
Commits
v1.6.0
...
identity-proof
+5
-5
@@ -18,7 +18,7 @@ tools:
|
||||
# used to sign mac binaries at release
|
||||
- name: quill
|
||||
version:
|
||||
want: v0.4.1
|
||||
want: v0.4.2
|
||||
method: github-release
|
||||
with:
|
||||
repo: anchore/quill
|
||||
@@ -42,7 +42,7 @@ tools:
|
||||
# used for signing the checksums file at release
|
||||
- name: cosign
|
||||
version:
|
||||
want: v2.2.4
|
||||
want: v2.3.0
|
||||
method: github-release
|
||||
with:
|
||||
repo: sigstore/cosign
|
||||
@@ -58,7 +58,7 @@ tools:
|
||||
# used to release all artifacts
|
||||
- name: goreleaser
|
||||
version:
|
||||
want: v2.0.0
|
||||
want: v2.1.0
|
||||
method: github-release
|
||||
with:
|
||||
repo: goreleaser/goreleaser
|
||||
@@ -103,7 +103,7 @@ tools:
|
||||
# used for running all local and CI tasks
|
||||
- name: task
|
||||
version:
|
||||
want: v3.37.2
|
||||
want: v3.38.0
|
||||
method: github-release
|
||||
with:
|
||||
repo: go-task/task
|
||||
@@ -111,7 +111,7 @@ tools:
|
||||
# used for triggering a release
|
||||
- name: gh
|
||||
version:
|
||||
want: v2.50.0
|
||||
want: v2.54.0
|
||||
method: github-release
|
||||
with:
|
||||
repo: cli/cli
|
||||
|
||||
@@ -6,6 +6,7 @@ permit:
|
||||
- MPL.*
|
||||
- ISC
|
||||
- WTFPL
|
||||
- Unlicense
|
||||
|
||||
ignore-packages:
|
||||
# packageurl-go is released under the MIT license located in the root of the repo at /mit.LICENSE
|
||||
|
||||
@@ -27,14 +27,14 @@ runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
# note: go mod and build is automatically cached on default with v4+
|
||||
- uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 #v5.0.1
|
||||
- uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
|
||||
if: inputs.go-version != ''
|
||||
with:
|
||||
go-version: ${{ inputs.go-version }}
|
||||
|
||||
- name: Restore tool cache
|
||||
id: tool-cache
|
||||
uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 #v3.3.2
|
||||
uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2
|
||||
with:
|
||||
path: ${{ github.workspace }}/.tool
|
||||
key: ${{ inputs.cache-key-prefix }}-${{ runner.os }}-tool-${{ hashFiles('.binny.yaml') }}
|
||||
|
||||
@@ -17,3 +17,11 @@ updates:
|
||||
open-pull-requests-limit: 10
|
||||
labels:
|
||||
- "dependencies"
|
||||
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/.github/actions/boostrap"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
open-pull-requests-limit: 10
|
||||
labels:
|
||||
- "dependencies"
|
||||
|
||||
@@ -4,12 +4,19 @@ set -eu
|
||||
bold=$(tput bold)
|
||||
normal=$(tput sgr0)
|
||||
|
||||
if ! [ -x "$(command -v gh)" ]; then
|
||||
echo "The GitHub CLI could not be found. To continue follow the instructions at https://github.com/cli/cli#installation"
|
||||
GH_CLI=.tool/gh
|
||||
|
||||
if ! [ -x "$(command -v $GH_CLI)" ]; then
|
||||
echo "The GitHub CLI could not be found. run: make bootstrap"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
gh auth status
|
||||
$GH_CLI auth status
|
||||
|
||||
# set the default repo in cases where multiple remotes are defined
|
||||
$GH_CLI repo set-default anchore/syft
|
||||
|
||||
export GITHUB_TOKEN="${GITHUB_TOKEN-"$($GH_CLI auth token)"}"
|
||||
|
||||
# we need all of the git state to determine the next version. Since tagging is done by
|
||||
# the release pipeline it is possible to not have all of the tags from previous releases.
|
||||
@@ -37,7 +44,7 @@ done
|
||||
|
||||
echo "${bold}Kicking off release for ${NEXT_VERSION}${normal}..."
|
||||
echo
|
||||
gh workflow run release.yaml -f version=${NEXT_VERSION}
|
||||
$GH_CLI workflow run release.yaml -f version=${NEXT_VERSION}
|
||||
|
||||
echo
|
||||
echo "${bold}Waiting for release to start...${normal}"
|
||||
@@ -45,6 +52,6 @@ sleep 10
|
||||
|
||||
set +e
|
||||
|
||||
echo "${bold}Head to the release workflow to monitor the release:${normal} $(gh run list --workflow=release.yaml --limit=1 --json url --jq '.[].url')"
|
||||
id=$(gh run list --workflow=release.yaml --limit=1 --json databaseId --jq '.[].databaseId')
|
||||
gh run watch $id --exit-status || (echo ; echo "${bold}Logs of failed step:${normal}" && GH_PAGER="" gh run view $id --log-failed)
|
||||
echo "${bold}Head to the release workflow to monitor the release:${normal} $($GH_CLI run list --workflow=release.yaml --limit=1 --json url --jq '.[].url')"
|
||||
id=$($GH_CLI run list --workflow=release.yaml --limit=1 --json databaseId --jq '.[].databaseId')
|
||||
$GH_CLI run watch $id --exit-status || (echo ; echo "${bold}Logs of failed step:${normal}" && GH_PAGER="" $GH_CLI run view $id --log-failed)
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
# the job by event.
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
OUTPUT="${OUTPUT//$'\r'/'%0D'}" # URL encode all '\r' characters
|
||||
echo "result=$OUTPUT" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
|
||||
- uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # v4.3.5
|
||||
with:
|
||||
name: benchmark-test-results
|
||||
path: test/results/**/*
|
||||
|
||||
@@ -36,16 +36,16 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 #v4.1.6
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 #v5.0.1
|
||||
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 #v5.0.2
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@2e230e8fe0ad3a14a340ad0815ddb96d599d2aff #v3.25.8
|
||||
uses: github/codeql-action/init@afb54ba388a7dca6ecae48f608c4ff05ff4cc77a #v3.25.15
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@2e230e8fe0ad3a14a340ad0815ddb96d599d2aff #v3.25.8
|
||||
uses: github/codeql-action/autobuild@afb54ba388a7dca6ecae48f608c4ff05ff4cc77a #v3.25.15
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
@@ -70,4 +70,4 @@ jobs:
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@2e230e8fe0ad3a14a340ad0815ddb96d599d2aff #v3.25.8
|
||||
uses: github/codeql-action/analyze@afb54ba388a7dca6ecae48f608c4ff05ff4cc77a #v3.25.15
|
||||
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 #v4.1.6
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7
|
||||
|
||||
- run: python .github/scripts/labeler.py
|
||||
env:
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
release:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 #v4.1.6
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7
|
||||
|
||||
- name: Update version file
|
||||
run: make ci-release-version-file
|
||||
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
environment: release
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 #v4.1.6
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7
|
||||
|
||||
- name: Check if running on main
|
||||
if: github.ref != 'refs/heads/main'
|
||||
@@ -105,7 +105,7 @@ jobs:
|
||||
# required for goreleaser signs section with cosign
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 #v4.1.6
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -113,13 +113,13 @@ jobs:
|
||||
uses: ./.github/actions/bootstrap
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 #v3.2.0
|
||||
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 #v3.3.0
|
||||
with:
|
||||
username: ${{ secrets.ANCHOREOSSWRITE_DH_USERNAME }}
|
||||
password: ${{ secrets.ANCHOREOSSWRITE_DH_PAT }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 #v3.2.0
|
||||
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 #v3.3.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
@@ -148,7 +148,7 @@ jobs:
|
||||
# for updating brew formula in anchore/homebrew-syft
|
||||
GITHUB_BREW_TOKEN: ${{ secrets.ANCHOREOPS_GITHUB_OSS_WRITE_TOKEN }}
|
||||
|
||||
- uses: anchore/sbom-action@e8d2a6937ecead383dfe75190d104edd1f9c5751 #v0.16.0
|
||||
- uses: anchore/sbom-action@d94f46e13c6c62f59525ac9a1e147a99dc0b9bf5 #v0.17.0
|
||||
continue-on-error: true
|
||||
with:
|
||||
artifact-name: sbom.spdx.json
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'anchore/syft' # only run for main repo
|
||||
steps:
|
||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 #v4.1.6
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
app_id: ${{ secrets.TOKEN_APP_ID }}
|
||||
private_key: ${{ secrets.TOKEN_APP_PRIVATE_KEY }}
|
||||
|
||||
- uses: peter-evans/create-pull-request@6d6857d36972b65feb161a90e484f2984215f83e #v6.0.5
|
||||
- uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c #v6.1.0
|
||||
with:
|
||||
signoff: true
|
||||
delete-branch: true
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'anchore/syft' # only run for main repo
|
||||
steps:
|
||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 #v4.1.6
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
app_id: ${{ secrets.TOKEN_APP_ID }}
|
||||
private_key: ${{ secrets.TOKEN_APP_PRIVATE_KEY }}
|
||||
|
||||
- uses: peter-evans/create-pull-request@6d6857d36972b65feb161a90e484f2984215f83e #v6.0.5
|
||||
- uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c #v6.1.0
|
||||
with:
|
||||
signoff: true
|
||||
delete-branch: true
|
||||
|
||||
@@ -17,9 +17,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'anchore/syft' # only run for main repo
|
||||
steps:
|
||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 #v4.1.6
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7
|
||||
|
||||
- uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 #v5.0.1
|
||||
- uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 #v5.0.2
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
stable: ${{ env.GO_STABLE_VERSION }}
|
||||
@@ -44,7 +44,7 @@ jobs:
|
||||
app_id: ${{ secrets.TOKEN_APP_ID }}
|
||||
private_key: ${{ secrets.TOKEN_APP_PRIVATE_KEY }}
|
||||
|
||||
- uses: peter-evans/create-pull-request@6d6857d36972b65feb161a90e484f2984215f83e #v6.0.5
|
||||
- uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c #v6.1.0
|
||||
with:
|
||||
signoff: true
|
||||
delete-branch: true
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
name: "Static analysis"
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 #v4.1.6
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
name: "Unit tests"
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 #v4.1.6
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
@@ -87,7 +87,7 @@ jobs:
|
||||
name: "Integration tests"
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 #v4.1.6
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
@@ -109,7 +109,7 @@ jobs:
|
||||
name: "Build snapshot artifacts"
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 #v4.1.6
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
@@ -139,7 +139,7 @@ jobs:
|
||||
needs: [Build-Snapshot-Artifacts]
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 #v4.1.6
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
@@ -190,7 +190,7 @@ jobs:
|
||||
- name: Install Cosign
|
||||
uses: sigstore/cosign-installer@v3.5.0
|
||||
|
||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 #v4.1.6
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
@@ -234,7 +234,7 @@ jobs:
|
||||
needs: [Build-Snapshot-Artifacts]
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 #v4.1.6
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
[opensource@anchore.com](mailto:opensource@anchore.com).
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
||||
@@ -11,7 +11,7 @@
|
||||
<a href="https://goreportcard.com/report/github.com/anchore/syft" target="_blank"><img alt="Go Report Card" src="https://goreportcard.com/badge/github.com/anchore/syft"></a>
|
||||
<a href="https://github.com/anchore/syft/releases/latest" target="_blank"><img alt="GitHub release" src="https://img.shields.io/github/release/anchore/syft.svg"></a>
|
||||
<a href="https://github.com/anchore/syft" target="_blank"><img alt="GitHub go.mod Go version" src="https://img.shields.io/github/go-mod/go-version/anchore/syft.svg"></a>
|
||||
<a href"" target="_blank"><img alt="License: Apache-2.0" src="https://img.shields.io/badge/License-Apache%202.0-blue.svg"></a>
|
||||
<a href="" target="_blank"><img alt="License: Apache-2.0" src="https://img.shields.io/badge/License-Apache%202.0-blue.svg"></a>
|
||||
<a href="https://anchore.com/slack" target="_blank"><img alt="Slack" src="https://img.shields.io/badge/Slack-Join-blue?logo=slack"></a>
|
||||
</p>
|
||||
|
||||
@@ -105,8 +105,10 @@ syft <image> -o <format>
|
||||
Where the `formats` available are:
|
||||
- `syft-json`: Use this to get as much information out of Syft as possible!
|
||||
- `syft-text`: A row-oriented, human-and-machine-friendly output.
|
||||
- `cyclonedx-xml`: A XML report conforming to the [CycloneDX 1.4 specification](https://cyclonedx.org/specification/overview/).
|
||||
- `cyclonedx-json`: A JSON report conforming to the [CycloneDX 1.4 specification](https://cyclonedx.org/specification/overview/).
|
||||
- `cyclonedx-xml`: A XML report conforming to the [CycloneDX 1.6 specification](https://cyclonedx.org/specification/overview/).
|
||||
- `cyclonedx-xml@1.5`: A XML report conforming to the [CycloneDX 1.5 specification](https://cyclonedx.org/specification/overview/).
|
||||
- `cyclonedx-json`: A JSON report conforming to the [CycloneDX 1.6 specification](https://cyclonedx.org/specification/overview/).
|
||||
- `cyclonedx-json@1.5`: A JSON report conforming to the [CycloneDX 1.5 specification](https://cyclonedx.org/specification/overview/).
|
||||
- `spdx-tag-value`: A tag-value formatted report conforming to the [SPDX 2.3 specification](https://spdx.github.io/spdx-spec/v2.3/).
|
||||
- `spdx-tag-value@2.2`: A tag-value formatted report conforming to the [SPDX 2.2 specification](https://spdx.github.io/spdx-spec/v2.2.2/).
|
||||
- `spdx-json`: A JSON report conforming to the [SPDX 2.3 JSON Schema](https://github.com/spdx/spdx-spec/blob/v2.3/schemas/spdx-schema.json).
|
||||
@@ -115,6 +117,8 @@ Where the `formats` available are:
|
||||
- `syft-table`: A columnar summary (default).
|
||||
- `template`: Lets the user specify the output format. See ["Using templates"](#using-templates) below.
|
||||
|
||||
Note that flags using the @<version> can be used for earlier versions of each specification as well.
|
||||
|
||||
### Supported Ecosystems
|
||||
|
||||
- Alpine (apk)
|
||||
@@ -164,9 +168,15 @@ Our [wiki](https://github.com/anchore/syft/wiki) contains further details on the
|
||||
* [Adding an SBOM to an image as an attestation using Syft](https://github.com/anchore/syft/wiki/attestation#adding-an-sbom-to-an-image-as-an-attestation-using-syft)
|
||||
* [Configuration](https://github.com/anchore/syft/wiki/configuration)
|
||||
|
||||
## Contributing
|
||||
|
||||
Check out our [contributing](/CONTRIBUTING.md) guide and [developer](/DEVELOPING.md) docs.
|
||||
|
||||
## Syft Team Meetings
|
||||
|
||||
The Syft Team hold regular community meetings online. All are welcome to join to bring topics for discussion.
|
||||
- Check the [calendar](https://calendar.google.com/calendar/u/0/r?cid=Y182OTM4dGt0MjRtajI0NnNzOThiaGtnM29qNEBncm91cC5jYWxlbmRhci5nb29nbGUuY29t) for the next meeting date.
|
||||
- Add items to the [agenda](https://docs.google.com/document/d/1ZtSAa6fj2a6KRWviTn3WoJm09edvrNUp4Iz_dOjjyY8/edit?usp=sharing) (join [this group](https://groups.google.com/g/anchore-oss-community) for write access to the [agenda](https://docs.google.com/document/d/1ZtSAa6fj2a6KRWviTn3WoJm09edvrNUp4Iz_dOjjyY8/edit?usp=sharing))
|
||||
- See you there!
|
||||
|
||||
<!-- KJIc1D4zXJbRybKEJrWd -->
|
||||
|
||||
@@ -43,6 +43,7 @@ type attestOptions struct {
|
||||
options.UpdateCheck `yaml:",inline" mapstructure:",squash"`
|
||||
options.Catalog `yaml:",inline" mapstructure:",squash"`
|
||||
Attest options.Attest `yaml:"attest" mapstructure:"attest"`
|
||||
Cache options.Cache `json:"-" yaml:"cache" mapstructure:"cache"`
|
||||
}
|
||||
|
||||
func Attest(app clio.Application) *cobra.Command {
|
||||
@@ -77,6 +78,7 @@ func defaultAttestOptions() attestOptions {
|
||||
Output: defaultAttestOutputOptions(),
|
||||
UpdateCheck: options.DefaultUpdateCheck(),
|
||||
Catalog: options.DefaultCatalog(),
|
||||
Cache: options.DefaultCache(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -68,6 +68,7 @@ type scanOptions struct {
|
||||
options.Output `yaml:",inline" mapstructure:",squash"`
|
||||
options.UpdateCheck `yaml:",inline" mapstructure:",squash"`
|
||||
options.Catalog `yaml:",inline" mapstructure:",squash"`
|
||||
Cache options.Cache `json:"-" yaml:"cache" mapstructure:"cache"`
|
||||
}
|
||||
|
||||
func defaultScanOptions() *scanOptions {
|
||||
@@ -75,6 +76,7 @@ func defaultScanOptions() *scanOptions {
|
||||
Output: options.DefaultOutput(),
|
||||
UpdateCheck: options.DefaultUpdateCheck(),
|
||||
Catalog: options.DefaultCatalog(),
|
||||
Cache: options.DefaultCache(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
package options
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/adrg/xdg"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
|
||||
"github.com/anchore/clio"
|
||||
"github.com/anchore/syft/internal/cache"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
)
|
||||
|
||||
// Cache provides configuration for the Syft caching behavior
|
||||
type Cache struct {
|
||||
Dir string `yaml:"dir" mapstructure:"dir"`
|
||||
TTL string `yaml:"ttl" mapstructure:"ttl"`
|
||||
}
|
||||
|
||||
func (c *Cache) DescribeFields(descriptions clio.FieldDescriptionSet) {
|
||||
descriptions.Add(&c.Dir, "root directory to cache any downloaded content; empty string will use an in-memory cache")
|
||||
descriptions.Add(&c.TTL, "time to live for cached data; setting this to 0 will disable caching entirely")
|
||||
}
|
||||
|
||||
func (c *Cache) PostLoad() error {
|
||||
ttl, err := parseDuration(c.TTL)
|
||||
if err != nil {
|
||||
log.Warnf("unable to parse duration '%v', using default (%s) due to: %v", c.TTL, durationToString(defaultTTL()), err)
|
||||
ttl = defaultTTL()
|
||||
}
|
||||
// if TTL is <= 0, disable caching entirely
|
||||
if ttl <= 0 {
|
||||
cache.SetManager(nil)
|
||||
return nil
|
||||
}
|
||||
// if dir == "" but we have a TTL, use an in-memory cache
|
||||
if c.Dir == "" {
|
||||
cache.SetManager(cache.NewInMemory(ttl))
|
||||
return nil
|
||||
}
|
||||
dir, err := homedir.Expand(c.Dir)
|
||||
if err != nil {
|
||||
log.Warnf("unable to expand cache directory %s: %v", c.Dir, err)
|
||||
cache.SetManager(cache.NewInMemory(ttl))
|
||||
} else {
|
||||
m, err := cache.NewFromDir(dir, ttl)
|
||||
if err != nil {
|
||||
log.Warnf("unable to get filesystem cache at %s: %v", c.Dir, err)
|
||||
cache.SetManager(cache.NewInMemory(ttl))
|
||||
} else {
|
||||
cache.SetManager(m)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ interface {
|
||||
clio.PostLoader
|
||||
clio.FieldDescriber
|
||||
} = (*Cache)(nil)
|
||||
|
||||
func DefaultCache() Cache {
|
||||
return Cache{
|
||||
Dir: defaultDir(),
|
||||
TTL: durationToString(defaultTTL()),
|
||||
}
|
||||
}
|
||||
|
||||
func defaultTTL() time.Duration {
|
||||
return 7 * 24 * time.Hour
|
||||
}
|
||||
|
||||
func defaultDir() string {
|
||||
var err error
|
||||
cacheRoot := xdg.CacheHome
|
||||
if cacheRoot == "" {
|
||||
cacheRoot, err = homedir.Dir()
|
||||
if err != nil {
|
||||
cacheRoot = os.TempDir()
|
||||
log.Debugf("unable to get stable cache directory due to: %v, defaulting cache to temp dir: %s", err, cacheRoot)
|
||||
} else {
|
||||
cacheRoot = filepath.Join(cacheRoot, ".cache")
|
||||
}
|
||||
}
|
||||
|
||||
return filepath.Join(cacheRoot, "syft")
|
||||
}
|
||||
|
||||
func durationToString(duration time.Duration) string {
|
||||
days := int64(duration / (24 * time.Hour))
|
||||
remain := duration % (24 * time.Hour)
|
||||
out := ""
|
||||
if days > 0 {
|
||||
out = fmt.Sprintf("%vd", days)
|
||||
}
|
||||
if remain != 0 {
|
||||
out += remain.String()
|
||||
}
|
||||
if out == "" {
|
||||
return "0"
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func parseDuration(duration string) (time.Duration, error) {
|
||||
whitespace := regexp.MustCompile(`\s+`)
|
||||
duration = strings.ToLower(whitespace.ReplaceAllString(duration, ""))
|
||||
parts := strings.SplitN(duration, "d", 2)
|
||||
var days time.Duration
|
||||
var remain time.Duration
|
||||
var err error
|
||||
if len(parts) > 1 {
|
||||
numDays, daysErr := strconv.Atoi(parts[0])
|
||||
if daysErr != nil {
|
||||
return 0, daysErr
|
||||
}
|
||||
days = time.Duration(numDays) * 24 * time.Hour
|
||||
if len(parts[1]) > 0 {
|
||||
remain, err = time.ParseDuration(parts[1])
|
||||
}
|
||||
} else {
|
||||
remain, err = time.ParseDuration(duration)
|
||||
}
|
||||
return days + remain, err
|
||||
}
|
||||
@@ -0,0 +1,313 @@
|
||||
package options
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/adrg/xdg"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/internal/cache"
|
||||
)
|
||||
|
||||
func Test_defaultDir(t *testing.T) {
|
||||
tmpDir := filepath.Join(t.TempDir(), "cache-temp")
|
||||
xdgCacheDir := filepath.Join(tmpDir, "fake-xdg-cache")
|
||||
homeDir := filepath.Join(tmpDir, "fake-home")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
env map[string]string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "no-xdg",
|
||||
env: map[string]string{
|
||||
"HOME": homeDir,
|
||||
},
|
||||
expected: homeDir,
|
||||
},
|
||||
{
|
||||
name: "xdg-cache",
|
||||
env: map[string]string{
|
||||
"XDG_CACHE_HOME": xdgCacheDir,
|
||||
},
|
||||
expected: xdgCacheDir,
|
||||
},
|
||||
}
|
||||
|
||||
// capture all the initial environment variables to reset them before we reset library caches
|
||||
env := map[string]string{
|
||||
"HOME": "",
|
||||
"XDG_DATA_HOME": "",
|
||||
"XDG_DATA_DIRS": "",
|
||||
"XDG_CONFIG_HOME": "",
|
||||
"XDG_CONFIG_DIRS": "",
|
||||
"XDG_STATE_HOME": "",
|
||||
"XDG_CACHE_HOME": "",
|
||||
"XDG_RUNTIME_DIR": "",
|
||||
}
|
||||
for k := range env {
|
||||
env[k] = os.Getenv(k)
|
||||
}
|
||||
|
||||
unsetEnv := func(t *testing.T) {
|
||||
for k := range env {
|
||||
t.Setenv(k, "")
|
||||
}
|
||||
}
|
||||
|
||||
resetEnv := func() {
|
||||
for k, v := range env {
|
||||
if v == "" {
|
||||
_ = os.Unsetenv(k)
|
||||
} else {
|
||||
_ = os.Setenv(k, v)
|
||||
}
|
||||
}
|
||||
homedir.Reset()
|
||||
xdg.Reload()
|
||||
}
|
||||
|
||||
t.Cleanup(resetEnv)
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
defer resetEnv()
|
||||
|
||||
unsetEnv(t)
|
||||
for k, v := range test.env {
|
||||
t.Setenv(k, v)
|
||||
}
|
||||
homedir.Reset()
|
||||
xdg.Reload()
|
||||
|
||||
got := defaultDir()
|
||||
|
||||
require.True(t, strings.HasPrefix(got, test.expected))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_cacheOptions(t *testing.T) {
|
||||
tmp := t.TempDir()
|
||||
tests := []struct {
|
||||
name string
|
||||
opts Cache
|
||||
test func(t *testing.T)
|
||||
}{
|
||||
{
|
||||
name: "disable-1",
|
||||
opts: Cache{
|
||||
Dir: "some-dir",
|
||||
TTL: "0",
|
||||
},
|
||||
test: func(t *testing.T) {
|
||||
c := cache.GetManager().GetCache("test-disable-1", "v-disable-1")
|
||||
err := c.Write("key-disable-1", strings.NewReader("some-value-disable-1"))
|
||||
require.NoError(t, err)
|
||||
rdr, err := c.Read("key-disable-1")
|
||||
require.Nil(t, rdr)
|
||||
require.Error(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "disable-2",
|
||||
opts: Cache{
|
||||
Dir: "some-dir",
|
||||
TTL: "0s",
|
||||
},
|
||||
test: func(t *testing.T) {
|
||||
c := cache.GetManager().GetCache("test-disable-2", "v-disable-2")
|
||||
err := c.Write("key-disable-2", strings.NewReader("some-value-disable-2"))
|
||||
require.NoError(t, err)
|
||||
rdr, err := c.Read("key-disable-2")
|
||||
require.Nil(t, rdr)
|
||||
require.Error(t, err)
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "disable-3",
|
||||
opts: Cache{
|
||||
Dir: "some-dir",
|
||||
TTL: "0d",
|
||||
},
|
||||
test: func(t *testing.T) {
|
||||
c := cache.GetManager().GetCache("test-disable-3", "v-disable-3")
|
||||
err := c.Write("key-disable-3", strings.NewReader("some-value-disable-3"))
|
||||
require.NoError(t, err)
|
||||
rdr, err := c.Read("key-disable-3")
|
||||
require.Nil(t, rdr)
|
||||
require.Error(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "in-memory",
|
||||
opts: Cache{
|
||||
Dir: "",
|
||||
TTL: "10m",
|
||||
},
|
||||
test: func(t *testing.T) {
|
||||
c := cache.GetManager().GetCache("test-mem", "v-mem")
|
||||
err := c.Write("key-mem", strings.NewReader("some-value-mem"))
|
||||
require.NoError(t, err)
|
||||
rdr, err := c.Read("key-mem")
|
||||
require.NotNil(t, rdr)
|
||||
defer internal.CloseAndLogError(rdr, "")
|
||||
require.NoError(t, err)
|
||||
data, err := io.ReadAll(rdr)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "some-value-mem", string(data))
|
||||
require.NoDirExists(t, filepath.Join("test-mem", "v-mem"))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "on disk",
|
||||
opts: Cache{
|
||||
Dir: tmp,
|
||||
TTL: "10m",
|
||||
},
|
||||
test: func(t *testing.T) {
|
||||
c := cache.GetManager().GetCache("test-disk", "v-disk")
|
||||
err := c.Write("key-disk", strings.NewReader("some-value-disk"))
|
||||
require.NoError(t, err)
|
||||
rdr, err := c.Read("key-disk")
|
||||
require.NotNil(t, rdr)
|
||||
defer internal.CloseAndLogError(rdr, "")
|
||||
require.NoError(t, err)
|
||||
data, err := io.ReadAll(rdr)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "some-value-disk", string(data))
|
||||
require.DirExists(t, filepath.Join(tmp, "test-disk", "v-disk"))
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
original := cache.GetManager()
|
||||
defer cache.SetManager(original)
|
||||
|
||||
err := test.opts.PostLoad()
|
||||
require.NoError(t, err)
|
||||
|
||||
test.test(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_parseDuration(t *testing.T) {
|
||||
tests := []struct {
|
||||
duration string
|
||||
expect time.Duration
|
||||
err require.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
duration: "0d",
|
||||
expect: 0,
|
||||
},
|
||||
{
|
||||
duration: "0m",
|
||||
expect: 0,
|
||||
},
|
||||
{
|
||||
duration: "0s",
|
||||
expect: 0,
|
||||
},
|
||||
{
|
||||
duration: "0",
|
||||
expect: 0,
|
||||
},
|
||||
{
|
||||
duration: "1d",
|
||||
expect: 24 * time.Hour,
|
||||
},
|
||||
{
|
||||
duration: "7d",
|
||||
expect: 7 * 24 * time.Hour,
|
||||
},
|
||||
{
|
||||
duration: "365D",
|
||||
expect: 365 * 24 * time.Hour,
|
||||
},
|
||||
{
|
||||
duration: "7d1h1m1s",
|
||||
expect: 7*24*time.Hour + time.Hour + time.Minute + time.Second,
|
||||
},
|
||||
{
|
||||
duration: "7d 1h 1m 1s",
|
||||
expect: 7*24*time.Hour + time.Hour + time.Minute + time.Second,
|
||||
},
|
||||
{
|
||||
duration: "2h",
|
||||
expect: 2 * time.Hour,
|
||||
},
|
||||
{
|
||||
duration: "2h5m",
|
||||
expect: 2*time.Hour + 5*time.Minute,
|
||||
},
|
||||
{
|
||||
duration: "2h 5m",
|
||||
expect: 2*time.Hour + 5*time.Minute,
|
||||
},
|
||||
{
|
||||
duration: "d24h",
|
||||
err: require.Error,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.duration, func(t *testing.T) {
|
||||
got, err := parseDuration(test.duration)
|
||||
if test.err != nil {
|
||||
test.err(t, err)
|
||||
return
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
require.Equal(t, test.expect, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_durationToString(t *testing.T) {
|
||||
tests := []struct {
|
||||
duration time.Duration
|
||||
expect string
|
||||
err require.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
expect: "1d",
|
||||
duration: 24 * time.Hour,
|
||||
},
|
||||
{
|
||||
expect: "7d",
|
||||
duration: 7 * 24 * time.Hour,
|
||||
},
|
||||
{
|
||||
expect: "7d1h1m1s",
|
||||
duration: 7*24*time.Hour + time.Hour + time.Minute + time.Second,
|
||||
},
|
||||
{
|
||||
expect: "2h0m0s",
|
||||
duration: 2 * time.Hour,
|
||||
},
|
||||
{
|
||||
expect: "2h5m0s",
|
||||
duration: 2*time.Hour + 5*time.Minute,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.expect, func(t *testing.T) {
|
||||
got := durationToString(test.duration)
|
||||
require.Equal(t, test.expect, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -64,6 +64,7 @@ func DefaultCatalog() Catalog {
|
||||
Package: defaultPackageConfig(),
|
||||
LinuxKernel: defaultLinuxKernelConfig(),
|
||||
Golang: defaultGolangConfig(),
|
||||
Java: defaultJavaConfig(),
|
||||
File: defaultFileConfig(),
|
||||
Relationships: defaultRelationshipsConfig(),
|
||||
Source: defaultSourceConfig(),
|
||||
@@ -150,6 +151,8 @@ func (cfg Catalog) ToPackagesConfig() pkgcataloging.Config {
|
||||
GuessUnpinnedRequirements: cfg.Python.GuessUnpinnedRequirements,
|
||||
},
|
||||
JavaArchive: java.DefaultArchiveCatalogerConfig().
|
||||
WithUseMavenLocalRepository(cfg.Java.UseMavenLocalRepository).
|
||||
WithMavenLocalRepositoryDir(cfg.Java.MavenLocalRepositoryDir).
|
||||
WithUseNetwork(cfg.Java.UseNetwork).
|
||||
WithMavenBaseURL(cfg.Java.MavenURL).
|
||||
WithArchiveTraversal(archiveSearch, cfg.Java.MaxParentRecursiveDepth),
|
||||
|
||||
@@ -1,24 +1,46 @@
|
||||
package options
|
||||
|
||||
import "github.com/anchore/clio"
|
||||
import (
|
||||
"github.com/anchore/clio"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/java"
|
||||
)
|
||||
|
||||
type javaConfig struct {
|
||||
UseNetwork bool `yaml:"use-network" json:"use-network" mapstructure:"use-network"`
|
||||
UseMavenLocalRepository bool `yaml:"use-maven-local-repository" json:"use-maven-local-repository" mapstructure:"use-maven-local-repository"`
|
||||
MavenLocalRepositoryDir string `yaml:"maven-local-repository-dir" json:"maven-local-repository-dir" mapstructure:"maven-local-repository-dir"`
|
||||
MavenURL string `yaml:"maven-url" json:"maven-url" mapstructure:"maven-url"`
|
||||
MaxParentRecursiveDepth int `yaml:"max-parent-recursive-depth" json:"max-parent-recursive-depth" mapstructure:"max-parent-recursive-depth"`
|
||||
}
|
||||
|
||||
func defaultJavaConfig() javaConfig {
|
||||
def := java.DefaultArchiveCatalogerConfig()
|
||||
|
||||
return javaConfig{
|
||||
UseNetwork: def.UseNetwork,
|
||||
MaxParentRecursiveDepth: def.MaxParentRecursiveDepth,
|
||||
UseMavenLocalRepository: def.UseMavenLocalRepository,
|
||||
MavenLocalRepositoryDir: def.MavenLocalRepositoryDir,
|
||||
MavenURL: def.MavenBaseURL,
|
||||
}
|
||||
}
|
||||
|
||||
var _ interface {
|
||||
clio.FieldDescriber
|
||||
} = (*javaConfig)(nil)
|
||||
|
||||
func (o *javaConfig) DescribeFields(descriptions clio.FieldDescriptionSet) {
|
||||
descriptions.Add(&o.UseNetwork, `enables Syft to use the network to fill in more detailed information about artifacts
|
||||
currently this enables searching maven-url for license data
|
||||
when running across pom.xml files that could have more information, syft will
|
||||
explicitly search maven for license information by querying the online pom when this is true
|
||||
this option is helpful for when the parent pom has more data,
|
||||
that is not accessible from within the final built artifact`)
|
||||
descriptions.Add(&o.UseNetwork, `enables Syft to use the network to fetch version and license information for packages when
|
||||
a parent or imported pom file is not found in the local maven repository.
|
||||
the pom files are downloaded from the remote Maven repository at 'maven-url'`)
|
||||
descriptions.Add(&o.MavenURL, `maven repository to use, defaults to Maven central`)
|
||||
descriptions.Add(&o.MaxParentRecursiveDepth, `depth to recursively resolve parent POMs`)
|
||||
descriptions.Add(&o.MaxParentRecursiveDepth, `depth to recursively resolve parent POMs, no limit if <= 0`)
|
||||
descriptions.Add(&o.UseMavenLocalRepository, `use the local Maven repository to retrieve pom files. When Maven is installed and was previously used
|
||||
for building the software that is being scanned, then most pom files will be available in this
|
||||
repository on the local file system. this greatly speeds up scans. when all pom files are available
|
||||
in the local repository, then 'use-network' is not needed.
|
||||
TIP: If you want to download all required pom files to the local repository without running a full
|
||||
build, run 'mvn help:effective-pom' before performing the scan with syft.`)
|
||||
descriptions.Add(&o.MavenLocalRepositoryDir, `override the default location of the local Maven repository.
|
||||
the default is the subdirectory '.m2/repository' in your home directory`)
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ var imageOnlyTestCases = []testCase{
|
||||
pkgType: pkg.PythonPkg,
|
||||
pkgLanguage: pkg.Python,
|
||||
pkgInfo: map[string]string{
|
||||
"Pygments": "2.6.1",
|
||||
"pygments": "2.6.1",
|
||||
"requests": "2.22.0",
|
||||
"somerequests": "3.22.0",
|
||||
"someotherpkg": "3.19.0",
|
||||
@@ -172,7 +172,7 @@ var dirOnlyTestCases = []testCase{
|
||||
"passlib": "1.7.2",
|
||||
"mypy": "v0.770",
|
||||
// common to image and directory
|
||||
"Pygments": "2.6.1",
|
||||
"pygments": "2.6.1",
|
||||
"requests": "2.22.0",
|
||||
"somerequests": "3.22.0",
|
||||
"someotherpkg": "3.19.0",
|
||||
@@ -388,6 +388,14 @@ var dirOnlyTestCases = []testCase{
|
||||
"swift-numerics": "1.0.2",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "find swipl pack package manager packages",
|
||||
pkgType: pkg.SwiplPackPkg,
|
||||
pkgLanguage: pkg.Swipl,
|
||||
pkgInfo: map[string]string{
|
||||
"hdt": "0.5.2",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "find github action packages (from usage in workflow files and composite actions)",
|
||||
pkgType: pkg.GithubActionPkg,
|
||||
|
||||
@@ -51,6 +51,7 @@ func TestPkgCoverageImage(t *testing.T) {
|
||||
definedLanguages.Remove(pkg.Rust.String())
|
||||
definedLanguages.Remove(pkg.Dart.String())
|
||||
definedLanguages.Remove(pkg.Swift.String())
|
||||
definedLanguages.Remove(pkg.Swipl.String())
|
||||
definedLanguages.Remove(pkg.CPP.String())
|
||||
definedLanguages.Remove(pkg.Haskell.String())
|
||||
definedLanguages.Remove(pkg.Elixir.String())
|
||||
@@ -76,6 +77,7 @@ func TestPkgCoverageImage(t *testing.T) {
|
||||
definedPkgs.Remove(string(pkg.LinuxKernelPkg))
|
||||
definedPkgs.Remove(string(pkg.LinuxKernelModulePkg))
|
||||
definedPkgs.Remove(string(pkg.SwiftPkg))
|
||||
definedPkgs.Remove(string(pkg.SwiplPackPkg))
|
||||
definedPkgs.Remove(string(pkg.GithubActionPkg))
|
||||
definedPkgs.Remove(string(pkg.GithubActionWorkflowPkg))
|
||||
|
||||
|
||||
@@ -10,7 +10,8 @@ import (
|
||||
func TestMarinerDistroless(t *testing.T) {
|
||||
sbom, _ := catalogFixtureImage(t, "image-mariner-distroless", source.SquashedScope)
|
||||
|
||||
expectedPkgs := 12
|
||||
// 12 RPMs + 2 binaries with ELF package notes claiming to be RPMs
|
||||
expectedPkgs := 14
|
||||
actualPkgs := 0
|
||||
for range sbom.Artifacts.Packages.Enumerate(pkg.RpmPkg) {
|
||||
actualPkgs += 1
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/internal/relationship"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
@@ -44,12 +45,13 @@ func TestBinaryElfRelationships(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
relationshipIndex := relationship.NewIndex(sbom.Relationships...)
|
||||
for name, expectedDepNames := range expectedGraph {
|
||||
pkgId := nameToId[name]
|
||||
p := sbom.Artifacts.Packages.Package(pkgId)
|
||||
require.NotNil(t, p, "expected package %q to be present in the SBOM", name)
|
||||
|
||||
rels := sbom.RelationshipsForPackage(*p, artifact.DependencyOfRelationship)
|
||||
rels := relationshipIndex.References(*p, artifact.DependencyOfRelationship)
|
||||
require.NotEmpty(t, rels, "expected package %q to have relationships", name)
|
||||
|
||||
toIds := map[artifact.ID]struct{}{}
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
name(hdt).
|
||||
version('0.5.2').
|
||||
% TODO: swipl_version([90121]).
|
||||
title('Access RDF HDT files').
|
||||
keywords(['RDF']).
|
||||
author( 'Jan Wielemaker', 'J.Wielemaker@vu.nl' ).
|
||||
packager( 'Jan Wielemaker', 'J.Wielemaker@vu.nl' ).
|
||||
maintainer( 'Jan Wielemaker', 'J.Wielemaker@vu.nl' ).
|
||||
home( 'https://github.com/JanWielemaker/hdt' ).
|
||||
download( 'https://github.com/JanWielemaker/hdt/archive/V*.zip' ).
|
||||
@@ -17,29 +17,29 @@ require (
|
||||
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04
|
||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20240507183024-848e011fc24f
|
||||
github.com/anchore/stereoscope v0.0.3-0.20240501181043-2e9894674185
|
||||
github.com/anchore/stereoscope v0.0.3-0.20240725180315-50ce3be7aa1f
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be
|
||||
// we are hinting brotli to latest due to warning when installing archiver v3:
|
||||
// go: warning: github.com/andybalholm/brotli@v1.0.1: retracted by module author: occasional panics and data corruption
|
||||
github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46
|
||||
github.com/bmatcuk/doublestar/v4 v4.6.1
|
||||
github.com/charmbracelet/bubbles v0.18.0
|
||||
github.com/charmbracelet/bubbletea v0.26.4
|
||||
github.com/charmbracelet/lipgloss v0.11.0
|
||||
github.com/charmbracelet/bubbletea v0.26.6
|
||||
github.com/charmbracelet/lipgloss v0.12.1
|
||||
github.com/dave/jennifer v1.7.0
|
||||
github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da
|
||||
github.com/distribution/reference v0.6.0
|
||||
github.com/docker/docker v26.1.4+incompatible
|
||||
github.com/docker/docker v27.1.1+incompatible
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
github.com/elliotchance/phpserialize v1.4.0
|
||||
github.com/facebookincubator/nvdtools v0.1.5
|
||||
github.com/github/go-spdx/v2 v2.2.0
|
||||
github.com/gkampitakis/go-snaps v0.5.4
|
||||
github.com/github/go-spdx/v2 v2.3.1
|
||||
github.com/gkampitakis/go-snaps v0.5.7
|
||||
github.com/go-git/go-billy/v5 v5.5.0
|
||||
github.com/go-git/go-git/v5 v5.12.0
|
||||
github.com/go-test/deep v1.1.0
|
||||
github.com/go-test/deep v1.1.1
|
||||
github.com/google/go-cmp v0.6.0
|
||||
github.com/google/go-containerregistry v0.19.1
|
||||
github.com/google/go-containerregistry v0.20.1
|
||||
github.com/google/licensecheck v0.3.1
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gookit/color v1.5.4
|
||||
@@ -55,38 +55,43 @@ require (
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/moby/sys/mountinfo v0.7.1
|
||||
github.com/moby/sys/mountinfo v0.7.2
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
github.com/opencontainers/go-digest v1.0.0
|
||||
github.com/pelletier/go-toml v1.9.5
|
||||
github.com/quasilyte/go-ruleguard/dsl v0.3.22
|
||||
github.com/saferwall/pe v1.5.3
|
||||
github.com/saferwall/pe v1.5.4
|
||||
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d
|
||||
github.com/sanity-io/litter v1.5.5
|
||||
github.com/sassoftware/go-rpmutils v0.4.0
|
||||
// pinned to pull in 386 arch fix: https://github.com/scylladb/go-set/commit/cc7b2070d91ebf40d233207b633e28f5bd8f03a5
|
||||
github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3
|
||||
github.com/spdx/tools-golang v0.5.4
|
||||
github.com/spdx/tools-golang v0.5.5
|
||||
github.com/spf13/afero v1.11.0
|
||||
github.com/spf13/cobra v1.8.0
|
||||
github.com/spf13/cobra v1.8.1
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/vbatts/go-mtree v0.5.3
|
||||
github.com/vbatts/go-mtree v0.5.4
|
||||
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-20230925121702-07e42b3cdba0
|
||||
github.com/xeipuuv/gojsonschema v1.2.0
|
||||
github.com/zyedidia/generic v1.2.2-0.20230320175451-4410d2372cb1
|
||||
go.uber.org/goleak v1.3.0
|
||||
golang.org/x/mod v0.18.0
|
||||
golang.org/x/net v0.26.0
|
||||
golang.org/x/mod v0.20.0
|
||||
golang.org/x/net v0.27.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
modernc.org/sqlite v1.30.0
|
||||
modernc.org/sqlite v1.31.1
|
||||
)
|
||||
|
||||
require google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 // indirect
|
||||
|
||||
require github.com/magiconair/properties v1.8.7
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.4.0
|
||||
github.com/adrg/xdg v0.5.0
|
||||
github.com/magiconair/properties v1.8.7
|
||||
golang.org/x/exp v0.0.0-20231108232855-2478ac86f678
|
||||
)
|
||||
|
||||
require (
|
||||
dario.cat/mergo v1.0.0 // indirect
|
||||
@@ -98,7 +103,6 @@ require (
|
||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||
github.com/Microsoft/hcsshim v0.11.4 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.0.0 // indirect
|
||||
github.com/adrg/xdg v0.4.0 // indirect
|
||||
github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 // indirect
|
||||
github.com/andybalholm/brotli v1.0.4 // indirect
|
||||
github.com/aquasecurity/go-version v0.0.0-20210121072130-637058cfe492 // indirect
|
||||
@@ -106,7 +110,7 @@ require (
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/becheran/wildmatch-go v1.0.0 // indirect
|
||||
github.com/charmbracelet/harmonica v0.2.0 // indirect
|
||||
github.com/charmbracelet/x/ansi v0.1.2 // indirect
|
||||
github.com/charmbracelet/x/ansi v0.1.4 // indirect
|
||||
github.com/charmbracelet/x/input v0.1.0 // indirect
|
||||
github.com/charmbracelet/x/term v0.1.1 // indirect
|
||||
github.com/charmbracelet/x/windows v0.1.0 // indirect
|
||||
@@ -121,7 +125,7 @@ require (
|
||||
github.com/containerd/typeurl/v2 v2.1.1 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/docker/cli v24.0.0+incompatible // indirect
|
||||
github.com/docker/cli v27.0.3+incompatible // indirect
|
||||
github.com/docker/distribution v2.8.3+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.7.0 // indirect
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
@@ -134,7 +138,7 @@ require (
|
||||
github.com/felixge/fgprof v0.9.3 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.3 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.4 // indirect
|
||||
github.com/gkampitakis/ciinfo v0.3.0 // indirect
|
||||
github.com/gkampitakis/go-diff v1.3.2 // indirect
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
@@ -159,7 +163,7 @@ require (
|
||||
github.com/klauspost/pgzip v1.2.5 // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381 // indirect
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/maruel/natural v1.1.1 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
@@ -179,13 +183,13 @@ require (
|
||||
github.com/muesli/termenv v0.15.2 // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/nwaples/rardecode v1.1.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0-rc3 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0 // indirect
|
||||
github.com/opencontainers/runc v1.1.12 // indirect
|
||||
github.com/opencontainers/runtime-spec v1.1.0-rc.1 // indirect
|
||||
github.com/opencontainers/selinux v1.11.0 // indirect
|
||||
github.com/pborman/indent v1.2.1 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.15 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.19 // indirect
|
||||
github.com/pjbgf/sha1cd v0.3.0 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pkg/profile v1.7.0 // indirect
|
||||
@@ -205,8 +209,8 @@ require (
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/spf13/viper v1.18.2 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/sylabs/sif/v2 v2.11.5 // indirect
|
||||
github.com/sylabs/squashfs v0.6.1 // indirect
|
||||
github.com/sylabs/sif/v2 v2.17.1 // indirect
|
||||
github.com/sylabs/squashfs v1.0.0 // indirect
|
||||
github.com/therootcompany/xz v1.0.1 // indirect
|
||||
github.com/tidwall/gjson v1.17.0 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
@@ -226,11 +230,10 @@ require (
|
||||
go.opentelemetry.io/otel/trace v1.19.0 // indirect
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
go.uber.org/multierr v1.9.0 // indirect
|
||||
golang.org/x/crypto v0.24.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 // indirect
|
||||
golang.org/x/crypto v0.25.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/sys v0.21.0 // indirect
|
||||
golang.org/x/term v0.21.0 // indirect
|
||||
golang.org/x/sys v0.22.0 // indirect
|
||||
golang.org/x/term v0.22.0 // indirect
|
||||
golang.org/x/text v0.16.0 // indirect
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||
@@ -240,7 +243,7 @@ require (
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect
|
||||
modernc.org/libc v1.50.9 // indirect
|
||||
modernc.org/libc v1.55.3 // indirect
|
||||
modernc.org/mathutil v1.6.0 // indirect
|
||||
modernc.org/memory v1.8.0 // indirect
|
||||
modernc.org/strutil v1.2.0 // indirect
|
||||
|
||||
@@ -57,6 +57,8 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg6
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
|
||||
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/CycloneDX/cyclonedx-go v0.9.0 h1:inaif7qD8bivyxp7XLgxUYtOXWtDez7+j72qKTMQTb8=
|
||||
github.com/CycloneDX/cyclonedx-go v0.9.0/go.mod h1:NE/EWvzELOFlG6+ljX/QeMlVt9VKcTwu8u0ccsACEsw=
|
||||
@@ -83,8 +85,8 @@ github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpH
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
|
||||
github.com/acobaugh/osrelease v0.1.0 h1:Yb59HQDGGNhCj4suHaFQQfBps5wyoKLSSX/J/+UifRE=
|
||||
github.com/acobaugh/osrelease v0.1.0/go.mod h1:4bFEs0MtgHNHBrmHCt67gNisnabCRAlzdVasCEGHTWY=
|
||||
github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls=
|
||||
github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E=
|
||||
github.com/adrg/xdg v0.5.0 h1:dDaZvhMXatArP1NPHhnfaQUqWBLBsmx1h1HXQdMoFCY=
|
||||
github.com/adrg/xdg v0.5.0/go.mod h1:dDdY4M4DF9Rjy4kHPeNL+ilVF+p2lK8IdM9/rTSGcI4=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
@@ -111,8 +113,8 @@ github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b h1:e1bmaoJfZV
|
||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b/go.mod h1:Bkc+JYWjMCF8OyZ340IMSIi2Ebf3uwByOk6ho4wne1E=
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20240507183024-848e011fc24f h1:B/E9ixKNCasntpoch61NDaQyGPDXLEJlL+B9B/PbdbA=
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20240507183024-848e011fc24f/go.mod h1:Blo6OgJNiYF41ufcgHKkbCKF2MDOMlrqhXv/ij6ocR4=
|
||||
github.com/anchore/stereoscope v0.0.3-0.20240501181043-2e9894674185 h1:SuViDJ27nZ+joGdKbAkxAlm7tYMt9NTxTZZ05po4hls=
|
||||
github.com/anchore/stereoscope v0.0.3-0.20240501181043-2e9894674185/go.mod h1:ckIamHiRMp8iBwWoTtE5Xkt9VQ5QC+6+O4VzwqyZr5Q=
|
||||
github.com/anchore/stereoscope v0.0.3-0.20240725180315-50ce3be7aa1f h1:xuBvotcht1Ns8IdaC4UuYV1U8MFln9c5ELeo5bzDEO8=
|
||||
github.com/anchore/stereoscope v0.0.3-0.20240725180315-50ce3be7aa1f/go.mod h1:DcQdMes8SwpFli3rDH0v+Vd9qU9Jariq7JSHNJV5X/A=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||
github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
||||
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
|
||||
@@ -155,14 +157,14 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/charmbracelet/bubbles v0.18.0 h1:PYv1A036luoBGroX6VWjQIE9Syf2Wby2oOl/39KLfy0=
|
||||
github.com/charmbracelet/bubbles v0.18.0/go.mod h1:08qhZhtIwzgrtBjAcJnij1t1H0ZRjwHyGsy6AL11PSw=
|
||||
github.com/charmbracelet/bubbletea v0.26.4 h1:2gDkkzLZaTjMl/dQBpNVtnvcCxsh/FCkimep7FC9c40=
|
||||
github.com/charmbracelet/bubbletea v0.26.4/go.mod h1:P+r+RRA5qtI1DOHNFn0otoNwB4rn+zNAzSj/EXz6xU0=
|
||||
github.com/charmbracelet/bubbletea v0.26.6 h1:zTCWSuST+3yZYZnVSvbXwKOPRSNZceVeqpzOLN2zq1s=
|
||||
github.com/charmbracelet/bubbletea v0.26.6/go.mod h1:dz8CWPlfCCGLFbBlTY4N7bjLiyOGDJEnd2Muu7pOWhk=
|
||||
github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=
|
||||
github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
|
||||
github.com/charmbracelet/lipgloss v0.11.0 h1:UoAcbQ6Qml8hDwSWs0Y1cB5TEQuZkDPH/ZqwWWYTG4g=
|
||||
github.com/charmbracelet/lipgloss v0.11.0/go.mod h1:1UdRTH9gYgpcdNN5oBtjbu/IzNKtzVtb7sqN1t9LNn8=
|
||||
github.com/charmbracelet/x/ansi v0.1.2 h1:6+LR39uG8DE6zAmbu023YlqjJHkYXDF1z36ZwzO4xZY=
|
||||
github.com/charmbracelet/x/ansi v0.1.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
|
||||
github.com/charmbracelet/lipgloss v0.12.1 h1:/gmzszl+pedQpjCOH+wFkZr/N90Snz40J/NR7A0zQcs=
|
||||
github.com/charmbracelet/lipgloss v0.12.1/go.mod h1:V2CiwIuhx9S1S1ZlADfOj9HmxeMAORuz5izHb0zGbB8=
|
||||
github.com/charmbracelet/x/ansi v0.1.4 h1:IEU3D6+dWwPSgZ6HBH+v6oUuZ/nVawMiWj5831KfiLM=
|
||||
github.com/charmbracelet/x/ansi v0.1.4/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
|
||||
github.com/charmbracelet/x/input v0.1.0 h1:TEsGSfZYQyOtp+STIjyBq6tpRaorH0qpwZUj8DavAhQ=
|
||||
github.com/charmbracelet/x/input v0.1.0/go.mod h1:ZZwaBxPF7IG8gWWzPUVqHEtWhc1+HXJPNuerJGRGZ28=
|
||||
github.com/charmbracelet/x/term v0.1.1 h1:3cosVAiPOig+EV4X9U+3LDgtwwAoEzJjNdwbXDjF6yI=
|
||||
@@ -209,7 +211,7 @@ github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
|
||||
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
|
||||
@@ -225,12 +227,12 @@ github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da/go.mod h1:B3tI9iGHi4i
|
||||
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4=
|
||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/docker/cli v24.0.0+incompatible h1:0+1VshNwBQzQAx9lOl+OYCTCEAD8fKs/qeXMx3O0wqM=
|
||||
github.com/docker/cli v24.0.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/cli v27.0.3+incompatible h1:usGs0/BoBW8MWxGeEtqPMkzOY56jZ6kYlSN5BLDioCQ=
|
||||
github.com/docker/cli v27.0.3+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 v26.1.4+incompatible h1:vuTpXDuoga+Z38m1OZHzl7NKisKWaWlhjQk7IDPSLsU=
|
||||
github.com/docker/docker v26.1.4+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY=
|
||||
github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A=
|
||||
github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0=
|
||||
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
||||
@@ -282,17 +284,17 @@ github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7z
|
||||
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.0 h1:Cn9dkdYsMIu56tGho+fqzh7XmvY2YyGU0FnbhiOsEro=
|
||||
github.com/gabriel-vasile/mimetype v1.4.0/go.mod h1:fA8fi6KUiG7MgQQ+mEWotXoEOvmxRtOJlERCzSmRvr8=
|
||||
github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I=
|
||||
github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/github/go-spdx/v2 v2.2.0 h1:yBBLMasHA70Ujd35OpL/OjJOWWVNXcJGbars0GinGRI=
|
||||
github.com/github/go-spdx/v2 v2.2.0/go.mod h1:hMCrsFgT0QnCwn7G8gxy/MxMpy67WgZrwFeISTn0o6w=
|
||||
github.com/github/go-spdx/v2 v2.3.1 h1:ffGuHTbHuHzWPt53n8f9o8clGutuLPObo3zB4JAjxU8=
|
||||
github.com/github/go-spdx/v2 v2.3.1/go.mod h1:2ZxKsOhvBp+OYBDlsGnUMcchLeo2mrpEBn2L1C+U3IQ=
|
||||
github.com/gkampitakis/ciinfo v0.3.0 h1:gWZlOC2+RYYttL0hBqcoQhM7h1qNkVqvRCV1fOvpAv8=
|
||||
github.com/gkampitakis/ciinfo v0.3.0/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo=
|
||||
github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M=
|
||||
github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk=
|
||||
github.com/gkampitakis/go-snaps v0.5.4 h1:GX+dkKmVsRenz7SoTbdIEL4KQARZctkMiZ8ZKprRwT8=
|
||||
github.com/gkampitakis/go-snaps v0.5.4/go.mod h1:ZABkO14uCuVxBHAXAfKG+bqNz+aa1bGPAg8jkI0Nk8Y=
|
||||
github.com/gkampitakis/go-snaps v0.5.7 h1:uVGjHR4t4pPHU944udMx7VKHpwepZXmvDMF+yDmI0rg=
|
||||
github.com/gkampitakis/go-snaps v0.5.7/go.mod h1:ZABkO14uCuVxBHAXAfKG+bqNz+aa1bGPAg8jkI0Nk8Y=
|
||||
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.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE=
|
||||
@@ -321,8 +323,8 @@ github.com/go-restruct/restruct v1.2.0-alpha h1:2Lp474S/9660+SJjpVxoKuWX09JsXHSr
|
||||
github.com/go-restruct/restruct v1.2.0-alpha/go.mod h1:KqrpKpn4M8OLznErihXTGLlsXFGeLxHUrLRRI/1YjGk=
|
||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg=
|
||||
github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
|
||||
github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
@@ -382,8 +384,8 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-containerregistry v0.19.1 h1:yMQ62Al6/V0Z7CqIrrS1iYoA5/oQCm88DeNujc7C1KY=
|
||||
github.com/google/go-containerregistry v0.19.1/go.mod h1:YCMFNQeeXeLF+dnhhWkqDItx/JSkH01j1Kis4PsjzFI=
|
||||
github.com/google/go-containerregistry v0.20.1 h1:eTgx9QNYugV4DN5mz4U8hiAGTi1ybXn0TPi4Smd8du0=
|
||||
github.com/google/go-containerregistry v0.20.1/go.mod h1:YCMFNQeeXeLF+dnhhWkqDItx/JSkH01j1Kis4PsjzFI=
|
||||
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=
|
||||
@@ -520,8 +522,9 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381 h1:bqDmpDG49ZRnB5PcgP0RXtQvnMSgIF14M7CBd2shtXs=
|
||||
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w=
|
||||
@@ -581,8 +584,8 @@ 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/sys/mountinfo v0.7.1 h1:/tTvQaSJRr2FshkhXiIpux6fQ2Zvc4j7tAhMTStAG2g=
|
||||
github.com/moby/sys/mountinfo v0.7.1/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI=
|
||||
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.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc=
|
||||
github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo=
|
||||
github.com/moby/sys/signal v0.7.0 h1:25RW3d5TnQEoKvRbEKUGay6DCQ46IxAVTT9CUMgmsSI=
|
||||
@@ -615,8 +618,8 @@ github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
|
||||
github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.0-rc3 h1:fzg1mXZFj8YdPeNkRXMg+zb88BFV0Ys52cJydRwBkb8=
|
||||
github.com/opencontainers/image-spec v1.1.0-rc3/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8=
|
||||
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
||||
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
|
||||
github.com/opencontainers/runc v1.1.12 h1:BOIssBaW1La0/qbNZHXOOa71dZfZEQOzW7dqQf3phss=
|
||||
github.com/opencontainers/runc v1.1.12/go.mod h1:S+lQwSfncpBha7XTy/5lBwWgm5+y5Ma/O44Ekby9FK8=
|
||||
github.com/opencontainers/runtime-spec v1.1.0-rc.1 h1:wHa9jroFfKGQqFHj0I1fMRKLl0pfj+ynAqBxo3v6u9w=
|
||||
@@ -633,8 +636,8 @@ github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCko
|
||||
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
|
||||
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||
github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0=
|
||||
github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pierrec/lz4/v4 v4.1.19 h1:tYLzDnjDXh9qIxSTKHwXwOYmm9d887Y7Y1ZkyXYHAN4=
|
||||
github.com/pierrec/lz4/v4 v4.1.19/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
|
||||
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
@@ -682,8 +685,8 @@ github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/saferwall/pe v1.5.3 h1:tZ2KJWeJK6N2jjJx+B9hm+wq6qe0o56pql2PXUaQ9eI=
|
||||
github.com/saferwall/pe v1.5.3/go.mod h1:mJx+PuptmNpoPFBNhWs/uDMFL/kTHVZIkg0d4OUJFbQ=
|
||||
github.com/saferwall/pe v1.5.4 h1:tLmMggEMUfeqrpJ25zS/okUQmyFdD5xWKL2+z9njCqg=
|
||||
github.com/saferwall/pe v1.5.4/go.mod h1:mJx+PuptmNpoPFBNhWs/uDMFL/kTHVZIkg0d4OUJFbQ=
|
||||
github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig=
|
||||
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
|
||||
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
|
||||
@@ -724,8 +727,8 @@ github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9yS
|
||||
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/go.mod h1:uKWaldnbMnjsSAXRurWqqrdyZen1R7kxl8TkmWk2OyM=
|
||||
github.com/spdx/tools-golang v0.5.4 h1:fRW4iz16P1ZCUtWStFqS6YiMgnK7WgfTFU/lrsYlvqY=
|
||||
github.com/spdx/tools-golang v0.5.4/go.mod h1:MVIsXx8ZZzaRWNQpUDhC4Dud34edUYJYecciXgrw5vE=
|
||||
github.com/spdx/tools-golang v0.5.5 h1:61c0KLfAcNqAjlg6UNMdkwpMernhw3zVRwDZ2x9XOmk=
|
||||
github.com/spdx/tools-golang v0.5.5/go.mod h1:MVIsXx8ZZzaRWNQpUDhC4Dud34edUYJYecciXgrw5vE=
|
||||
github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
|
||||
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
|
||||
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
||||
@@ -735,8 +738,8 @@ github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU
|
||||
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
|
||||
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4=
|
||||
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
||||
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
||||
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
||||
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
@@ -766,10 +769,10 @@ github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8
|
||||
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.11.5 h1:7ssPH3epSonsTrzbS1YxeJ9KuqAN7ISlSM61a7j/mQM=
|
||||
github.com/sylabs/sif/v2 v2.11.5/go.mod h1:GBoZs9LU3e4yJH1dcZ3Akf/jsqYgy5SeguJQC+zd75Y=
|
||||
github.com/sylabs/squashfs v0.6.1 h1:4hgvHnD9JGlYWwT0bPYNt9zaz23mAV3Js+VEgQoRGYQ=
|
||||
github.com/sylabs/squashfs v0.6.1/go.mod h1:ZwpbPCj0ocIvMy2br6KZmix6Gzh6fsGQcCnydMF+Kx8=
|
||||
github.com/sylabs/sif/v2 v2.17.1 h1:p6Sl0LWyShXBj2SBsS1dMOMIMrZHe8pwBnBrYt6uo4M=
|
||||
github.com/sylabs/sif/v2 v2.17.1/go.mod h1:XUGB6AQUXGkms3qPOPdevctT3lBLRLWZNWHVnt5HMKE=
|
||||
github.com/sylabs/squashfs v1.0.0 h1:xAyMS21ogglkuR5HaY55PCfqY3H32ma9GkasTYo28Zg=
|
||||
github.com/sylabs/squashfs v1.0.0/go.mod h1:rhWzvgefq1X+R+LZdts10hfMsTg3g74OfGunW8tvg/4=
|
||||
github.com/terminalstatic/go-xsd-validate v0.1.5 h1:RqpJnf6HGE2CB/lZB1A8BYguk8uRtcvYAPLCF15qguo=
|
||||
github.com/terminalstatic/go-xsd-validate v0.1.5/go.mod h1:18lsvYFofBflqCrvo1umpABZ99+GneNTw2kEEc8UPJw=
|
||||
github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw=
|
||||
@@ -791,8 +794,8 @@ github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
|
||||
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8=
|
||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||
github.com/vbatts/go-mtree v0.5.3 h1:S/jYlfG8rZ+a0bhZd+RANXejy7M4Js8fq9U+XoWTd5w=
|
||||
github.com/vbatts/go-mtree v0.5.3/go.mod h1:eXsdoPMdL2jcJx6HweWi9lYQxBsTp4lNhqqAjgkZUg8=
|
||||
github.com/vbatts/go-mtree v0.5.4 h1:OMAb8jaCyiFA7zXj0Zc/oARcxBDBoeu2LizjB8BVJl0=
|
||||
github.com/vbatts/go-mtree v0.5.4/go.mod h1:5GqJbVhm9BBiCc4K5uc/c42FPgXulHaQs4sFUEfIWMo=
|
||||
github.com/vbatts/tar-split v0.11.3 h1:hLFqsOLQ1SsppQNTMpkpPXClLDfC2A3Zgy9OUU+RVck=
|
||||
github.com/vbatts/tar-split v0.11.3/go.mod h1:9QlHN18E+fEH7RdG+QAJJcuya3rqT7eXSTY7wGrAokY=
|
||||
github.com/vifraa/gopom v1.0.0 h1:L9XlKbyvid8PAIK8nr0lihMApJQg/12OBvMA28BcWh0=
|
||||
@@ -875,8 +878,8 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0
|
||||
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
|
||||
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
||||
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
|
||||
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
|
||||
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=
|
||||
@@ -917,8 +920,8 @@ golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
|
||||
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
|
||||
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
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=
|
||||
@@ -959,15 +962,14 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
|
||||
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
||||
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
||||
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=
|
||||
@@ -1079,15 +1081,15 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0/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.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
|
||||
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
|
||||
golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
|
||||
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
|
||||
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=
|
||||
@@ -1357,18 +1359,18 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
modernc.org/cc/v4 v4.21.2 h1:dycHFB/jDc3IyacKipCNSDrjIC0Lm1hyoWOZTRR20Lk=
|
||||
modernc.org/cc/v4 v4.21.2/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
|
||||
modernc.org/ccgo/v4 v4.17.8 h1:yyWBf2ipA0Y9GGz/MmCmi3EFpKgeS7ICrAFes+suEbs=
|
||||
modernc.org/ccgo/v4 v4.17.8/go.mod h1:buJnJ6Fn0tyAdP/dqePbrrvLyr6qslFfTbFrCuaYvtA=
|
||||
modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ=
|
||||
modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
|
||||
modernc.org/ccgo/v4 v4.19.2 h1:lwQZgvboKD0jBwdaeVCTouxhxAyN6iawF3STraAal8Y=
|
||||
modernc.org/ccgo/v4 v4.19.2/go.mod h1:ysS3mxiMV38XGRTTcgo0DQTeTmAO4oCmJl1nX9VFI3s=
|
||||
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
|
||||
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
|
||||
modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=
|
||||
modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
|
||||
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI=
|
||||
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
|
||||
modernc.org/libc v1.50.9 h1:hIWf1uz55lorXQhfoEoezdUHjxzuO6ceshET/yWjSjk=
|
||||
modernc.org/libc v1.50.9/go.mod h1:15P6ublJ9FJR8YQCGy8DeQ2Uwur7iW9Hserr/T3OFZE=
|
||||
modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U=
|
||||
modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w=
|
||||
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
||||
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
||||
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
|
||||
@@ -1377,8 +1379,8 @@ modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
||||
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
|
||||
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
|
||||
modernc.org/sqlite v1.30.0 h1:8YhPUs/HTnlEgErn/jSYQTwHN/ex8CjHHjg+K9iG7LM=
|
||||
modernc.org/sqlite v1.30.0/go.mod h1:cgkTARJ9ugeXSNaLBPK3CqbOe7Ec7ZhWPoMFGldEYEw=
|
||||
modernc.org/sqlite v1.31.1 h1:XVU0VyzxrYHlBhIs1DiEgSl0ZtdnPtbLVy8hSkzxGrs=
|
||||
modernc.org/sqlite v1.31.1/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA=
|
||||
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
|
||||
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
|
||||
Vendored
+51
@@ -0,0 +1,51 @@
|
||||
# Caching
|
||||
|
||||
All caches are created from a global `manager`. By defaut this is a `bypassedCache`, which performs no caching.
|
||||
One benefit of this is that tests don't need to worry about caching causing issues unless they explicitly need
|
||||
to test the cache and can opt-in using the `cache.TestCache(t)` helper.
|
||||
|
||||
Syft sets a `filesystemCache` when the [cache options](../../cmd/syft/internal/options/cache.go) are loaded.
|
||||
|
||||
When using the `filesystemCache` all items are stored on disk under a root directory, generally in the form of:
|
||||
```
|
||||
<rootDir>/<named-cache>/<data-version>/path/to/data
|
||||
```
|
||||
|
||||
# Using the cache
|
||||
|
||||
The easiest and preferred method to use the cache is a `cache.Resolver`, which automatically creates a `<data-version>`
|
||||
based on the _structure_ of the provided type.
|
||||
If the structure changes in any way it will end up with a new version key and all will re populate this new key,
|
||||
ignoring cached values from older, different versions.
|
||||
The resolver will store items using the `json` package to serialize/deserialize values, so to save space
|
||||
it is encouraged to use `omitempty`. For example:
|
||||
|
||||
```go
|
||||
type myCacheItem struct {
|
||||
Name string `json:"name",omitempty`
|
||||
}
|
||||
```
|
||||
|
||||
It is possible to use core types such as `pkg.Package` as long as they support the standard `json` serialization,
|
||||
but this is discouraged in order to decouple changes to them from affecting the information stored in the cache.
|
||||
|
||||
To get a cache for this type:
|
||||
```go
|
||||
resolver := cache.GetResolver[myCacheItem]("myCacheName", "v1")
|
||||
```
|
||||
|
||||
Using the `resolver` is a single call, which manages checking for items in the cache, expiry times,
|
||||
and if not found invoking the callback to populate the cache and return a value:
|
||||
```go
|
||||
data := resolver.Resolve("some/cache/key", func() (myCacheItem, error) {
|
||||
// do things to return a myCacheItem or error
|
||||
})
|
||||
```
|
||||
|
||||
If it is common that checking for an item will result in errors, and you do not want to re-run the resolve function
|
||||
when errors are encountered, instead of using `GetResolver`, you can use `GetResolverCachingErrors`, which is useful
|
||||
for things such as resolving artifacts over a network, where a number of them will not be resolved, and you do not want
|
||||
to continue to have the expense of running the network resolution. This should be used when it is acceptable a network
|
||||
outage and cached errors is an acceptable risk.
|
||||
|
||||
An example can be seen in the [golang cataloger](../../syft/pkg/cataloger/golang/licenses.go) fetching remote licenses.
|
||||
Vendored
+24
@@ -0,0 +1,24 @@
|
||||
package cache
|
||||
|
||||
import "io"
|
||||
|
||||
type bypassedCache struct{}
|
||||
|
||||
func (b *bypassedCache) Read(_ string) (ReaderAtCloser, error) {
|
||||
return nil, errNotFound
|
||||
}
|
||||
|
||||
func (b *bypassedCache) Write(_ string, contents io.Reader) error {
|
||||
if closer, ok := contents.(io.Closer); ok {
|
||||
_ = closer.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *bypassedCache) GetCache(_, _ string) Cache {
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *bypassedCache) RootDirs() []string {
|
||||
return nil
|
||||
}
|
||||
Vendored
+18
@@ -0,0 +1,18 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_bypassedCache(t *testing.T) {
|
||||
m := bypassedCache{}
|
||||
cache := m.GetCache("name", "version")
|
||||
err := cache.Write("test", strings.NewReader("value"))
|
||||
require.NoError(t, err)
|
||||
rdr, err := cache.Read("test")
|
||||
require.Nil(t, rdr)
|
||||
require.ErrorIs(t, err, errNotFound)
|
||||
}
|
||||
Vendored
+49
@@ -0,0 +1,49 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// Manager is responsible for managing cache data and instantiating all caches
|
||||
type Manager interface {
|
||||
// GetCache returns a cache scoped to the given named, versioned data
|
||||
GetCache(name, version string) Cache
|
||||
|
||||
// RootDirs returns any root directories this cache manager uses
|
||||
RootDirs() []string
|
||||
}
|
||||
|
||||
// ReaderAtCloser is an amalgamation of: io.Reader, io.ReaderAt, and io.Closer
|
||||
type ReaderAtCloser interface {
|
||||
io.Reader
|
||||
io.ReaderAt
|
||||
io.Closer
|
||||
}
|
||||
|
||||
// Cache is what the application interacts with to get and set cached data
|
||||
type Cache interface {
|
||||
// Read returns a reader for the cache value, if found and not expired
|
||||
// or errors when unable to find / expired
|
||||
Read(key string) (ReaderAtCloser, error)
|
||||
|
||||
// Write writes the contents of the reader to the cache
|
||||
// and closes it, if the reader implements io.Closer
|
||||
Write(key string, contents io.Reader) error
|
||||
}
|
||||
|
||||
// GetManager returns the global cache manager, which is used to instantiate all caches
|
||||
func GetManager() Manager {
|
||||
return manager
|
||||
}
|
||||
|
||||
// SetManager sets the global cache manager, which is used to instantiate all caches.
|
||||
// Setting this to nil disables caching.
|
||||
func SetManager(m Manager) {
|
||||
if m == nil {
|
||||
manager = &bypassedCache{}
|
||||
} else {
|
||||
manager = m
|
||||
}
|
||||
}
|
||||
|
||||
var manager Manager = &bypassedCache{}
|
||||
Vendored
+32
@@ -0,0 +1,32 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_SetManager(t *testing.T) {
|
||||
original := GetManager()
|
||||
defer SetManager(original)
|
||||
|
||||
SetManager(nil)
|
||||
|
||||
require.NotNil(t, GetManager())
|
||||
require.IsType(t, &bypassedCache{}, GetManager())
|
||||
|
||||
SetManager(NewInMemory(0))
|
||||
|
||||
require.NotNil(t, GetManager())
|
||||
require.IsType(t, &bypassedCache{}, GetManager())
|
||||
|
||||
SetManager(NewInMemory(1 * time.Hour))
|
||||
|
||||
require.NotNil(t, GetManager())
|
||||
require.IsType(t, &filesystemCache{}, GetManager())
|
||||
|
||||
SetManager(nil)
|
||||
require.NotNil(t, GetManager())
|
||||
require.IsType(t, &bypassedCache{}, GetManager())
|
||||
}
|
||||
Vendored
+40
@@ -0,0 +1,40 @@
|
||||
package cache
|
||||
|
||||
import "fmt"
|
||||
|
||||
// GetResolverCachingErrors returns a Resolver that caches errors and will return them
|
||||
// instead of continuing to call the provided resolve functions
|
||||
func GetResolverCachingErrors[T any](name, version string) Resolver[T] {
|
||||
return &errorResolver[T]{
|
||||
resolver: GetResolver[errResponse[T]](name, version),
|
||||
}
|
||||
}
|
||||
|
||||
type errResponse[T any] struct {
|
||||
Error string `json:"err,omitempty"`
|
||||
Value T `json:"val,omitempty"`
|
||||
}
|
||||
|
||||
type errorResolver[T any] struct {
|
||||
resolver Resolver[errResponse[T]]
|
||||
}
|
||||
|
||||
func (r *errorResolver[T]) Resolve(key string, resolver resolverFunc[T]) (T, error) {
|
||||
v, err := r.resolver.Resolve(key, func() (errResponse[T], error) {
|
||||
v, err := resolver()
|
||||
out := errResponse[T]{
|
||||
Value: v,
|
||||
}
|
||||
if err != nil {
|
||||
out.Error = err.Error()
|
||||
}
|
||||
return out, nil
|
||||
})
|
||||
if err != nil {
|
||||
return v.Value, err
|
||||
}
|
||||
if v.Error != "" {
|
||||
return v.Value, fmt.Errorf(v.Error)
|
||||
}
|
||||
return v.Value, nil
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_errorResolver(t *testing.T) {
|
||||
original := GetManager()
|
||||
defer SetManager(original)
|
||||
SetManager(NewInMemory(time.Hour))
|
||||
|
||||
resolver := GetResolverCachingErrors[string]("theCache", "theVersion")
|
||||
|
||||
resolveCount := 0
|
||||
resolveFunc := func() (string, error) {
|
||||
resolveCount++
|
||||
return "theValue", nil
|
||||
}
|
||||
|
||||
val, err := resolver.Resolve("theKey", resolveFunc)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, resolveCount)
|
||||
require.Equal(t, "theValue", val)
|
||||
|
||||
val, err = resolver.Resolve("theKey", resolveFunc)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, resolveCount)
|
||||
require.Equal(t, "theValue", val)
|
||||
|
||||
errorCount := 0
|
||||
errorFunc := func() (string, error) {
|
||||
errorCount++
|
||||
return "", fmt.Errorf("an error")
|
||||
}
|
||||
|
||||
_, err = resolver.Resolve("errorValue", errorFunc)
|
||||
require.ErrorContains(t, err, "an error")
|
||||
require.Equal(t, 1, errorCount)
|
||||
|
||||
_, err = resolver.Resolve("errorValue", errorFunc)
|
||||
require.ErrorContains(t, err, "an error")
|
||||
require.Equal(t, 1, errorCount)
|
||||
}
|
||||
Vendored
+120
@@ -0,0 +1,120 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
)
|
||||
|
||||
// NewFromDir creates a new cache manager which returns caches stored on disk, rooted at the given directory
|
||||
func NewFromDir(dir string, ttl time.Duration) (Manager, error) {
|
||||
dir = filepath.Clean(dir)
|
||||
fsys, err := subFs(afero.NewOsFs(), dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &filesystemCache{
|
||||
dir: dir,
|
||||
fs: fsys,
|
||||
ttl: ttl,
|
||||
}, nil
|
||||
}
|
||||
|
||||
const filePermissions = 0700
|
||||
const dirPermissions = os.ModeDir | filePermissions
|
||||
|
||||
type filesystemCache struct {
|
||||
dir string
|
||||
fs afero.Fs
|
||||
ttl time.Duration
|
||||
}
|
||||
|
||||
func (d *filesystemCache) GetCache(name, version string) Cache {
|
||||
fsys, err := subFs(d.fs, name, version)
|
||||
if err != nil {
|
||||
log.Warnf("error getting cache for: %s/%s: %v", name, version, err)
|
||||
return &bypassedCache{}
|
||||
}
|
||||
return &filesystemCache{
|
||||
dir: filepath.Join(d.dir, name, version),
|
||||
fs: fsys,
|
||||
ttl: d.ttl,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *filesystemCache) RootDirs() []string {
|
||||
if d.dir == "" {
|
||||
return nil
|
||||
}
|
||||
return []string{d.dir}
|
||||
}
|
||||
|
||||
func (d *filesystemCache) Read(key string) (ReaderAtCloser, error) {
|
||||
path := makeDiskKey(key)
|
||||
f, err := d.fs.Open(path)
|
||||
if err != nil {
|
||||
log.Tracef("no cache entry for %s %s: %v", d.dir, key, err)
|
||||
return nil, errNotFound
|
||||
} else if stat, err := f.Stat(); err != nil || stat == nil || time.Since(stat.ModTime()) > d.ttl {
|
||||
log.Tracef("cache entry is too old for %s %s", d.dir, key)
|
||||
return nil, errExpired
|
||||
}
|
||||
log.Tracef("using cache for %s %s", d.dir, key)
|
||||
return f, nil
|
||||
}
|
||||
|
||||
func (d *filesystemCache) Write(key string, contents io.Reader) error {
|
||||
path := makeDiskKey(key)
|
||||
return afero.WriteReader(d.fs, path, contents)
|
||||
}
|
||||
|
||||
// subFs returns a writable directory with the given name under the root cache directory returned from findRoot,
|
||||
// the directory will be created if it does not exist
|
||||
func subFs(fsys afero.Fs, subDirs ...string) (afero.Fs, error) {
|
||||
dir := filepath.Join(subDirs...)
|
||||
dir = filepath.Clean(dir)
|
||||
stat, err := fsys.Stat(dir)
|
||||
if errors.Is(err, afero.ErrFileNotFound) {
|
||||
err = fsys.MkdirAll(dir, dirPermissions)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to create directory at '%s': %v", dir, err)
|
||||
}
|
||||
stat, err = fsys.Stat(dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if err != nil || stat == nil || !stat.IsDir() {
|
||||
return nil, fmt.Errorf("unable to verify directory '%s': %v", dir, err)
|
||||
}
|
||||
fsys = afero.NewBasePathFs(fsys, dir)
|
||||
return fsys, err
|
||||
}
|
||||
|
||||
var keyReplacer = regexp.MustCompile("[^-._/a-zA-Z0-9]")
|
||||
|
||||
// makeDiskKey makes a safe sub-path but not escape forward slashes, this allows for logical partitioning on disk
|
||||
func makeDiskKey(key string) string {
|
||||
// encode single dot directory
|
||||
if key == "." {
|
||||
return "%2E"
|
||||
}
|
||||
// replace any disallowed chars with encoded form
|
||||
key = keyReplacer.ReplaceAllStringFunc(key, url.QueryEscape)
|
||||
// allow . in names but not ..
|
||||
key = strings.ReplaceAll(key, "..", "%2E%2E")
|
||||
return key
|
||||
}
|
||||
|
||||
var errNotFound = fmt.Errorf("not found")
|
||||
var errExpired = fmt.Errorf("expired")
|
||||
Vendored
+106
@@ -0,0 +1,106 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
)
|
||||
|
||||
func Test_filesystemCache(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
man, err := NewFromDir(dir, 1*time.Minute)
|
||||
require.NoError(t, err)
|
||||
|
||||
cacheName := "test"
|
||||
cacheVersion := "v1"
|
||||
cache := man.GetCache(cacheName, cacheVersion)
|
||||
|
||||
cacheKey := "test-key"
|
||||
contentsValue := "some contents to cache"
|
||||
|
||||
err = cache.Write(cacheKey, strings.NewReader(contentsValue))
|
||||
require.NoError(t, err)
|
||||
|
||||
rdr, err := cache.Read(cacheKey)
|
||||
require.NoError(t, err)
|
||||
defer internal.CloseAndLogError(rdr, cacheKey)
|
||||
|
||||
contents, err := io.ReadAll(rdr)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, contentsValue, string(contents))
|
||||
|
||||
// check the contents were actually written to disk as expected
|
||||
contents, err = os.ReadFile(filepath.Join(dir, cacheName, cacheVersion, cacheKey))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, contentsValue, string(contents))
|
||||
|
||||
_, err = cache.Read("otherKey")
|
||||
require.ErrorIs(t, err, errNotFound)
|
||||
}
|
||||
|
||||
func Test_makeDiskKey(t *testing.T) {
|
||||
tests := []struct {
|
||||
in string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
in: "",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
in: ".",
|
||||
expected: "%2E",
|
||||
},
|
||||
{
|
||||
in: "..",
|
||||
expected: "%2E%2E",
|
||||
},
|
||||
{
|
||||
in: "github.com",
|
||||
expected: "github.com",
|
||||
},
|
||||
{
|
||||
in: "../github.com",
|
||||
expected: "%2E%2E/github.com",
|
||||
},
|
||||
{
|
||||
in: "github.com/../..",
|
||||
expected: "github.com/%2E%2E/%2E%2E",
|
||||
},
|
||||
{
|
||||
in: "github.com/%2E../..",
|
||||
expected: "github.com/%252E%2E%2E/%2E%2E",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.in, func(t *testing.T) {
|
||||
got := makeDiskKey(test.in)
|
||||
// validate appropriate escaping
|
||||
require.Equal(t, test.expected, got)
|
||||
// also validate that unescaped string matches original
|
||||
unescaped, err := url.QueryUnescape(got)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, test.in, unescaped)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_errors(t *testing.T) {
|
||||
tmp := t.TempDir()
|
||||
cache := filepath.Join(tmp, "cache")
|
||||
// make a non-writable directory
|
||||
require.NoError(t, os.MkdirAll(cache, 0500|os.ModeDir))
|
||||
// attempt to make cache in non-writable directory
|
||||
dir := filepath.Join(cache, "dir")
|
||||
_, err := NewFromDir(dir, time.Hour)
|
||||
require.ErrorContains(t, err, fmt.Sprintf("unable to create directory at '%s':", dir))
|
||||
}
|
||||
Vendored
+71
@@ -0,0 +1,71 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/mitchellh/hashstructure/v2"
|
||||
)
|
||||
|
||||
// hashType returns a stable hash based on the structure of the type
|
||||
func hashType[T any]() string {
|
||||
// get the base type and hash an empty instance
|
||||
var t T
|
||||
empty := emptyValue(reflect.TypeOf(t)).Interface()
|
||||
hash, err := hashstructure.Hash(empty, hashstructure.FormatV2, &hashstructure.HashOptions{
|
||||
ZeroNil: false,
|
||||
IgnoreZeroValue: false,
|
||||
SlicesAsSets: false,
|
||||
UseStringer: false,
|
||||
})
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("unable to use type as cache key: %w", err))
|
||||
}
|
||||
return fmt.Sprintf("%x", hash)
|
||||
}
|
||||
|
||||
func emptyValue(t reflect.Type) reflect.Value {
|
||||
switch t.Kind() {
|
||||
case reflect.Pointer:
|
||||
e := t.Elem()
|
||||
v := emptyValue(e)
|
||||
if v.CanAddr() {
|
||||
return v.Addr()
|
||||
}
|
||||
ptrv := reflect.New(e)
|
||||
ptrv.Elem().Set(v)
|
||||
return ptrv
|
||||
case reflect.Slice:
|
||||
v := emptyValue(t.Elem())
|
||||
s := reflect.MakeSlice(t, 1, 1)
|
||||
s.Index(0).Set(v)
|
||||
return s
|
||||
case reflect.Struct:
|
||||
v := reflect.New(t).Elem()
|
||||
// get all empty field values, too
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
f := t.Field(i)
|
||||
if isIgnored(f) {
|
||||
continue
|
||||
}
|
||||
fv := v.Field(i)
|
||||
if fv.CanSet() {
|
||||
fv.Set(emptyValue(f.Type))
|
||||
}
|
||||
}
|
||||
return v
|
||||
default:
|
||||
return reflect.New(t).Elem()
|
||||
}
|
||||
}
|
||||
|
||||
func isIgnored(f reflect.StructField) bool {
|
||||
if !f.IsExported() {
|
||||
return true
|
||||
}
|
||||
tag := f.Tag.Get("hash")
|
||||
if tag == "-" || tag == "ignore" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
Vendored
+123
@@ -0,0 +1,123 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/mitchellh/hashstructure/v2"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_hashType(t *testing.T) {
|
||||
type t1 struct {
|
||||
Name string
|
||||
}
|
||||
type t2 struct {
|
||||
Name string
|
||||
}
|
||||
type generic[T any] struct {
|
||||
Val T
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
hash func() string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "struct 1",
|
||||
hash: func() string { return hashType[t1]() },
|
||||
expected: "d106c3ffbf98a0b1",
|
||||
},
|
||||
{
|
||||
name: "slice of struct 1",
|
||||
hash: func() string { return hashType[[]t1]() },
|
||||
expected: "8122ace4ee1af0b4",
|
||||
},
|
||||
{
|
||||
name: "slice of struct 2",
|
||||
hash: func() string { return hashType[[]t2]() },
|
||||
expected: "8cc04b5808be5bf9",
|
||||
},
|
||||
{
|
||||
name: "ptr 1",
|
||||
hash: func() string { return hashType[*t1]() },
|
||||
expected: "d106c3ffbf98a0b1", // same hash as t1, which is ok since the structs are the same
|
||||
},
|
||||
{
|
||||
name: "slice of ptr 1",
|
||||
hash: func() string { return hashType[[]*t1]() },
|
||||
expected: "8122ace4ee1af0b4", // same hash as []t1, again underlying serialization is the same
|
||||
},
|
||||
{
|
||||
name: "slice of ptr 2",
|
||||
hash: func() string { return hashType[[]*t2]() },
|
||||
expected: "8cc04b5808be5bf9", // same hash as []t2, underlying serialization is the same
|
||||
},
|
||||
{
|
||||
name: "slice of ptr of slice of ptr",
|
||||
hash: func() string { return hashType[[]*[]*t1]() },
|
||||
expected: "500d9f5b3a5977ce",
|
||||
},
|
||||
{
|
||||
name: "generic 1",
|
||||
hash: func() string { return hashType[generic[t1]]() },
|
||||
expected: "b5fbb30e24400e81",
|
||||
},
|
||||
{
|
||||
name: "generic 2",
|
||||
hash: func() string { return hashType[generic[t2]]() },
|
||||
expected: "becdb767c6b22bfa",
|
||||
},
|
||||
{
|
||||
name: "generic with ptr 1",
|
||||
hash: func() string { return hashType[generic[*t1]]() },
|
||||
expected: "30c8855bf290fd83",
|
||||
},
|
||||
{
|
||||
name: "generic with ptr 2",
|
||||
hash: func() string { return hashType[generic[*t2]]() },
|
||||
expected: "b66366b6ce9e6361",
|
||||
},
|
||||
{
|
||||
name: "generic with slice 1",
|
||||
hash: func() string { return hashType[generic[[]t1]]() },
|
||||
expected: "d2ed158942fa6c29",
|
||||
},
|
||||
{
|
||||
name: "generic with slice 2",
|
||||
hash: func() string { return hashType[generic[[]t2]]() },
|
||||
expected: "7a7bec575871c179",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
require.Equal(t, test.expected, test.hash())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_hashIgnores(t *testing.T) {
|
||||
hash := func(v any) string {
|
||||
v, err := hashstructure.Hash(v, hashstructure.FormatV2, &hashstructure.HashOptions{})
|
||||
require.NoError(t, err)
|
||||
return fmt.Sprintf("%x", v)
|
||||
}
|
||||
type t1 struct {
|
||||
Name string
|
||||
notExported string
|
||||
}
|
||||
require.Equal(t, hash(t1{notExported: "a value"}), hashType[t1]())
|
||||
|
||||
type t2 struct {
|
||||
Name string
|
||||
Exported string `hash:"ignore"`
|
||||
}
|
||||
require.Equal(t, hash(t2{Exported: "another value"}), hashType[t2]())
|
||||
|
||||
type t3 struct {
|
||||
Name string
|
||||
Exported string `hash:"-"`
|
||||
}
|
||||
require.Equal(t, hash(t3{Exported: "still valued"}), hashType[t3]())
|
||||
}
|
||||
Vendored
+19
@@ -0,0 +1,19 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
// NewInMemory returns an in-memory only cache manager
|
||||
func NewInMemory(ttl time.Duration) Manager {
|
||||
if ttl <= 0 {
|
||||
return &bypassedCache{}
|
||||
}
|
||||
return &filesystemCache{
|
||||
dir: "",
|
||||
fs: afero.NewMemMapFs(),
|
||||
ttl: ttl,
|
||||
}
|
||||
}
|
||||
Vendored
+37
@@ -0,0 +1,37 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
)
|
||||
|
||||
func Test_NewInMemory(t *testing.T) {
|
||||
man := NewInMemory(time.Hour)
|
||||
|
||||
cacheName := "test"
|
||||
cacheVersion := "v1"
|
||||
cache := man.GetCache(cacheName, cacheVersion)
|
||||
|
||||
cacheKey := "test-key"
|
||||
contentsValue := "some contents to cache"
|
||||
|
||||
err := cache.Write(cacheKey, strings.NewReader(contentsValue))
|
||||
require.NoError(t, err)
|
||||
|
||||
rdr, err := cache.Read(cacheKey)
|
||||
require.NoError(t, err)
|
||||
defer internal.CloseAndLogError(rdr, cacheKey)
|
||||
|
||||
contents, err := io.ReadAll(rdr)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, contentsValue, string(contents))
|
||||
|
||||
_, err = cache.Read("otherKey")
|
||||
require.ErrorIs(t, err, errNotFound)
|
||||
}
|
||||
Vendored
+83
@@ -0,0 +1,83 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
)
|
||||
|
||||
// Resolver interface provides a single Resolve method, which will return from cache
|
||||
// or call the provided resolve function to get the value if not available in cache
|
||||
type Resolver[T any] interface {
|
||||
// Resolve attempts to resolve the given key from cache and convert it to the type of the cache,
|
||||
// or calls the resolver function if unable to resolve a cached value
|
||||
Resolve(key string, resolver resolverFunc[T]) (T, error)
|
||||
}
|
||||
|
||||
// GetResolver returns a cache resolver for persistent cached data across Syft runs, stored in a unique
|
||||
// location based on the provided name and versioned by the type
|
||||
func GetResolver[T any](name, version string) Resolver[T] {
|
||||
typeHash := hashType[T]()
|
||||
versionKey := path.Join(version, typeHash)
|
||||
return &cacheResolver[T]{
|
||||
name: fmt.Sprintf("%s/%s", name, versionKey),
|
||||
cache: manager.GetCache(name, versionKey),
|
||||
}
|
||||
}
|
||||
|
||||
const resolverKeySuffix = ".json"
|
||||
|
||||
type resolverFunc[T any] func() (T, error)
|
||||
|
||||
type cacheResolver[T any] struct {
|
||||
name string
|
||||
cache Cache
|
||||
}
|
||||
|
||||
var _ interface {
|
||||
Resolver[int]
|
||||
} = (*cacheResolver[int])(nil)
|
||||
|
||||
func (r *cacheResolver[T]) Resolve(key string, resolver resolverFunc[T]) (T, error) {
|
||||
key += resolverKeySuffix
|
||||
|
||||
rdr, err := r.cache.Read(key)
|
||||
if rdr == nil || err != nil {
|
||||
return r.resolveAndCache(key, resolver)
|
||||
}
|
||||
defer internal.CloseAndLogError(rdr, key)
|
||||
|
||||
dec := json.NewDecoder(rdr)
|
||||
if dec == nil {
|
||||
log.Tracef("error getting cache json decoder for %s %v: %v", r.name, key, err)
|
||||
return r.resolveAndCache(key, resolver)
|
||||
}
|
||||
var t T
|
||||
err = dec.Decode(&t)
|
||||
if err != nil {
|
||||
log.Tracef("error decoding cached entry for %s %v: %v", r.name, key, err)
|
||||
return r.resolveAndCache(key, resolver)
|
||||
}
|
||||
// no error, able to resolve from cache
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func (r *cacheResolver[T]) resolveAndCache(key string, resolver func() (T, error)) (T, error) {
|
||||
t, err := resolver()
|
||||
if err != nil {
|
||||
return t, err
|
||||
}
|
||||
var data bytes.Buffer
|
||||
enc := json.NewEncoder(&data)
|
||||
enc.SetEscapeHTML(false)
|
||||
err = enc.Encode(t)
|
||||
if err != nil {
|
||||
return t, err
|
||||
}
|
||||
err = r.cache.Write(key, &data)
|
||||
return t, err
|
||||
}
|
||||
Vendored
+92
@@ -0,0 +1,92 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_Resolver(t *testing.T) {
|
||||
original := GetManager()
|
||||
defer SetManager(original)
|
||||
SetManager(NewInMemory(time.Hour))
|
||||
|
||||
type sub struct {
|
||||
Name string
|
||||
Value bool
|
||||
}
|
||||
|
||||
type thing struct {
|
||||
Value string
|
||||
Values []int
|
||||
Subs []*sub
|
||||
}
|
||||
|
||||
versionHash := hashType[thing]()
|
||||
cache := GetManager().GetCache("test", "v7/"+versionHash)
|
||||
|
||||
resolver := GetResolver[thing]("test", "v7")
|
||||
require.NotNil(t, resolver)
|
||||
|
||||
require.IsType(t, &cacheResolver[thing]{}, resolver)
|
||||
cr := resolver.(*cacheResolver[thing])
|
||||
|
||||
require.IsType(t, cache, cr.cache)
|
||||
|
||||
resolveErrCount := 0
|
||||
resolveThingErr := func() (thing, error) {
|
||||
resolveErrCount++
|
||||
return thing{}, fmt.Errorf("an error")
|
||||
}
|
||||
|
||||
_, err := resolver.Resolve("err", resolveThingErr)
|
||||
require.ErrorContains(t, err, "an error")
|
||||
require.Equal(t, 1, resolveErrCount)
|
||||
|
||||
_, err = resolver.Resolve("err", resolveThingErr)
|
||||
require.ErrorContains(t, err, "an error")
|
||||
require.Equal(t, 2, resolveErrCount)
|
||||
|
||||
aThing := thing{
|
||||
Value: "a value",
|
||||
Values: []int{7, 8, 9},
|
||||
Subs: []*sub{
|
||||
{
|
||||
Name: "sub1",
|
||||
Value: true,
|
||||
},
|
||||
{
|
||||
Name: "sub2",
|
||||
Value: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
resolveThingCount := 0
|
||||
resolveThing := func() (thing, error) {
|
||||
resolveThingCount++
|
||||
return aThing, nil
|
||||
}
|
||||
|
||||
val, err := resolver.Resolve("thing", resolveThing)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, resolveThingCount)
|
||||
require.Equal(t, aThing, val)
|
||||
|
||||
val, err = resolver.Resolve("thing", resolveThing)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, resolveThingCount)
|
||||
require.Equal(t, aThing, val)
|
||||
|
||||
rdr, err := cache.Read("thing" + resolverKeySuffix)
|
||||
require.NoError(t, err)
|
||||
decoder := json.NewDecoder(rdr)
|
||||
|
||||
var val2 thing
|
||||
err = decoder.Decode(&val2)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, aThing, val2)
|
||||
}
|
||||
@@ -3,5 +3,5 @@ package internal
|
||||
const (
|
||||
// JSONSchemaVersion is the current schema version output by the JSON encoder
|
||||
// This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment.
|
||||
JSONSchemaVersion = "16.0.14"
|
||||
JSONSchemaVersion = "16.0.15"
|
||||
)
|
||||
|
||||
@@ -21,27 +21,22 @@ func NewDependencyRelationships(resolver file.Resolver, accessor sbomsync.Access
|
||||
|
||||
// 3. craft package-to-package relationships for each binary that represent shared library dependencies
|
||||
//note: we only care about package-to-package relationships
|
||||
var relIndex *relationship.Index
|
||||
accessor.ReadFromSBOM(func(s *sbom.SBOM) {
|
||||
relIndex = relationship.NewIndex(s.Relationships...)
|
||||
})
|
||||
|
||||
return generateRelationships(resolver, accessor, index, relIndex)
|
||||
return generateRelationships(resolver, accessor, index)
|
||||
}
|
||||
|
||||
func generateRelationships(resolver file.Resolver, accessor sbomsync.Accessor, index *sharedLibraryIndex, relIndex *relationship.Index) []artifact.Relationship {
|
||||
// read all existing dependencyOf relationships
|
||||
accessor.ReadFromSBOM(func(s *sbom.SBOM) {
|
||||
for _, r := range s.Relationships {
|
||||
if r.Type != artifact.DependencyOfRelationship {
|
||||
continue
|
||||
}
|
||||
relIndex.Track(r)
|
||||
}
|
||||
})
|
||||
func generateRelationships(resolver file.Resolver, accessor sbomsync.Accessor, index *sharedLibraryIndex) []artifact.Relationship {
|
||||
newRelationships := relationship.NewIndex()
|
||||
|
||||
// find all package-to-package relationships for shared library dependencies
|
||||
accessor.ReadFromSBOM(func(s *sbom.SBOM) {
|
||||
relIndex := relationship.NewIndex(s.Relationships...)
|
||||
|
||||
addRelationship := func(r artifact.Relationship) {
|
||||
if !relIndex.Contains(r) {
|
||||
newRelationships.Add(r)
|
||||
}
|
||||
}
|
||||
|
||||
for _, parentPkg := range s.Artifacts.Packages.Sorted(pkg.BinaryPkg) {
|
||||
for _, evidentLocation := range parentPkg.Locations.ToSlice() {
|
||||
if evidentLocation.Annotations[pkg.EvidenceAnnotationKey] != pkg.PrimaryEvidenceAnnotation {
|
||||
@@ -54,12 +49,12 @@ func generateRelationships(resolver file.Resolver, accessor sbomsync.Accessor, i
|
||||
continue
|
||||
}
|
||||
|
||||
populateRelationships(exec, parentPkg, resolver, relIndex, index)
|
||||
populateRelationships(exec, parentPkg, resolver, addRelationship, index)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return relIndex.NewRelationships()
|
||||
return newRelationships.All()
|
||||
}
|
||||
|
||||
// PackagesToRemove returns a list of binary packages (resolved by the ELF cataloger) that should be removed from the SBOM
|
||||
@@ -147,7 +142,7 @@ func getBinaryPackagesToDelete(resolver file.Resolver, s *sbom.SBOM) []artifact.
|
||||
return pkgsToDelete
|
||||
}
|
||||
|
||||
func populateRelationships(exec file.Executable, parentPkg pkg.Package, resolver file.Resolver, relIndex *relationship.Index, index *sharedLibraryIndex) {
|
||||
func populateRelationships(exec file.Executable, parentPkg pkg.Package, resolver file.Resolver, addRelationship func(artifact.Relationship), index *sharedLibraryIndex) {
|
||||
for _, libReference := range exec.ImportedLibraries {
|
||||
// for each library reference, check s.Artifacts.Packages.Sorted(pkg.BinaryPkg) for a binary package that represents that library
|
||||
// if found, create a relationship between the parent package and the library package
|
||||
@@ -167,7 +162,7 @@ func populateRelationships(exec file.Executable, parentPkg pkg.Package, resolver
|
||||
realBaseName := path.Base(loc.RealPath)
|
||||
pkgCollection := index.owningLibraryPackage(realBaseName)
|
||||
if pkgCollection.PackageCount() < 1 {
|
||||
relIndex.Add(
|
||||
addRelationship(
|
||||
artifact.Relationship{
|
||||
From: loc.Coordinates,
|
||||
To: parentPkg,
|
||||
@@ -176,7 +171,7 @@ func populateRelationships(exec file.Executable, parentPkg pkg.Package, resolver
|
||||
)
|
||||
}
|
||||
for _, p := range pkgCollection.Sorted() {
|
||||
relIndex.Add(
|
||||
addRelationship(
|
||||
artifact.Relationship{
|
||||
From: p,
|
||||
To: parentPkg,
|
||||
|
||||
@@ -2,6 +2,7 @@ package binary
|
||||
|
||||
import (
|
||||
"path"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
@@ -328,7 +329,20 @@ func relationshipComparer(x, y []artifact.Relationship) string {
|
||||
artifact.Relationship{},
|
||||
file.LocationSet{},
|
||||
pkg.LicenseSet{},
|
||||
))
|
||||
), cmpopts.SortSlices(lessRelationships))
|
||||
}
|
||||
|
||||
func lessRelationships(r1, r2 artifact.Relationship) bool {
|
||||
c := strings.Compare(string(r1.Type), string(r2.Type))
|
||||
if c != 0 {
|
||||
return c < 0
|
||||
}
|
||||
c = strings.Compare(string(r1.From.ID()), string(r2.From.ID()))
|
||||
if c != 0 {
|
||||
return c < 0
|
||||
}
|
||||
c = strings.Compare(string(r1.To.ID()), string(r2.To.ID()))
|
||||
return c < 0
|
||||
}
|
||||
|
||||
func newAccessor(pkgs []pkg.Package, coordinateIndex map[file.Coordinates]file.Executable, preexistingRelationships []artifact.Relationship) sbomsync.Accessor {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package relationship
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"slices"
|
||||
|
||||
"github.com/anchore/syft/internal/sbomsync"
|
||||
@@ -21,6 +22,10 @@ var (
|
||||
binaryCatalogerTypes = []pkg.Type{
|
||||
pkg.BinaryPkg,
|
||||
}
|
||||
binaryMetadataTypes = []string{
|
||||
reflect.TypeOf(pkg.ELFBinaryPackageNoteJSONPayload{}).Name(),
|
||||
reflect.TypeOf(pkg.BinarySignature{}).Name(),
|
||||
}
|
||||
)
|
||||
|
||||
func ExcludeBinariesByFileOwnershipOverlap(accessor sbomsync.Accessor) {
|
||||
@@ -60,5 +65,15 @@ func excludeBinaryByFileOwnershipOverlap(r artifact.Relationship, c *pkg.Collect
|
||||
return false
|
||||
}
|
||||
|
||||
return slices.Contains(binaryCatalogerTypes, child.Type)
|
||||
if slices.Contains(binaryCatalogerTypes, child.Type) {
|
||||
return true
|
||||
}
|
||||
|
||||
if child.Metadata == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
childMetadataType := reflect.TypeOf(child.Metadata)
|
||||
|
||||
return slices.Contains(binaryMetadataTypes, childMetadataType.Name())
|
||||
}
|
||||
|
||||
@@ -12,7 +12,9 @@ func TestExclude(t *testing.T) {
|
||||
packageB := pkg.Package{Name: "package-a", Type: pkg.PythonPkg}
|
||||
packageC := pkg.Package{Name: "package-a", Type: pkg.BinaryPkg}
|
||||
packageD := pkg.Package{Name: "package-d", Type: pkg.BinaryPkg}
|
||||
for _, p := range []*pkg.Package{&packageA, &packageB, &packageC, &packageD} {
|
||||
packageE := pkg.Package{Name: "package-e", Type: pkg.RpmPkg, Metadata: pkg.ELFBinaryPackageNoteJSONPayload{Type: "rpm"}}
|
||||
packageF := pkg.Package{Name: "package-f", Type: pkg.RpmPkg, Metadata: pkg.BinarySignature{}}
|
||||
for _, p := range []*pkg.Package{&packageA, &packageB, &packageC, &packageD, &packageE, &packageF} {
|
||||
p := p
|
||||
p.SetID()
|
||||
}
|
||||
@@ -43,6 +45,26 @@ func TestExclude(t *testing.T) {
|
||||
packages: pkg.NewCollection(packageA, packageC),
|
||||
shouldExclude: true,
|
||||
},
|
||||
{
|
||||
name: "exclusions from os -> elf binary (as RPM)",
|
||||
relationship: artifact.Relationship{
|
||||
Type: artifact.OwnershipByFileOverlapRelationship,
|
||||
From: packageA,
|
||||
To: packageE,
|
||||
},
|
||||
packages: pkg.NewCollection(packageA, packageE),
|
||||
shouldExclude: true,
|
||||
},
|
||||
{
|
||||
name: "exclusions from os -> binary (masquerading as RPM)",
|
||||
relationship: artifact.Relationship{
|
||||
Type: artifact.OwnershipByFileOverlapRelationship,
|
||||
From: packageA,
|
||||
To: packageF,
|
||||
},
|
||||
packages: pkg.NewCollection(packageA, packageF),
|
||||
shouldExclude: true,
|
||||
},
|
||||
{
|
||||
name: "no exclusions from python -> binary",
|
||||
relationship: artifact.Relationship{
|
||||
|
||||
+149
-56
@@ -1,88 +1,181 @@
|
||||
package relationship
|
||||
|
||||
import (
|
||||
"github.com/scylladb/go-set/strset"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
)
|
||||
|
||||
// Index indexes relationships, preventing duplicates
|
||||
type Index struct {
|
||||
typesByFromTo map[artifact.ID]map[artifact.ID]*strset.Set
|
||||
existing []artifact.Relationship
|
||||
additional []artifact.Relationship
|
||||
all []*sortableRelationship
|
||||
fromID map[artifact.ID]*mappedRelationships
|
||||
toID map[artifact.ID]*mappedRelationships
|
||||
}
|
||||
|
||||
func NewIndex(existing ...artifact.Relationship) *Index {
|
||||
r := &Index{
|
||||
typesByFromTo: make(map[artifact.ID]map[artifact.ID]*strset.Set),
|
||||
}
|
||||
r.TrackAll(existing...)
|
||||
return r
|
||||
// NewIndex returns a new relationship Index
|
||||
func NewIndex(relationships ...artifact.Relationship) *Index {
|
||||
out := Index{}
|
||||
out.Add(relationships...)
|
||||
return &out
|
||||
}
|
||||
|
||||
func (i *Index) track(r artifact.Relationship) bool {
|
||||
fromID := r.From.ID()
|
||||
if _, ok := i.typesByFromTo[fromID]; !ok {
|
||||
i.typesByFromTo[fromID] = make(map[artifact.ID]*strset.Set)
|
||||
// Add adds all the given relationships to the index, without adding duplicates
|
||||
func (i *Index) Add(relationships ...artifact.Relationship) {
|
||||
if i.fromID == nil {
|
||||
i.fromID = map[artifact.ID]*mappedRelationships{}
|
||||
}
|
||||
if i.toID == nil {
|
||||
i.toID = map[artifact.ID]*mappedRelationships{}
|
||||
}
|
||||
|
||||
toID := r.To.ID()
|
||||
if _, ok := i.typesByFromTo[fromID][toID]; !ok {
|
||||
i.typesByFromTo[fromID][toID] = strset.New()
|
||||
// store appropriate indexes for stable ordering to minimize ID() calls
|
||||
for _, r := range relationships {
|
||||
// prevent duplicates
|
||||
if i.Contains(r) {
|
||||
continue
|
||||
}
|
||||
|
||||
fromID := r.From.ID()
|
||||
toID := r.To.ID()
|
||||
|
||||
relationship := &sortableRelationship{
|
||||
from: fromID,
|
||||
to: toID,
|
||||
relationship: r,
|
||||
}
|
||||
|
||||
// add to all relationships
|
||||
i.all = append(i.all, relationship)
|
||||
|
||||
// add from -> to mapping
|
||||
mapped := i.fromID[fromID]
|
||||
if mapped == nil {
|
||||
mapped = &mappedRelationships{}
|
||||
i.fromID[fromID] = mapped
|
||||
}
|
||||
mapped.add(toID, relationship)
|
||||
|
||||
// add to -> from mapping
|
||||
mapped = i.toID[toID]
|
||||
if mapped == nil {
|
||||
mapped = &mappedRelationships{}
|
||||
i.toID[toID] = mapped
|
||||
}
|
||||
mapped.add(fromID, relationship)
|
||||
}
|
||||
|
||||
var exists bool
|
||||
if i.typesByFromTo[fromID][toID].Has(string(r.Type)) {
|
||||
exists = true
|
||||
}
|
||||
|
||||
i.typesByFromTo[fromID][toID].Add(string(r.Type))
|
||||
|
||||
return !exists
|
||||
}
|
||||
|
||||
// Track this relationship as "exists" in the index (this is used to prevent duplicate relationships from being added).
|
||||
// returns true if the relationship is new to the index, false otherwise.
|
||||
func (i *Index) Track(r artifact.Relationship) bool {
|
||||
unique := i.track(r)
|
||||
if unique {
|
||||
i.existing = append(i.existing, r)
|
||||
}
|
||||
return unique
|
||||
// From returns all relationships from the given identifiable, with specified types
|
||||
func (i *Index) From(identifiable artifact.Identifiable, types ...artifact.RelationshipType) []artifact.Relationship {
|
||||
return toSortedSlice(fromMapped(i.fromID, identifiable), types)
|
||||
}
|
||||
|
||||
// Add a new relationship to the index, returning true if the relationship is new to the index, false otherwise (thus is a duplicate).
|
||||
func (i *Index) Add(r artifact.Relationship) bool {
|
||||
if i.track(r) {
|
||||
i.additional = append(i.additional, r)
|
||||
return true
|
||||
// To returns all relationships to the given identifiable, with specified types
|
||||
func (i *Index) To(identifiable artifact.Identifiable, types ...artifact.RelationshipType) []artifact.Relationship {
|
||||
return toSortedSlice(fromMapped(i.toID, identifiable), types)
|
||||
}
|
||||
|
||||
// References returns all relationships that reference to or from the given identifiable
|
||||
func (i *Index) References(identifiable artifact.Identifiable, types ...artifact.RelationshipType) []artifact.Relationship {
|
||||
return toSortedSlice(append(fromMapped(i.fromID, identifiable), fromMapped(i.toID, identifiable)...), types)
|
||||
}
|
||||
|
||||
// Coordinates returns all coordinates for the provided identifiable for provided relationship types
|
||||
// If no types are provided, all relationship types are considered.
|
||||
func (i *Index) Coordinates(identifiable artifact.Identifiable, types ...artifact.RelationshipType) []file.Coordinates {
|
||||
var coordinates []file.Coordinates
|
||||
for _, relationship := range i.References(identifiable, types...) {
|
||||
cords := extractCoordinates(relationship)
|
||||
coordinates = append(coordinates, cords...)
|
||||
}
|
||||
return coordinates
|
||||
}
|
||||
|
||||
// Contains indicates the relationship is present in this index
|
||||
func (i *Index) Contains(r artifact.Relationship) bool {
|
||||
if mapped := i.fromID[r.From.ID()]; mapped != nil {
|
||||
if ids := mapped.typeMap[r.Type]; ids != nil {
|
||||
return ids[r.To.ID()] != nil
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (i *Index) TrackAll(rs ...artifact.Relationship) {
|
||||
for _, r := range rs {
|
||||
i.Track(r)
|
||||
// All returns a sorted set of relationships matching all types, or all relationships if no types specified
|
||||
func (i *Index) All(types ...artifact.RelationshipType) []artifact.Relationship {
|
||||
return toSortedSlice(i.all, types)
|
||||
}
|
||||
|
||||
func fromMapped(idMap map[artifact.ID]*mappedRelationships, identifiable artifact.Identifiable) []*sortableRelationship {
|
||||
if identifiable == nil || idMap == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Index) AddAll(rs ...artifact.Relationship) {
|
||||
for _, r := range rs {
|
||||
i.Add(r)
|
||||
mapped := idMap[identifiable.ID()]
|
||||
if mapped == nil {
|
||||
return nil
|
||||
}
|
||||
return mapped.allRelated
|
||||
}
|
||||
|
||||
func (i *Index) NewRelationships() []artifact.Relationship {
|
||||
return i.additional
|
||||
func toSortedSlice(relationships []*sortableRelationship, types []artifact.RelationshipType) []artifact.Relationship {
|
||||
// always return sorted for SBOM stability
|
||||
slices.SortFunc(relationships, sortFunc)
|
||||
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
|
||||
}
|
||||
|
||||
func (i *Index) ExistingRelationships() []artifact.Relationship {
|
||||
return i.existing
|
||||
func extractCoordinates(relationship artifact.Relationship) (results []file.Coordinates) {
|
||||
if coordinates, exists := relationship.From.(file.Coordinates); exists {
|
||||
results = append(results, coordinates)
|
||||
}
|
||||
|
||||
if coordinates, exists := relationship.To.(file.Coordinates); exists {
|
||||
results = append(results, coordinates)
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func (i *Index) AllUniqueRelationships() []artifact.Relationship {
|
||||
var all []artifact.Relationship
|
||||
all = append(all, i.existing...)
|
||||
all = append(all, i.additional...)
|
||||
return all
|
||||
type mappedRelationships struct {
|
||||
typeMap map[artifact.RelationshipType]map[artifact.ID]*sortableRelationship
|
||||
allRelated []*sortableRelationship
|
||||
}
|
||||
|
||||
func (m *mappedRelationships) add(id artifact.ID, newRelationship *sortableRelationship) {
|
||||
m.allRelated = append(m.allRelated, newRelationship)
|
||||
if m.typeMap == nil {
|
||||
m.typeMap = map[artifact.RelationshipType]map[artifact.ID]*sortableRelationship{}
|
||||
}
|
||||
typeMap := m.typeMap[newRelationship.relationship.Type]
|
||||
if typeMap == nil {
|
||||
typeMap = map[artifact.ID]*sortableRelationship{}
|
||||
m.typeMap[newRelationship.relationship.Type] = typeMap
|
||||
}
|
||||
typeMap[id] = newRelationship
|
||||
}
|
||||
|
||||
type sortableRelationship struct {
|
||||
from artifact.ID
|
||||
to artifact.ID
|
||||
relationship artifact.Relationship
|
||||
}
|
||||
|
||||
func sortFunc(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))
|
||||
}
|
||||
|
||||
+208
-200
@@ -3,223 +3,231 @@ package relationship
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
func Test_newRelationshipIndex(t *testing.T) {
|
||||
from := fakeIdentifiable{id: "from"}
|
||||
to := fakeIdentifiable{id: "to"}
|
||||
tests := []struct {
|
||||
name string
|
||||
given []artifact.Relationship
|
||||
track []artifact.Relationship
|
||||
add []artifact.Relationship
|
||||
wantExisting []string
|
||||
wantAdditional []string
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
},
|
||||
{
|
||||
name: "tracks existing relationships",
|
||||
given: []artifact.Relationship{
|
||||
{
|
||||
From: from,
|
||||
To: to,
|
||||
Type: artifact.EvidentByRelationship,
|
||||
},
|
||||
},
|
||||
wantExisting: []string{"from [evident-by] to"},
|
||||
},
|
||||
{
|
||||
name: "deduplicate tracked relationships",
|
||||
given: []artifact.Relationship{
|
||||
{
|
||||
From: from,
|
||||
To: to,
|
||||
Type: artifact.EvidentByRelationship,
|
||||
},
|
||||
{
|
||||
From: from,
|
||||
To: to,
|
||||
Type: artifact.EvidentByRelationship,
|
||||
},
|
||||
{
|
||||
From: from,
|
||||
To: to,
|
||||
Type: artifact.EvidentByRelationship,
|
||||
},
|
||||
},
|
||||
track: []artifact.Relationship{
|
||||
{
|
||||
From: from,
|
||||
To: to,
|
||||
Type: artifact.EvidentByRelationship,
|
||||
},
|
||||
{
|
||||
From: from,
|
||||
To: to,
|
||||
Type: artifact.EvidentByRelationship,
|
||||
},
|
||||
},
|
||||
wantExisting: []string{"from [evident-by] to"},
|
||||
},
|
||||
{
|
||||
name: "deduplicate any input relationships",
|
||||
given: []artifact.Relationship{
|
||||
{
|
||||
From: from,
|
||||
To: to,
|
||||
Type: artifact.EvidentByRelationship,
|
||||
},
|
||||
{
|
||||
From: from,
|
||||
To: to,
|
||||
Type: artifact.EvidentByRelationship,
|
||||
},
|
||||
},
|
||||
track: []artifact.Relationship{
|
||||
{
|
||||
From: from,
|
||||
To: to,
|
||||
Type: artifact.EvidentByRelationship,
|
||||
},
|
||||
{
|
||||
From: from,
|
||||
To: to,
|
||||
Type: artifact.EvidentByRelationship,
|
||||
},
|
||||
},
|
||||
add: []artifact.Relationship{
|
||||
{
|
||||
From: from,
|
||||
To: to,
|
||||
Type: artifact.EvidentByRelationship,
|
||||
},
|
||||
{
|
||||
From: from,
|
||||
To: to,
|
||||
Type: artifact.EvidentByRelationship,
|
||||
},
|
||||
},
|
||||
wantExisting: []string{"from [evident-by] to"},
|
||||
},
|
||||
{
|
||||
name: "deduplicate any added relationships",
|
||||
add: []artifact.Relationship{
|
||||
{
|
||||
From: from,
|
||||
To: to,
|
||||
Type: artifact.EvidentByRelationship,
|
||||
},
|
||||
{
|
||||
From: from,
|
||||
To: to,
|
||||
Type: artifact.EvidentByRelationship,
|
||||
},
|
||||
},
|
||||
wantAdditional: []string{"from [evident-by] to"},
|
||||
},
|
||||
func Test_Index(t *testing.T) {
|
||||
p1 := pkg.Package{
|
||||
Name: "pkg-1",
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
idx := NewIndex(tt.given...)
|
||||
idx.TrackAll(tt.track...)
|
||||
idx.AddAll(tt.add...)
|
||||
diffRelationships(t, tt.wantExisting, idx.existing)
|
||||
diffRelationships(t, tt.wantAdditional, idx.additional)
|
||||
})
|
||||
p2 := pkg.Package{
|
||||
Name: "pkg-2",
|
||||
}
|
||||
p3 := pkg.Package{
|
||||
Name: "pkg-3",
|
||||
}
|
||||
c1 := file.Coordinates{
|
||||
RealPath: "/coords/1",
|
||||
}
|
||||
c2 := file.Coordinates{
|
||||
RealPath: "/coords/2",
|
||||
}
|
||||
|
||||
for _, p := range []*pkg.Package{&p1, &p2, &p3} {
|
||||
p.SetID()
|
||||
}
|
||||
|
||||
r1 := artifact.Relationship{
|
||||
From: p1,
|
||||
To: p2,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
}
|
||||
r2 := artifact.Relationship{
|
||||
From: p1,
|
||||
To: p3,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
}
|
||||
r3 := artifact.Relationship{
|
||||
From: p1,
|
||||
To: c1,
|
||||
Type: artifact.ContainsRelationship,
|
||||
}
|
||||
r4 := artifact.Relationship{
|
||||
From: p2,
|
||||
To: c2,
|
||||
Type: artifact.ContainsRelationship,
|
||||
}
|
||||
r5 := artifact.Relationship{
|
||||
From: p3,
|
||||
To: c2,
|
||||
Type: artifact.ContainsRelationship,
|
||||
}
|
||||
|
||||
dup := artifact.Relationship{
|
||||
From: p3,
|
||||
To: c2,
|
||||
Type: artifact.ContainsRelationship,
|
||||
}
|
||||
|
||||
idx := NewIndex(r1, r2, r3, r4, r5, dup)
|
||||
require.ElementsMatch(t, slice(r1, r2, r3, r4, r5), idx.All())
|
||||
|
||||
require.ElementsMatch(t, slice(r1, r4), idx.References(p2))
|
||||
require.ElementsMatch(t, slice(r4), idx.References(p2, artifact.ContainsRelationship))
|
||||
|
||||
require.ElementsMatch(t, slice(r1), idx.To(p2))
|
||||
require.ElementsMatch(t, []artifact.Relationship(nil), idx.To(p2, artifact.ContainsRelationship))
|
||||
|
||||
require.ElementsMatch(t, slice(r4), idx.From(p2))
|
||||
require.ElementsMatch(t, slice(r4), idx.From(p2, artifact.ContainsRelationship))
|
||||
}
|
||||
|
||||
func diffRelationships(t *testing.T, expected []string, actual []artifact.Relationship) {
|
||||
if d := cmp.Diff(expected, stringRelationships(actual)); d != "" {
|
||||
t.Errorf("unexpected relationships (-want, +got): %s", d)
|
||||
func Test_sortOrder(t *testing.T) {
|
||||
r1 := artifact.Relationship{
|
||||
From: id("1"),
|
||||
To: id("2"),
|
||||
Type: "1",
|
||||
}
|
||||
}
|
||||
|
||||
func stringRelationships(relationships []artifact.Relationship) []string {
|
||||
var result []string
|
||||
for _, r := range relationships {
|
||||
result = append(result, string(r.From.ID())+" ["+string(r.Type)+"] "+string(r.To.ID()))
|
||||
r2 := artifact.Relationship{
|
||||
From: id("2"),
|
||||
To: id("3"),
|
||||
Type: "1",
|
||||
}
|
||||
return result
|
||||
|
||||
}
|
||||
|
||||
func Test_relationshipIndex_track(t *testing.T) {
|
||||
from := fakeIdentifiable{id: "from"}
|
||||
to := fakeIdentifiable{id: "to"}
|
||||
relationship := artifact.Relationship{From: from, To: to, Type: artifact.EvidentByRelationship}
|
||||
tests := []struct {
|
||||
name string
|
||||
existing []artifact.Relationship
|
||||
given artifact.Relationship
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "track returns true for a new relationship",
|
||||
existing: []artifact.Relationship{},
|
||||
given: relationship,
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "track returns false for an existing relationship",
|
||||
existing: []artifact.Relationship{relationship},
|
||||
given: relationship,
|
||||
want: false,
|
||||
},
|
||||
r3 := artifact.Relationship{
|
||||
From: id("3"),
|
||||
To: id("4"),
|
||||
Type: "1",
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
i := NewIndex(tt.existing...)
|
||||
if got := i.Track(tt.given); got != tt.want {
|
||||
t.Errorf("track() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
r4 := artifact.Relationship{
|
||||
From: id("1"),
|
||||
To: id("2"),
|
||||
Type: "2",
|
||||
}
|
||||
}
|
||||
|
||||
func Test_relationshipIndex_add(t *testing.T) {
|
||||
from := fakeIdentifiable{id: "from"}
|
||||
to := fakeIdentifiable{id: "to"}
|
||||
relationship := artifact.Relationship{From: from, To: to, Type: artifact.EvidentByRelationship}
|
||||
tests := []struct {
|
||||
name string
|
||||
existing []artifact.Relationship
|
||||
given artifact.Relationship
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "add returns true for a new relationship",
|
||||
existing: []artifact.Relationship{},
|
||||
given: relationship,
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "add returns false for an existing relationship",
|
||||
existing: []artifact.Relationship{relationship},
|
||||
given: relationship,
|
||||
want: false,
|
||||
},
|
||||
r5 := artifact.Relationship{
|
||||
From: id("2"),
|
||||
To: id("3"),
|
||||
Type: "2",
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
i := NewIndex(tt.existing...)
|
||||
if got := i.Add(tt.given); got != tt.want {
|
||||
t.Errorf("add() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
dup := artifact.Relationship{
|
||||
From: id("2"),
|
||||
To: id("3"),
|
||||
Type: "2",
|
||||
}
|
||||
r6 := artifact.Relationship{
|
||||
From: id("2"),
|
||||
To: id("3"),
|
||||
Type: "3",
|
||||
}
|
||||
|
||||
idx := NewIndex(r5, r2, r6, r4, r1, r3, dup)
|
||||
require.EqualValues(t, slice(r1, r2, r3, r4, r5, r6), idx.All())
|
||||
|
||||
require.EqualValues(t, slice(r1, r4), idx.From(id("1")))
|
||||
|
||||
require.EqualValues(t, slice(r2, r5, r6), idx.To(id("3")))
|
||||
|
||||
rLast := artifact.Relationship{
|
||||
From: id("0"),
|
||||
To: id("3"),
|
||||
Type: "9999",
|
||||
}
|
||||
|
||||
rFirst := artifact.Relationship{
|
||||
From: id("0"),
|
||||
To: id("3"),
|
||||
Type: "1",
|
||||
}
|
||||
|
||||
rMid := artifact.Relationship{
|
||||
From: id("0"),
|
||||
To: id("1"),
|
||||
Type: "2",
|
||||
}
|
||||
|
||||
idx.Add(rLast, rFirst, rMid)
|
||||
|
||||
require.EqualValues(t, slice(rFirst, r1, r2, r3, rMid, r4, r5, r6, rLast), idx.All())
|
||||
|
||||
require.EqualValues(t, slice(rFirst, r2, r5, r6, rLast), idx.To(id("3")))
|
||||
}
|
||||
|
||||
type fakeIdentifiable struct {
|
||||
id string
|
||||
func Test_Coordinates(t *testing.T) {
|
||||
p1 := pkg.Package{
|
||||
Name: "pkg-1",
|
||||
}
|
||||
p2 := pkg.Package{
|
||||
Name: "pkg-2",
|
||||
}
|
||||
p3 := pkg.Package{
|
||||
Name: "pkg-3",
|
||||
}
|
||||
c1 := file.Coordinates{
|
||||
RealPath: "/coords/1",
|
||||
}
|
||||
c2 := file.Coordinates{
|
||||
RealPath: "/coords/2",
|
||||
}
|
||||
c3 := file.Coordinates{
|
||||
RealPath: "/coords/3",
|
||||
}
|
||||
c4 := file.Coordinates{
|
||||
RealPath: "/coords/4",
|
||||
}
|
||||
|
||||
for _, p := range []*pkg.Package{&p1, &p2, &p3} {
|
||||
p.SetID()
|
||||
}
|
||||
|
||||
r1 := artifact.Relationship{
|
||||
From: p1,
|
||||
To: p2,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
}
|
||||
r2 := artifact.Relationship{
|
||||
From: p1,
|
||||
To: p3,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
}
|
||||
r3 := artifact.Relationship{
|
||||
From: p1,
|
||||
To: c1,
|
||||
Type: artifact.ContainsRelationship,
|
||||
}
|
||||
r4 := artifact.Relationship{
|
||||
From: p2,
|
||||
To: c2,
|
||||
Type: artifact.ContainsRelationship,
|
||||
}
|
||||
r5 := artifact.Relationship{
|
||||
From: p3,
|
||||
To: c1,
|
||||
Type: artifact.ContainsRelationship,
|
||||
}
|
||||
r6 := artifact.Relationship{
|
||||
From: p3,
|
||||
To: c2,
|
||||
Type: artifact.ContainsRelationship,
|
||||
}
|
||||
r7 := artifact.Relationship{
|
||||
From: c1,
|
||||
To: c3,
|
||||
Type: artifact.ContainsRelationship,
|
||||
}
|
||||
r8 := artifact.Relationship{
|
||||
From: c3,
|
||||
To: c4,
|
||||
Type: artifact.ContainsRelationship,
|
||||
}
|
||||
|
||||
idx := NewIndex(r1, r2, r3, r4, r5, r6, r7, r8)
|
||||
|
||||
got := idx.Coordinates(p1)
|
||||
require.ElementsMatch(t, slice(c1), got)
|
||||
|
||||
got = idx.Coordinates(p3)
|
||||
require.ElementsMatch(t, slice(c1, c2), got)
|
||||
}
|
||||
|
||||
func (f fakeIdentifiable) ID() artifact.ID {
|
||||
return artifact.ID(f.id)
|
||||
type id string
|
||||
|
||||
func (i id) ID() artifact.ID {
|
||||
return artifact.ID(i)
|
||||
}
|
||||
|
||||
func slice[T any](values ...T) []T {
|
||||
return values
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import (
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/rust"
|
||||
sbomCataloger "github.com/anchore/syft/syft/pkg/cataloger/sbom"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/swift"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/swipl"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/wordpress"
|
||||
)
|
||||
|
||||
@@ -93,6 +94,7 @@ func DefaultPackageTaskFactories() PackageTaskFactories {
|
||||
newSimplePackageTaskFactory(rust.NewCargoLockCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "rust", "cargo"),
|
||||
newSimplePackageTaskFactory(swift.NewCocoapodsCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "swift", "cocoapods"),
|
||||
newSimplePackageTaskFactory(swift.NewSwiftPackageManagerCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "swift", "spm"),
|
||||
newSimplePackageTaskFactory(swipl.NewSwiplPackCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "swipl", "pack"),
|
||||
|
||||
// language-specific package for both image and directory scans (but not necessarily declared) ////////////////////////////////////////
|
||||
newSimplePackageTaskFactory(dotnet.NewDotnetPortableExecutableCataloger, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, pkgcataloging.LanguageTag, "dotnet", "c#", "binary"),
|
||||
|
||||
@@ -7,3 +7,12 @@ For this reason we've included a copy of all schemas needed to validate `syft` o
|
||||
to reference local copies of dependent schemas.
|
||||
|
||||
You can get the latest schemas from the [CycloneDX specifications repo](https://github.com/CycloneDX/specification/tree/master/schema).
|
||||
|
||||
When the spec version is bumped an approach to determining prior modifications is to compare the
|
||||
prior spec version (e.g. if updating to 1.7, compare the files in this directory against the 1.6
|
||||
equivalents).
|
||||
|
||||
One can also update the schemas and observe the errors in order to make the necessary updates.
|
||||
At the time of writing, the cyclonedx.xsd needed modifications to link to the local spdx.xsd,
|
||||
and also to changes the minOccurs for a license tag to 0. (The json schema does not require
|
||||
modification for the generated file to lint properly, but can simply be copy/pasted).
|
||||
|
||||
+2104
-230
File diff suppressed because one or more lines are too long
+2918
-92
File diff suppressed because it is too large
Load Diff
+126
-1
@@ -2,7 +2,7 @@
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
elementFormDefault="qualified"
|
||||
targetNamespace="http://cyclonedx.org/schema/spdx"
|
||||
version="1.0-3.23">
|
||||
version="1.0-3.24.0">
|
||||
|
||||
<xs:simpleType name="licenseId">
|
||||
<xs:restriction base="xs:string">
|
||||
@@ -12,6 +12,11 @@
|
||||
<xs:documentation>BSD Zero Clause License</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="3D-Slicer-1.0">
|
||||
<xs:annotation>
|
||||
<xs:documentation>3D Slicer License v1.0</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="AAL">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Attribution Assurance License</xs:documentation>
|
||||
@@ -117,6 +122,11 @@
|
||||
<xs:documentation>Aladdin Free Public License</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="AMD-newlib">
|
||||
<xs:annotation>
|
||||
<xs:documentation>AMD newlib License</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="AMDPLPA">
|
||||
<xs:annotation>
|
||||
<xs:documentation>AMD's plpa_map.c License</xs:documentation>
|
||||
@@ -147,6 +157,11 @@
|
||||
<xs:documentation>ANTLR Software Rights Notice with license fallback</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="any-OSI">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Any OSI License</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="Apache-1.0">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Apache License 1.0</xs:documentation>
|
||||
@@ -322,6 +337,11 @@
|
||||
<xs:documentation>BSD 2-Clause - Ian Darwin variant</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="BSD-2-Clause-first-lines">
|
||||
<xs:annotation>
|
||||
<xs:documentation>BSD 2-Clause - first lines requirement</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="BSD-2-Clause-FreeBSD">
|
||||
<xs:annotation>
|
||||
<xs:documentation>BSD 2-Clause FreeBSD License</xs:documentation>
|
||||
@@ -522,6 +542,11 @@
|
||||
<xs:documentation>Caldera License (without preamble)</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="Catharon">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Catharon License</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="CATOSL-1.1">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Computer Associates Trusted Open Source License 1.1</xs:documentation>
|
||||
@@ -1002,6 +1027,11 @@
|
||||
<xs:documentation>curl License</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="cve-tou">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Common Vulnerability Enumeration ToU License</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="D-FSL-1.0">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Deutsche Freie Software Lizenz</xs:documentation>
|
||||
@@ -1482,6 +1512,11 @@
|
||||
<xs:documentation>gtkbook License</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="Gutmann">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Gutmann License</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="HaskellReport">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Haskell Language Report License</xs:documentation>
|
||||
@@ -1532,11 +1567,21 @@
|
||||
<xs:documentation>HPND with US Government export control warning</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="HPND-export-US-acknowledgement">
|
||||
<xs:annotation>
|
||||
<xs:documentation>HPND with US Government export control warning and acknowledgment</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="HPND-export-US-modify">
|
||||
<xs:annotation>
|
||||
<xs:documentation>HPND with US Government export control warning and modification rqmt</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="HPND-export2-US">
|
||||
<xs:annotation>
|
||||
<xs:documentation>HPND with US Government export control and 2 disclaimers</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="HPND-Fenneberg-Livingston">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Historical Permission Notice and Disclaimer - Fenneberg-Livingston variant</xs:documentation>
|
||||
@@ -1547,6 +1592,11 @@
|
||||
<xs:documentation>Historical Permission Notice and Disclaimer - INRIA-IMAG variant</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="HPND-Intel">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Historical Permission Notice and Disclaimer - Intel variant</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="HPND-Kevlin-Henney">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Historical Permission Notice and Disclaimer - Kevlin Henney variant</xs:documentation>
|
||||
@@ -1557,6 +1607,11 @@
|
||||
<xs:documentation>Historical Permission Notice and Disclaimer - Markus Kuhn variant</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="HPND-merchantability-variant">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Historical Permission Notice and Disclaimer - merchantability variant</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="HPND-MIT-disclaimer">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Historical Permission Notice and Disclaimer with MIT disclaimer</xs:documentation>
|
||||
@@ -1587,11 +1642,21 @@
|
||||
<xs:documentation>HPND sell variant with MIT disclaimer</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="HPND-sell-variant-MIT-disclaimer-rev">
|
||||
<xs:annotation>
|
||||
<xs:documentation>HPND sell variant with MIT disclaimer - reverse</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="HPND-UC">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Historical Permission Notice and Disclaimer - University of California variant</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="HPND-UC-export-US">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Historical Permission Notice and Disclaimer - University of California, US export warning</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="HTMLTIDY">
|
||||
<xs:annotation>
|
||||
<xs:documentation>HTML Tidy License</xs:documentation>
|
||||
@@ -2027,6 +2092,11 @@
|
||||
<xs:documentation>MIT Festival Variant</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="MIT-Khronos-old">
|
||||
<xs:annotation>
|
||||
<xs:documentation>MIT Khronos - old variant</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="MIT-Modern-Variant">
|
||||
<xs:annotation>
|
||||
<xs:documentation>MIT License Modern Variant</xs:documentation>
|
||||
@@ -2162,11 +2232,21 @@
|
||||
<xs:documentation>Net Boolean Public License v1</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="NCBI-PD">
|
||||
<xs:annotation>
|
||||
<xs:documentation>NCBI Public Domain Notice</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="NCGL-UK-2.0">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Non-Commercial Government Licence</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="NCL">
|
||||
<xs:annotation>
|
||||
<xs:documentation>NCL Source Code License</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="NCSA">
|
||||
<xs:annotation>
|
||||
<xs:documentation>University of Illinois/NCSA Open Source License</xs:documentation>
|
||||
@@ -2282,6 +2362,11 @@
|
||||
<xs:documentation>Open Use of Data Agreement v1.0</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="OAR">
|
||||
<xs:annotation>
|
||||
<xs:documentation>OAR License</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="OCCT-PL">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Open CASCADE Technology Public License</xs:documentation>
|
||||
@@ -2562,6 +2647,11 @@
|
||||
<xs:documentation>Pixar License</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="pkgconf">
|
||||
<xs:annotation>
|
||||
<xs:documentation>pkgconf License</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="Plexus">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Plexus Classworlds License</xs:documentation>
|
||||
@@ -2587,6 +2677,11 @@
|
||||
<xs:documentation>PostgreSQL License</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="PPL">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Peer Production License</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="PSF-2.0">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Python Software Foundation License 2.0</xs:documentation>
|
||||
@@ -2862,6 +2957,11 @@
|
||||
<xs:documentation>Sun PPP License</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="Sun-PPP-2000">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Sun PPP License (2000)</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="SunPro">
|
||||
<xs:annotation>
|
||||
<xs:documentation>SunPro License</xs:documentation>
|
||||
@@ -2907,6 +3007,11 @@
|
||||
<xs:documentation>Transitive Grace Period Public Licence 1.0</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="threeparttable">
|
||||
<xs:annotation>
|
||||
<xs:documentation>threeparttable License</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="TMate">
|
||||
<xs:annotation>
|
||||
<xs:documentation>TMate Open Source License</xs:documentation>
|
||||
@@ -3132,6 +3237,11 @@
|
||||
<xs:documentation>XSkat License</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="xzoom">
|
||||
<xs:annotation>
|
||||
<xs:documentation>xzoom License</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="YPL-1.0">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Yahoo! Public License v1.0</xs:documentation>
|
||||
@@ -3203,6 +3313,11 @@
|
||||
<xs:documentation>Asterisk exception</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="Asterisk-linking-protocols-exception">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Asterisk linking protocols exception</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="Autoconf-exception-2.0">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Autoconf exception 2.0</xs:documentation>
|
||||
@@ -3438,6 +3553,11 @@
|
||||
<xs:documentation>OpenVPN OpenSSL Exception</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="PCRE2-exception">
|
||||
<xs:annotation>
|
||||
<xs:documentation>PCRE2 exception</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="PS-or-PDF-font-exception-20170817">
|
||||
<xs:annotation>
|
||||
<xs:documentation>PS/PDF font exception (2017-08-17)</xs:documentation>
|
||||
@@ -3463,6 +3583,11 @@
|
||||
<xs:documentation>Qwt exception 1.0</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="RRDtool-FLOSS-exception-2.0">
|
||||
<xs:annotation>
|
||||
<xs:documentation>RRDtool FLOSS exception 2.0</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:enumeration>
|
||||
<xs:enumeration value="SANE-exception">
|
||||
<xs:annotation>
|
||||
<xs:documentation>SANE Exception</xs:documentation>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "anchore.io/schema/syft/json/16.0.14/document",
|
||||
"$id": "anchore.io/schema/syft/json/16.0.15/document",
|
||||
"$ref": "#/$defs/Document",
|
||||
"$defs": {
|
||||
"AlpmDbEntry": {
|
||||
@@ -1608,6 +1608,9 @@
|
||||
{
|
||||
"$ref": "#/$defs/SwiftPackageManagerLockEntry"
|
||||
},
|
||||
{
|
||||
"$ref": "#/$defs/SwiplpackPackage"
|
||||
},
|
||||
{
|
||||
"$ref": "#/$defs/WordpressPluginEntry"
|
||||
}
|
||||
@@ -2504,6 +2507,48 @@
|
||||
"revision"
|
||||
]
|
||||
},
|
||||
"SwiplpackPackage": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"version": {
|
||||
"type": "string"
|
||||
},
|
||||
"author": {
|
||||
"type": "string"
|
||||
},
|
||||
"authorEmail": {
|
||||
"type": "string"
|
||||
},
|
||||
"packager": {
|
||||
"type": "string"
|
||||
},
|
||||
"packagerEmail": {
|
||||
"type": "string"
|
||||
},
|
||||
"homepage": {
|
||||
"type": "string"
|
||||
},
|
||||
"dependencies": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name",
|
||||
"version",
|
||||
"author",
|
||||
"authorEmail",
|
||||
"packager",
|
||||
"packagerEmail",
|
||||
"homepage",
|
||||
"dependencies"
|
||||
]
|
||||
},
|
||||
"WordpressPluginEntry": {
|
||||
"properties": {
|
||||
"pluginInstallDirectory": {
|
||||
|
||||
@@ -64,6 +64,28 @@ func TestBySourceThenSpecificity(t *testing.T) {
|
||||
Must("cpe:2.3:a:some:package:*:*:*:*:*:*:*:*", "some-unknown-source"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "lexical sorting on equal sources puts escaped characters later",
|
||||
input: []CPE{
|
||||
Must("cpe:2.3:a:jenkins:pipeline\\\\:_supporting_apis:865.v43e78cc44e0d:*:*:*:*:jenkins:*:*", "nvd-cpe-dictionary"),
|
||||
Must("cpe:2.3:a:jenkins:pipeline_supporting_apis:865.v43e78cc44e0d:*:*:*:*:jenkins:*:*", "nvd-cpe-dictionary"),
|
||||
},
|
||||
want: []CPE{
|
||||
Must("cpe:2.3:a:jenkins:pipeline_supporting_apis:865.v43e78cc44e0d:*:*:*:*:jenkins:*:*", "nvd-cpe-dictionary"),
|
||||
Must("cpe:2.3:a:jenkins:pipeline\\\\:_supporting_apis:865.v43e78cc44e0d:*:*:*:*:jenkins:*:*", "nvd-cpe-dictionary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "lexical sorting on equal sources puts more specific attributes earlier",
|
||||
input: []CPE{
|
||||
Must("cpe:2.3:a:jenkins:mailer:472.vf7c289a_4b_420:*:*:*:*:*:*:*", "nvd-cpe-dictionary"),
|
||||
Must("cpe:2.3:a:jenkins:mailer:472.vf7c289a_4b_420:*:*:*:*:jenkins:*:*", "nvd-cpe-dictionary"),
|
||||
},
|
||||
want: []CPE{
|
||||
Must("cpe:2.3:a:jenkins:mailer:472.vf7c289a_4b_420:*:*:*:*:jenkins:*:*", "nvd-cpe-dictionary"),
|
||||
Must("cpe:2.3:a:jenkins:mailer:472.vf7c289a_4b_420:*:*:*:*:*:*:*", "nvd-cpe-dictionary"),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
@@ -21,7 +21,7 @@ type LicenseEvidence struct {
|
||||
func NewLicense(value string) License {
|
||||
spdxExpression, err := license.ParseExpression(value)
|
||||
if err != nil {
|
||||
log.Trace("unable to parse license expression: %s, %w", value, err)
|
||||
log.WithFields("error", err, "value", value).Trace("unable to parse license expression")
|
||||
}
|
||||
|
||||
return License{
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/anchore/packageurl-go"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/mimetype"
|
||||
"github.com/anchore/syft/internal/relationship"
|
||||
"github.com/anchore/syft/internal/spdxlicense"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
@@ -45,9 +46,10 @@ const (
|
||||
func ToFormatModel(s sbom.SBOM) *spdx.Document {
|
||||
name, namespace := helpers.DocumentNameAndNamespace(s.Source, s.Descriptor)
|
||||
|
||||
packages := toPackages(s.Artifacts.Packages, s)
|
||||
rels := relationship.NewIndex(s.Relationships...)
|
||||
packages := toPackages(rels, s.Artifacts.Packages, s)
|
||||
|
||||
relationships := toRelationships(s.RelationshipsSorted())
|
||||
allRelationships := toRelationships(rels.All())
|
||||
|
||||
// for valid SPDX we need a document describes relationship
|
||||
describesID := spdx.ElementID("DOCUMENT")
|
||||
@@ -57,7 +59,7 @@ func ToFormatModel(s sbom.SBOM) *spdx.Document {
|
||||
describesID = rootPackage.PackageSPDXIdentifier
|
||||
|
||||
// add all relationships from the document root to all other packages
|
||||
relationships = append(relationships, toRootRelationships(rootPackage, packages)...)
|
||||
allRelationships = append(allRelationships, toRootRelationships(rootPackage, packages)...)
|
||||
|
||||
// append the root package
|
||||
packages = append(packages, rootPackage)
|
||||
@@ -75,7 +77,7 @@ func ToFormatModel(s sbom.SBOM) *spdx.Document {
|
||||
}
|
||||
|
||||
// add the root document relationship
|
||||
relationships = append(relationships, documentDescribesRelationship)
|
||||
allRelationships = append(allRelationships, documentDescribesRelationship)
|
||||
|
||||
return &spdx.Document{
|
||||
// 6.1: SPDX Version; should be in the format "SPDX-x.x"
|
||||
@@ -150,7 +152,7 @@ func ToFormatModel(s sbom.SBOM) *spdx.Document {
|
||||
},
|
||||
Packages: packages,
|
||||
Files: toFiles(s),
|
||||
Relationships: relationships,
|
||||
Relationships: allRelationships,
|
||||
OtherLicenses: toOtherLicenses(s.Artifacts.Packages),
|
||||
}
|
||||
}
|
||||
@@ -302,7 +304,7 @@ func toSPDXID(identifiable artifact.Identifiable) spdx.ElementID {
|
||||
// packages populates all Package Information from the package Collection (see https://spdx.github.io/spdx-spec/3-package-information/)
|
||||
//
|
||||
//nolint:funlen
|
||||
func toPackages(catalog *pkg.Collection, sbom sbom.SBOM) (results []*spdx.Package) {
|
||||
func toPackages(rels *relationship.Index, catalog *pkg.Collection, sbom sbom.SBOM) (results []*spdx.Package) {
|
||||
for _, p := range catalog.Sorted() {
|
||||
// name should be guaranteed to be unique, but semantically useful and stable
|
||||
id := toSPDXID(p)
|
||||
@@ -318,7 +320,7 @@ func toPackages(catalog *pkg.Collection, sbom sbom.SBOM) (results []*spdx.Packag
|
||||
// 2. syft has generated a sha1 digest for the package's contents
|
||||
packageChecksums, filesAnalyzed := toPackageChecksums(p)
|
||||
|
||||
packageVerificationCode := newPackageVerificationCode(p, sbom)
|
||||
packageVerificationCode := newPackageVerificationCode(rels, p, sbom)
|
||||
if packageVerificationCode != nil {
|
||||
filesAnalyzed = true
|
||||
}
|
||||
@@ -744,12 +746,12 @@ func toOtherLicenses(catalog *pkg.Collection) []*spdx.OtherLicense {
|
||||
// f file is an "excludes" file, skip it /* exclude SPDX analysis file(s) */
|
||||
// see: https://spdx.github.io/spdx-spec/v2.3/package-information/#79-package-verification-code-field
|
||||
// the above link contains the SPDX algorithm for a package verification code
|
||||
func newPackageVerificationCode(p pkg.Package, sbom sbom.SBOM) *spdx.PackageVerificationCode {
|
||||
func newPackageVerificationCode(rels *relationship.Index, p pkg.Package, sbom sbom.SBOM) *spdx.PackageVerificationCode {
|
||||
// key off of the contains relationship;
|
||||
// spdx validator will fail if a package claims to contain a file but no sha1 provided
|
||||
// if a sha1 for a file is provided then the validator will fail if the package does not have
|
||||
// a package verification code
|
||||
coordinates := sbom.CoordinatesForPackage(p, artifact.ContainsRelationship)
|
||||
coordinates := rels.Coordinates(p, artifact.ContainsRelationship)
|
||||
var digests []file.Digest
|
||||
for _, c := range coordinates {
|
||||
digest := sbom.Artifacts.FileDigests[c]
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/internal/relationship"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/format/internal/spdxutil/helpers"
|
||||
@@ -665,7 +666,7 @@ func Test_H1Digest(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
catalog := pkg.NewCollection(test.pkg)
|
||||
pkgs := toPackages(catalog, s)
|
||||
pkgs := toPackages(relationship.NewIndex(), catalog, s)
|
||||
require.Len(t, pkgs, 1)
|
||||
for _, p := range pkgs {
|
||||
if test.expectedDigest == "" {
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
|
||||
"bomFormat": "CycloneDX",
|
||||
"specVersion": "1.6",
|
||||
"serialNumber": "urn:uuid:5208fea9-73dd-4624-b596-69fddccdb9e7",
|
||||
"version": 1,
|
||||
"metadata": {
|
||||
"timestamp": "2023-09-29T12:02:02-04:00",
|
||||
"tools": [
|
||||
{
|
||||
"vendor": "anchore",
|
||||
"name": "syft",
|
||||
"version": "[not provided]"
|
||||
}
|
||||
],
|
||||
"component": {
|
||||
"bom-ref": "a0ff99a6af10f11f",
|
||||
"type": "file",
|
||||
"name": "go.mod",
|
||||
"version": "sha256:sha256:dc333f342905248a52e424d8dfd061251d01867d01a4f9d7397144a775ff9ebd"
|
||||
}
|
||||
},
|
||||
"components": [
|
||||
{
|
||||
"bom-ref": "pkg:golang/github.com/wagoodman/go-partybus@v0.0.0-20230516145632-8ccac152c651?package-id=2ff71a67fb024c86",
|
||||
"type": "library",
|
||||
"name": "github.com/wagoodman/go-partybus",
|
||||
"version": "v0.0.0-20230516145632-8ccac152c651",
|
||||
"cpe": "cpe:2.3:a:wagoodman:go-partybus:v0.0.0-20230516145632-8ccac152c651:*:*:*:*:*:*:*",
|
||||
"purl": "pkg:golang/github.com/wagoodman/go-partybus@v0.0.0-20230516145632-8ccac152c651",
|
||||
"properties": [
|
||||
{
|
||||
"name": "syft:package:foundBy",
|
||||
"value": "go-module-file-cataloger"
|
||||
},
|
||||
{
|
||||
"name": "syft:package:language",
|
||||
"value": "go"
|
||||
},
|
||||
{
|
||||
"name": "syft:package:metadataType",
|
||||
"value": "GolangModMetadata"
|
||||
},
|
||||
{
|
||||
"name": "syft:package:type",
|
||||
"value": "go-module"
|
||||
},
|
||||
{
|
||||
"name": "syft:cpe23",
|
||||
"value": "cpe:2.3:a:wagoodman:go_partybus:v0.0.0-20230516145632-8ccac152c651:*:*:*:*:*:*:*"
|
||||
},
|
||||
{
|
||||
"name": "syft:location:0:path",
|
||||
"value": "/go.mod"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"bomFormat": "CycloneDX",
|
||||
"specVersion": "1.6",
|
||||
"version": 1,
|
||||
"components": [
|
||||
{
|
||||
"type": "library",
|
||||
"group": "io.netty",
|
||||
"name": "netty-codec-http2",
|
||||
"version": "4.1.73.Final",
|
||||
"properties": [
|
||||
{
|
||||
"name": "syft:cpe23",
|
||||
"value": "cpe:2.3:a:codec:codec:4.1.73.Final:*:*:*:*:*:*:*"
|
||||
},
|
||||
{
|
||||
"name": "syft:cpe23",
|
||||
"value": "cpe:2.3:a:codec:netty-codec-http2:4.1.73.Final:*:*:*:*:*:*:*"
|
||||
},
|
||||
{
|
||||
"name": "syft:cpe23",
|
||||
"value": "cpe:2.3:a:codec:netty_codec_http2:4.1.73.Final:*:*:*:*:*:*:*"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"serialNumber": "urn:uuid:3eb5ec7a-cb05-4339-b873-e27b1c1efaba"
|
||||
}
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
|
||||
"$schema": "http://cyclonedx.org/schema/bom-1.6.schema.json",
|
||||
"bomFormat": "CycloneDX",
|
||||
"specVersion": "1.5",
|
||||
"specVersion": "1.6",
|
||||
"serialNumber": "urn:uuid:redacted",
|
||||
"version": 1,
|
||||
"metadata": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
|
||||
"$schema": "http://cyclonedx.org/schema/bom-1.6.schema.json",
|
||||
"bomFormat": "CycloneDX",
|
||||
"specVersion": "1.5",
|
||||
"specVersion": "1.6",
|
||||
"serialNumber": "urn:uuid:redacted",
|
||||
"version": 1,
|
||||
"metadata": {
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<bom xmlns="http://cyclonedx.org/schema/bom/1.6" serialNumber="urn:uuid:098e8516-ecd5-4130-9d5f-c32ba1ddb0dd" version="1">
|
||||
<metadata>
|
||||
<timestamp>2023-09-29T11:48:10-04:00</timestamp>
|
||||
<tools>
|
||||
<tool>
|
||||
<vendor>anchore</vendor>
|
||||
<name>syft</name>
|
||||
<version>[not provided]</version>
|
||||
</tool>
|
||||
</tools>
|
||||
<component bom-ref="a0ff99a6af10f11f" type="file">
|
||||
<name>go.mod</name>
|
||||
<version>sha256:sha256:dc333f342905248a52e424d8dfd061251d01867d01a4f9d7397144a775ff9ebd</version>
|
||||
</component>
|
||||
</metadata>
|
||||
<components>
|
||||
<component bom-ref="pkg:golang/github.com/wagoodman/go-partybus@v0.0.0-20230516145632-8ccac152c651?package-id=2ff71a67fb024c86" type="library">
|
||||
<name>github.com/wagoodman/go-partybus</name>
|
||||
<version>v0.0.0-20230516145632-8ccac152c651</version>
|
||||
<cpe>cpe:2.3:a:wagoodman:go-partybus:v0.0.0-20230516145632-8ccac152c651:*:*:*:*:*:*:*</cpe>
|
||||
<purl>pkg:golang/github.com/wagoodman/go-partybus@v0.0.0-20230516145632-8ccac152c651</purl>
|
||||
<properties>
|
||||
<property name="syft:package:foundBy">go-module-file-cataloger</property>
|
||||
<property name="syft:package:language">go</property>
|
||||
<property name="syft:package:metadataType">GolangModMetadata</property>
|
||||
<property name="syft:package:type">go-module</property>
|
||||
<property name="syft:cpe23">cpe:2.3:a:wagoodman:go_partybus:v0.0.0-20230516145632-8ccac152c651:*:*:*:*:*:*:*</property>
|
||||
<property name="syft:location:0:path">/go.mod</property>
|
||||
</properties>
|
||||
</component>
|
||||
</components>
|
||||
</bom>
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<bom xmlns="http://cyclonedx.org/schema/bom/1.5" serialNumber="redacted" version="1">
|
||||
<bom xmlns="http://cyclonedx.org/schema/bom/1.6" serialNumber="redacted" version="1">
|
||||
<metadata>
|
||||
<timestamp>redacted</timestamp>
|
||||
<tools>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<bom xmlns="http://cyclonedx.org/schema/bom/1.5" serialNumber="redacted" version="1">
|
||||
<bom xmlns="http://cyclonedx.org/schema/bom/1.6" serialNumber="redacted" version="1">
|
||||
<metadata>
|
||||
<timestamp>redacted</timestamp>
|
||||
<tools>
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
)
|
||||
|
||||
const DefaultVersion = "1.5"
|
||||
const DefaultVersion = "1.6"
|
||||
|
||||
type Encoder struct {
|
||||
version cyclonedx.SpecVersion
|
||||
|
||||
@@ -19,6 +19,7 @@ func SupportedVersions(id sbom.FormatID) []string {
|
||||
"1.3",
|
||||
"1.4",
|
||||
"1.5",
|
||||
"1.6",
|
||||
}
|
||||
|
||||
if id != JSONFormatID {
|
||||
@@ -43,6 +44,8 @@ func SpecVersionFromString(v string) (cyclonedx.SpecVersion, error) {
|
||||
return cyclonedx.SpecVersion1_4, nil
|
||||
case "1.5":
|
||||
return cyclonedx.SpecVersion1_5, nil
|
||||
case "1.6":
|
||||
return cyclonedx.SpecVersion1_6, nil
|
||||
}
|
||||
return -1, fmt.Errorf("unsupported CycloneDX version %q", v)
|
||||
}
|
||||
@@ -61,6 +64,8 @@ func VersionFromSpecVersion(spec cyclonedx.SpecVersion) string {
|
||||
return "1.4"
|
||||
case cyclonedx.SpecVersion1_5:
|
||||
return "1.5"
|
||||
case cyclonedx.SpecVersion1_6:
|
||||
return "1.6"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -22,6 +22,10 @@ func DownloadLocation(p pkg.Package) string {
|
||||
return NoneIfEmpty(metadata.URL)
|
||||
case pkg.NpmPackageLockEntry:
|
||||
return NoneIfEmpty(metadata.Resolved)
|
||||
case pkg.PhpComposerLockEntry:
|
||||
return NoneIfEmpty(metadata.Dist.URL)
|
||||
case pkg.PhpComposerInstalledEntry:
|
||||
return NoneIfEmpty(metadata.Dist.URL)
|
||||
}
|
||||
}
|
||||
return NOASSERTION
|
||||
|
||||
@@ -64,6 +64,50 @@ func Test_DownloadLocation(t *testing.T) {
|
||||
},
|
||||
expected: NONE,
|
||||
},
|
||||
{
|
||||
name: "from php installed.json",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.PhpComposerInstalledEntry{
|
||||
Dist: pkg.PhpComposerExternalReference{
|
||||
URL: "http://package-lock.test",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: "http://package-lock.test",
|
||||
},
|
||||
{
|
||||
name: "empty",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.PhpComposerInstalledEntry{
|
||||
Dist: pkg.PhpComposerExternalReference{
|
||||
URL: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: "NONE",
|
||||
},
|
||||
{
|
||||
name: "from php composer.lock",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.PhpComposerLockEntry{
|
||||
Dist: pkg.PhpComposerExternalReference{
|
||||
URL: "http://package-lock.test",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: "http://package-lock.test",
|
||||
},
|
||||
{
|
||||
name: "empty",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.PhpComposerLockEntry{
|
||||
Dist: pkg.PhpComposerExternalReference{
|
||||
URL: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: "NONE",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
|
||||
@@ -56,6 +56,10 @@ func Originator(p pkg.Package) (typ string, author string) { // nolint: funlen
|
||||
if author == "" {
|
||||
author = metadata.Manifest.Main.MustGet("Implementation-Vendor")
|
||||
}
|
||||
// Vendor is specified, hence set 'Organization' as the PackageSupplier
|
||||
if author != "" {
|
||||
typ = orgType
|
||||
}
|
||||
}
|
||||
|
||||
case pkg.LinuxKernelModule:
|
||||
@@ -103,6 +107,8 @@ func Originator(p pkg.Package) (typ string, author string) { // nolint: funlen
|
||||
// it seems that the vast majority of the time the author is an org, not a person
|
||||
typ = orgType
|
||||
author = metadata.Author
|
||||
case pkg.SwiplPackEntry:
|
||||
author = formatPersonOrOrg(metadata.Author, metadata.AuthorEmail)
|
||||
}
|
||||
|
||||
if typ == "" && author != "" {
|
||||
@@ -144,6 +150,10 @@ func Supplier(p pkg.Package) (typ string, author string) {
|
||||
author = metadata.Packager
|
||||
}
|
||||
|
||||
if metadata, ok := p.Metadata.(pkg.SwiplPackEntry); ok {
|
||||
author = formatPersonOrOrg(metadata.Packager, metadata.PackagerEmail)
|
||||
}
|
||||
|
||||
if author == "" {
|
||||
// TODO: this uses the Originator function for now until a better distinction can be made for supplier
|
||||
return Originator(p)
|
||||
|
||||
@@ -40,6 +40,7 @@ func Test_OriginatorSupplier(t *testing.T) {
|
||||
pkg.RustBinaryAuditEntry{},
|
||||
pkg.RustCargoLockEntry{},
|
||||
pkg.SwiftPackageManagerResolvedEntry{},
|
||||
pkg.SwiplPackEntry{},
|
||||
pkg.YarnLockEntry{},
|
||||
)
|
||||
tests := []struct {
|
||||
@@ -137,8 +138,8 @@ func Test_OriginatorSupplier(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
originator: "Person: auth-spec",
|
||||
supplier: "Person: auth-spec",
|
||||
originator: "Organization: auth-spec",
|
||||
supplier: "Organization: auth-spec",
|
||||
},
|
||||
{
|
||||
name: "from java -- fallback to impl vendor in main manifest section",
|
||||
@@ -154,8 +155,8 @@ func Test_OriginatorSupplier(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
originator: "Person: auth-impl",
|
||||
supplier: "Person: auth-impl",
|
||||
originator: "Organization: auth-impl",
|
||||
supplier: "Organization: auth-impl",
|
||||
},
|
||||
{
|
||||
name: "from java -- non-main manifest sections ignored",
|
||||
@@ -336,6 +337,19 @@ func Test_OriginatorSupplier(t *testing.T) {
|
||||
originator: "Organization: auth",
|
||||
supplier: "Organization: auth",
|
||||
},
|
||||
{
|
||||
name: "from swipl pack",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.SwiplPackEntry{
|
||||
Author: "auth",
|
||||
AuthorEmail: "auth@auth.gov",
|
||||
Packager: "me",
|
||||
PackagerEmail: "me@auth.com",
|
||||
},
|
||||
},
|
||||
originator: "Person: auth (auth@auth.gov)",
|
||||
supplier: "Person: me (me@auth.com)",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
|
||||
@@ -62,6 +62,8 @@ func SourceInfo(p pkg.Package) string {
|
||||
answer = "acquired package info from Rockspec package file"
|
||||
case pkg.SwiftPkg:
|
||||
answer = "acquired package info from resolved Swift package manifest"
|
||||
case pkg.SwiplPackPkg:
|
||||
answer = "acquired package info from SWI Prolo pack package file"
|
||||
case pkg.GithubActionPkg, pkg.GithubActionWorkflowPkg:
|
||||
answer = "acquired package info from GitHub Actions workflow file or composite action file"
|
||||
case pkg.WordpressPluginPkg:
|
||||
|
||||
@@ -263,6 +263,14 @@ func Test_SourceInfo(t *testing.T) {
|
||||
"from resolved Swift package manifest",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: pkg.Package{
|
||||
Type: pkg.SwiplPackPkg,
|
||||
},
|
||||
expected: []string{
|
||||
"acquired package info from SWI Prolo pack package file",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: pkg.Package{
|
||||
Type: pkg.GithubActionPkg,
|
||||
|
||||
@@ -49,6 +49,7 @@ func AllTypes() []any {
|
||||
pkg.RustBinaryAuditEntry{},
|
||||
pkg.RustCargoLockEntry{},
|
||||
pkg.SwiftPackageManagerResolvedEntry{},
|
||||
pkg.SwiplPackEntry{},
|
||||
pkg.WordpressPluginEntry{},
|
||||
pkg.YarnLockEntry{},
|
||||
}
|
||||
|
||||
@@ -102,6 +102,7 @@ var jsonTypes = makeJSONTypes(
|
||||
jsonNames(pkg.RpmDBEntry{}, "rpm-db-entry", "RpmMetadata", "RpmdbMetadata"),
|
||||
jsonNamesWithoutLookup(pkg.RpmArchive{}, "rpm-archive", "RpmMetadata"), // the legacy value is split into two types, where the other is preferred
|
||||
jsonNames(pkg.SwiftPackageManagerResolvedEntry{}, "swift-package-manager-lock-entry", "SwiftPackageManagerMetadata"),
|
||||
jsonNames(pkg.SwiplPackEntry{}, "swiplpack-package"),
|
||||
jsonNames(pkg.RustCargoLockEntry{}, "rust-cargo-lock-entry", "RustCargoPackageMetadata"),
|
||||
jsonNamesWithoutLookup(pkg.RustBinaryAuditEntry{}, "rust-cargo-audit-entry", "RustCargoPackageMetadata"), // the legacy value is split into two types, where the other is preferred
|
||||
jsonNames(pkg.WordpressPluginEntry{}, "wordpress-plugin-entry", "WordpressMetadata"),
|
||||
|
||||
@@ -473,12 +473,6 @@ func Test_JSONName_JSONLegacyName(t *testing.T) {
|
||||
expectedJSONName: "rpm-archive",
|
||||
expectedLegacyName: "RpmMetadata", // note: conflicts with <=v11.x schema for "rpm-db-entry" metadata type
|
||||
},
|
||||
{
|
||||
name: "SwiftPackageManagerMetadata",
|
||||
metadata: pkg.SwiftPackageManagerResolvedEntry{},
|
||||
expectedJSONName: "swift-package-manager-lock-entry",
|
||||
expectedLegacyName: "SwiftPackageManagerMetadata",
|
||||
},
|
||||
{
|
||||
name: "CargoPackageMetadata",
|
||||
metadata: pkg.RustCargoLockEntry{},
|
||||
|
||||
@@ -48,6 +48,17 @@ func Test_Cataloger_PositiveCases(t *testing.T) {
|
||||
Metadata: metadata("arangodb-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "arangodb/3.12.0-2/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
Name: "arangodb",
|
||||
Version: "3.12.0-2",
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/arangodb@3.12.0-2",
|
||||
Locations: locations("arangosh"),
|
||||
Metadata: metadata("arangodb-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "postgres/15beta4/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
@@ -193,6 +204,17 @@ func Test_Cataloger_PositiveCases(t *testing.T) {
|
||||
Metadata: metadata("traefik-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "traefik/3.0.4/linux-riscv64",
|
||||
expected: pkg.Package{
|
||||
Name: "traefik",
|
||||
Version: "3.0.4",
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/traefik@3.0.4",
|
||||
Locations: locations("traefik"),
|
||||
Metadata: metadata("traefik-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "memcached/1.6.18/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
@@ -896,6 +918,28 @@ func Test_Cataloger_PositiveCases(t *testing.T) {
|
||||
Metadata: metadata("erlang-library"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "erlang/27.0/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
Name: "erlang",
|
||||
Version: "27.0",
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/erlang@27.0",
|
||||
Locations: locations("beam.smp"),
|
||||
Metadata: metadata("erlang-alpine-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "swipl/9.3.8/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
Name: "swipl",
|
||||
Version: "9.3.8",
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/swipl@9.3.8",
|
||||
Locations: locations("swipl"),
|
||||
Metadata: metadata("swipl-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "nginx/1.25.1/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
|
||||
@@ -263,7 +263,8 @@ func DefaultClassifiers() []Classifier {
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
// [NUL]v1.7.34[NUL]
|
||||
// [NUL]2.9.6[NUL]
|
||||
`(?m)(\x00|\x{FFFD})v?(?P<version>[0-9]+\.[0-9]+\.[0-9]+(-alpha[0-9]|-beta[0-9]|-rc[0-9])?)\x00`),
|
||||
// 3.0.4[NUL]
|
||||
`(?m)(\x00|\x{FFFD})?v?(?P<version>[0-9]+\.[0-9]+\.[0-9]+(-alpha[0-9]|-beta[0-9]|-rc[0-9])?)\x00`),
|
||||
Package: "traefik",
|
||||
PURL: mustPURL("pkg:generic/traefik@version"),
|
||||
CPEs: singleCPE("cpe:2.3:a:traefik:traefik:*:*:*:*:*:*:*:*"),
|
||||
@@ -272,7 +273,7 @@ func DefaultClassifiers() []Classifier {
|
||||
Class: "arangodb-binary",
|
||||
FileGlob: "**/arangosh",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
`(?m)ArangoDB\s\x00*(?P<version>[0-9]+\.[0-9]+\.[0-9]+)\s\[linux\]`),
|
||||
`(?m)\x00*(?P<version>[0-9]+\.[0-9]+\.[0-9]+(-[0-9]+)?)\s\[linux\]`),
|
||||
Package: "arangodb",
|
||||
PURL: mustPURL("pkg:generic/arangodb@version"),
|
||||
CPEs: singleCPE("cpe:2.3:a:arangodb:arangodb:*:*:*:*:*:*:*:*"),
|
||||
@@ -393,6 +394,23 @@ func DefaultClassifiers() []Classifier {
|
||||
PURL: mustPURL("pkg:generic/erlang@version"),
|
||||
CPEs: singleCPE("cpe:2.3:a:erlang:erlang\\/otp:*:*:*:*:*:*:*:*"),
|
||||
},
|
||||
{
|
||||
Class: "erlang-alpine-binary",
|
||||
FileGlob: "**/beam.smp",
|
||||
EvidenceMatcher: evidenceMatchers(
|
||||
FileContentsVersionMatcher(
|
||||
// <artificial>[NUL]/usr/src/otp_src_25.3.2.6/erts/
|
||||
`(?m)/src/otp_src_(?P<version>[0-9]+\.[0-9]+(\.[0-9]+){0,2}(-rc[0-9])?)/erts/`,
|
||||
),
|
||||
FileContentsVersionMatcher(
|
||||
// <artificial>[NUL]/usr/local/src/otp-25.3.2.7/erts/
|
||||
`(?m)/usr/local/src/otp-(?P<version>[0-9]+\.[0-9]+(\.[0-9]+){0,2}(-rc[0-9])?)/erts/`,
|
||||
),
|
||||
),
|
||||
Package: "erlang",
|
||||
PURL: mustPURL("pkg:generic/erlang@version"),
|
||||
CPEs: singleCPE("cpe:2.3:a:erlang:erlang\\/otp:*:*:*:*:*:*:*:*"),
|
||||
},
|
||||
{
|
||||
Class: "erlang-library",
|
||||
FileGlob: "**/liberts_internal.a",
|
||||
@@ -410,6 +428,16 @@ func DefaultClassifiers() []Classifier {
|
||||
PURL: mustPURL("pkg:generic/erlang@version"),
|
||||
CPEs: singleCPE("cpe:2.3:a:erlang:erlang\\/otp:*:*:*:*:*:*:*:*"),
|
||||
},
|
||||
{
|
||||
Class: "swipl-binary",
|
||||
FileGlob: "**/swipl",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
`(?m)swipl-(?P<version>[0-9]+\.[0-9]+\.[0-9]+)\/`,
|
||||
),
|
||||
Package: "swipl",
|
||||
PURL: mustPURL("pkg:generic/swipl@version"),
|
||||
CPEs: singleCPE("cpe:2.3:a:erlang:erlang\\/otp:*:*:*:*:*:*:*:*"),
|
||||
},
|
||||
{
|
||||
Class: "consul-binary",
|
||||
FileGlob: "**/consul",
|
||||
|
||||
@@ -13,7 +13,7 @@ func newELFPackage(metadata elfBinaryPackageNotes, locations file.LocationSet) p
|
||||
Version: metadata.Version,
|
||||
Licenses: pkg.NewLicenseSet(pkg.NewLicense(metadata.License)),
|
||||
PURL: packageURL(metadata),
|
||||
Type: pkg.BinaryPkg,
|
||||
Type: pkgType(metadata.Type),
|
||||
Locations: locations,
|
||||
Metadata: metadata.ELFBinaryPackageNoteJSONPayload,
|
||||
}
|
||||
@@ -67,6 +67,8 @@ func packageURL(metadata elfBinaryPackageNotes) string {
|
||||
).ToString()
|
||||
}
|
||||
|
||||
const alpmType = "alpm"
|
||||
|
||||
func purlDistroType(ty string) string {
|
||||
switch ty {
|
||||
case "rpm":
|
||||
@@ -75,8 +77,22 @@ func purlDistroType(ty string) string {
|
||||
return packageurl.TypeDebian
|
||||
case "apk":
|
||||
return packageurl.TypeAlpine
|
||||
case "alpm":
|
||||
return "alpm"
|
||||
case alpmType:
|
||||
return alpmType
|
||||
}
|
||||
return packageurl.TypeGeneric
|
||||
}
|
||||
|
||||
func pkgType(ty string) pkg.Type {
|
||||
switch ty {
|
||||
case "rpm":
|
||||
return pkg.RpmPkg
|
||||
case "deb":
|
||||
return pkg.DebPkg
|
||||
case "apk":
|
||||
return pkg.ApkPkg
|
||||
case alpmType:
|
||||
return pkg.AlpmPkg
|
||||
}
|
||||
return pkg.BinaryPkg
|
||||
}
|
||||
|
||||
@@ -70,14 +70,14 @@ func Test_ELF_Package_Cataloger(t *testing.T) {
|
||||
expected: []pkg.Package{
|
||||
{
|
||||
Name: "coreutils",
|
||||
Version: "9.5-1.fc41",
|
||||
PURL: "pkg:rpm/fedora/coreutils@9.5-1.fc41?distro=fedora-40",
|
||||
Version: "9.5-3.fc41",
|
||||
PURL: "pkg:rpm/fedora/coreutils@9.5-3.fc41?distro=fedora-40",
|
||||
Locations: file.NewLocationSet(
|
||||
file.NewLocation("/sha256sum").WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
file.NewLocation("/sha1sum").WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
),
|
||||
Licenses: pkg.NewLicenseSet(),
|
||||
Type: pkg.BinaryPkg,
|
||||
Type: pkg.RpmPkg,
|
||||
Metadata: pkg.ELFBinaryPackageNoteJSONPayload{
|
||||
Type: "rpm",
|
||||
Architecture: "x86_64",
|
||||
@@ -99,7 +99,7 @@ func Test_ELF_Package_Cataloger(t *testing.T) {
|
||||
file.NewLocation("/sha1sum").WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
),
|
||||
Licenses: pkg.NewLicenseSet(),
|
||||
Type: pkg.BinaryPkg,
|
||||
Type: pkg.RpmPkg,
|
||||
Metadata: pkg.ELFBinaryPackageNoteJSONPayload{
|
||||
Type: "rpm",
|
||||
Architecture: "arm",
|
||||
|
||||
BIN
Binary file not shown.
+9
@@ -0,0 +1,9 @@
|
||||
name: beam.smp
|
||||
offset: 6378906
|
||||
length: 140
|
||||
snippetSha256: 186c882decc4160bfcfbdddaee08e8364675c795d3e85697422fbd7218452ee2
|
||||
fileSha256: f84f0ffc2e397abcf5f081b865e241017b1793c2ec38f5136bff4e5e21ffdad7
|
||||
|
||||
### byte snippet to follow ###
|
||||
ied by configure */
|
||||
#define ERTS_EMU_CMDLINE_FLAGS " -fno-strict-aliasing -fno-common -g -O2 -I/usr/src/otp_src_27.0/erts/x86_64-pc-linux-mu
|
||||
BIN
Binary file not shown.
@@ -63,6 +63,21 @@ from-images:
|
||||
paths:
|
||||
- /usr/local/lib/erlang/erts-14.2.4/lib/internal/liberts_internal.a
|
||||
|
||||
- name: erlang
|
||||
version: 27.0
|
||||
images:
|
||||
- ref: erlang:27.0.0.0-alpine@sha256:82064a65eb1f9901a479d8ada5f622be3bfac8b474dfc876dc00c28fdef4b1b8
|
||||
platform: linux/amd64
|
||||
paths:
|
||||
- /usr/local/lib/erlang/erts-15.0/bin/beam.smp
|
||||
|
||||
- version: 9.3.8
|
||||
images:
|
||||
- ref: swipl:9.3.8@sha256:6a15e6a03afe943228924a5502ba763e653ff28d9b3391e2b3e1fc3e991f37d4
|
||||
platform: linux/amd64
|
||||
paths:
|
||||
- /usr/lib/swipl/bin/x86_64-linux/swipl
|
||||
|
||||
- version: 1.21.3
|
||||
images:
|
||||
- ref: golang:1.21.3@sha256:3ce8313c3513515040870c55e0c041a2b94f3576a58cfd3948633604214aa811
|
||||
@@ -285,6 +300,13 @@ from-images:
|
||||
platform: linux/amd64
|
||||
paths:
|
||||
- /usr/bin/arangosh
|
||||
- name: arangodb
|
||||
version: 3.12.0-2
|
||||
images:
|
||||
- ref: arangodb:3.12.0.2@sha256:6ba2e4284cd0f4835e3ef034be90fb947c3d61225d65d07e3b2d2d893e47de25
|
||||
platform: linux/amd64
|
||||
paths:
|
||||
- /usr/bin/arangosh
|
||||
- version: 15.1
|
||||
images:
|
||||
- ref: postgres:15.1@sha256:b4140dd3a62f364f16a82c1bd88d28b9887ecb47f07dbe2941237d073574d428
|
||||
@@ -433,6 +455,12 @@ from-images:
|
||||
paths:
|
||||
- /usr/local/bin/traefik
|
||||
|
||||
- version: 3.0.4
|
||||
images:
|
||||
- ref: traefik:3.0.4@sha256:12a7cc4232b5b7fe027673da8c096144525f59a8eabc87e52260aac0ec5a1219
|
||||
platform: linux/riscv64
|
||||
paths:
|
||||
- /usr/local/bin/traefik
|
||||
|
||||
# from the original dynamic fixtures...
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
FROM --platform=linux/amd64 fedora:41 as build
|
||||
FROM --platform=linux/amd64 fedora:41@sha256:c05bf79137835bf5c521c58f8252d6031780ae865a0379ab57f412e0ac6b42aa as build
|
||||
|
||||
|
||||
FROM scratch
|
||||
COPY --from=build /bin/sha256sum /sha256sum
|
||||
|
||||
@@ -20,12 +20,8 @@ const (
|
||||
|
||||
// NewGoModuleFileCataloger returns a new cataloger object that searches within go.mod files.
|
||||
func NewGoModuleFileCataloger(opts CatalogerConfig) pkg.Cataloger {
|
||||
c := goModCataloger{
|
||||
licenses: newGoLicenses(modFileCatalogerName, opts),
|
||||
}
|
||||
|
||||
return generic.NewCataloger(modFileCatalogerName).
|
||||
WithParserByGlobs(c.parseGoModFile, "**/go.mod")
|
||||
WithParserByGlobs(newGoModCataloger(opts).parseGoModFile, "**/go.mod")
|
||||
}
|
||||
|
||||
// NewGoModuleBinaryCataloger returns a new cataloger object that searches within binaries built by the go compiler.
|
||||
|
||||
@@ -20,7 +20,7 @@ func Test_PackageCataloger_Binary(t *testing.T) {
|
||||
name: "simple module with dependencies",
|
||||
fixture: "image-small",
|
||||
expectedPkgs: []string{
|
||||
"anchore.io/not/real @ (devel) (/run-me)",
|
||||
"anchore.io/not/real @ v1.0.0 (/run-me)",
|
||||
"github.com/andybalholm/brotli @ v1.0.1 (/run-me)",
|
||||
"github.com/dsnet/compress @ v0.0.2-0.20210315054119-f66993602bf5 (/run-me)",
|
||||
"github.com/golang/snappy @ v0.0.2 (/run-me)",
|
||||
@@ -34,17 +34,17 @@ func Test_PackageCataloger_Binary(t *testing.T) {
|
||||
"stdlib @ go1.22.4 (/run-me)",
|
||||
},
|
||||
expectedRels: []string{
|
||||
"github.com/andybalholm/brotli @ v1.0.1 (/run-me) [dependency-of] anchore.io/not/real @ (devel) (/run-me)",
|
||||
"github.com/dsnet/compress @ v0.0.2-0.20210315054119-f66993602bf5 (/run-me) [dependency-of] anchore.io/not/real @ (devel) (/run-me)",
|
||||
"github.com/golang/snappy @ v0.0.2 (/run-me) [dependency-of] anchore.io/not/real @ (devel) (/run-me)",
|
||||
"github.com/klauspost/compress @ v1.11.4 (/run-me) [dependency-of] anchore.io/not/real @ (devel) (/run-me)",
|
||||
"github.com/klauspost/pgzip @ v1.2.5 (/run-me) [dependency-of] anchore.io/not/real @ (devel) (/run-me)",
|
||||
"github.com/mholt/archiver/v3 @ v3.5.1 (/run-me) [dependency-of] anchore.io/not/real @ (devel) (/run-me)",
|
||||
"github.com/nwaples/rardecode @ v1.1.0 (/run-me) [dependency-of] anchore.io/not/real @ (devel) (/run-me)",
|
||||
"github.com/pierrec/lz4/v4 @ v4.1.2 (/run-me) [dependency-of] anchore.io/not/real @ (devel) (/run-me)",
|
||||
"github.com/ulikunitz/xz @ v0.5.9 (/run-me) [dependency-of] anchore.io/not/real @ (devel) (/run-me)",
|
||||
"github.com/xi2/xz @ v0.0.0-20171230120015-48954b6210f8 (/run-me) [dependency-of] anchore.io/not/real @ (devel) (/run-me)",
|
||||
"stdlib @ go1.22.4 (/run-me) [dependency-of] anchore.io/not/real @ (devel) (/run-me)",
|
||||
"github.com/andybalholm/brotli @ v1.0.1 (/run-me) [dependency-of] anchore.io/not/real @ v1.0.0 (/run-me)",
|
||||
"github.com/dsnet/compress @ v0.0.2-0.20210315054119-f66993602bf5 (/run-me) [dependency-of] anchore.io/not/real @ v1.0.0 (/run-me)",
|
||||
"github.com/golang/snappy @ v0.0.2 (/run-me) [dependency-of] anchore.io/not/real @ v1.0.0 (/run-me)",
|
||||
"github.com/klauspost/compress @ v1.11.4 (/run-me) [dependency-of] anchore.io/not/real @ v1.0.0 (/run-me)",
|
||||
"github.com/klauspost/pgzip @ v1.2.5 (/run-me) [dependency-of] anchore.io/not/real @ v1.0.0 (/run-me)",
|
||||
"github.com/mholt/archiver/v3 @ v3.5.1 (/run-me) [dependency-of] anchore.io/not/real @ v1.0.0 (/run-me)",
|
||||
"github.com/nwaples/rardecode @ v1.1.0 (/run-me) [dependency-of] anchore.io/not/real @ v1.0.0 (/run-me)",
|
||||
"github.com/pierrec/lz4/v4 @ v4.1.2 (/run-me) [dependency-of] anchore.io/not/real @ v1.0.0 (/run-me)",
|
||||
"github.com/ulikunitz/xz @ v0.5.9 (/run-me) [dependency-of] anchore.io/not/real @ v1.0.0 (/run-me)",
|
||||
"github.com/xi2/xz @ v0.0.0-20171230120015-48954b6210f8 (/run-me) [dependency-of] anchore.io/not/real @ v1.0.0 (/run-me)",
|
||||
"stdlib @ go1.22.4 (/run-me) [dependency-of] anchore.io/not/real @ v1.0.0 (/run-me)",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -41,6 +41,7 @@ type MainModuleVersionConfig struct {
|
||||
func DefaultCatalogerConfig() CatalogerConfig {
|
||||
g := CatalogerConfig{
|
||||
MainModuleVersion: DefaultMainModuleVersionConfig(),
|
||||
LocalModCacheDir: defaultGoModDir(),
|
||||
}
|
||||
|
||||
// first process the proxy settings
|
||||
@@ -67,24 +68,25 @@ func DefaultCatalogerConfig() CatalogerConfig {
|
||||
}
|
||||
}
|
||||
|
||||
if g.LocalModCacheDir == "" {
|
||||
goPath := os.Getenv("GOPATH")
|
||||
|
||||
if goPath == "" {
|
||||
homeDir, err := homedir.Dir()
|
||||
if err != nil {
|
||||
log.Debug("unable to determine user home dir: %v", err)
|
||||
} else {
|
||||
goPath = filepath.Join(homeDir, "go")
|
||||
}
|
||||
}
|
||||
if goPath != "" {
|
||||
g.LocalModCacheDir = filepath.Join(goPath, "pkg", "mod")
|
||||
}
|
||||
}
|
||||
return g
|
||||
}
|
||||
|
||||
// defaultGoModDir returns $GOPATH/pkg/mod or $HOME/go/pkg/mod based on environment variables available
|
||||
func defaultGoModDir() string {
|
||||
goPath := os.Getenv("GOPATH")
|
||||
|
||||
if goPath == "" {
|
||||
homeDir, err := homedir.Dir()
|
||||
if err != nil {
|
||||
log.Warnf("unable to determine GOPATH or user home dir: %w", err)
|
||||
return ""
|
||||
}
|
||||
goPath = filepath.Join(homeDir, "go")
|
||||
}
|
||||
|
||||
return filepath.Join(goPath, "pkg", "mod")
|
||||
}
|
||||
|
||||
func DefaultMainModuleVersionConfig() MainModuleVersionConfig {
|
||||
return MainModuleVersionConfig{
|
||||
FromLDFlags: true,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package golang
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/mitchellh/go-homedir"
|
||||
@@ -47,7 +48,7 @@ func Test_Config(t *testing.T) {
|
||||
opts: opts{},
|
||||
expected: CatalogerConfig{
|
||||
SearchLocalModCacheLicenses: false,
|
||||
LocalModCacheDir: "/go/pkg/mod",
|
||||
LocalModCacheDir: filepath.Join("/go", "pkg", "mod"),
|
||||
SearchRemoteLicenses: false,
|
||||
Proxies: []string{"https://my.proxy"},
|
||||
NoProxy: []string{"my.private", "no.proxy"},
|
||||
|
||||
@@ -21,25 +21,41 @@ import (
|
||||
"github.com/scylladb/go-set/strset"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/internal/cache"
|
||||
"github.com/anchore/syft/internal/licenses"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/internal/fileresolver"
|
||||
"github.com/anchore/syft/syft/license"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
type goLicenses struct {
|
||||
type goLicense struct {
|
||||
Value string `json:"val,omitempty"`
|
||||
SPDXExpression string `json:"spdx,omitempty"`
|
||||
Type license.Type `json:"type,omitempty"`
|
||||
URLs []string `json:"urls,omitempty"`
|
||||
Locations []string `json:"locations,omitempty"`
|
||||
}
|
||||
|
||||
type goLicenseResolver struct {
|
||||
catalogerName string
|
||||
opts CatalogerConfig
|
||||
localModCacheResolver file.WritableResolver
|
||||
localModCacheDir fs.FS
|
||||
licenseCache cache.Resolver[[]goLicense]
|
||||
lowerLicenseFileNames *strset.Set
|
||||
}
|
||||
|
||||
func newGoLicenses(catalogerName string, opts CatalogerConfig) goLicenses {
|
||||
return goLicenses{
|
||||
func newGoLicenseResolver(catalogerName string, opts CatalogerConfig) goLicenseResolver {
|
||||
var localModCacheDir fs.FS
|
||||
if opts.SearchLocalModCacheLicenses {
|
||||
localModCacheDir = os.DirFS(opts.LocalModCacheDir)
|
||||
}
|
||||
|
||||
return goLicenseResolver{
|
||||
catalogerName: catalogerName,
|
||||
opts: opts,
|
||||
localModCacheResolver: modCacheResolver(opts.LocalModCacheDir),
|
||||
localModCacheDir: localModCacheDir,
|
||||
licenseCache: cache.GetResolverCachingErrors[[]goLicense]("golang", "v1"),
|
||||
lowerLicenseFileNames: strset.New(lowercaseLicenseFiles()...),
|
||||
}
|
||||
}
|
||||
@@ -63,98 +79,103 @@ func remotesForModule(proxies []string, noProxy []string, module string) []strin
|
||||
return proxies
|
||||
}
|
||||
|
||||
func modCacheResolver(modCacheDir string) file.WritableResolver {
|
||||
var r file.WritableResolver
|
||||
func (c *goLicenseResolver) getLicenses(resolver file.Resolver, moduleName, moduleVersion string) ([]pkg.License, error) {
|
||||
// search the scan target first, ignoring local and remote sources
|
||||
goLicenses, err := c.findLicensesInSource(resolver,
|
||||
fmt.Sprintf(`**/go/pkg/mod/%s@%s/*`, processCaps(moduleName), moduleVersion),
|
||||
)
|
||||
if err != nil || len(goLicenses) > 0 {
|
||||
return toPkgLicenses(goLicenses), err
|
||||
}
|
||||
|
||||
if modCacheDir == "" {
|
||||
log.Trace("unable to determine mod cache directory, skipping mod cache resolver")
|
||||
r = fileresolver.Empty{}
|
||||
} else {
|
||||
stat, err := os.Stat(modCacheDir)
|
||||
|
||||
if os.IsNotExist(err) || stat == nil || !stat.IsDir() {
|
||||
log.Tracef("unable to open mod cache directory: %s, skipping mod cache resolver", modCacheDir)
|
||||
r = fileresolver.Empty{}
|
||||
} else {
|
||||
r = fileresolver.NewFromUnindexedDirectory(modCacheDir)
|
||||
// look in the local host mod directory...
|
||||
if c.opts.SearchLocalModCacheLicenses {
|
||||
goLicenses, err = c.getLicensesFromLocal(moduleName, moduleVersion)
|
||||
if err != nil || len(goLicenses) > 0 {
|
||||
return toPkgLicenses(goLicenses), err
|
||||
}
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (c *goLicenses) getLicenses(resolver file.Resolver, moduleName, moduleVersion string) (licenses []pkg.License, err error) {
|
||||
licenses, err = c.findLicenses(resolver,
|
||||
fmt.Sprintf(`**/go/pkg/mod/%s@%s/*`, processCaps(moduleName), moduleVersion),
|
||||
)
|
||||
if err != nil || len(licenses) > 0 {
|
||||
return requireCollection(licenses), err
|
||||
// download from remote sources
|
||||
if c.opts.SearchRemoteLicenses {
|
||||
goLicenses, err = c.getLicensesFromRemote(moduleName, moduleVersion)
|
||||
}
|
||||
|
||||
// look in the local host mod cache...
|
||||
licenses, err = c.getLicensesFromLocal(moduleName, moduleVersion)
|
||||
if err != nil || len(licenses) > 0 {
|
||||
return requireCollection(licenses), err
|
||||
}
|
||||
|
||||
// we did not find it yet and remote searching was enabled
|
||||
licenses, err = c.getLicensesFromRemote(moduleName, moduleVersion)
|
||||
return requireCollection(licenses), err
|
||||
return toPkgLicenses(goLicenses), err
|
||||
}
|
||||
|
||||
func (c *goLicenses) getLicensesFromLocal(moduleName, moduleVersion string) ([]pkg.License, error) {
|
||||
if !c.opts.SearchLocalModCacheLicenses {
|
||||
func (c *goLicenseResolver) getLicensesFromLocal(moduleName, moduleVersion string) ([]goLicense, error) {
|
||||
if c.localModCacheDir == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// if we're running against a directory on the filesystem, it may not include the
|
||||
// user's homedir / GOPATH, so we defer to using the localModCacheResolver
|
||||
return c.findLicenses(c.localModCacheResolver, moduleSearchGlob(moduleName, moduleVersion))
|
||||
}
|
||||
subdir := moduleDir(moduleName, moduleVersion)
|
||||
|
||||
func (c *goLicenses) getLicensesFromRemote(moduleName, moduleVersion string) ([]pkg.License, error) {
|
||||
if !c.opts.SearchRemoteLicenses {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
proxies := remotesForModule(c.opts.Proxies, c.opts.NoProxy, moduleName)
|
||||
|
||||
fsys, err := getModule(proxies, moduleName, moduleVersion)
|
||||
// get the local subdirectory containing the specific go module
|
||||
dir, err := fs.Sub(c.localModCacheDir, subdir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dir := moduleDir(moduleName, moduleVersion)
|
||||
|
||||
// populate the mod cache with the results
|
||||
err = fs.WalkDir(fsys, ".", func(filePath string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
log.Debug(err)
|
||||
return nil
|
||||
}
|
||||
if d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
f, err := fsys.Open(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.localModCacheResolver.Write(file.NewLocation(path.Join(dir, filePath)), f)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Tracef("remote proxy walk failed for: %s", moduleName)
|
||||
}
|
||||
|
||||
return c.findLicenses(c.localModCacheResolver, moduleSearchGlob(moduleName, moduleVersion))
|
||||
// if we're running against a directory on the filesystem, it may not include the
|
||||
// user's homedir / GOPATH, so we defer to using the localModCacheResolver
|
||||
// we use $GOPATH/pkg/mod to avoid leaking information about the user's system
|
||||
return c.findLicensesInFS("file://$GOPATH/pkg/mod/"+subdir+"/", dir)
|
||||
}
|
||||
|
||||
func (c *goLicenses) findLicenses(resolver file.Resolver, globMatch string) (out []pkg.License, err error) {
|
||||
out = make([]pkg.License, 0)
|
||||
if resolver == nil {
|
||||
return
|
||||
}
|
||||
func (c *goLicenseResolver) getLicensesFromRemote(moduleName, moduleVersion string) ([]goLicense, error) {
|
||||
return c.licenseCache.Resolve(fmt.Sprintf("%s/%s", moduleName, moduleVersion), func() ([]goLicense, error) {
|
||||
proxies := remotesForModule(c.opts.Proxies, c.opts.NoProxy, moduleName)
|
||||
|
||||
urlPrefix, fsys, err := getModule(proxies, moduleName, moduleVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c.findLicensesInFS(urlPrefix, fsys)
|
||||
})
|
||||
}
|
||||
|
||||
func (c *goLicenseResolver) findLicensesInFS(urlPrefix string, fsys fs.FS) ([]goLicense, error) {
|
||||
var out []goLicense
|
||||
err := fs.WalkDir(fsys, ".", func(filePath string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
log.Debugf("error reading %s#%s: %v", urlPrefix, filePath, err)
|
||||
return err
|
||||
}
|
||||
if d == nil {
|
||||
log.Debugf("nil entry for %s#%s", urlPrefix, filePath)
|
||||
return nil
|
||||
}
|
||||
if !c.lowerLicenseFileNames.Has(strings.ToLower(d.Name())) {
|
||||
return nil
|
||||
}
|
||||
rdr, err := fsys.Open(filePath)
|
||||
if err != nil {
|
||||
log.Debugf("error opening license file %s: %v", filePath, err)
|
||||
return nil
|
||||
}
|
||||
defer internal.CloseAndLogError(rdr, filePath)
|
||||
parsed, err := licenses.Parse(rdr, file.NewLocation(filePath))
|
||||
if err != nil {
|
||||
log.Debugf("error parsing license file %s: %v", filePath, err)
|
||||
return nil
|
||||
}
|
||||
// since these licenses are found in an external fs.FS, not in the scanned source,
|
||||
// get rid of the locations but keep information about the where the license was found
|
||||
// by prepending the urlPrefix to the internal path for an accurate representation
|
||||
for _, l := range toGoLicenses(parsed) {
|
||||
l.URLs = []string{urlPrefix + filePath}
|
||||
l.Locations = nil
|
||||
out = append(out, l)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return out, err
|
||||
}
|
||||
|
||||
func (c *goLicenseResolver) findLicensesInSource(resolver file.Resolver, globMatch string) ([]goLicense, error) {
|
||||
var out []goLicense
|
||||
locations, err := resolver.FilesByGlob(globMatch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -168,11 +189,19 @@ func (c *goLicenses) findLicenses(resolver file.Resolver, globMatch string) (out
|
||||
out = append(out, parsed...)
|
||||
}
|
||||
|
||||
return
|
||||
// if we have a directory but simply don't have any found license files, indicate this so we
|
||||
// don't re-download modules continually
|
||||
if len(locations) > 0 && len(out) == 0 {
|
||||
return nil, noLicensesFound{
|
||||
glob: globMatch,
|
||||
}
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *goLicenses) parseLicenseFromLocation(l file.Location, resolver file.Resolver) ([]pkg.License, error) {
|
||||
var out []pkg.License
|
||||
func (c *goLicenseResolver) parseLicenseFromLocation(l file.Location, resolver file.Resolver) ([]goLicense, error) {
|
||||
var out []goLicense
|
||||
fileName := path.Base(l.RealPath)
|
||||
if c.lowerLicenseFileNames.Has(strings.ToLower(fileName)) {
|
||||
contents, err := resolver.FileContentsByLocation(l)
|
||||
@@ -185,7 +214,7 @@ func (c *goLicenses) parseLicenseFromLocation(l file.Location, resolver file.Res
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out = append(out, parsed...)
|
||||
out = append(out, toGoLicenses(parsed)...)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
@@ -194,13 +223,9 @@ func moduleDir(moduleName, moduleVersion string) string {
|
||||
return fmt.Sprintf("%s@%s", processCaps(moduleName), moduleVersion)
|
||||
}
|
||||
|
||||
func moduleSearchGlob(moduleName, moduleVersion string) string {
|
||||
return fmt.Sprintf("%s/*", moduleDir(moduleName, moduleVersion))
|
||||
}
|
||||
|
||||
func requireCollection(licenses []pkg.License) []pkg.License {
|
||||
func requireCollection[T any](licenses []T) []T {
|
||||
if licenses == nil {
|
||||
return make([]pkg.License, 0)
|
||||
return make([]T, 0)
|
||||
}
|
||||
return licenses
|
||||
}
|
||||
@@ -213,18 +238,19 @@ func processCaps(s string) string {
|
||||
})
|
||||
}
|
||||
|
||||
func getModule(proxies []string, moduleName, moduleVersion string) (fsys fs.FS, err error) {
|
||||
func getModule(proxies []string, moduleName, moduleVersion string) (urlPrefix string, fsys fs.FS, err error) {
|
||||
for _, proxy := range proxies {
|
||||
u, _ := url.Parse(proxy)
|
||||
if proxy == "direct" {
|
||||
fsys, err = getModuleRepository(moduleName, moduleVersion)
|
||||
urlPrefix, fsys, err = getModuleRepository(moduleName, moduleVersion)
|
||||
continue
|
||||
}
|
||||
switch u.Scheme {
|
||||
case "https", "http":
|
||||
fsys, err = getModuleProxy(proxy, moduleName, moduleVersion)
|
||||
urlPrefix, fsys, err = getModuleProxy(proxy, moduleName, moduleVersion)
|
||||
case "file":
|
||||
p := filepath.Join(u.Path, moduleName, "@v", moduleVersion)
|
||||
urlPrefix = path.Join("file://", p) + "/"
|
||||
fsys = os.DirFS(p)
|
||||
}
|
||||
if fsys != nil {
|
||||
@@ -234,13 +260,13 @@ func getModule(proxies []string, moduleName, moduleVersion string) (fsys fs.FS,
|
||||
return
|
||||
}
|
||||
|
||||
func getModuleProxy(proxy string, moduleName string, moduleVersion string) (out fs.FS, _ error) {
|
||||
func getModuleProxy(proxy string, moduleName string, moduleVersion string) (moduleURL string, out fs.FS, _ error) {
|
||||
u := fmt.Sprintf("%s/%s/@v/%s.zip", proxy, moduleName, moduleVersion)
|
||||
|
||||
// get the module zip
|
||||
resp, err := http.Get(u) //nolint:gosec
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return "", nil, err
|
||||
}
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
@@ -250,25 +276,25 @@ func getModuleProxy(proxy string, moduleName string, moduleVersion string) (out
|
||||
// try lowercasing it; some packages have mixed casing that really messes up the proxy
|
||||
resp, err = http.Get(u) //nolint:gosec
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return "", nil, err
|
||||
}
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("failed to get module zip: %s", resp.Status)
|
||||
return "", nil, fmt.Errorf("failed to get module zip: %s", resp.Status)
|
||||
}
|
||||
}
|
||||
|
||||
// read the zip
|
||||
b, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
out, err = zip.NewReader(bytes.NewReader(b), resp.ContentLength)
|
||||
versionPath := findVersionPath(out, ".")
|
||||
out = getSubFS(out, versionPath)
|
||||
|
||||
return out, err
|
||||
return u + "#" + versionPath + "/", out, err
|
||||
}
|
||||
|
||||
func findVersionPath(f fs.FS, dir string) string {
|
||||
@@ -288,26 +314,111 @@ func findVersionPath(f fs.FS, dir string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func getModuleRepository(moduleName string, moduleVersion string) (fs.FS, error) {
|
||||
func getModuleRepository(moduleName string, moduleVersion string) (string, fs.FS, error) {
|
||||
repoName := moduleName
|
||||
parts := strings.Split(moduleName, "/")
|
||||
if len(parts) > 2 {
|
||||
repoName = fmt.Sprintf("%s/%s/%s", parts[0], parts[1], parts[2])
|
||||
}
|
||||
|
||||
// see if there's a hash and use that if so, otherwise use a tag
|
||||
splitVersion := strings.Split(moduleVersion, "-")
|
||||
var cloneRefName plumbing.ReferenceName
|
||||
refPath := ""
|
||||
if len(splitVersion) < 3 {
|
||||
tagName := splitVersion[0]
|
||||
cloneRefName = plumbing.NewTagReferenceName(tagName)
|
||||
refPath = "/tags/" + tagName
|
||||
}
|
||||
|
||||
f := memfs.New()
|
||||
buf := &bytes.Buffer{}
|
||||
_, err := git.Clone(memory.NewStorage(), f, &git.CloneOptions{
|
||||
URL: fmt.Sprintf("https://%s", repoName),
|
||||
ReferenceName: plumbing.NewTagReferenceName(moduleVersion), // FIXME version might be a SHA
|
||||
repoURL := fmt.Sprintf("https://%s", repoName)
|
||||
r, err := git.Clone(memory.NewStorage(), f, &git.CloneOptions{
|
||||
URL: repoURL,
|
||||
ReferenceName: cloneRefName,
|
||||
SingleBranch: true,
|
||||
Depth: 1,
|
||||
Progress: buf,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w -- %s", err, buf.String())
|
||||
return "", nil, fmt.Errorf("%w -- %s", err, buf.String())
|
||||
}
|
||||
|
||||
return billyFSAdapter{fs: f}, nil
|
||||
if len(splitVersion) > 2 {
|
||||
sha := splitVersion[len(splitVersion)-1]
|
||||
hash, err := r.ResolveRevision(plumbing.Revision(sha))
|
||||
if err != nil || hash == nil {
|
||||
log.Tracef("unable to resolve hash %s: %v", sha, err)
|
||||
} else {
|
||||
w, err := r.Worktree()
|
||||
if err != nil {
|
||||
log.Tracef("unable to get worktree, using default: %v", err)
|
||||
}
|
||||
err = w.Checkout(&git.CheckoutOptions{
|
||||
Hash: *hash,
|
||||
})
|
||||
if err != nil {
|
||||
log.Tracef("unable to checkout commit, using default: %v", err)
|
||||
} else {
|
||||
refPath = "/refs/" + hash.String()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return repoURL + refPath + "/", billyFSAdapter{fs: f}, err
|
||||
}
|
||||
|
||||
type noLicensesFound struct {
|
||||
glob string
|
||||
}
|
||||
|
||||
func (l noLicensesFound) Error() string {
|
||||
return fmt.Sprintf("unable to find license information matching: %s", l.glob)
|
||||
}
|
||||
|
||||
var _ error = (*noLicensesFound)(nil)
|
||||
|
||||
func toPkgLicenses(goLicenses []goLicense) []pkg.License {
|
||||
var out []pkg.License
|
||||
for _, l := range goLicenses {
|
||||
out = append(out, pkg.License{
|
||||
Value: l.Value,
|
||||
SPDXExpression: l.SPDXExpression,
|
||||
Type: l.Type,
|
||||
URLs: l.URLs,
|
||||
Locations: toPkgLocations(l.Locations),
|
||||
})
|
||||
}
|
||||
return requireCollection(out)
|
||||
}
|
||||
|
||||
func toPkgLocations(goLocations []string) file.LocationSet {
|
||||
out := file.NewLocationSet()
|
||||
for _, l := range goLocations {
|
||||
out.Add(file.NewLocation(l))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func toGoLicenses(pkgLicenses []pkg.License) []goLicense {
|
||||
var out []goLicense
|
||||
for _, l := range pkgLicenses {
|
||||
out = append(out, goLicense{
|
||||
Value: l.Value,
|
||||
SPDXExpression: l.SPDXExpression,
|
||||
Type: l.Type,
|
||||
URLs: l.URLs,
|
||||
Locations: toGoLocations(l.Locations),
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func toGoLocations(locations file.LocationSet) []string {
|
||||
var out []string
|
||||
for _, l := range locations.ToSlice() {
|
||||
out = append(out, l.RealPath)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -4,10 +4,12 @@ import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -36,7 +38,8 @@ func Test_LocalLicenseSearch(t *testing.T) {
|
||||
Value: "Apache-2.0",
|
||||
SPDXExpression: "Apache-2.0",
|
||||
Type: license.Concluded,
|
||||
Locations: file.NewLocationSet(loc1),
|
||||
URLs: []string{"file://$GOPATH/pkg/mod/" + loc1.RealPath},
|
||||
Locations: file.NewLocationSet(),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -46,7 +49,8 @@ func Test_LocalLicenseSearch(t *testing.T) {
|
||||
Value: "MIT",
|
||||
SPDXExpression: "MIT",
|
||||
Type: license.Concluded,
|
||||
Locations: file.NewLocationSet(loc2),
|
||||
URLs: []string{"file://$GOPATH/pkg/mod/" + loc2.RealPath},
|
||||
Locations: file.NewLocationSet(),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -56,7 +60,8 @@ func Test_LocalLicenseSearch(t *testing.T) {
|
||||
Value: "Apache-2.0",
|
||||
SPDXExpression: "Apache-2.0",
|
||||
Type: license.Concluded,
|
||||
Locations: file.NewLocationSet(loc3),
|
||||
URLs: []string{"file://$GOPATH/pkg/mod/" + loc3.RealPath},
|
||||
Locations: file.NewLocationSet(),
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -66,11 +71,11 @@ func Test_LocalLicenseSearch(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
l := newGoLicenses(
|
||||
l := newGoLicenseResolver(
|
||||
"",
|
||||
CatalogerConfig{
|
||||
SearchLocalModCacheLicenses: true,
|
||||
LocalModCacheDir: path.Join(wd, "test-fixtures", "licenses", "pkg", "mod"),
|
||||
LocalModCacheDir: filepath.Join(wd, "test-fixtures", "licenses", "pkg", "mod"),
|
||||
},
|
||||
)
|
||||
licenses, err := l.getLicenses(fileresolver.Empty{}, test.name, test.version)
|
||||
@@ -97,7 +102,7 @@ func Test_RemoteProxyLicenseSearch(t *testing.T) {
|
||||
|
||||
wd, err := os.Getwd()
|
||||
require.NoError(t, err)
|
||||
testDir := path.Join(wd, "test-fixtures", "licenses", "pkg", "mod", processCaps(modPath)+"@"+modVersion)
|
||||
testDir := filepath.Join(wd, "test-fixtures", "licenses", "pkg", "mod", processCaps(modPath)+"@"+modVersion)
|
||||
|
||||
archive := zip.NewWriter(buf)
|
||||
|
||||
@@ -106,9 +111,9 @@ func Test_RemoteProxyLicenseSearch(t *testing.T) {
|
||||
for _, f := range entries {
|
||||
// the zip files downloaded contain a path to the repo that somewhat matches where it ends up on disk,
|
||||
// so prefix entries with something similar
|
||||
writer, err := archive.Create(path.Join("github.com/something/some@version", f.Name()))
|
||||
writer, err := archive.Create(path.Join(moduleDir(modPath, modVersion), f.Name()))
|
||||
require.NoError(t, err)
|
||||
contents, err := os.ReadFile(path.Join(testDir, f.Name()))
|
||||
contents, err := os.ReadFile(filepath.Join(testDir, f.Name()))
|
||||
require.NoError(t, err)
|
||||
_, err = writer.Write(contents)
|
||||
require.NoError(t, err)
|
||||
@@ -136,7 +141,8 @@ func Test_RemoteProxyLicenseSearch(t *testing.T) {
|
||||
Value: "Apache-2.0",
|
||||
SPDXExpression: "Apache-2.0",
|
||||
Type: license.Concluded,
|
||||
Locations: file.NewLocationSet(loc1),
|
||||
URLs: []string{server.URL + "/github.com/someorg/somename/@v/v0.3.2.zip#" + loc1.RealPath},
|
||||
Locations: file.NewLocationSet(),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -146,21 +152,20 @@ func Test_RemoteProxyLicenseSearch(t *testing.T) {
|
||||
Value: "MIT",
|
||||
SPDXExpression: "MIT",
|
||||
Type: license.Concluded,
|
||||
Locations: file.NewLocationSet(loc2),
|
||||
URLs: []string{server.URL + "/github.com/CapORG/CapProject/@v/v4.111.5.zip#" + loc2.RealPath},
|
||||
Locations: file.NewLocationSet(),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
modDir := path.Join(t.TempDir())
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
l := newGoLicenses(
|
||||
|
||||
l := newGoLicenseResolver(
|
||||
"",
|
||||
CatalogerConfig{
|
||||
SearchRemoteLicenses: true,
|
||||
Proxies: []string{server.URL},
|
||||
LocalModCacheDir: modDir,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -240,3 +245,62 @@ func Test_findVersionPath(t *testing.T) {
|
||||
vp := findVersionPath(f, ".")
|
||||
require.Equal(t, "github.com/someorg/somepkg@version", vp)
|
||||
}
|
||||
|
||||
func Test_walkDirErrors(t *testing.T) {
|
||||
resolver := newGoLicenseResolver("", CatalogerConfig{})
|
||||
_, err := resolver.findLicensesInFS("somewhere", badFS{})
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
type badFS struct{}
|
||||
|
||||
func (b badFS) Open(_ string) (fs.File, error) {
|
||||
return nil, fmt.Errorf("error")
|
||||
}
|
||||
|
||||
var _ fs.FS = (*badFS)(nil)
|
||||
|
||||
func Test_noLocalGoModDir(t *testing.T) {
|
||||
emptyTmp := t.TempDir()
|
||||
|
||||
validTmp := t.TempDir()
|
||||
require.NoError(t, os.MkdirAll(filepath.Join(validTmp, "mod@ver"), 0700|os.ModeDir))
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
dir string
|
||||
wantErr require.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
dir: "",
|
||||
wantErr: require.Error,
|
||||
},
|
||||
{
|
||||
name: "invalid dir",
|
||||
dir: filepath.Join(emptyTmp, "invalid-dir"),
|
||||
wantErr: require.Error,
|
||||
},
|
||||
{
|
||||
name: "missing mod dir",
|
||||
dir: emptyTmp,
|
||||
wantErr: require.Error,
|
||||
},
|
||||
{
|
||||
name: "valid mod dir",
|
||||
dir: validTmp,
|
||||
wantErr: require.NoError,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
resolver := newGoLicenseResolver("", CatalogerConfig{
|
||||
SearchLocalModCacheLicenses: true,
|
||||
LocalModCacheDir: test.dir,
|
||||
})
|
||||
_, err := resolver.getLicensesFromLocal("mod", "ver")
|
||||
test.wantErr(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ func (c *goBinaryCataloger) newGoBinaryPackage(resolver file.Resolver, dep *debu
|
||||
dep = dep.Replace
|
||||
}
|
||||
|
||||
licenses, err := c.licenses.getLicenses(resolver, dep.Path, dep.Version)
|
||||
licenses, err := c.licenseResolver.getLicenses(resolver, dep.Path, dep.Version)
|
||||
if err != nil {
|
||||
log.Tracef("error getting licenses for golang package: %s %v", dep.Path, err)
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"io"
|
||||
"regexp"
|
||||
"runtime/debug"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -38,21 +39,21 @@ var (
|
||||
// inject the correct version into the main module of the build process
|
||||
|
||||
knownBuildFlagPatterns = []*regexp.Regexp{
|
||||
regexp.MustCompile(`(?m)\.([gG]it)?([bB]uild)?[vV]er(sion)?=(\S+/)*(?P<version>v?\d+.\d+.\d+[-\w]*)`),
|
||||
regexp.MustCompile(`(?m)\.([tT]ag)=(\S+/)*(?P<version>v?\d+.\d+.\d+[-\w]*)`),
|
||||
regexp.MustCompile(`(?m)\.[a-zA-Z0-9]*([rR]elease)?([gG]it)?([bB]uild)?[vV]er(sion)?=(\S+/)*(?P<version>v?\d+.\d+.\d+[-\w]*)`),
|
||||
regexp.MustCompile(`(?m)\.[a-zA-Z0-9]*([tT]ag)=(\S+/)*(?P<version>v?\d+.\d+.\d+[-\w]*)`),
|
||||
}
|
||||
)
|
||||
|
||||
const devel = "(devel)"
|
||||
|
||||
type goBinaryCataloger struct {
|
||||
licenses goLicenses
|
||||
licenseResolver goLicenseResolver
|
||||
mainModuleVersion MainModuleVersionConfig
|
||||
}
|
||||
|
||||
func newGoBinaryCataloger(opts CatalogerConfig) *goBinaryCataloger {
|
||||
return &goBinaryCataloger{
|
||||
licenses: newGoLicenses(binaryCatalogerName, opts),
|
||||
licenseResolver: newGoLicenseResolver(binaryCatalogerName, opts),
|
||||
mainModuleVersion: opts.MainModuleVersion,
|
||||
}
|
||||
}
|
||||
@@ -97,17 +98,19 @@ func createModuleRelationships(main pkg.Package, deps []pkg.Package) []artifact.
|
||||
return relationships
|
||||
}
|
||||
|
||||
var emptyModule debug.Module
|
||||
var moduleFromPartialPackageBuild = debug.Module{Path: "command-line-arguments"}
|
||||
|
||||
func (c *goBinaryCataloger) buildGoPkgInfo(resolver file.Resolver, location file.Location, mod *extendedBuildInfo, arch string, reader io.ReadSeekCloser) (*pkg.Package, []pkg.Package) {
|
||||
var pkgs []pkg.Package
|
||||
if mod == nil {
|
||||
return nil, pkgs
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var empty debug.Module
|
||||
if mod.Main == empty && mod.Path != "" {
|
||||
mod.Main = createMainModuleFromPath(mod.Path)
|
||||
if missingMainModule(mod) {
|
||||
mod.Main = createMainModuleFromPath(mod)
|
||||
}
|
||||
|
||||
var pkgs []pkg.Package
|
||||
for _, dep := range mod.Deps {
|
||||
if dep == nil {
|
||||
continue
|
||||
@@ -130,7 +133,7 @@ func (c *goBinaryCataloger) buildGoPkgInfo(resolver file.Resolver, location file
|
||||
}
|
||||
}
|
||||
|
||||
if mod.Main == empty {
|
||||
if mod.Main == emptyModule {
|
||||
return nil, pkgs
|
||||
}
|
||||
|
||||
@@ -139,6 +142,16 @@ func (c *goBinaryCataloger) buildGoPkgInfo(resolver file.Resolver, location file
|
||||
return &main, pkgs
|
||||
}
|
||||
|
||||
func missingMainModule(mod *extendedBuildInfo) bool {
|
||||
if mod.Main == emptyModule && mod.Path != "" {
|
||||
return true
|
||||
}
|
||||
// special case: when invoking go build with a source file and not a package (directory) then you will
|
||||
// see "command-line-arguments" as the main module path... even though that's not the main module. In this
|
||||
// circumstance, we should treat the main module as missing and search for it within the dependencies.
|
||||
return mod.Main == moduleFromPartialPackageBuild
|
||||
}
|
||||
|
||||
func (c *goBinaryCataloger) makeGoMainPackage(resolver file.Resolver, mod *extendedBuildInfo, arch string, location file.Location, reader io.ReadSeekCloser) pkg.Package {
|
||||
gbs := getBuildSettings(mod.Settings)
|
||||
gover, experiments := getExperimentsFromVersion(mod.GoVersion)
|
||||
@@ -167,6 +180,10 @@ func (c *goBinaryCataloger) makeGoMainPackage(resolver file.Resolver, mod *exten
|
||||
version := c.findMainModuleVersion(metadata, gbs, reader)
|
||||
|
||||
if version != "" {
|
||||
// make sure version is prefixed with v as some build systems parsed
|
||||
// during `findMainModuleVersion` can include incomplete semver
|
||||
// vx.x.x is correct
|
||||
version = ensurePrefix(version, "v")
|
||||
main.Version = version
|
||||
main.PURL = packageURL(main.Name, main.Version)
|
||||
|
||||
@@ -195,7 +212,7 @@ func (c *goBinaryCataloger) findMainModuleVersion(metadata *pkg.GolangBinaryBuil
|
||||
// version of the package.
|
||||
ldflags, _ = metadata.BuildSettings.Get("-ldflags")
|
||||
|
||||
majorVersion, fullVersion = extractVersionFromLDFlags(ldflags)
|
||||
majorVersion, fullVersion = extractVersionFromLDFlags(ldflags, metadata.MainModule)
|
||||
if fullVersion != "" {
|
||||
return fullVersion
|
||||
}
|
||||
@@ -245,13 +262,14 @@ func extractVersionFromContents(reader io.Reader) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func extractVersionFromLDFlags(ldflags string) (majorVersion string, fullVersion string) {
|
||||
func extractVersionFromLDFlags(ldflags string, maimModule string) (majorVersion string, fullVersion string) {
|
||||
if ldflags == "" {
|
||||
return "", ""
|
||||
}
|
||||
|
||||
for _, pattern := range knownBuildFlagPatterns {
|
||||
groups := internal.MatchNamedCaptureGroups(pattern, ldflags)
|
||||
newPattern := regexp.MustCompile(fmt.Sprintf(`(main|%s\/[^\s]*)%s`, strings.ReplaceAll(maimModule, "/", "\\/"), pattern.String()))
|
||||
groups := internal.MatchNamedCaptureGroups(newPattern, ldflags)
|
||||
v, ok := groups["version"]
|
||||
|
||||
if !ok {
|
||||
@@ -358,8 +376,36 @@ func getExperimentsFromVersion(version string) (string, []string) {
|
||||
return version, experiments
|
||||
}
|
||||
|
||||
func createMainModuleFromPath(path string) (mod debug.Module) {
|
||||
mod.Path = path
|
||||
mod.Version = devel
|
||||
return
|
||||
func createMainModuleFromPath(existing *extendedBuildInfo) debug.Module {
|
||||
// search for a main module candidate within the dependencies
|
||||
var mainModuleCandidates []debug.Module
|
||||
var usedIndex int
|
||||
for i, dep := range existing.Deps {
|
||||
if dep == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if dep.Version == devel {
|
||||
usedIndex = i
|
||||
mainModuleCandidates = append(mainModuleCandidates, *dep)
|
||||
}
|
||||
}
|
||||
if len(mainModuleCandidates) == 1 {
|
||||
// we need to prune the dependency from module list
|
||||
existing.Deps = slices.Delete(existing.Deps, usedIndex, usedIndex+1)
|
||||
return mainModuleCandidates[0]
|
||||
}
|
||||
|
||||
// otherwise craft a main module from the path (a bit of a cop out, but allows us to have a main module)
|
||||
return debug.Module{
|
||||
Path: existing.Path,
|
||||
Version: devel,
|
||||
}
|
||||
}
|
||||
|
||||
func ensurePrefix(s, prefix string) string {
|
||||
if !strings.HasPrefix(s, prefix) {
|
||||
return prefix + s
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
@@ -210,7 +210,7 @@ func TestBuildGoPkgInfo(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "buildGoPkgInfo parses a blank mod and returns no packages",
|
||||
mod: &extendedBuildInfo{&debug.BuildInfo{}, nil, ""},
|
||||
mod: &extendedBuildInfo{BuildInfo: &debug.BuildInfo{}, cryptoSettings: nil, arch: ""},
|
||||
expected: []pkg.Package(nil),
|
||||
},
|
||||
{
|
||||
@@ -946,6 +946,95 @@ func TestBuildGoPkgInfo(t *testing.T) {
|
||||
},
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "parse a mod from path (partial build of package)",
|
||||
mod: &extendedBuildInfo{
|
||||
BuildInfo: &debug.BuildInfo{
|
||||
GoVersion: "go1.22.2",
|
||||
Main: debug.Module{Path: "command-line-arguments"},
|
||||
Settings: []debug.BuildSetting{
|
||||
{
|
||||
Key: "-ldflags",
|
||||
Value: `build -ldflags="-w -s -X github.com/kuskoman/logstash-exporter/config.Version=v1.7.0 -X github.com/kuskoman/logstash-exporter/config.GitCommit=db696dbcfe5a91d288d5ad44ce8ccbea97e65978 -X github.com/kuskoman/logstash-exporter/config.BuildDate=2024-07-17T08:12:17Z"`,
|
||||
},
|
||||
{Key: "GOARCH", Value: archDetails},
|
||||
{Key: "GOOS", Value: "darwin"},
|
||||
{Key: "GOAMD64", Value: "v1"},
|
||||
},
|
||||
Deps: []*debug.Module{
|
||||
{
|
||||
Path: "github.com/kuskoman/something-else",
|
||||
Version: "v1.2.3",
|
||||
},
|
||||
{
|
||||
Path: "github.com/kuskoman/logstash-exporter",
|
||||
Version: "(devel)",
|
||||
},
|
||||
},
|
||||
},
|
||||
arch: archDetails,
|
||||
},
|
||||
expected: []pkg.Package{
|
||||
{
|
||||
Name: "github.com/kuskoman/something-else",
|
||||
Language: pkg.Go,
|
||||
Type: pkg.GoModulePkg,
|
||||
Version: "v1.2.3",
|
||||
PURL: "pkg:golang/github.com/kuskoman/something-else@v1.2.3",
|
||||
Locations: file.NewLocationSet(
|
||||
file.NewLocationFromCoordinates(
|
||||
file.Coordinates{
|
||||
RealPath: "/a-path",
|
||||
FileSystemID: "layer-id",
|
||||
},
|
||||
).WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
),
|
||||
Metadata: pkg.GolangBinaryBuildinfoEntry{
|
||||
GoCompiledVersion: "go1.22.2",
|
||||
Architecture: archDetails,
|
||||
MainModule: "github.com/kuskoman/logstash-exporter", // correctly attached the main module
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "github.com/kuskoman/logstash-exporter",
|
||||
Language: pkg.Go,
|
||||
Type: pkg.GoModulePkg,
|
||||
Version: "v1.7.0",
|
||||
PURL: "pkg:golang/github.com/kuskoman/logstash-exporter@v1.7.0",
|
||||
Locations: file.NewLocationSet(
|
||||
file.NewLocationFromCoordinates(
|
||||
file.Coordinates{
|
||||
RealPath: "/a-path",
|
||||
FileSystemID: "layer-id",
|
||||
},
|
||||
).WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
),
|
||||
Metadata: pkg.GolangBinaryBuildinfoEntry{
|
||||
GoCompiledVersion: "go1.22.2",
|
||||
BuildSettings: []pkg.KeyValue{
|
||||
{
|
||||
Key: "-ldflags",
|
||||
Value: `build -ldflags="-w -s -X github.com/kuskoman/logstash-exporter/config.Version=v1.7.0 -X github.com/kuskoman/logstash-exporter/config.GitCommit=db696dbcfe5a91d288d5ad44ce8ccbea97e65978 -X github.com/kuskoman/logstash-exporter/config.BuildDate=2024-07-17T08:12:17Z"`,
|
||||
},
|
||||
{
|
||||
Key: "GOARCH",
|
||||
Value: "amd64",
|
||||
},
|
||||
{
|
||||
Key: "GOOS",
|
||||
Value: "darwin",
|
||||
},
|
||||
{
|
||||
Key: "GOAMD64",
|
||||
Value: "v1",
|
||||
},
|
||||
},
|
||||
Architecture: archDetails,
|
||||
MainModule: "github.com/kuskoman/logstash-exporter",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
@@ -979,6 +1068,7 @@ func TestBuildGoPkgInfo(t *testing.T) {
|
||||
func Test_extractVersionFromLDFlags(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
mainModule string
|
||||
ldflags string
|
||||
wantMajorVersion string
|
||||
wantFullVersion string
|
||||
@@ -989,12 +1079,14 @@ func Test_extractVersionFromLDFlags(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "syft ldflags",
|
||||
mainModule: "github.com/anchore/syft",
|
||||
ldflags: ` build -ldflags="-w -s -extldflags '-static' -X github.com/anchore/syft/internal/version.version=0.79.0 -X github.com/anchore/syft/internal/version.gitCommit=b2b332e8b2b66af0905e98b54ebd713a922be1a8 -X github.com/anchore/syft/internal/version.buildDate=2023-04-21T16:20:25Z -X github.com/anchore/syft/internal/version.gitDescription=v0.79.0 "`,
|
||||
wantMajorVersion: "0",
|
||||
wantFullVersion: "v0.79.0",
|
||||
},
|
||||
{
|
||||
name: "kubectl ldflags",
|
||||
name: "kubectl ldflags",
|
||||
mainModule: "k8s.io/kubernetes/vendor/k8s.io/client-go",
|
||||
ldflags: ` build -asmflags=all=-trimpath=/workspace/src/k8s.io/kubernetes/_output/dockerized/go/src/k8s.io/kubernetes
|
||||
build -compiler=gc
|
||||
build -gcflags="all=-trimpath=/workspace/src/k8s.io/kubernetes/_output/dockerized/go/src/k8s.io/kubernetes "
|
||||
@@ -1004,24 +1096,28 @@ func Test_extractVersionFromLDFlags(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "nerdctl ldflags",
|
||||
mainModule: "github.com/containerd/nerdctl",
|
||||
ldflags: ` build -ldflags="-s -w -X github.com/containerd/nerdctl/pkg/version.Version=v1.3.1 -X github.com/containerd/nerdctl/pkg/version.Revision=b224b280ff3086516763c7335fc0e0997aca617a"`,
|
||||
wantMajorVersion: "1",
|
||||
wantFullVersion: "v1.3.1",
|
||||
},
|
||||
{
|
||||
name: "limactl ldflags",
|
||||
mainModule: "github.com/lima-vm/lima",
|
||||
ldflags: ` build -ldflags="-s -w -X github.com/lima-vm/lima/pkg/version.Version=v0.15.1"`,
|
||||
wantMajorVersion: "0",
|
||||
wantFullVersion: "v0.15.1",
|
||||
},
|
||||
{
|
||||
name: "terraform ldflags",
|
||||
mainModule: "github.com/hashicorp/terraform",
|
||||
ldflags: ` build -ldflags="-w -s -X 'github.com/hashicorp/terraform/version.Version=1.4.6' -X 'github.com/hashicorp/terraform/version.Prerelease='"`,
|
||||
wantMajorVersion: "1",
|
||||
wantFullVersion: "v1.4.6",
|
||||
},
|
||||
{
|
||||
name: "kube-apiserver ldflags",
|
||||
name: "kube-apiserver ldflags",
|
||||
mainModule: "k8s.io/kubernetes/vendor/k8s.io/client-go",
|
||||
ldflags: ` build -asmflags=all=-trimpath=/workspace/src/k8s.io/kubernetes/_output/dockerized/go/src/k8s.io/kubernetes
|
||||
build -buildmode=exe
|
||||
build -compiler=gc
|
||||
@@ -1031,14 +1127,16 @@ func Test_extractVersionFromLDFlags(t *testing.T) {
|
||||
wantFullVersion: "v1.27.1",
|
||||
},
|
||||
{
|
||||
name: "prometheus ldflags",
|
||||
name: "prometheus ldflags",
|
||||
mainModule: "github.com/prometheus/common",
|
||||
ldflags: ` build -ldflags="-X github.com/prometheus/common/version.Version=2.44.0 -X github.com/prometheus/common/version.Revision=1ac5131f698ebc60f13fe2727f89b115a41f6558 -X github.com/prometheus/common/version.Branch=HEAD -X github.com/prometheus/common/version.BuildUser=root@739e8181c5db -X github.com/prometheus/common/version.BuildDate=20230514-06:18:11 -extldflags '-static'"
|
||||
build -tags=netgo,builtinassets,stringlabels`,
|
||||
wantMajorVersion: "2",
|
||||
wantFullVersion: "v2.44.0",
|
||||
},
|
||||
{
|
||||
name: "influxdb ldflags",
|
||||
name: "influxdb ldflags",
|
||||
mainModule: "github.com/influxdata/influxdb-client-go/v2",
|
||||
ldflags: ` build -ldflags="-s -w -X main.version=v2.7.1 -X main.commit=407fa622e9 -X main.date=2023-04-28T13:24:27Z -linkmode=external -extld=/musl/x86_64/bin/musl-gcc -extldflags '-fno-PIC -static-pie -Wl,-z,stack-size=8388608'"
|
||||
build -tags=assets,sqlite_foreign_keys,sqlite_json,static_build,noasm`,
|
||||
wantMajorVersion: "2",
|
||||
@@ -1046,68 +1144,106 @@ func Test_extractVersionFromLDFlags(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "gitea ldflags",
|
||||
mainModule: "code.gitea.io/gitea",
|
||||
ldflags: ` build -ldflags=" -X \"main.MakeVersion=GNU Make 4.1\" -X \"main.Version=1.19.3\" -X \"main.Tags=bindata sqlite sqlite_unlock_notify\" "`,
|
||||
wantMajorVersion: "1",
|
||||
wantFullVersion: "v1.19.3",
|
||||
},
|
||||
{
|
||||
name: "docker sbom cli ldflags",
|
||||
mainModule: "github.com/docker/sbom-cli-plugin",
|
||||
ldflags: ` build -ldflags="-w -s -extldflags '-static' -X github.com/docker/sbom-cli-plugin/internal/version.version=0.6.1-SNAPSHOT-02cf1c8 -X github.com/docker/sbom-cli-plugin/internal/version.gitCommit=02cf1c888ad6662109ac6e3be618392514a56316 -X github.com/docker/sbom-cli-plugin/internal/version.gitDescription=v0.6.1-dirty "`,
|
||||
wantMajorVersion: "0",
|
||||
wantFullVersion: "v0.6.1-SNAPSHOT-02cf1c8",
|
||||
},
|
||||
{
|
||||
name: "docker scout ldflags",
|
||||
mainModule: "github.com/docker/scout-cli-plugin",
|
||||
ldflags: ` build -ldflags="-w -s -extldflags '-static' -X github.com/docker/scout-cli-plugin/internal.version=0.10.0 "`,
|
||||
wantMajorVersion: "0",
|
||||
wantFullVersion: "v0.10.0",
|
||||
},
|
||||
{
|
||||
name: "influx telegraf ldflags",
|
||||
mainModule: "github.com/influxdata/telegraf",
|
||||
ldflags: ` build -ldflags="-w -s -X github.com/influxdata/telegraf/internal.Commit=a3a884a1 -X github.com/influxdata/telegraf/internal.Branch=HEAD -X github.com/influxdata/telegraf/internal.Version=1.26.2"`,
|
||||
wantMajorVersion: "1",
|
||||
wantFullVersion: "v1.26.2",
|
||||
},
|
||||
{
|
||||
name: "argocd ldflags",
|
||||
mainModule: "github.com/argoproj/argo-cd/v2",
|
||||
ldflags: ` build -ldflags="-X github.com/argoproj/argo-cd/v2/common.version=2.7.2 -X github.com/argoproj/argo-cd/v2/common.buildDate=2023-05-12T14:06:49Z -X github.com/argoproj/argo-cd/v2/common.gitCommit=cbee7e6011407ed2d1066c482db74e97e0cc6bdb -X github.com/argoproj/argo-cd/v2/common.gitTreeState=clean -X github.com/argoproj/argo-cd/v2/common.kubectlVersion=v0.24.2 -extldflags=\"-static\""`,
|
||||
wantMajorVersion: "2",
|
||||
wantFullVersion: "v2.7.2",
|
||||
},
|
||||
{
|
||||
name: "kustomize ldflags",
|
||||
mainModule: "sigs.k8s.io/kustomize/api",
|
||||
ldflags: ` build -ldflags="-s -X sigs.k8s.io/kustomize/api/provenance.version=kustomize/v4.5.7 -X sigs.k8s.io/kustomize/api/provenance.gitCommit=56d82a8378dfc8dc3b3b1085e5a6e67b82966bd7 -X sigs.k8s.io/kustomize/api/provenance.buildDate=2022-08-02T16:35:54Z "`,
|
||||
wantMajorVersion: "4",
|
||||
wantFullVersion: "v4.5.7",
|
||||
},
|
||||
{
|
||||
name: "TiDB 7.5.0 ldflags",
|
||||
mainModule: "github.com/pingcap/tidb",
|
||||
ldflags: `build -ldflags="-X \"github.com/pingcap/tidb/pkg/parser/mysql.TiDBReleaseVersion=v7.5.0\" -X \"github.com/pingcap/tidb/pkg/util/versioninfo.TiDBBuildTS=2023-11-24 08:51:04\" -X \"github.com/pingcap/tidb/pkg/util/versioninfo.TiDBGitHash=069631e2ecfedc000ffb92c67207bea81380f020\" -X \"github.com/pingcap/tidb/pkg/util/versioninfo.TiDBGitBranch=heads/refs/tags/v7.5.0\" -X \"github.com/pingcap/tidb/pkg/util/versioninfo.TiDBEdition=Community\" "`,
|
||||
wantMajorVersion: "7",
|
||||
wantFullVersion: "v7.5.0",
|
||||
},
|
||||
{
|
||||
name: "TiDB 6.1.7 ldflags",
|
||||
mainModule: "github.com/pingcap/tidb",
|
||||
ldflags: `build -ldflags="-X \"github.com/pingcap/tidb/parser/mysql.TiDBReleaseVersion=v6.1.7\" -X \"github.com/pingcap/tidb/util/versioninfo.TiDBBuildTS=2023-07-04 12:06:03\" -X \"github.com/pingcap/tidb/util/versioninfo.TiDBGitHash=613ecc5f731b2843e1d53a43915e2cd8da795936\" -X \"github.com/pingcap/tidb/util/versioninfo.TiDBGitBranch=heads/refs/tags/v6.1.7\" -X \"github.com/pingcap/tidb/util/versioninfo.TiDBEdition=Community\" "`,
|
||||
wantMajorVersion: "6",
|
||||
wantFullVersion: "v6.1.7",
|
||||
},
|
||||
{
|
||||
name: "logstash-exporter",
|
||||
ldflags: `build -ldflags="-w -s -X github.com/kuskoman/logstash-exporter/config.Version=v1.7.0 -X github.com/kuskoman/logstash-exporter/config.GitCommit=db696dbcfe5a91d288d5ad44ce8ccbea97e65978 -X github.com/kuskoman/logstash-exporter/config.BuildDate=2024-07-17T08:12:17Z"`,
|
||||
wantMajorVersion: "1",
|
||||
wantFullVersion: "v1.7.0",
|
||||
},
|
||||
//////////////////////////////////////////////////////////////////
|
||||
// negative cases
|
||||
{
|
||||
name: "hugo ldflags",
|
||||
ldflags: ` build -ldflags="-s -w -X github.com/gohugoio/hugo/common/hugo.vendorInfo=gohugoio"`,
|
||||
name: "hugo ldflags",
|
||||
mainModule: "github.com/gohugoio/hugo",
|
||||
ldflags: ` build -ldflags="-s -w -X github.com/gohugoio/hugo/common/hugo.vendorInfo=gohugoio"`,
|
||||
},
|
||||
{
|
||||
name: "ghostunnel ldflags",
|
||||
ldflags: ` build -ldflags="-X main.version=77d9aaa"`,
|
||||
name: "ghostunnel ldflags",
|
||||
mainModule: "github.com/ghostunnel/ghostunnel",
|
||||
ldflags: ` build -ldflags="-X main.version=77d9aaa"`,
|
||||
},
|
||||
{
|
||||
name: "opa ldflags",
|
||||
ldflags: `build -ldflags=" -X github.com/open-policy-agent/opa/version.Hostname=9549178459bc"`,
|
||||
name: "opa ldflags",
|
||||
mainModule: "github.com/open-policy-agent/opa",
|
||||
ldflags: `build -ldflags=" -X github.com/open-policy-agent/opa/version.Hostname=9549178459bc"`,
|
||||
},
|
||||
///////////////////////////////////////////////////////////////////
|
||||
// trickier cases
|
||||
{
|
||||
name: "macvlan plugin for cri-o ldflags",
|
||||
mainModule: "github.com/containernetworking/plugins",
|
||||
ldflags: ` build -ldflags="-extldflags -static -X github.com/containernetworking/plugins/pkg/utils/buildversion.BuildVersion=v1.2.0"`,
|
||||
wantMajorVersion: "1",
|
||||
wantFullVersion: "v1.2.0",
|
||||
},
|
||||
{
|
||||
name: "coder ldflags",
|
||||
mainModule: "github.com/coder/coder",
|
||||
ldflags: ` build -ldflags="-s -w -X 'github.com/coder/coder/buildinfo.tag=0.23.4'"`,
|
||||
wantMajorVersion: "0",
|
||||
wantFullVersion: "v0.23.4",
|
||||
},
|
||||
{
|
||||
name: "hypothetical multiple versions in ldflags",
|
||||
mainModule: "github.com/foo/baz",
|
||||
ldflags: ` build -ldflags="-extldflags -static -X github.com/foo/bar/buildversion.BuildVersion=v1.2.0 -X github.com/foo/baz/buildversion.BuildVersion=v2.4.5"`,
|
||||
wantMajorVersion: "2",
|
||||
wantFullVersion: "v2.4.5",
|
||||
},
|
||||
///////////////////////////////////////////////////////////////////
|
||||
// don't know how to handle these... yet
|
||||
//{
|
||||
@@ -1127,7 +1263,7 @@ func Test_extractVersionFromLDFlags(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gotMajorVersion, gotFullVersion := extractVersionFromLDFlags(tt.ldflags)
|
||||
gotMajorVersion, gotFullVersion := extractVersionFromLDFlags(tt.ldflags, tt.mainModule)
|
||||
assert.Equal(t, tt.wantMajorVersion, gotMajorVersion, "unexpected major version")
|
||||
assert.Equal(t, tt.wantFullVersion, gotFullVersion, "unexpected full version")
|
||||
})
|
||||
|
||||
@@ -19,7 +19,13 @@ import (
|
||||
)
|
||||
|
||||
type goModCataloger struct {
|
||||
licenses goLicenses
|
||||
licenseResolver goLicenseResolver
|
||||
}
|
||||
|
||||
func newGoModCataloger(opts CatalogerConfig) *goModCataloger {
|
||||
return &goModCataloger{
|
||||
licenseResolver: newGoLicenseResolver(modFileCatalogerName, opts),
|
||||
}
|
||||
}
|
||||
|
||||
// parseGoModFile takes a go.mod and lists all packages discovered.
|
||||
@@ -44,7 +50,7 @@ func (c *goModCataloger) parseGoModFile(_ context.Context, resolver file.Resolve
|
||||
}
|
||||
|
||||
for _, m := range f.Require {
|
||||
licenses, err := c.licenses.getLicenses(resolver, m.Mod.Path, m.Mod.Version)
|
||||
licenses, err := c.licenseResolver.getLicenses(resolver, m.Mod.Path, m.Mod.Version)
|
||||
if err != nil {
|
||||
log.Tracef("error getting licenses for package: %s %v", m.Mod.Path, err)
|
||||
}
|
||||
@@ -65,7 +71,7 @@ func (c *goModCataloger) parseGoModFile(_ context.Context, resolver file.Resolve
|
||||
|
||||
// remove any old packages and replace with new ones...
|
||||
for _, m := range f.Replace {
|
||||
licenses, err := c.licenses.getLicenses(resolver, m.New.Path, m.New.Version)
|
||||
licenses, err := c.licenseResolver.getLicenses(resolver, m.New.Path, m.New.Version)
|
||||
if err != nil {
|
||||
log.Tracef("error getting licenses for package: %s %v", m.New.Path, err)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/internal/fileresolver"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
||||
)
|
||||
@@ -91,10 +92,11 @@ func TestParseGoMod(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.fixture, func(t *testing.T) {
|
||||
c := goModCataloger{}
|
||||
c := newGoModCataloger(DefaultCatalogerConfig())
|
||||
pkgtest.NewCatalogTester().
|
||||
FromFile(t, test.fixture).
|
||||
Expects(test.expected, nil).
|
||||
WithResolver(fileresolver.Empty{}).
|
||||
TestParser(t, c.parseGoModFile)
|
||||
})
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user