mirror of
https://github.com/anchore/syft.git
synced 2026-10-15 07:37:20 +02:00
Compare commits
+6
-6
@@ -26,7 +26,7 @@ tools:
|
||||
# used for linting
|
||||
- name: golangci-lint
|
||||
version:
|
||||
want: v1.60.3
|
||||
want: v1.61.0
|
||||
method: github-release
|
||||
with:
|
||||
repo: golangci/golangci-lint
|
||||
@@ -42,7 +42,7 @@ tools:
|
||||
# used for signing the checksums file at release
|
||||
- name: cosign
|
||||
version:
|
||||
want: v2.4.0
|
||||
want: v2.4.1
|
||||
method: github-release
|
||||
with:
|
||||
repo: sigstore/cosign
|
||||
@@ -58,7 +58,7 @@ tools:
|
||||
# used to release all artifacts
|
||||
- name: goreleaser
|
||||
version:
|
||||
want: v2.2.0
|
||||
want: v2.3.2
|
||||
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.38.0
|
||||
want: v3.39.2
|
||||
method: github-release
|
||||
with:
|
||||
repo: go-task/task
|
||||
@@ -111,7 +111,7 @@ tools:
|
||||
# used for triggering a release
|
||||
- name: gh
|
||||
version:
|
||||
want: v2.55.0
|
||||
want: v2.59.0
|
||||
method: github-release
|
||||
with:
|
||||
repo: cli/cli
|
||||
@@ -130,4 +130,4 @@ tools:
|
||||
want: v4.44.3
|
||||
method: github-release
|
||||
with:
|
||||
repo: mikefarah/yq
|
||||
repo: mikefarah/yq
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
# Description
|
||||
|
||||
Please include a summary of the changes along with any relevant motivation and context,
|
||||
or link to an issue where this is explained.
|
||||
|
||||
<!-- If this completes an issue, please include: -->
|
||||
|
||||
- Fixes #
|
||||
|
||||
## Type of change
|
||||
|
||||
<!-- Delete any that are not relevant -->
|
||||
|
||||
- [ ] Bug fix (non-breaking change which fixes an issue)
|
||||
- [ ] New feature (non-breaking change which adds functionality)
|
||||
- [ ] Breaking change (please discuss with the team first; Syft is 1.0 software and we won't accept breaking changes without going to 2.0)
|
||||
- [ ] Documentation (updates the documentation)
|
||||
- [ ] Chore (improve the developer experience, fix a test flake, etc, without changing the visible behavior of Syft)
|
||||
- [ ] Performance (make Syft run faster or use less memory, without changing visible behavior much)
|
||||
|
||||
# Checklist:
|
||||
|
||||
- [ ] I have added unit tests that cover changed behavior
|
||||
- [ ] I have tested my code in common scenarios and confirmed there are no regressions
|
||||
- [ ] I have added comments to my code, particularly in hard-to-understand sections
|
||||
Executable
+11
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
red=$(tput setaf 1)
|
||||
bold=$(tput bold)
|
||||
normal=$(tput sgr0)
|
||||
|
||||
# assert we are running in CI (or die!)
|
||||
if [[ -z "$CI" ]]; then
|
||||
echo "${bold}${red}This step should ONLY be run in CI. Exiting...${normal}"
|
||||
exit 1
|
||||
fi
|
||||
@@ -17,13 +17,13 @@ jobs:
|
||||
# the job by event.
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
|
||||
- name: Restore base benchmark result
|
||||
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 #v4.0.2
|
||||
uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 #v4.1.1
|
||||
with:
|
||||
path: test/results/benchmark-main.txt
|
||||
# use base sha for PR or new commit hash for main push in benchmark result key
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
OUTPUT="${OUTPUT//$'\r'/'%0D'}" # URL encode all '\r' characters
|
||||
echo "result=$OUTPUT" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0
|
||||
- uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||
with:
|
||||
name: benchmark-test-results
|
||||
path: test/results/**/*
|
||||
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 #v4.2.1
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 #v5.0.2
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@4dd16135b69a43b6c8efb853346f8437d92d3c93 #v3.26.6
|
||||
uses: github/codeql-action/init@f779452ac5af1c261dce0346a8f964149f49322b #v3.26.13
|
||||
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@4dd16135b69a43b6c8efb853346f8437d92d3c93 #v3.26.6
|
||||
uses: github/codeql-action/autobuild@f779452ac5af1c261dce0346a8f964149f49322b #v3.26.13
|
||||
|
||||
# ℹ️ 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@4dd16135b69a43b6c8efb853346f8437d92d3c93 #v3.26.6
|
||||
uses: github/codeql-action/analyze@f779452ac5af1c261dce0346a8f964149f49322b #v3.26.13
|
||||
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 #v4.2.1
|
||||
|
||||
- run: python .github/scripts/labeler.py
|
||||
env:
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
release:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 #v4.2.1
|
||||
|
||||
- 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@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 #v4.2.1
|
||||
|
||||
- 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@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 #v4.2.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -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@61119d458adab75f756bc0b9e4bde25725f86a7a #v0.17.2
|
||||
- uses: anchore/sbom-action@8d0a6505bf28ced3e85154d13dc6af83299e13f1 #v0.17.4
|
||||
continue-on-error: true
|
||||
with:
|
||||
artifact-name: sbom.spdx.json
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
name: "Manage Awaiting Response Label"
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
|
||||
jobs:
|
||||
run:
|
||||
uses: "anchore/workflows/.github/workflows/remove-awaiting-response-label.yaml@main"
|
||||
secrets:
|
||||
token: ${{ secrets.OSS_PROJECT_GH_TOKEN }}
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
permissions:
|
||||
packages: write
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 #v4.2.1
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'anchore/syft' # only run for main repo
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 #v4.2.1
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
app_id: ${{ secrets.TOKEN_APP_ID }}
|
||||
private_key: ${{ secrets.TOKEN_APP_PRIVATE_KEY }}
|
||||
|
||||
- uses: peter-evans/create-pull-request@8867c4aba1b742c39f8d0ba35429c2dfa4b6cb20 #v7.0.1
|
||||
- uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f #v7.0.5
|
||||
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@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 #v4.2.1
|
||||
|
||||
- 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@8867c4aba1b742c39f8d0ba35429c2dfa4b6cb20 #v7.0.1
|
||||
- uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f #v7.0.5
|
||||
with:
|
||||
signoff: true
|
||||
delete-branch: true
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'anchore/syft' # only run for main repo
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 #v4.2.1
|
||||
|
||||
- uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 #v5.0.2
|
||||
with:
|
||||
@@ -44,7 +44,7 @@ jobs:
|
||||
app_id: ${{ secrets.TOKEN_APP_ID }}
|
||||
private_key: ${{ secrets.TOKEN_APP_PRIVATE_KEY }}
|
||||
|
||||
- uses: peter-evans/create-pull-request@8867c4aba1b742c39f8d0ba35429c2dfa4b6cb20 #v7.0.1
|
||||
- uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f #v7.0.5
|
||||
with:
|
||||
signoff: true
|
||||
delete-branch: true
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
name: "Static analysis"
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 #v4.2.1
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
name: "Unit tests"
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 #v4.2.1
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
name: "Integration tests"
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 #v4.2.1
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
@@ -65,7 +65,7 @@ jobs:
|
||||
name: "Build snapshot artifacts"
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 #v4.2.1
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
@@ -78,7 +78,7 @@ jobs:
|
||||
# why not use actions/upload-artifact? It is very slow (3 minutes to upload ~600MB of data, vs 10 seconds with this approach).
|
||||
# see https://github.com/actions/upload-artifact/issues/199 for more info
|
||||
- name: Upload snapshot artifacts
|
||||
uses: actions/cache/save@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 #v4.0.2
|
||||
uses: actions/cache/save@3624ceb22c1c5a301c8db4169662070a689d9ea8 #v4.1.1
|
||||
with:
|
||||
# we need to preserve the snapshot data itself as well as the task data that confirms if the
|
||||
# snapshot build is stale or not. Otherwise the downstream jobs will attempt to rebuild the snapshot
|
||||
@@ -95,7 +95,7 @@ jobs:
|
||||
needs: [Build-Snapshot-Artifacts]
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 #v4.2.1
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
@@ -104,7 +104,7 @@ jobs:
|
||||
|
||||
- name: Download snapshot build
|
||||
id: snapshot-cache
|
||||
uses: actions/cache/restore@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 #v4.0.2
|
||||
uses: actions/cache/restore@3624ceb22c1c5a301c8db4169662070a689d9ea8 #v4.1.1
|
||||
with:
|
||||
path: |
|
||||
snapshot
|
||||
@@ -139,9 +139,9 @@ jobs:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- name: Install Cosign
|
||||
uses: sigstore/cosign-installer@v3.6.0
|
||||
uses: sigstore/cosign-installer@v3.7.0
|
||||
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 #v4.2.1
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
@@ -152,7 +152,7 @@ jobs:
|
||||
|
||||
- name: Download snapshot build
|
||||
id: snapshot-cache
|
||||
uses: actions/cache/restore@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 #v4.0.2
|
||||
uses: actions/cache/restore@3624ceb22c1c5a301c8db4169662070a689d9ea8 #v4.1.1
|
||||
with:
|
||||
path: |
|
||||
snapshot
|
||||
@@ -178,7 +178,7 @@ jobs:
|
||||
needs: [Build-Snapshot-Artifacts]
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 #v4.2.1
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
@@ -187,7 +187,7 @@ jobs:
|
||||
|
||||
- name: Download snapshot build
|
||||
id: snapshot-cache
|
||||
uses: actions/cache/restore@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 #v4.0.2
|
||||
uses: actions/cache/restore@3624ceb22c1c5a301c8db4169662070a689d9ea8 #v4.1.1
|
||||
with:
|
||||
path: |
|
||||
snapshot
|
||||
@@ -215,7 +215,7 @@ jobs:
|
||||
- Acceptance-Mac
|
||||
- Cli-Linux
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 #v4.2.1
|
||||
|
||||
- name: Delete snapshot cache
|
||||
run: gh cache delete "snapshot-build-${{ github.run_id }}"
|
||||
|
||||
@@ -57,6 +57,9 @@ linters-settings:
|
||||
settings:
|
||||
ruleguard:
|
||||
rules: "test/rules/rules.go"
|
||||
gosec:
|
||||
excludes:
|
||||
- G115
|
||||
output:
|
||||
uniq-by-line: false
|
||||
run:
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
<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="https://anchore.com/discourse" target="_blank"><img alt="Join our Discourse" src="https://img.shields.io/badge/Discourse-Join-blue?logo=discourse"/></a>
|
||||
<a rel="me" href="https://fosstodon.org/@syft"><img alt="Follow on Mastodon" src="https://img.shields.io/badge/Mastodon-Follow-blue?logoColor=white&logo=mastodon"/></a>
|
||||
</p>
|
||||
|
||||

|
||||
|
||||
+13
-86
@@ -1,5 +1,16 @@
|
||||
# Release
|
||||
|
||||
A release of syft comprises:
|
||||
- a new semver git tag from the current tip of the main branch
|
||||
- a new [github release](https://github.com/anchore/syft/releases) with a changelog and archived binary assets
|
||||
- docker images published to `ghcr.io` and `dockerhub`, including multi architecture images + manifest
|
||||
- [`anchore/homebrew-syft`](https://github.com/anchore/homebrew-syft) tap updated to point to assets in the latest github release
|
||||
|
||||
Ideally releasing should be done often with small increments when possible. Unless a
|
||||
breaking change is blocking the release, or no fixes/features have been merged, a good
|
||||
target release cadence is between every 1 or 2 weeks.
|
||||
|
||||
|
||||
## Creating a release
|
||||
|
||||
This release process itself should be as automated as possible, and has only a few steps:
|
||||
@@ -9,16 +20,10 @@ This release process itself should be as automated as possible, and has only a f
|
||||
you can abort and adjust the labels on the PRs and issues to be included in the release and
|
||||
re-run the release trigger command.
|
||||
|
||||
1. A release admin must approve the release on the GitHub Actions release pipeline run page.
|
||||
1. A release admin must approve the release on the GitHub Actions [release pipeline](https://github.com/anchore/syft/actions/workflows/release.yaml) run page.
|
||||
Once approved, the release pipeline will generate all assets and publish a GitHub Release.
|
||||
|
||||
1. If there is a release Milestone, close it.
|
||||
|
||||
Ideally releasing should be done often with small increments when possible. Unless a
|
||||
breaking change is blocking the release, or no fixes/features have been merged, a good
|
||||
target release cadence is between every 1 or 2 weeks.
|
||||
|
||||
|
||||
|
||||
## Retracting a release
|
||||
|
||||
If a release is found to be problematic, it can be retracted with the following steps:
|
||||
@@ -32,81 +37,3 @@ If a release is found to be problematic, it can be retracted with the following
|
||||
in the go proxy, which will cause confusion when trying to reuse the tag later (the H1 hash will not match and there
|
||||
will be a warning when users try to pull the new release).
|
||||
|
||||
|
||||
## Background
|
||||
|
||||
A good release process has the following qualities:
|
||||
|
||||
1. There is a way to plan what should be in a release
|
||||
1. There is a way to see what is actually in a release
|
||||
1. Allow for different kinds of releases (major breaking vs backwards compatible enhancements vs patch updates)
|
||||
1. Specify a repeatable way to build and publish software artifacts
|
||||
|
||||
|
||||
### Planning a release
|
||||
|
||||
To indicate a set of features to be released together add each issue to an in-repository
|
||||
Milestone named with major-minor version to be released (e.g. `v0.1`). It is OK for other
|
||||
features to be in the release that were not originally planned, and these issues and PRs
|
||||
do not need to be added to the Milestone in question. Only the set of features that, when
|
||||
completed, would allow the release to be considered complete. A Milestone is only used to:
|
||||
|
||||
- Plan what is desired to be in a release
|
||||
- Track progress to indicate when we may be ready to cut a new release
|
||||
|
||||
Not all releases need to be planned. For instance, patch releases for fixes should be
|
||||
released when they are ready and when releasing would not interfere with another current
|
||||
release (where some partial or breaking features have already been merged).
|
||||
|
||||
Unless necessary, feature releases should be small and frequent, which may obviate the
|
||||
need for regular release planning under a Milestone.
|
||||
|
||||
|
||||
### What is in a release
|
||||
|
||||
Milestones are specifically for planning a release, not necessarily tracking all changes
|
||||
that a release may bring (and more importantly, not all releases are necessarily planned
|
||||
either).
|
||||
|
||||
This is one of the (many) reasons for a Changelog. A good Changelog lists changes grouped
|
||||
by the type of change (new, enhancement, deprecation, breaking, bug fix, security fix), in
|
||||
chronological order (within groups), linking the PR where the change was made in the
|
||||
Changelog line. Furthermore, there should be a place to see all released versions, the
|
||||
release date for each release, the semantic version of the release, and the set of changes
|
||||
for each release.
|
||||
|
||||
**This project auto-generates the Changelog contents for each current release and posts the
|
||||
generated contents to the GitHub Release page**. Leveraging the GitHub Releases feature
|
||||
allows GitHub to manage the Changelog on each release outside of the git source tree while
|
||||
still being hosted with the released assets.
|
||||
|
||||
The Changelog is generated from the metadata from in-repository issues and PRs, using
|
||||
labels to guide what kind of change each item is (e.g. breaking, new feature, bug fix,
|
||||
etx). Only issues/PRs with select labels are included in the Changelog, and only if the
|
||||
issue/PR was created after the last release. Additional labels are used to exclude items
|
||||
from the Changelog.
|
||||
|
||||
The above suggestions imply that we should:
|
||||
|
||||
- Ensure there is a sufficient title for each PR and issue title to be included in the
|
||||
Changelog
|
||||
- The appropriate label is applied to PRs and/or issues to drive specific change type
|
||||
sections (deprecated, breaking, security, bug, etc)
|
||||
|
||||
**With this approach as we cultivate good organization of PRs and issues we automatically
|
||||
get an equally good Changelog.**
|
||||
|
||||
|
||||
### Major, minor, and patch releases
|
||||
|
||||
The latest version of the tool is the only supported version, which implies that multiple
|
||||
parallel release branches will not be a regular process (if ever). Multiple releases can
|
||||
be planned in parallel, however, only one can be actively developed at a time. That is, if
|
||||
PRs attached to a release Milestone have been merged into the main branch, that release is
|
||||
now the "next" release. **This implies that the source of truth for release lies with the
|
||||
git log and Changelog, not with the release Milestones** (which are purely for planning and
|
||||
tracking).
|
||||
|
||||
Semantic versioning should be used to indicate breaking changes, new features, and fixes.
|
||||
The exception to this is `< 1.0`, where the major version is not bumped for breaking changes,
|
||||
instead the minor version indicates both new features and breaking changes.
|
||||
|
||||
+1
-10
@@ -593,16 +593,7 @@ tasks:
|
||||
ci-check:
|
||||
# desc: "[CI only] Are you in CI?"
|
||||
cmds:
|
||||
- cmd: |
|
||||
red=$(tput setaf 1)
|
||||
bold=$(tput bold)
|
||||
normal=$(tput sgr0)
|
||||
|
||||
# assert we are running in CI (or die!)
|
||||
if [[ -z "$CI" ]]; then
|
||||
echo "${bold}${red}This step should ONLY be run in CI. Exiting...${normal}"
|
||||
exit 1
|
||||
fi
|
||||
- cmd: .github/scripts/ci-check.sh
|
||||
silent: true
|
||||
|
||||
ci-release:
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/anchore/fangs"
|
||||
intFile "github.com/anchore/syft/internal/file"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/task"
|
||||
"github.com/anchore/syft/syft"
|
||||
"github.com/anchore/syft/syft/cataloging"
|
||||
"github.com/anchore/syft/syft/cataloging/filecataloging"
|
||||
@@ -36,6 +37,8 @@ type Catalog struct {
|
||||
Scope string `yaml:"scope" json:"scope" mapstructure:"scope"`
|
||||
Parallelism int `yaml:"parallelism" json:"parallelism" mapstructure:"parallelism"` // the number of catalog workers to run in parallel
|
||||
Relationships relationshipsConfig `yaml:"relationships" json:"relationships" mapstructure:"relationships"`
|
||||
Compliance complianceConfig `yaml:"compliance" json:"compliance" mapstructure:"compliance"`
|
||||
Enrich []string `yaml:"enrich" json:"enrich" mapstructure:"enrich"`
|
||||
|
||||
// ecosystem-specific cataloger configuration
|
||||
Golang golangConfig `yaml:"golang" json:"golang" mapstructure:"golang"`
|
||||
@@ -50,16 +53,20 @@ type Catalog struct {
|
||||
Platform string `yaml:"platform" json:"platform" mapstructure:"platform"`
|
||||
Source sourceConfig `yaml:"source" json:"source" mapstructure:"source"`
|
||||
Exclusions []string `yaml:"exclude" json:"exclude" mapstructure:"exclude"`
|
||||
|
||||
// configuration for inclusion of unknown information within elements
|
||||
Unknowns unknownsConfig `yaml:"unknowns" mapstructure:"unknowns"`
|
||||
}
|
||||
|
||||
var _ interface {
|
||||
clio.FlagAdder
|
||||
clio.PostLoader
|
||||
fangs.FieldDescriber
|
||||
clio.FieldDescriber
|
||||
} = (*Catalog)(nil)
|
||||
|
||||
func DefaultCatalog() Catalog {
|
||||
return Catalog{
|
||||
Compliance: defaultComplianceConfig(),
|
||||
Scope: source.SquashedScope.String(),
|
||||
Package: defaultPackageConfig(),
|
||||
LinuxKernel: defaultLinuxKernelConfig(),
|
||||
@@ -67,6 +74,7 @@ func DefaultCatalog() Catalog {
|
||||
Java: defaultJavaConfig(),
|
||||
File: defaultFileConfig(),
|
||||
Relationships: defaultRelationshipsConfig(),
|
||||
Unknowns: defaultUnknowns(),
|
||||
Source: defaultSourceConfig(),
|
||||
Parallelism: 1,
|
||||
}
|
||||
@@ -77,6 +85,8 @@ func (cfg Catalog) ToSBOMConfig(id clio.Identification) *syft.CreateSBOMConfig {
|
||||
WithTool(id.Name, id.Version).
|
||||
WithParallelism(cfg.Parallelism).
|
||||
WithRelationshipsConfig(cfg.ToRelationshipsConfig()).
|
||||
WithComplianceConfig(cfg.ToComplianceConfig()).
|
||||
WithUnknownsConfig(cfg.ToUnknownsConfig()).
|
||||
WithSearchConfig(cfg.ToSearchConfig()).
|
||||
WithPackagesConfig(cfg.ToPackagesConfig()).
|
||||
WithFilesConfig(cfg.ToFilesConfig()).
|
||||
@@ -102,6 +112,20 @@ func (cfg Catalog) ToRelationshipsConfig() cataloging.RelationshipsConfig {
|
||||
}
|
||||
}
|
||||
|
||||
func (cfg Catalog) ToComplianceConfig() cataloging.ComplianceConfig {
|
||||
return cataloging.ComplianceConfig{
|
||||
MissingName: cfg.Compliance.MissingName,
|
||||
MissingVersion: cfg.Compliance.MissingVersion,
|
||||
}
|
||||
}
|
||||
|
||||
func (cfg Catalog) ToUnknownsConfig() cataloging.UnknownsConfig {
|
||||
return cataloging.UnknownsConfig{
|
||||
IncludeExecutablesWithoutPackages: cfg.Unknowns.ExecutablesWithoutPackages,
|
||||
IncludeUnexpandedArchives: cfg.Unknowns.UnexpandedArchives,
|
||||
}
|
||||
}
|
||||
|
||||
func (cfg Catalog) ToFilesConfig() filecataloging.Config {
|
||||
hashers, err := intFile.Hashers(cfg.File.Metadata.Digests...)
|
||||
if err != nil {
|
||||
@@ -130,9 +154,9 @@ func (cfg Catalog) ToPackagesConfig() pkgcataloging.Config {
|
||||
return pkgcataloging.Config{
|
||||
Binary: binary.DefaultClassifierCatalogerConfig(),
|
||||
Golang: golang.DefaultCatalogerConfig().
|
||||
WithSearchLocalModCacheLicenses(cfg.Golang.SearchLocalModCacheLicenses).
|
||||
WithSearchLocalModCacheLicenses(*multiLevelOption(false, enrichmentEnabled(cfg.Enrich, task.Go, task.Golang), cfg.Golang.SearchLocalModCacheLicenses)).
|
||||
WithLocalModCacheDir(cfg.Golang.LocalModCacheDir).
|
||||
WithSearchRemoteLicenses(cfg.Golang.SearchRemoteLicenses).
|
||||
WithSearchRemoteLicenses(*multiLevelOption(false, enrichmentEnabled(cfg.Enrich, task.Go, task.Golang), cfg.Golang.SearchRemoteLicenses)).
|
||||
WithProxy(cfg.Golang.Proxy).
|
||||
WithNoProxy(cfg.Golang.NoProxy).
|
||||
WithMainModuleVersion(
|
||||
@@ -142,7 +166,7 @@ func (cfg Catalog) ToPackagesConfig() pkgcataloging.Config {
|
||||
WithFromLDFlags(cfg.Golang.MainModuleVersion.FromLDFlags),
|
||||
),
|
||||
JavaScript: javascript.DefaultCatalogerConfig().
|
||||
WithSearchRemoteLicenses(cfg.JavaScript.SearchRemoteLicenses).
|
||||
WithSearchRemoteLicenses(*multiLevelOption(false, enrichmentEnabled(cfg.Enrich, task.JavaScript, task.Node, task.NPM), cfg.JavaScript.SearchRemoteLicenses)).
|
||||
WithNpmBaseURL(cfg.JavaScript.NpmBaseURL),
|
||||
LinuxKernel: kernel.LinuxKernelCatalogerConfig{
|
||||
CatalogModules: cfg.LinuxKernel.CatalogModules,
|
||||
@@ -151,9 +175,9 @@ func (cfg Catalog) ToPackagesConfig() pkgcataloging.Config {
|
||||
GuessUnpinnedRequirements: cfg.Python.GuessUnpinnedRequirements,
|
||||
},
|
||||
JavaArchive: java.DefaultArchiveCatalogerConfig().
|
||||
WithUseMavenLocalRepository(cfg.Java.UseMavenLocalRepository).
|
||||
WithUseMavenLocalRepository(*multiLevelOption(false, enrichmentEnabled(cfg.Enrich, task.Java, task.Maven), cfg.Java.UseMavenLocalRepository)).
|
||||
WithMavenLocalRepositoryDir(cfg.Java.MavenLocalRepositoryDir).
|
||||
WithUseNetwork(cfg.Java.UseNetwork).
|
||||
WithUseNetwork(*multiLevelOption(false, enrichmentEnabled(cfg.Enrich, task.Java, task.Maven), cfg.Java.UseNetwork)).
|
||||
WithMavenBaseURL(cfg.Java.MavenURL).
|
||||
WithArchiveTraversal(archiveSearch, cfg.Java.MaxParentRecursiveDepth),
|
||||
}
|
||||
@@ -193,6 +217,9 @@ func (cfg *Catalog) AddFlags(flags clio.FlagSet) {
|
||||
flags.StringArrayVarP(&cfg.SelectCatalogers, "select-catalogers", "",
|
||||
"add, remove, and filter the catalogers to be used")
|
||||
|
||||
flags.StringArrayVarP(&cfg.Enrich, "enrich", "",
|
||||
fmt.Sprintf("enable package data enrichment from local and online sources (options: %s)", strings.Join(publicisedEnrichmentOptions, ", ")))
|
||||
|
||||
flags.StringVarP(&cfg.Source.Name, "source-name", "",
|
||||
"set the name of the target being analyzed")
|
||||
|
||||
@@ -205,6 +232,10 @@ func (cfg *Catalog) AddFlags(flags clio.FlagSet) {
|
||||
|
||||
func (cfg *Catalog) DescribeFields(descriptions fangs.FieldDescriptionSet) {
|
||||
descriptions.Add(&cfg.Parallelism, "number of cataloger workers to run in parallel")
|
||||
|
||||
descriptions.Add(&cfg.Enrich, fmt.Sprintf(`Enable data enrichment operations, which can utilize services such as Maven Central and NPM.
|
||||
By default all enrichment is disabled, use: all to enable everything.
|
||||
Available options are: %s`, strings.Join(publicisedEnrichmentOptions, ", ")))
|
||||
}
|
||||
|
||||
func (cfg *Catalog) PostLoad() error {
|
||||
@@ -215,23 +246,12 @@ func (cfg *Catalog) PostLoad() error {
|
||||
return fmt.Errorf("cannot use both 'catalogers' and 'select-catalogers'/'default-catalogers' flags")
|
||||
}
|
||||
|
||||
flatten := func(l []string) []string {
|
||||
var out []string
|
||||
for _, v := range l {
|
||||
for _, s := range strings.Split(v, ",") {
|
||||
out = append(out, strings.TrimSpace(s))
|
||||
}
|
||||
}
|
||||
sort.Strings(out)
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
cfg.From = flatten(cfg.From)
|
||||
|
||||
cfg.Catalogers = flatten(cfg.Catalogers)
|
||||
cfg.DefaultCatalogers = flatten(cfg.DefaultCatalogers)
|
||||
cfg.SelectCatalogers = flatten(cfg.SelectCatalogers)
|
||||
cfg.Enrich = flatten(cfg.Enrich)
|
||||
|
||||
// for backwards compatibility
|
||||
cfg.DefaultCatalogers = append(cfg.DefaultCatalogers, cfg.Catalogers...)
|
||||
@@ -241,5 +261,75 @@ func (cfg *Catalog) PostLoad() error {
|
||||
return fmt.Errorf("bad scope value %q", cfg.Scope)
|
||||
}
|
||||
|
||||
// the binary package exclusion code depends on the file overlap relationships being created upstream in processing
|
||||
if !cfg.Relationships.PackageFileOwnershipOverlap && cfg.Package.ExcludeBinaryOverlapByOwnership {
|
||||
return fmt.Errorf("cannot enable exclude-binary-overlap-by-ownership without enabling package-file-ownership-overlap")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func flatten(commaSeparatedEntries []string) []string {
|
||||
var out []string
|
||||
for _, v := range commaSeparatedEntries {
|
||||
for _, s := range strings.Split(v, ",") {
|
||||
out = append(out, strings.TrimSpace(s))
|
||||
}
|
||||
}
|
||||
sort.Strings(out)
|
||||
return out
|
||||
}
|
||||
|
||||
var publicisedEnrichmentOptions = []string{
|
||||
"all",
|
||||
task.Golang,
|
||||
task.Java,
|
||||
task.JavaScript,
|
||||
}
|
||||
|
||||
func enrichmentEnabled(enrichDirectives []string, features ...string) *bool {
|
||||
if len(enrichDirectives) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
enabled := func(features ...string) *bool {
|
||||
for _, directive := range enrichDirectives {
|
||||
enable := true
|
||||
directive = strings.TrimPrefix(directive, "+") // +java and java are equivalent
|
||||
if strings.HasPrefix(directive, "-") {
|
||||
directive = directive[1:]
|
||||
enable = false
|
||||
}
|
||||
for _, feature := range features {
|
||||
if directive == feature {
|
||||
return &enable
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
enableAll := enabled("all")
|
||||
disableAll := enabled("none")
|
||||
|
||||
if disableAll != nil && *disableAll {
|
||||
if enableAll != nil {
|
||||
log.Warn("you have specified to both enable and disable all enrichment functionality, defaulting to disabled")
|
||||
}
|
||||
enableAll = ptr(false)
|
||||
}
|
||||
|
||||
// check for explicit enable/disable of feature names
|
||||
for _, feat := range features {
|
||||
enableFeature := enabled(feat)
|
||||
if enableFeature != nil {
|
||||
return enableFeature
|
||||
}
|
||||
}
|
||||
|
||||
return enableAll
|
||||
}
|
||||
|
||||
func ptr[T any](val T) *T {
|
||||
return &val
|
||||
}
|
||||
|
||||
@@ -57,6 +57,14 @@ func TestCatalog_PostLoad(t *testing.T) {
|
||||
assert.Empty(t, options.Catalogers)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "must have package overlap flag when pruning binaries by overlap",
|
||||
options: Catalog{
|
||||
Package: packageConfig{ExcludeBinaryOverlapByOwnership: true},
|
||||
Relationships: relationshipsConfig{PackageFileOwnershipOverlap: false},
|
||||
},
|
||||
wantErr: assert.Error,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
@@ -70,3 +78,69 @@ func TestCatalog_PostLoad(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_enrichmentEnabled(t *testing.T) {
|
||||
tests := []struct {
|
||||
directives string
|
||||
test string
|
||||
expected *bool
|
||||
}{
|
||||
{
|
||||
directives: "",
|
||||
test: "java",
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
directives: "none",
|
||||
test: "java",
|
||||
expected: ptr(false),
|
||||
},
|
||||
{
|
||||
directives: "none,+java",
|
||||
test: "java",
|
||||
expected: ptr(true),
|
||||
},
|
||||
{
|
||||
directives: "all,none",
|
||||
test: "java",
|
||||
expected: ptr(false),
|
||||
},
|
||||
{
|
||||
directives: "all",
|
||||
test: "java",
|
||||
expected: ptr(true),
|
||||
},
|
||||
{
|
||||
directives: "golang,js",
|
||||
test: "java",
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
directives: "golang,-js,java",
|
||||
test: "java",
|
||||
expected: ptr(true),
|
||||
},
|
||||
{
|
||||
directives: "golang,js,-java",
|
||||
test: "java",
|
||||
expected: ptr(false),
|
||||
},
|
||||
{
|
||||
directives: "all",
|
||||
test: "java",
|
||||
expected: ptr(true),
|
||||
},
|
||||
{
|
||||
directives: "all,-java",
|
||||
test: "java",
|
||||
expected: ptr(false),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.directives, func(t *testing.T) {
|
||||
got := enrichmentEnabled(flatten([]string{test.directives}), test.test)
|
||||
assert.Equal(t, test.expected, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package options
|
||||
|
||||
import (
|
||||
"github.com/anchore/fangs"
|
||||
"github.com/anchore/syft/syft/cataloging"
|
||||
)
|
||||
|
||||
var (
|
||||
_ fangs.FieldDescriber = (*complianceConfig)(nil)
|
||||
_ fangs.PostLoader = (*complianceConfig)(nil)
|
||||
)
|
||||
|
||||
type complianceConfig struct {
|
||||
MissingName cataloging.ComplianceAction `mapstructure:"missing-name" json:"missing-name" yaml:"missing-name"`
|
||||
MissingVersion cataloging.ComplianceAction `mapstructure:"missing-version" json:"missing-version" yaml:"missing-version"`
|
||||
}
|
||||
|
||||
func defaultComplianceConfig() complianceConfig {
|
||||
def := cataloging.DefaultComplianceConfig()
|
||||
return complianceConfig{
|
||||
MissingName: def.MissingName,
|
||||
MissingVersion: def.MissingVersion,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *complianceConfig) DescribeFields(descriptions fangs.FieldDescriptionSet) {
|
||||
descriptions.Add(&r.MissingName, "action to take when a package is missing a name")
|
||||
descriptions.Add(&r.MissingVersion, "action to take when a package is missing a version")
|
||||
}
|
||||
|
||||
func (r *complianceConfig) PostLoad() error {
|
||||
r.MissingName = r.MissingName.Parse()
|
||||
r.MissingVersion = r.MissingVersion.Parse()
|
||||
return nil
|
||||
}
|
||||
@@ -8,9 +8,9 @@ import (
|
||||
)
|
||||
|
||||
type golangConfig struct {
|
||||
SearchLocalModCacheLicenses bool `json:"search-local-mod-cache-licenses" yaml:"search-local-mod-cache-licenses" mapstructure:"search-local-mod-cache-licenses"`
|
||||
SearchLocalModCacheLicenses *bool `json:"search-local-mod-cache-licenses" yaml:"search-local-mod-cache-licenses" mapstructure:"search-local-mod-cache-licenses"`
|
||||
LocalModCacheDir string `json:"local-mod-cache-dir" yaml:"local-mod-cache-dir" mapstructure:"local-mod-cache-dir"`
|
||||
SearchRemoteLicenses bool `json:"search-remote-licenses" yaml:"search-remote-licenses" mapstructure:"search-remote-licenses"`
|
||||
SearchRemoteLicenses *bool `json:"search-remote-licenses" yaml:"search-remote-licenses" mapstructure:"search-remote-licenses"`
|
||||
Proxy string `json:"proxy" yaml:"proxy" mapstructure:"proxy"`
|
||||
NoProxy string `json:"no-proxy" yaml:"no-proxy" mapstructure:"no-proxy"`
|
||||
MainModuleVersion golangMainModuleVersionConfig `json:"main-module-version" yaml:"main-module-version" mapstructure:"main-module-version"`
|
||||
@@ -47,9 +47,9 @@ type golangMainModuleVersionConfig struct {
|
||||
func defaultGolangConfig() golangConfig {
|
||||
def := golang.DefaultCatalogerConfig()
|
||||
return golangConfig{
|
||||
SearchLocalModCacheLicenses: def.SearchLocalModCacheLicenses,
|
||||
SearchLocalModCacheLicenses: nil, // this defaults to false, which is the API default
|
||||
LocalModCacheDir: def.LocalModCacheDir,
|
||||
SearchRemoteLicenses: def.SearchRemoteLicenses,
|
||||
SearchRemoteLicenses: nil, // this defaults to false, which is the API default
|
||||
Proxy: strings.Join(def.Proxies, ","),
|
||||
NoProxy: strings.Join(def.NoProxy, ","),
|
||||
MainModuleVersion: golangMainModuleVersionConfig{
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
)
|
||||
|
||||
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"`
|
||||
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"`
|
||||
@@ -17,9 +17,9 @@ func defaultJavaConfig() javaConfig {
|
||||
def := java.DefaultArchiveCatalogerConfig()
|
||||
|
||||
return javaConfig{
|
||||
UseNetwork: def.UseNetwork,
|
||||
UseNetwork: nil, // this defaults to false, which is the API default
|
||||
MaxParentRecursiveDepth: def.MaxParentRecursiveDepth,
|
||||
UseMavenLocalRepository: def.UseMavenLocalRepository,
|
||||
UseMavenLocalRepository: nil, // this defaults to false, which is the API default
|
||||
MavenLocalRepositoryDir: def.MavenLocalRepositoryDir,
|
||||
MavenURL: def.MavenBaseURL,
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package options
|
||||
import "github.com/anchore/clio"
|
||||
|
||||
type javaScriptConfig struct {
|
||||
SearchRemoteLicenses bool `json:"search-remote-licenses" yaml:"search-remote-licenses" mapstructure:"search-remote-licenses"`
|
||||
SearchRemoteLicenses *bool `json:"search-remote-licenses" yaml:"search-remote-licenses" mapstructure:"search-remote-licenses"`
|
||||
NpmBaseURL string `json:"npm-base-url" yaml:"npm-base-url" mapstructure:"npm-base-url"`
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ func (o *OutputFile) AddFlags(flags clio.FlagSet) {
|
||||
|
||||
if pfp, ok := flags.(fangs.PFlagSetProvider); ok {
|
||||
flagSet := pfp.PFlagSet()
|
||||
flagSet.Lookup("file").Deprecated = "use: output"
|
||||
flagSet.Lookup("file").Deprecated = "use: --output FORMAT=PATH"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package options
|
||||
|
||||
import (
|
||||
"github.com/anchore/clio"
|
||||
"github.com/anchore/syft/syft/cataloging"
|
||||
)
|
||||
|
||||
type unknownsConfig struct {
|
||||
RemoveWhenPackagesDefined bool `json:"remove-when-packages-defined" yaml:"remove-when-packages-defined" mapstructure:"remove-when-packages-defined"`
|
||||
ExecutablesWithoutPackages bool `json:"executables-without-packages" yaml:"executables-without-packages" mapstructure:"executables-without-packages"`
|
||||
UnexpandedArchives bool `json:"unexpanded-archives" yaml:"unexpanded-archives" mapstructure:"unexpanded-archives"`
|
||||
}
|
||||
|
||||
var _ interface {
|
||||
clio.FieldDescriber
|
||||
} = (*unknownsConfig)(nil)
|
||||
|
||||
func (o *unknownsConfig) DescribeFields(descriptions clio.FieldDescriptionSet) {
|
||||
descriptions.Add(&o.RemoveWhenPackagesDefined, `remove unknown errors on files with discovered packages`)
|
||||
descriptions.Add(&o.ExecutablesWithoutPackages, `include executables without any identified packages`)
|
||||
descriptions.Add(&o.UnexpandedArchives, `include archives which were not expanded and searched`)
|
||||
}
|
||||
|
||||
func defaultUnknowns() unknownsConfig {
|
||||
def := cataloging.DefaultUnknownsConfig()
|
||||
return unknownsConfig{
|
||||
RemoveWhenPackagesDefined: def.RemoveWhenPackagesDefined,
|
||||
ExecutablesWithoutPackages: def.IncludeExecutablesWithoutPackages,
|
||||
UnexpandedArchives: def.IncludeUnexpandedArchives,
|
||||
}
|
||||
}
|
||||
@@ -1,39 +1,41 @@
|
||||
module github.com/anchore/syft
|
||||
|
||||
go 1.22.0
|
||||
go 1.22.5
|
||||
|
||||
toolchain go1.22.8
|
||||
|
||||
require (
|
||||
github.com/CycloneDX/cyclonedx-go v0.9.0
|
||||
github.com/CycloneDX/cyclonedx-go v0.9.1
|
||||
github.com/Masterminds/semver v1.5.0
|
||||
github.com/Masterminds/sprig/v3 v3.3.0
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
|
||||
github.com/acobaugh/osrelease v0.1.0
|
||||
github.com/anchore/bubbly v0.0.0-20231115134915-def0aba654a9
|
||||
github.com/anchore/clio v0.0.0-20240522144804-d81e109008aa
|
||||
github.com/anchore/fangs v0.0.0-20240508143433-f016b099950f
|
||||
github.com/anchore/fangs v0.0.0-20240903175602-e716ef12c23d
|
||||
github.com/anchore/go-collections v0.0.0-20240216171411-9321230ce537
|
||||
github.com/anchore/go-logger v0.0.0-20230725134548-c21dafa1ec5a
|
||||
github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb
|
||||
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04
|
||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20240507183024-848e011fc24f
|
||||
github.com/anchore/stereoscope v0.0.3
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20241018175412-5c22e6360c4f
|
||||
github.com/anchore/stereoscope v0.0.5-0.20241018131503-a38c93517fc7
|
||||
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/bmatcuk/doublestar/v4 v4.7.1
|
||||
github.com/charmbracelet/bubbles v0.20.0
|
||||
github.com/charmbracelet/bubbletea v1.1.0
|
||||
github.com/charmbracelet/bubbletea v1.1.1
|
||||
github.com/charmbracelet/lipgloss v0.13.0
|
||||
github.com/dave/jennifer v1.7.1
|
||||
github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da
|
||||
github.com/distribution/reference v0.6.0
|
||||
github.com/docker/docker v27.2.1+incompatible
|
||||
github.com/docker/docker v27.3.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.3.1
|
||||
github.com/github/go-spdx/v2 v2.3.2
|
||||
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
|
||||
@@ -46,7 +48,7 @@ require (
|
||||
github.com/hashicorp/go-multierror v1.1.1
|
||||
github.com/iancoleman/strcase v0.3.0
|
||||
github.com/invopop/jsonschema v0.7.0
|
||||
github.com/jedib0t/go-pretty/v6 v6.5.9
|
||||
github.com/jedib0t/go-pretty/v6 v6.6.1
|
||||
github.com/jinzhu/copier v0.4.0
|
||||
github.com/kastenhq/goversion v0.0.0-20230811215019-93b2f8823953
|
||||
github.com/knqyf263/go-rpmdb v0.1.1
|
||||
@@ -79,17 +81,17 @@ require (
|
||||
github.com/zyedidia/generic v1.2.2-0.20230320175451-4410d2372cb1
|
||||
go.uber.org/goleak v1.3.0
|
||||
golang.org/x/mod v0.21.0
|
||||
golang.org/x/net v0.29.0
|
||||
golang.org/x/net v0.30.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
modernc.org/sqlite v1.33.0
|
||||
modernc.org/sqlite v1.33.1
|
||||
)
|
||||
|
||||
require google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 // indirect
|
||||
require google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 // indirect
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.4.0
|
||||
github.com/OneOfOne/xxhash v1.2.8
|
||||
github.com/adrg/xdg v0.5.0
|
||||
github.com/adrg/xdg v0.5.1
|
||||
github.com/magiconair/properties v1.8.7
|
||||
golang.org/x/exp v0.0.0-20231108232855-2478ac86f678
|
||||
)
|
||||
@@ -101,8 +103,8 @@ require (
|
||||
github.com/DataDog/zstd v1.5.5 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.3.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||
github.com/Microsoft/hcsshim v0.11.4 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/Microsoft/hcsshim v0.11.7 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.0.0 // indirect
|
||||
github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 // indirect
|
||||
github.com/andybalholm/brotli v1.0.4 // indirect
|
||||
@@ -115,16 +117,19 @@ require (
|
||||
github.com/charmbracelet/x/term v0.2.0 // indirect
|
||||
github.com/cloudflare/circl v1.3.8 // indirect
|
||||
github.com/containerd/cgroups v1.1.0 // indirect
|
||||
github.com/containerd/containerd v1.7.11 // indirect
|
||||
github.com/containerd/containerd v1.7.23 // indirect
|
||||
github.com/containerd/containerd/api v1.7.19 // indirect
|
||||
github.com/containerd/continuity v0.4.2 // indirect
|
||||
github.com/containerd/errdefs v0.3.0 // indirect
|
||||
github.com/containerd/fifo v1.1.0 // indirect
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/containerd/platforms v0.2.1 // indirect
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect
|
||||
github.com/containerd/ttrpc v1.2.2 // indirect
|
||||
github.com/containerd/ttrpc v1.2.5 // indirect
|
||||
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 v27.1.1+incompatible // indirect
|
||||
github.com/docker/cli v27.3.1+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
|
||||
@@ -135,18 +140,18 @@ require (
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
||||
github.com/felixge/fgprof v0.9.3 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.3 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.4 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.6 // 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
|
||||
github.com/go-logr/logr v1.2.4 // indirect
|
||||
github.com/go-logr/logr v1.4.1 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-restruct/restruct v1.2.0-alpha // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
@@ -175,17 +180,18 @@ require (
|
||||
github.com/moby/locker v1.0.1 // indirect
|
||||
github.com/moby/sys/sequential v0.5.0 // indirect
|
||||
github.com/moby/sys/signal v0.7.0 // indirect
|
||||
github.com/moby/sys/user v0.3.0 // indirect
|
||||
github.com/moby/sys/userns v0.1.0 // indirect
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||
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 // indirect
|
||||
github.com/opencontainers/runc v1.1.12 // indirect
|
||||
github.com/opencontainers/runtime-spec v1.1.0-rc.1 // indirect
|
||||
github.com/opencontainers/runtime-spec v1.1.0 // 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/pelletier/go-toml/v2 v2.2.2 // 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
|
||||
@@ -204,9 +210,9 @@ require (
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/cast v1.7.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/spf13/viper v1.18.2 // indirect
|
||||
github.com/spf13/viper v1.19.0 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/sylabs/sif/v2 v2.17.1 // indirect
|
||||
github.com/sylabs/sif/v2 v2.19.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
|
||||
@@ -221,22 +227,21 @@ require (
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 // indirect
|
||||
go.opentelemetry.io/otel v1.19.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.19.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.19.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
|
||||
go.opentelemetry.io/otel v1.24.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.24.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.24.0 // indirect
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
go.uber.org/multierr v1.9.0 // indirect
|
||||
golang.org/x/crypto v0.27.0 // indirect
|
||||
golang.org/x/crypto v0.28.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/sys v0.25.0 // indirect
|
||||
golang.org/x/term v0.24.0 // indirect
|
||||
golang.org/x/text v0.18.0 // indirect
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
|
||||
golang.org/x/sys v0.26.0 // indirect
|
||||
golang.org/x/term v0.25.0 // indirect
|
||||
golang.org/x/text v0.19.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect
|
||||
google.golang.org/grpc v1.59.0 // indirect
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c // indirect
|
||||
google.golang.org/grpc v1.62.1 // indirect
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
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
|
||||
|
||||
@@ -60,8 +60,8 @@ github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbi
|
||||
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=
|
||||
github.com/CycloneDX/cyclonedx-go v0.9.1 h1:yffaWOZsv77oTJa/SdVZYdgAgFioCeycBUKkqS2qzQM=
|
||||
github.com/CycloneDX/cyclonedx-go v0.9.1/go.mod h1:NE/EWvzELOFlG6+ljX/QeMlVt9VKcTwu8u0ccsACEsw=
|
||||
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
||||
github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ=
|
||||
github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
|
||||
@@ -74,10 +74,10 @@ github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lpr
|
||||
github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=
|
||||
github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
|
||||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
||||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
||||
github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8=
|
||||
github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/Microsoft/hcsshim v0.11.7 h1:vl/nj3Bar/CvJSYo7gIQPyRWc9f3c6IeSNavBTSZNZQ=
|
||||
github.com/Microsoft/hcsshim v0.11.7/go.mod h1:MV8xMfmECjl5HdO7U/3/hFVnkmSBjAjmA09d4bExKcU=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8=
|
||||
github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
|
||||
@@ -87,8 +87,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.5.0 h1:dDaZvhMXatArP1NPHhnfaQUqWBLBsmx1h1HXQdMoFCY=
|
||||
github.com/adrg/xdg v0.5.0/go.mod h1:dDdY4M4DF9Rjy4kHPeNL+ilVF+p2lK8IdM9/rTSGcI4=
|
||||
github.com/adrg/xdg v0.5.1 h1:Im8iDbEFARltY09yOJlSGu4Asjk2vF85+3Dyru8uJ0U=
|
||||
github.com/adrg/xdg v0.5.1/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ=
|
||||
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=
|
||||
@@ -99,8 +99,8 @@ github.com/anchore/bubbly v0.0.0-20231115134915-def0aba654a9 h1:p0ZIe0htYOX284Y4
|
||||
github.com/anchore/bubbly v0.0.0-20231115134915-def0aba654a9/go.mod h1:3ZsFB9tzW3vl4gEiUeuSOMDnwroWxIxJelOOHUp8dSw=
|
||||
github.com/anchore/clio v0.0.0-20240522144804-d81e109008aa h1:pwlAn4O9SBUnlgfa69YcqIynbUyobLVFYu8HxSoCffA=
|
||||
github.com/anchore/clio v0.0.0-20240522144804-d81e109008aa/go.mod h1:nD3H5uIvjxlfmakOBgtyFQbk5Zjp3l538kxfpHPslzI=
|
||||
github.com/anchore/fangs v0.0.0-20240508143433-f016b099950f h1:NOhzafCyNYFi88qxkBFjMzQo4dRa1vDhBzx+0Uovx8Q=
|
||||
github.com/anchore/fangs v0.0.0-20240508143433-f016b099950f/go.mod h1:sVpRS2yNCw6tLVpvA1QSDVWTJVpCuAm8JNZgn4Sjz/k=
|
||||
github.com/anchore/fangs v0.0.0-20240903175602-e716ef12c23d h1:ZD4wdCBgJJzJybjTUIEiiupLF7B9H3WLuBTjspBO2Mc=
|
||||
github.com/anchore/fangs v0.0.0-20240903175602-e716ef12c23d/go.mod h1:Xh4ObY3fmoMzOEVXwDtS1uK44JC7+nRD0n29/1KYFYg=
|
||||
github.com/anchore/go-collections v0.0.0-20240216171411-9321230ce537 h1:GjNGuwK5jWjJMyVppBjYS54eOiiSNv4Ba869k4wh72Q=
|
||||
github.com/anchore/go-collections v0.0.0-20240216171411-9321230ce537/go.mod h1:1aiktV46ATCkuVg0O573ZrH56BUawTECPETbZyBcqT8=
|
||||
github.com/anchore/go-logger v0.0.0-20230725134548-c21dafa1ec5a h1:nJ2G8zWKASyVClGVgG7sfM5mwoZlZ2zYpIzN2OhjWkw=
|
||||
@@ -113,10 +113,10 @@ github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04 h1:VzprUTpc0v
|
||||
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04/go.mod h1:6dK64g27Qi1qGQZ67gFmBFvEHScy0/C8qhQhNe5B5pQ=
|
||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b h1:e1bmaoJfZVsCYMrIZBpFxwV26CbsuoEh5muXD5I1Ods=
|
||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b/go.mod h1:Bkc+JYWjMCF8OyZ340IMSIi2Ebf3uwByOk6ho4wne1E=
|
||||
github.com/anchore/packageurl-go v0.1.1-0.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 h1:JRPHySy8S6P+Ff3IDiQ29ap1i8/laUQxDk9K1eFh/2U=
|
||||
github.com/anchore/stereoscope v0.0.3/go.mod h1:5DJheGPjVRsSqegTB24Zi6SCHnYQnA519yeIG+RG+I4=
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20241018175412-5c22e6360c4f h1:dAQPIrQ3a5PBqZeZ+B9NGZsGmodk4NO9OjDIsQmQyQM=
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20241018175412-5c22e6360c4f/go.mod h1:KoYIv7tdP5+CC9VGkeZV4/vGCKsY55VvoG+5dadg4YI=
|
||||
github.com/anchore/stereoscope v0.0.5-0.20241018131503-a38c93517fc7 h1:CBKDfuMWKRt12EnKtgT/an9FQSQI83mOU1KT57z81IU=
|
||||
github.com/anchore/stereoscope v0.0.5-0.20241018131503-a38c93517fc7/go.mod h1:wH79ZauZcd3erKPocg9+qhDC6Q/z125sD+Isi4d61a4=
|
||||
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=
|
||||
@@ -145,8 +145,8 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I=
|
||||
github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
|
||||
github.com/bmatcuk/doublestar/v4 v4.7.1 h1:fdDeAqgT47acgwd9bd9HxJRDmc9UAmPpc+2m0CXv75Q=
|
||||
github.com/bmatcuk/doublestar/v4 v4.7.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
|
||||
github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M=
|
||||
github.com/bradleyjkemp/cupaloy/v2 v2.8.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0=
|
||||
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
||||
@@ -159,8 +159,8 @@ 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.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE=
|
||||
github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU=
|
||||
github.com/charmbracelet/bubbletea v1.1.0 h1:FjAl9eAL3HBCHenhz/ZPjkKdScmaS5SK69JAK2YJK9c=
|
||||
github.com/charmbracelet/bubbletea v1.1.0/go.mod h1:9Ogk0HrdbHolIKHdjfFpyXJmiCzGwy+FesYkZr7hYU4=
|
||||
github.com/charmbracelet/bubbletea v1.1.1 h1:KJ2/DnmpfqFtDNVTvYZ6zpPFL9iRCRr0qqKOCvppbPY=
|
||||
github.com/charmbracelet/bubbletea v1.1.1/go.mod h1:9Ogk0HrdbHolIKHdjfFpyXJmiCzGwy+FesYkZr7hYU4=
|
||||
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.13.0 h1:4X3PPeoWEDCMvzDvGmTajSyYPcZM4+y8sCA/SsA3cjw=
|
||||
@@ -190,18 +190,24 @@ github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWH
|
||||
github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM=
|
||||
github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw=
|
||||
github.com/containerd/containerd v1.7.11 h1:lfGKw3eU35sjV0aG2eYZTiwFEY1pCzxdzicHP3SZILw=
|
||||
github.com/containerd/containerd v1.7.11/go.mod h1:5UluHxHTX2rdvYuZ5OJTC5m/KJNs0Zs9wVoJm9zf5ZE=
|
||||
github.com/containerd/containerd v1.7.23 h1:H2CClyUkmpKAGlhQp95g2WXHfLYc7whAuvZGBNYOOwQ=
|
||||
github.com/containerd/containerd v1.7.23/go.mod h1:7QUzfURqZWCZV7RLNEn1XjUCQLEf0bkaK4GjUaZehxw=
|
||||
github.com/containerd/containerd/api v1.7.19 h1:VWbJL+8Ap4Ju2mx9c9qS1uFSB1OVYr5JJrW2yT5vFoA=
|
||||
github.com/containerd/containerd/api v1.7.19/go.mod h1:fwGavl3LNwAV5ilJ0sbrABL44AQxmNjDRcwheXDb6Ig=
|
||||
github.com/containerd/continuity v0.4.2 h1:v3y/4Yz5jwnvqPKJJ+7Wf93fyWoCB3F5EclWG023MDM=
|
||||
github.com/containerd/continuity v0.4.2/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ=
|
||||
github.com/containerd/errdefs v0.3.0 h1:FSZgGOeK4yuT/+DnF07/Olde/q4KBoMsaamhXxIMDp4=
|
||||
github.com/containerd/errdefs v0.3.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
|
||||
github.com/containerd/fifo v1.1.0 h1:4I2mbh5stb1u6ycIABlBw9zgtlK8viPI9QkQNRQEEmY=
|
||||
github.com/containerd/fifo v1.1.0/go.mod h1:bmC4NWMbXlt2EZ0Hc7Fx7QzTFxgPID13eH0Qu+MAb2o=
|
||||
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||
github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=
|
||||
github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.14.3 h1:OqlDCK3ZVUO6C3B/5FSkDwbkEETK84kQgEeFwDC+62k=
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.14.3/go.mod h1:KY//uOCIkSuNAHhJogcZtrNHdKrA99/FCCRjE3HD36o=
|
||||
github.com/containerd/ttrpc v1.2.2 h1:9vqZr0pxwOF5koz6N0N3kJ0zDHokrcPxIR/ZR2YFtOs=
|
||||
github.com/containerd/ttrpc v1.2.2/go.mod h1:sIT6l32Ph/H9cvnJsfXM5drIVzTr5A2flTf1G5tYZak=
|
||||
github.com/containerd/ttrpc v1.2.5 h1:IFckT1EFQoFBMG4c3sMdT8EP3/aKfumK1msY+Ze4oLU=
|
||||
github.com/containerd/ttrpc v1.2.5/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o=
|
||||
github.com/containerd/typeurl/v2 v2.1.1 h1:3Q4Pt7i8nYwy2KmQWIw2+1hTvwTE/6w9FqcttATPO/4=
|
||||
github.com/containerd/typeurl/v2 v2.1.1/go.mod h1:IDp2JFvbwZ31H8dQbEIY7sDl2L3o3HZj1hsSQlywkQ0=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
@@ -225,12 +231,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 v27.1.1+incompatible h1:goaZxOqs4QKxznZjjBWKONQci/MywhtRv2oNn0GkeZE=
|
||||
github.com/docker/cli v27.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/cli v27.3.1+incompatible h1:qEGdFBF3Xu6SCvCYhc7CzaQTlBmqDuzxPDpigSyeKQQ=
|
||||
github.com/docker/cli v27.3.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
|
||||
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v27.2.1+incompatible h1:fQdiLfW7VLscyoeYEBz7/J8soYFDZV1u6VW6gJEjNMI=
|
||||
github.com/docker/docker v27.2.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v27.3.1+incompatible h1:KttF0XoteNTicmUtBO0L2tP+J7FGRFTjaEF4k6WdhfI=
|
||||
github.com/docker/docker v27.3.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=
|
||||
@@ -275,18 +281,18 @@ github.com/fatih/set v0.2.1 h1:nn2CaJyknWE/6txyUDGwysr3G5QC6xWB/PtVjPBbeaA=
|
||||
github.com/fatih/set v0.2.1/go.mod h1:+RKtMCH+favT2+3YecHGxcc0b4KyVWA1QWWJUs4E0CI=
|
||||
github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
|
||||
github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
|
||||
github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
|
||||
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
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.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I=
|
||||
github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s=
|
||||
github.com/gabriel-vasile/mimetype v1.4.6 h1:3+PzJTKLkvgjeTbts6msPJt4DixhT4YtFNf1gtGe3zc=
|
||||
github.com/gabriel-vasile/mimetype v1.4.6/go.mod h1:JX1qVKqZd40hUPpAfiNTe0Sne7hdfKSbOqqmkq8GCXc=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
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/github/go-spdx/v2 v2.3.2 h1:IfdyNHTqzs4zAJjXdVQfRnxt1XMfycXoHBE2Vsm1bjs=
|
||||
github.com/github/go-spdx/v2 v2.3.2/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=
|
||||
@@ -313,8 +319,8 @@ github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-restruct/restruct v1.2.0-alpha h1:2Lp474S/9660+SJjpVxoKuWX09JsXHSrdV7Nv3/gkvc=
|
||||
@@ -359,8 +365,8 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||
@@ -477,8 +483,8 @@ github.com/invopop/jsonschema v0.7.0 h1:2vgQcBz1n256N+FpX3Jq7Y17AjYt46Ig3zIWyy77
|
||||
github.com/invopop/jsonschema v0.7.0/go.mod h1:O9uiLokuu0+MGFlyiaqtWxwqJm41/+8Nj0lD7A36YH0=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||
github.com/jedib0t/go-pretty/v6 v6.5.9 h1:ACteMBRrrmm1gMsXe9PSTOClQ63IXDUt03H5U+UV8OU=
|
||||
github.com/jedib0t/go-pretty/v6 v6.5.9/go.mod h1:zbn98qrYlh95FIhwwsbIip0LYpwSG8SUOScs+v9/t0E=
|
||||
github.com/jedib0t/go-pretty/v6 v6.6.1 h1:iJ65Xjb680rHcikRj6DSIbzCex2huitmc7bDtxYVWyc=
|
||||
github.com/jedib0t/go-pretty/v6 v6.6.1/go.mod h1:zbn98qrYlh95FIhwwsbIip0LYpwSG8SUOScs+v9/t0E=
|
||||
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
|
||||
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
@@ -581,6 +587,10 @@ github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5
|
||||
github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo=
|
||||
github.com/moby/sys/signal v0.7.0 h1:25RW3d5TnQEoKvRbEKUGay6DCQ46IxAVTT9CUMgmsSI=
|
||||
github.com/moby/sys/signal v0.7.0/go.mod h1:GQ6ObYZfqacOwTtlXvcmh9A26dVRul/hbOZn88Kg8Tg=
|
||||
github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo=
|
||||
github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
|
||||
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
|
||||
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
|
||||
github.com/moby/term v0.0.0-20221205130635-1aeaba878587 h1:HfkjXDfhgVaN5rmueG8cL8KKeFNecRCXFhaJ2qZ5SKA=
|
||||
github.com/moby/term v0.0.0-20221205130635-1aeaba878587/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
@@ -609,10 +619,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
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=
|
||||
github.com/opencontainers/runtime-spec v1.1.0-rc.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/opencontainers/runtime-spec v1.1.0 h1:HHUyrt9mwHUjtasSbXSMvs4cyFxh+Bll4AjJ9odEGpg=
|
||||
github.com/opencontainers/runtime-spec v1.1.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/opencontainers/selinux v1.11.0 h1:+5Zbo97w3Lbmb3PeqQtpmTkMwsW5nRI3YaLpt7tQ7oU=
|
||||
github.com/opencontainers/selinux v1.11.0/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
@@ -622,8 +630,8 @@ github.com/pborman/indent v1.2.1/go.mod h1:FitS+t35kIYtB5xWTZAPhnmrxcciEEOdbyrrp
|
||||
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
||||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
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/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
github.com/pierrec/lz4/v4 v4.1.2/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=
|
||||
@@ -655,7 +663,6 @@ github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8b
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
|
||||
github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
|
||||
github.com/quasilyte/go-ruleguard/dsl v0.3.22 h1:wd8zkOhSNr+I+8Qeciml08ivDt1pSXe60+5DqOpCjPE=
|
||||
@@ -691,8 +698,8 @@ github.com/sassoftware/go-rpmutils v0.4.0/go.mod h1:3goNWi7PGAT3/dlql2lv3+MSN5jN
|
||||
github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e h1:7q6NSFZDeGfvvtIRwBrU/aegEYJYmvev0cHAwo17zZQ=
|
||||
github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e/go.mod h1:DkpGd78rljTxKAnTDPFqXSGxvETQnJyuSOQwsHycqfs=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/sebdah/goldie/v2 v2.5.3 h1:9ES/mNN+HNUbNWpVAlrzuZ7jE+Nrczbj8uFRjM7624Y=
|
||||
github.com/sebdah/goldie/v2 v2.5.3/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI=
|
||||
github.com/sebdah/goldie/v2 v2.5.5 h1:rx1mwF95RxZ3/83sdS4Yp7t2C5TCokvWP4TBRbAyEWY=
|
||||
github.com/sebdah/goldie/v2 v2.5.5/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI=
|
||||
github.com/secDre4mer/pkcs7 v0.0.0-20240322103146-665324a4461d h1:RQqyEogx5J6wPdoxqL132b100j8KjcVHO1c0KLRoIhc=
|
||||
github.com/secDre4mer/pkcs7 v0.0.0-20240322103146-665324a4461d/go.mod h1:PegD7EVqlN88z7TpCqH92hHP+GBpfomGCCnw1PFtNOA=
|
||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
@@ -705,7 +712,6 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
@@ -731,8 +737,8 @@ github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM=
|
||||
github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=
|
||||
github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
|
||||
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
|
||||
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
@@ -756,8 +762,8 @@ 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.17.1 h1:p6Sl0LWyShXBj2SBsS1dMOMIMrZHe8pwBnBrYt6uo4M=
|
||||
github.com/sylabs/sif/v2 v2.17.1/go.mod h1:XUGB6AQUXGkms3qPOPdevctT3lBLRLWZNWHVnt5HMKE=
|
||||
github.com/sylabs/sif/v2 v2.19.1 h1:1eeMmFc8elqJe60ZiWwXgL3gMheb0IP4GmNZ4q0IEA0=
|
||||
github.com/sylabs/sif/v2 v2.19.1/go.mod h1:U1SUhvl8X1JIxAylC0DYz1fa/Xba6EMZD1dGPGBH83E=
|
||||
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=
|
||||
@@ -824,20 +830,20 @@ go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 h1:x8Z78aZx8cOF0+Kkazoc7lwUNMGy0LrzEMxTm4BbTxg=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0/go.mod h1:62CPTSry9QZtOaSsE3tOzhx6LzDhHnXJ6xHeMNNiM6Q=
|
||||
go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs=
|
||||
go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
|
||||
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
|
||||
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU=
|
||||
go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE=
|
||||
go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8=
|
||||
go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o=
|
||||
go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A=
|
||||
go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg=
|
||||
go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo=
|
||||
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
|
||||
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
|
||||
go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8=
|
||||
go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E=
|
||||
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
|
||||
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
|
||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
|
||||
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
|
||||
@@ -864,8 +870,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
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.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
|
||||
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
|
||||
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
||||
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
||||
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=
|
||||
@@ -954,8 +960,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
|
||||
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.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
|
||||
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
|
||||
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
|
||||
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
|
||||
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=
|
||||
@@ -1062,20 +1068,19 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220906165534-d0df966e6959/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||
golang.org/x/sys v0.26.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.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
|
||||
golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
|
||||
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
|
||||
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
|
||||
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=
|
||||
@@ -1088,8 +1093,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
|
||||
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@@ -1261,12 +1266,12 @@ google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ6
|
||||
google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ=
|
||||
google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:J7XzRzVy1+IPwWHZUzoD0IccYZIrXILAQpc+Qy9CMhY=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 h1:JpwMPBpFN3uKhdaekDpiNlImDdkUAyiJ6ez/uxGaUSo=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:0xJLfVdJqpAPl8tDg1ujOCGzx6LFLttXT5NhllGOXY4=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f h1:ultW7fxlIvee4HYrtnaRPon9HpEgFk5zYpmfMgtKB5I=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go.mod h1:L9KNLi232K1/xB6f7AlSX692koaRnKaWSR0stBki0Yc=
|
||||
google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y=
|
||||
google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 h1:rIo7ocm2roD9DcFIX67Ym8icoGCKSARAiPljFhh5suQ=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2/go.mod h1:O1cOfN1Cy6QEYr7VxtjOyP5AdAuR0aJ/MYZaaof623Y=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c h1:lfpJ/2rWPa/kJgxyyXM8PrNnfCzcmxJ265mADgwmvLI=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
@@ -1294,8 +1299,8 @@ google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnD
|
||||
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||
google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||
google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
|
||||
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
|
||||
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
|
||||
google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk=
|
||||
google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE=
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
@@ -1310,8 +1315,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -1344,8 +1349,14 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ=
|
||||
modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
|
||||
modernc.org/ccgo/v4 v4.19.2 h1:lwQZgvboKD0jBwdaeVCTouxhxAyN6iawF3STraAal8Y=
|
||||
modernc.org/ccgo/v4 v4.19.2/go.mod h1:ysS3mxiMV38XGRTTcgo0DQTeTmAO4oCmJl1nX9VFI3s=
|
||||
modernc.org/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.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U=
|
||||
@@ -1354,8 +1365,12 @@ modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
||||
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
||||
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
|
||||
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
|
||||
modernc.org/sqlite v1.33.0 h1:WWkA/T2G17okiLGgKAj4/RMIvgyMT19yQ038160IeYk=
|
||||
modernc.org/sqlite v1.33.0/go.mod h1:9uQ9hF/pCZoYZK73D/ud5Z7cIRIILSZI8NdIemVMTX8=
|
||||
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.33.1 h1:trb6Z3YYoeM9eDL1O8do81kP+0ejv+YzgyFo+Gwy0nM=
|
||||
modernc.org/sqlite v1.33.1/go.mod h1:pXV2xHxhzXZsgT/RtTFAPY6JJDEvOTcTdwADQCCWD4k=
|
||||
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=
|
||||
|
||||
@@ -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.16"
|
||||
JSONSchemaVersion = "16.0.18"
|
||||
)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package file
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
@@ -19,12 +18,13 @@ func NewZipFileManifest(archivePath string) (ZipFileManifest, error) {
|
||||
zipReader, err := OpenZip(archivePath)
|
||||
manifest := make(ZipFileManifest)
|
||||
if err != nil {
|
||||
return manifest, fmt.Errorf("unable to open zip archive (%s): %w", archivePath, err)
|
||||
log.Debugf("unable to open zip archive (%s): %v", archivePath, err)
|
||||
return manifest, err
|
||||
}
|
||||
defer func() {
|
||||
err = zipReader.Close()
|
||||
if err != nil {
|
||||
log.Warnf("unable to close zip archive (%s): %+v", archivePath, err)
|
||||
log.Debugf("unable to close zip archive (%s): %+v", archivePath, err)
|
||||
}
|
||||
}()
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
)
|
||||
|
||||
// directoryEndLen, readByf, directoryEnd, and findSignatureInBlock were copied from the golang stdlib, specifically:
|
||||
@@ -46,7 +48,8 @@ func OpenZip(filepath string) (*ZipReadCloser, error) {
|
||||
// need to find the start of the archive and keep track of this offset.
|
||||
offset, err := findArchiveStartOffset(f, fi.Size())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot find beginning of zip archive=%q : %w", filepath, err)
|
||||
log.Debugf("cannot find beginning of zip archive=%q : %v", filepath, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := f.Seek(0, io.SeekStart); err != nil {
|
||||
@@ -56,13 +59,14 @@ func OpenZip(filepath string) (*ZipReadCloser, error) {
|
||||
if offset > math.MaxInt64 {
|
||||
return nil, fmt.Errorf("archive start offset too large: %v", offset)
|
||||
}
|
||||
offset64 := int64(offset) //nolint:gosec // lint bug, checked above: https://github.com/securego/gosec/issues/1187
|
||||
offset64 := int64(offset)
|
||||
|
||||
size := fi.Size() - offset64
|
||||
|
||||
r, err := zip.NewReader(io.NewSectionReader(f, offset64, size), size)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to open ZipReadCloser @ %q: %w", filepath, err)
|
||||
log.Debugf("unable to open ZipReadCloser @ %q: %v", filepath, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ZipReadCloser{
|
||||
@@ -183,7 +187,7 @@ func findDirectory64End(r io.ReaderAt, directoryEndOffset int64) (int64, error)
|
||||
if b.uint32() != 1 { // total number of disks
|
||||
return -1, nil // the file is not a valid zip64-file
|
||||
}
|
||||
return int64(p), nil //nolint:gosec
|
||||
return int64(p), nil
|
||||
}
|
||||
|
||||
// readDirectory64End reads the zip64 directory end and updates the
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package licenses
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
type licenseScannerKey struct{}
|
||||
|
||||
func SetContextLicenseScanner(ctx context.Context, s Scanner) context.Context {
|
||||
return context.WithValue(ctx, licenseScannerKey{}, s)
|
||||
}
|
||||
|
||||
func ContextLicenseScanner(ctx context.Context) Scanner {
|
||||
if s, ok := ctx.Value(licenseScannerKey{}).(Scanner); ok {
|
||||
return s
|
||||
}
|
||||
return NewDefaultScanner()
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package licenses
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/google/licensecheck"
|
||||
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/license"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
const (
|
||||
coverageThreshold = 75
|
||||
unknownLicenseType = "UNKNOWN"
|
||||
)
|
||||
|
||||
// Parse scans the contents of a license file to attempt to determine the type of license it is
|
||||
func Parse(reader io.Reader, l file.Location) (licenses []pkg.License, err error) {
|
||||
licenses = make([]pkg.License, 0)
|
||||
contents, err := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cov := licensecheck.Scan(contents)
|
||||
if cov.Percent < coverageThreshold {
|
||||
// unknown or no licenses here?
|
||||
return licenses, nil
|
||||
}
|
||||
|
||||
for _, m := range cov.Match {
|
||||
lic := pkg.NewLicenseFromLocations(m.ID, l)
|
||||
lic.Type = license.Concluded
|
||||
|
||||
licenses = append(licenses, lic)
|
||||
}
|
||||
|
||||
return licenses, nil
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package licenses
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"github.com/google/licensecheck"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
)
|
||||
|
||||
const coverageThreshold = 75 // determined by experimentation
|
||||
|
||||
type Scanner interface {
|
||||
IdentifyLicenseIDs(context.Context, io.Reader) ([]string, error)
|
||||
}
|
||||
|
||||
var _ Scanner = (*scanner)(nil)
|
||||
|
||||
type scanner struct {
|
||||
coverageThreshold float64 // between 0 and 100
|
||||
scanner func([]byte) licensecheck.Coverage
|
||||
}
|
||||
|
||||
// NewDefaultScanner returns a scanner that uses a new instance of the default licensecheck package scanner.
|
||||
func NewDefaultScanner() Scanner {
|
||||
s, err := licensecheck.NewScanner(licensecheck.BuiltinLicenses())
|
||||
if err != nil {
|
||||
log.WithFields("error", err).Trace("unable to create default license scanner")
|
||||
s = nil
|
||||
}
|
||||
return &scanner{
|
||||
coverageThreshold: coverageThreshold,
|
||||
scanner: s.Scan,
|
||||
}
|
||||
}
|
||||
|
||||
// TestingOnlyScanner returns a scanner that uses the built-in license scanner from the licensecheck package.
|
||||
// THIS IS ONLY MEANT FOR TEST CODE, NOT PRODUCTION CODE.
|
||||
func TestingOnlyScanner() Scanner {
|
||||
return &scanner{
|
||||
coverageThreshold: coverageThreshold,
|
||||
scanner: licensecheck.Scan,
|
||||
}
|
||||
}
|
||||
|
||||
func (s scanner) IdentifyLicenseIDs(_ context.Context, reader io.Reader) ([]string, error) {
|
||||
if s.scanner == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
content, err := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cov := s.scanner(content)
|
||||
if cov.Percent < s.coverageThreshold {
|
||||
// unknown or no licenses here?
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var ids []string
|
||||
for _, m := range cov.Match {
|
||||
ids = append(ids, m.ID)
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package licenses
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/license"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
// Search scans the contents of a license file to attempt to determine the type of license it is
|
||||
func Search(ctx context.Context, scanner Scanner, reader file.LocationReadCloser) (licenses []pkg.License, err error) {
|
||||
licenses = make([]pkg.License, 0)
|
||||
|
||||
ids, err := scanner.IdentifyLicenseIDs(ctx, reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, id := range ids {
|
||||
lic := pkg.NewLicenseFromLocations(id, reader.Location)
|
||||
lic.Type = license.Concluded
|
||||
|
||||
licenses = append(licenses, lic)
|
||||
}
|
||||
|
||||
return licenses, nil
|
||||
}
|
||||
@@ -9,15 +9,18 @@ func EvidentBy(catalog *pkg.Collection) []artifact.Relationship {
|
||||
var edges []artifact.Relationship
|
||||
for _, p := range catalog.Sorted() {
|
||||
for _, l := range p.Locations.ToSlice() {
|
||||
if v, exists := l.Annotations[pkg.EvidenceAnnotationKey]; !exists || v != pkg.PrimaryEvidenceAnnotation {
|
||||
// skip non-primary evidence from being expressed as a relationship.
|
||||
// note: this may be configurable in the future.
|
||||
continue
|
||||
kind := pkg.SupportingEvidenceAnnotation
|
||||
if v, exists := l.Annotations[pkg.EvidenceAnnotationKey]; exists {
|
||||
kind = v
|
||||
}
|
||||
|
||||
edges = append(edges, artifact.Relationship{
|
||||
From: p,
|
||||
To: l.Coordinates,
|
||||
Type: artifact.EvidentByRelationship,
|
||||
Data: map[string]string{
|
||||
"kind": kind,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,55 +25,102 @@ var (
|
||||
binaryMetadataTypes = []string{
|
||||
reflect.TypeOf(pkg.ELFBinaryPackageNoteJSONPayload{}).Name(),
|
||||
reflect.TypeOf(pkg.BinarySignature{}).Name(),
|
||||
reflect.TypeOf(pkg.JavaVMInstallation{}).Name(),
|
||||
}
|
||||
)
|
||||
|
||||
func ExcludeBinariesByFileOwnershipOverlap(accessor sbomsync.Accessor) {
|
||||
accessor.WriteToSBOM(func(s *sbom.SBOM) {
|
||||
for _, r := range s.Relationships {
|
||||
if excludeBinaryByFileOwnershipOverlap(r, s.Artifacts.Packages) {
|
||||
s.Artifacts.Packages.Delete(r.To.ID())
|
||||
s.Relationships = RemoveRelationshipsByID(s.Relationships, r.To.ID())
|
||||
if idToRemove := excludeByFileOwnershipOverlap(r, s.Artifacts.Packages); idToRemove != "" {
|
||||
s.Artifacts.Packages.Delete(idToRemove)
|
||||
s.Relationships = RemoveRelationshipsByID(s.Relationships, idToRemove)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// excludeBinaryByFileOwnershipOverlap will remove packages from a collection given the following properties are true
|
||||
// 1) the relationship between packages is OwnershipByFileOverlap
|
||||
// 2) the parent is an "os" package
|
||||
// 3) the child is a synthetic package generated by the binary cataloger
|
||||
// 4) the package names are identical
|
||||
// This was implemented as a way to help resolve: https://github.com/anchore/syft/issues/931
|
||||
func excludeBinaryByFileOwnershipOverlap(r artifact.Relationship, c *pkg.Collection) bool {
|
||||
// excludeByFileOwnershipOverlap will remove packages that should be overridden by a more authoritative package,
|
||||
// such as an OS package or a package from a cataloger with more specific information being raised up.
|
||||
func excludeByFileOwnershipOverlap(r artifact.Relationship, c *pkg.Collection) artifact.ID {
|
||||
if artifact.OwnershipByFileOverlapRelationship != r.Type {
|
||||
return false
|
||||
return ""
|
||||
}
|
||||
|
||||
parent := c.Package(r.From.ID())
|
||||
if parent == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
parentInExclusion := slices.Contains(osCatalogerTypes, parent.Type)
|
||||
if !parentInExclusion {
|
||||
return false
|
||||
return ""
|
||||
}
|
||||
|
||||
child := c.Package(r.To.ID())
|
||||
if child == nil {
|
||||
return false
|
||||
return ""
|
||||
}
|
||||
|
||||
if slices.Contains(binaryCatalogerTypes, child.Type) {
|
||||
return true
|
||||
if idToRemove := identifyOverlappingOSRelationship(parent, child); idToRemove != "" {
|
||||
return idToRemove
|
||||
}
|
||||
|
||||
if idToRemove := identifyOverlappingJVMRelationship(parent, child); idToRemove != "" {
|
||||
return idToRemove
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// identifyOverlappingJVMRelationship indicates the package to remove if this is a binary -> binary pkg relationship
|
||||
// with a java binary signature package and a more authoritative JVM release package.
|
||||
func identifyOverlappingJVMRelationship(parent *pkg.Package, child *pkg.Package) artifact.ID {
|
||||
if !slices.Contains(binaryCatalogerTypes, parent.Type) {
|
||||
return ""
|
||||
}
|
||||
|
||||
if !slices.Contains(binaryCatalogerTypes, child.Type) {
|
||||
return ""
|
||||
}
|
||||
|
||||
if child.Metadata == nil {
|
||||
return false
|
||||
return ""
|
||||
}
|
||||
|
||||
childMetadataType := reflect.TypeOf(child.Metadata)
|
||||
var (
|
||||
foundJVM bool
|
||||
idToRemove artifact.ID
|
||||
)
|
||||
for _, p := range []*pkg.Package{parent, child} {
|
||||
switch p.Metadata.(type) {
|
||||
case pkg.JavaVMInstallation:
|
||||
foundJVM = true
|
||||
default:
|
||||
idToRemove = p.ID()
|
||||
}
|
||||
}
|
||||
|
||||
return slices.Contains(binaryMetadataTypes, childMetadataType.Name())
|
||||
if foundJVM {
|
||||
return idToRemove
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// identifyOverlappingOSRelationship indicates the package ID to remove if this is an OS pkg -> bin pkg relationship.
|
||||
// This was implemented as a way to help resolve: https://github.com/anchore/syft/issues/931
|
||||
func identifyOverlappingOSRelationship(parent *pkg.Package, child *pkg.Package) artifact.ID {
|
||||
if !slices.Contains(osCatalogerTypes, parent.Type) {
|
||||
return ""
|
||||
}
|
||||
|
||||
if slices.Contains(binaryCatalogerTypes, child.Type) {
|
||||
return child.ID()
|
||||
}
|
||||
|
||||
if child.Metadata == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
if !slices.Contains(binaryMetadataTypes, reflect.TypeOf(child.Metadata).Name()) {
|
||||
return ""
|
||||
}
|
||||
|
||||
return child.ID()
|
||||
}
|
||||
|
||||
@@ -3,18 +3,17 @@ package relationship
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
func TestExclude(t *testing.T) {
|
||||
func TestExcludeByFileOwnershipOverlap(t *testing.T) {
|
||||
packageA := pkg.Package{Name: "package-a", Type: pkg.ApkPkg}
|
||||
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}
|
||||
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} {
|
||||
packageB := pkg.Package{Name: "package-b", Type: pkg.BinaryPkg, Metadata: pkg.JavaVMInstallation{}}
|
||||
packageC := pkg.Package{Name: "package-c", Type: pkg.BinaryPkg, Metadata: pkg.ELFBinaryPackageNoteJSONPayload{Type: "rpm"}}
|
||||
for _, p := range []*pkg.Package{&packageA, &packageB, &packageC} {
|
||||
p := p
|
||||
p.SetID()
|
||||
}
|
||||
@@ -26,73 +25,152 @@ func TestExclude(t *testing.T) {
|
||||
shouldExclude bool
|
||||
}{
|
||||
{
|
||||
name: "no exclusions from os -> python",
|
||||
// prove that OS -> bin exclusions are wired
|
||||
name: "exclusions from os -> elf binary (as RPM)",
|
||||
relationship: artifact.Relationship{
|
||||
Type: artifact.OwnershipByFileOverlapRelationship,
|
||||
From: packageA,
|
||||
To: packageB,
|
||||
},
|
||||
packages: pkg.NewCollection(packageA, packageB),
|
||||
shouldExclude: false,
|
||||
},
|
||||
{
|
||||
name: "exclusions from os -> binary",
|
||||
relationship: artifact.Relationship{
|
||||
Type: artifact.OwnershipByFileOverlapRelationship,
|
||||
From: packageA,
|
||||
To: packageC,
|
||||
From: packageA, // OS
|
||||
To: packageC, // ELF binary
|
||||
},
|
||||
packages: pkg.NewCollection(packageA, packageC),
|
||||
shouldExclude: true,
|
||||
},
|
||||
{
|
||||
name: "exclusions from os -> elf binary (as RPM)",
|
||||
// prove that bin -> JVM exclusions are wired
|
||||
name: "exclusions from binary -> binary with JVM metadata",
|
||||
relationship: artifact.Relationship{
|
||||
Type: artifact.OwnershipByFileOverlapRelationship,
|
||||
From: packageA,
|
||||
To: packageE,
|
||||
From: packageB, // binary with JVM metadata
|
||||
To: packageC, // binary
|
||||
},
|
||||
packages: pkg.NewCollection(packageA, packageE),
|
||||
packages: pkg.NewCollection(packageC, packageB),
|
||||
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{
|
||||
Type: artifact.OwnershipByFileOverlapRelationship,
|
||||
From: packageB,
|
||||
To: packageC,
|
||||
},
|
||||
packages: pkg.NewCollection(packageB, packageC),
|
||||
shouldExclude: false,
|
||||
},
|
||||
{
|
||||
name: "no exclusions for different package names",
|
||||
relationship: artifact.Relationship{
|
||||
Type: artifact.OwnershipByFileOverlapRelationship,
|
||||
From: packageA,
|
||||
To: packageD,
|
||||
},
|
||||
packages: pkg.NewCollection(packageA, packageD),
|
||||
shouldExclude: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
if !excludeBinaryByFileOwnershipOverlap(test.relationship, test.packages) && test.shouldExclude {
|
||||
actualExclude := excludeByFileOwnershipOverlap(test.relationship, test.packages)
|
||||
didExclude := actualExclude != ""
|
||||
if !didExclude && test.shouldExclude {
|
||||
t.Errorf("expected to exclude relationship %+v", test.relationship)
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func TestIdentifyOverlappingOSRelationship(t *testing.T) {
|
||||
packageA := pkg.Package{Name: "package-a", Type: pkg.ApkPkg} // OS package
|
||||
packageB := pkg.Package{Name: "package-b", Type: pkg.BinaryPkg}
|
||||
packageC := pkg.Package{Name: "package-c", Type: pkg.BinaryPkg, Metadata: pkg.BinarySignature{}}
|
||||
packageD := pkg.Package{Name: "package-d", Type: pkg.PythonPkg} // Language package
|
||||
packageE := pkg.Package{Name: "package-e", Type: pkg.BinaryPkg, Metadata: pkg.ELFBinaryPackageNoteJSONPayload{}}
|
||||
|
||||
for _, p := range []*pkg.Package{&packageA, &packageB, &packageC, &packageD, &packageE} {
|
||||
p.SetID()
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
parent *pkg.Package
|
||||
child *pkg.Package
|
||||
expectedID artifact.ID
|
||||
}{
|
||||
{
|
||||
name: "OS -> binary without metadata",
|
||||
parent: &packageA,
|
||||
child: &packageB,
|
||||
expectedID: packageB.ID(), // OS package to binary package, should return child ID
|
||||
},
|
||||
{
|
||||
name: "OS -> binary with binary metadata",
|
||||
parent: &packageA,
|
||||
child: &packageC,
|
||||
expectedID: packageC.ID(), // OS package to binary package with binary metadata, should return child ID
|
||||
},
|
||||
{
|
||||
name: "OS -> non-binary package",
|
||||
parent: &packageA,
|
||||
child: &packageD,
|
||||
expectedID: "", // OS package to non-binary package, no exclusion
|
||||
},
|
||||
{
|
||||
name: "OS -> binary with ELF metadata",
|
||||
parent: &packageA,
|
||||
child: &packageE,
|
||||
expectedID: packageE.ID(), // OS package to binary package with ELF metadata, should return child ID
|
||||
},
|
||||
{
|
||||
name: "non-OS parent",
|
||||
parent: &packageD, // non-OS package
|
||||
child: &packageC,
|
||||
expectedID: "", // non-OS parent, no exclusion
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
resultID := identifyOverlappingOSRelationship(tt.parent, tt.child)
|
||||
assert.Equal(t, tt.expectedID, resultID)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIdentifyOverlappingJVMRelationship(t *testing.T) {
|
||||
|
||||
packageA := pkg.Package{Name: "package-a", Type: pkg.BinaryPkg}
|
||||
packageB := pkg.Package{Name: "package-b", Type: pkg.BinaryPkg, Metadata: pkg.BinarySignature{}}
|
||||
packageC := pkg.Package{Name: "package-c", Type: pkg.BinaryPkg, Metadata: pkg.JavaVMInstallation{}}
|
||||
packageD := pkg.Package{Name: "package-d", Type: pkg.PythonPkg}
|
||||
packageE := pkg.Package{Name: "package-e", Type: pkg.BinaryPkg}
|
||||
|
||||
for _, p := range []*pkg.Package{&packageA, &packageB, &packageC, &packageD, &packageE} {
|
||||
p.SetID()
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
parent *pkg.Package
|
||||
child *pkg.Package
|
||||
expectedID artifact.ID
|
||||
}{
|
||||
{
|
||||
name: "binary -> binary with JVM installation",
|
||||
parent: &packageA,
|
||||
child: &packageC,
|
||||
expectedID: packageA.ID(), // JVM found, return BinaryPkg ID
|
||||
},
|
||||
{
|
||||
name: "binary -> binary with binary signature",
|
||||
parent: &packageA,
|
||||
child: &packageB,
|
||||
expectedID: "", // binary signatures only found, no exclusion
|
||||
},
|
||||
{
|
||||
name: "binary -> python (non-binary child)",
|
||||
parent: &packageA,
|
||||
child: &packageD,
|
||||
expectedID: "", // non-binary child, no exclusion
|
||||
},
|
||||
{
|
||||
name: "no JVM or signature in binary -> binary",
|
||||
parent: &packageA,
|
||||
child: &packageE,
|
||||
expectedID: "", // no JVM or binary signature, no exclusion
|
||||
},
|
||||
{
|
||||
name: "non-binary parent",
|
||||
parent: &packageD,
|
||||
child: &packageC,
|
||||
expectedID: "", // non-binary parent, no exclusion
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
resultID := identifyOverlappingJVMRelationship(tt.parent, tt.child)
|
||||
assert.Equal(t, tt.expectedID, resultID)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,20 +17,16 @@ type Index struct {
|
||||
|
||||
// NewIndex returns a new relationship Index
|
||||
func NewIndex(relationships ...artifact.Relationship) *Index {
|
||||
out := Index{}
|
||||
out := Index{
|
||||
fromID: make(map[artifact.ID]*mappedRelationships),
|
||||
toID: make(map[artifact.ID]*mappedRelationships),
|
||||
}
|
||||
out.Add(relationships...)
|
||||
return &out
|
||||
}
|
||||
|
||||
// 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{}
|
||||
}
|
||||
|
||||
// store appropriate indexes for stable ordering to minimize ID() calls
|
||||
for _, r := range relationships {
|
||||
// prevent duplicates
|
||||
@@ -68,6 +64,39 @@ func (i *Index) Add(relationships ...artifact.Relationship) {
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Index) Remove(id artifact.ID) {
|
||||
delete(i.fromID, id)
|
||||
delete(i.toID, id)
|
||||
|
||||
for idx := 0; idx < len(i.all); {
|
||||
if i.all[idx].from == id || i.all[idx].to == id {
|
||||
i.all = append(i.all[:idx], i.all[idx+1:]...)
|
||||
} else {
|
||||
idx++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Index) Replace(ogID artifact.ID, replacement artifact.Identifiable) {
|
||||
for _, mapped := range fromMappedByID(i.fromID, ogID) {
|
||||
i.Add(artifact.Relationship{
|
||||
From: replacement,
|
||||
To: mapped.relationship.To,
|
||||
Type: mapped.relationship.Type,
|
||||
})
|
||||
}
|
||||
|
||||
for _, mapped := range fromMappedByID(i.toID, ogID) {
|
||||
i.Add(artifact.Relationship{
|
||||
From: mapped.relationship.From,
|
||||
To: replacement,
|
||||
Type: mapped.relationship.Type,
|
||||
})
|
||||
}
|
||||
|
||||
i.Remove(ogID)
|
||||
}
|
||||
|
||||
// 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)
|
||||
@@ -110,10 +139,17 @@ func (i *Index) All(types ...artifact.RelationshipType) []artifact.Relationship
|
||||
}
|
||||
|
||||
func fromMapped(idMap map[artifact.ID]*mappedRelationships, identifiable artifact.Identifiable) []*sortableRelationship {
|
||||
if identifiable == nil || idMap == nil {
|
||||
if identifiable == nil {
|
||||
return nil
|
||||
}
|
||||
mapped := idMap[identifiable.ID()]
|
||||
return fromMappedByID(idMap, identifiable.ID())
|
||||
}
|
||||
|
||||
func fromMappedByID(idMap map[artifact.ID]*mappedRelationships, id artifact.ID) []*sortableRelationship {
|
||||
if idMap == nil {
|
||||
return nil
|
||||
}
|
||||
mapped := idMap[id]
|
||||
if mapped == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package relationship
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
@@ -231,3 +232,179 @@ func (i id) ID() artifact.ID {
|
||||
func slice[T any](values ...T) []T {
|
||||
return values
|
||||
}
|
||||
|
||||
func TestRemove(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,
|
||||
}
|
||||
|
||||
index := NewIndex(r1, r2, r3, r4, r5, r6, r7, r8)
|
||||
|
||||
assert.Equal(t, 8, len(index.All()))
|
||||
|
||||
// removal of p1 should remove r1, r2, and r3
|
||||
index.Remove(p1.ID())
|
||||
remaining := index.All()
|
||||
|
||||
assert.Equal(t, 5, len(remaining))
|
||||
assert.NotContains(t, remaining, r1)
|
||||
assert.NotContains(t, remaining, r2)
|
||||
assert.NotContains(t, remaining, r3)
|
||||
|
||||
assert.Empty(t, index.From(p1))
|
||||
assert.Empty(t, index.To(p1))
|
||||
|
||||
// removal of c1 should remove r5 and r7
|
||||
index.Remove(c1.ID())
|
||||
remaining = index.All()
|
||||
|
||||
// r8 remains since c3->c4 should still exist
|
||||
assert.Equal(t, 3, len(remaining))
|
||||
assert.NotContains(t, remaining, r5)
|
||||
assert.NotContains(t, remaining, r7)
|
||||
assert.Contains(t, remaining, r8)
|
||||
|
||||
assert.Empty(t, index.From(c1))
|
||||
assert.Empty(t, index.To(c1))
|
||||
|
||||
// removal of c3 should remove r8
|
||||
index.Remove(c3.ID())
|
||||
remaining = index.All()
|
||||
|
||||
assert.Equal(t, 2, len(remaining))
|
||||
assert.Contains(t, remaining, r4)
|
||||
assert.Contains(t, remaining, r6)
|
||||
|
||||
assert.Empty(t, index.From(c3))
|
||||
assert.Empty(t, index.To(c3))
|
||||
}
|
||||
|
||||
func TestReplace(t *testing.T) {
|
||||
p1 := pkg.Package{Name: "pkg-1"}
|
||||
p2 := pkg.Package{Name: "pkg-2"}
|
||||
p3 := pkg.Package{Name: "pkg-3"}
|
||||
p4 := pkg.Package{Name: "pkg-4"}
|
||||
|
||||
for _, p := range []*pkg.Package{&p1, &p2, &p3, &p4} {
|
||||
p.SetID()
|
||||
}
|
||||
|
||||
r1 := artifact.Relationship{
|
||||
From: p1,
|
||||
To: p2,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
}
|
||||
r2 := artifact.Relationship{
|
||||
From: p3,
|
||||
To: p1,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
}
|
||||
r3 := artifact.Relationship{
|
||||
From: p2,
|
||||
To: p3,
|
||||
Type: artifact.ContainsRelationship,
|
||||
}
|
||||
|
||||
index := NewIndex(r1, r2, r3)
|
||||
|
||||
// replace p1 with p4 in the relationships
|
||||
index.Replace(p1.ID(), &p4)
|
||||
|
||||
expectedRels := []artifact.Relationship{
|
||||
{
|
||||
From: p4, // replaced
|
||||
To: p2,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: p3,
|
||||
To: p4, // replaced
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: p2,
|
||||
To: p3,
|
||||
Type: artifact.ContainsRelationship,
|
||||
},
|
||||
}
|
||||
|
||||
compareRelationships(t, expectedRels, index.All())
|
||||
}
|
||||
|
||||
func compareRelationships(t testing.TB, expected, actual []artifact.Relationship) {
|
||||
assert.Equal(t, len(expected), len(actual), "number of relationships should match")
|
||||
for _, e := range expected {
|
||||
found := false
|
||||
for _, a := range actual {
|
||||
if a.From.ID() == e.From.ID() && a.To.ID() == e.To.ID() && a.Type == e.Type {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.True(t, found, "expected relationship not found: %+v", e)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReplace_NoExistingRelations(t *testing.T) {
|
||||
p1 := pkg.Package{Name: "pkg-1"}
|
||||
p2 := pkg.Package{Name: "pkg-2"}
|
||||
|
||||
p1.SetID()
|
||||
p2.SetID()
|
||||
|
||||
index := NewIndex()
|
||||
|
||||
index.Replace(p1.ID(), &p2)
|
||||
|
||||
allRels := index.All()
|
||||
assert.Len(t, allRels, 0)
|
||||
}
|
||||
|
||||
@@ -11,8 +11,10 @@ import (
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/sbomsync"
|
||||
"github.com/anchore/syft/internal/unknown"
|
||||
"github.com/anchore/syft/syft/event/monitor"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
)
|
||||
|
||||
type Executor struct {
|
||||
@@ -35,6 +37,12 @@ func NewTaskExecutor(tasks []Task, numWorkers int) *Executor {
|
||||
}
|
||||
|
||||
func (p *Executor) Execute(ctx context.Context, resolver file.Resolver, s sbomsync.Builder, prog *monitor.CatalogerTaskProgress) error {
|
||||
var lock sync.Mutex
|
||||
withLock := func(fn func()) {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
fn()
|
||||
}
|
||||
var errs error
|
||||
wg := &sync.WaitGroup{}
|
||||
for i := 0; i < p.numWorkers; i++ {
|
||||
@@ -48,9 +56,16 @@ func (p *Executor) Execute(ctx context.Context, resolver file.Resolver, s sbomsy
|
||||
return
|
||||
}
|
||||
|
||||
if err := runTaskSafely(ctx, tsk, resolver, s); err != nil {
|
||||
errs = multierror.Append(errs, fmt.Errorf("failed to run task: %w", err))
|
||||
prog.SetError(err)
|
||||
err := runTaskSafely(ctx, tsk, resolver, s)
|
||||
unknowns, err := unknown.ExtractCoordinateErrors(err)
|
||||
if len(unknowns) > 0 {
|
||||
appendUnknowns(s, tsk.Name(), unknowns)
|
||||
}
|
||||
if err != nil {
|
||||
withLock(func() {
|
||||
errs = multierror.Append(errs, fmt.Errorf("failed to run task: %w", err))
|
||||
prog.SetError(err)
|
||||
})
|
||||
}
|
||||
prog.Increment()
|
||||
}
|
||||
@@ -62,6 +77,19 @@ func (p *Executor) Execute(ctx context.Context, resolver file.Resolver, s sbomsy
|
||||
return errs
|
||||
}
|
||||
|
||||
func appendUnknowns(builder sbomsync.Builder, taskName string, unknowns []unknown.CoordinateError) {
|
||||
if accessor, ok := builder.(sbomsync.Accessor); ok {
|
||||
accessor.WriteToSBOM(func(sb *sbom.SBOM) {
|
||||
for _, u := range unknowns {
|
||||
if sb.Artifacts.Unknowns == nil {
|
||||
sb.Artifacts.Unknowns = map[file.Coordinates][]string{}
|
||||
}
|
||||
sb.Artifacts.Unknowns[u.Coordinates] = append(sb.Artifacts.Unknowns[u.Coordinates], formatUnknown(u.Reason.Error(), taskName))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func runTaskSafely(ctx context.Context, t Task, resolver file.Resolver, s sbomsync.Builder) (err error) {
|
||||
// handle individual cataloger panics
|
||||
defer func() {
|
||||
|
||||
+16
-21
@@ -3,7 +3,6 @@ package task
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"fmt"
|
||||
|
||||
"github.com/anchore/syft/internal/sbomsync"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
@@ -32,15 +31,12 @@ func NewFileDigestCatalogerTask(selection file.Selection, hashers ...crypto.Hash
|
||||
}
|
||||
|
||||
result, err := digestsCataloger.Catalog(ctx, resolver, coordinates...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to catalog file digests: %w", err)
|
||||
}
|
||||
|
||||
accessor.WriteToSBOM(func(sbom *sbom.SBOM) {
|
||||
sbom.Artifacts.FileDigests = result
|
||||
})
|
||||
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
return NewTask("file-digest-cataloger", fn)
|
||||
@@ -62,15 +58,12 @@ func NewFileMetadataCatalogerTask(selection file.Selection) Task {
|
||||
}
|
||||
|
||||
result, err := metadataCataloger.Catalog(ctx, resolver, coordinates...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
accessor.WriteToSBOM(func(sbom *sbom.SBOM) {
|
||||
sbom.Artifacts.FileMetadata = result
|
||||
})
|
||||
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
return NewTask("file-metadata-cataloger", fn)
|
||||
@@ -87,15 +80,12 @@ func NewFileContentCatalogerTask(cfg filecontent.Config) Task {
|
||||
accessor := builder.(sbomsync.Accessor)
|
||||
|
||||
result, err := cat.Catalog(ctx, resolver)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
accessor.WriteToSBOM(func(sbom *sbom.SBOM) {
|
||||
sbom.Artifacts.FileContents = result
|
||||
})
|
||||
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
return NewTask("file-content-cataloger", fn)
|
||||
@@ -112,15 +102,12 @@ func NewExecutableCatalogerTask(selection file.Selection, cfg executable.Config)
|
||||
accessor := builder.(sbomsync.Accessor)
|
||||
|
||||
result, err := cat.Catalog(resolver)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
accessor.WriteToSBOM(func(sbom *sbom.SBOM) {
|
||||
sbom.Artifacts.Executables = result
|
||||
})
|
||||
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
return NewTask("file-executable-cataloger", fn)
|
||||
@@ -134,9 +121,10 @@ func coordinatesForSelection(selection file.Selection, accessor sbomsync.Accesso
|
||||
}
|
||||
|
||||
if selection == file.FilesOwnedByPackageSelection {
|
||||
var coordinates []file.Coordinates
|
||||
var coordinates file.CoordinateSet
|
||||
|
||||
accessor.ReadFromSBOM(func(sbom *sbom.SBOM) {
|
||||
// get any file coordinates that are owned by a package
|
||||
for _, r := range sbom.Relationships {
|
||||
if r.Type != artifact.ContainsRelationship {
|
||||
continue
|
||||
@@ -145,16 +133,23 @@ func coordinatesForSelection(selection file.Selection, accessor sbomsync.Accesso
|
||||
continue
|
||||
}
|
||||
if c, ok := r.To.(file.Coordinates); ok {
|
||||
coordinates = append(coordinates, c)
|
||||
coordinates.Add(c)
|
||||
}
|
||||
}
|
||||
|
||||
// get any file coordinates referenced by a package directly
|
||||
for p := range sbom.Artifacts.Packages.Enumerate() {
|
||||
coordinates.Add(p.Locations.CoordinateSet().ToSlice()...)
|
||||
}
|
||||
})
|
||||
|
||||
if len(coordinates) == 0 {
|
||||
coords := coordinates.ToSlice()
|
||||
|
||||
if len(coords) == 0 {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return coordinates, true
|
||||
return coords, true
|
||||
}
|
||||
|
||||
return nil, false
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
"github.com/anchore/syft/internal/bus"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/relationship"
|
||||
"github.com/anchore/syft/internal/sbomsync"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/cataloging"
|
||||
@@ -27,6 +28,7 @@ type packageTaskFactory func(cfg CatalogingFactoryConfig) Task
|
||||
type PackageTaskFactories []packageTaskFactory
|
||||
|
||||
type CatalogingFactoryConfig struct {
|
||||
ComplianceConfig cataloging.ComplianceConfig
|
||||
SearchConfig cataloging.SearchConfig
|
||||
RelationshipsConfig cataloging.RelationshipsConfig
|
||||
DataGenerationConfig cataloging.DataGenerationConfig
|
||||
@@ -35,6 +37,7 @@ type CatalogingFactoryConfig struct {
|
||||
|
||||
func DefaultCatalogingFactoryConfig() CatalogingFactoryConfig {
|
||||
return CatalogingFactoryConfig{
|
||||
ComplianceConfig: cataloging.DefaultComplianceConfig(),
|
||||
SearchConfig: cataloging.DefaultSearchConfig(),
|
||||
RelationshipsConfig: cataloging.DefaultRelationshipsConfig(),
|
||||
DataGenerationConfig: cataloging.DefaultDataGenerationConfig(),
|
||||
@@ -82,7 +85,7 @@ func (f PackageTaskFactories) Tasks(cfg CatalogingFactoryConfig) ([]Task, error)
|
||||
}
|
||||
|
||||
// NewPackageTask creates a Task function for a generic pkg.Cataloger, honoring the common configuration options.
|
||||
func NewPackageTask(cfg CatalogingFactoryConfig, c pkg.Cataloger, tags ...string) Task { //nolint: funlen
|
||||
func NewPackageTask(cfg CatalogingFactoryConfig, c pkg.Cataloger, tags ...string) Task {
|
||||
fn := func(ctx context.Context, resolver file.Resolver, sbom sbomsync.Builder) error {
|
||||
catalogerName := c.Name()
|
||||
log.WithFields("name", catalogerName).Trace("starting package cataloger")
|
||||
@@ -100,48 +103,12 @@ func NewPackageTask(cfg CatalogingFactoryConfig, c pkg.Cataloger, tags ...string
|
||||
t := bus.StartCatalogerTask(info, -1, "")
|
||||
|
||||
pkgs, relationships, err := c.Catalog(ctx, resolver)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to catalog packages with %q: %w", catalogerName, err)
|
||||
}
|
||||
|
||||
log.WithFields("cataloger", catalogerName).Debugf("discovered %d packages", len(pkgs))
|
||||
|
||||
for i, p := range pkgs {
|
||||
if p.FoundBy == "" {
|
||||
p.FoundBy = catalogerName
|
||||
}
|
||||
pkgs, relationships = finalizePkgCatalogerResults(cfg, resolver, catalogerName, pkgs, relationships)
|
||||
|
||||
if cfg.DataGenerationConfig.GenerateCPEs && !hasAuthoritativeCPE(p.CPEs) {
|
||||
// generate CPEs (note: this is excluded from package ID, so is safe to mutate)
|
||||
// we might have binary classified CPE already with the package so we want to append here
|
||||
dictionaryCPEs, ok := cpeutils.DictionaryFind(p)
|
||||
if ok {
|
||||
log.Tracef("used CPE dictionary to find CPEs for %s package %q: %s", p.Type, p.Name, dictionaryCPEs)
|
||||
p.CPEs = append(p.CPEs, dictionaryCPEs...)
|
||||
} else {
|
||||
p.CPEs = append(p.CPEs, cpeutils.Generate(p)...)
|
||||
}
|
||||
}
|
||||
|
||||
// if we were not able to identify the language we have an opportunity
|
||||
// to try and get this value from the PURL. Worst case we assert that
|
||||
// we could not identify the language at either stage and set UnknownLanguage
|
||||
if p.Language == "" {
|
||||
p.Language = pkg.LanguageFromPURL(p.PURL)
|
||||
}
|
||||
|
||||
if cfg.RelationshipsConfig.PackageFileOwnership {
|
||||
// create file-to-package relationships for files owned by the package
|
||||
owningRelationships, err := packageFileOwnershipRelationships(p, resolver)
|
||||
if err != nil {
|
||||
log.Warnf("unable to create any package-file relationships for package name=%q type=%q: %w", p.Name, p.Type, err)
|
||||
} else {
|
||||
relationships = append(relationships, owningRelationships...)
|
||||
}
|
||||
}
|
||||
|
||||
pkgs[i] = p
|
||||
}
|
||||
pkgs, relationships = applyCompliance(cfg.ComplianceConfig, pkgs, relationships)
|
||||
|
||||
sbom.AddPackages(pkgs...)
|
||||
sbom.AddRelationships(relationships...)
|
||||
@@ -150,13 +117,143 @@ func NewPackageTask(cfg CatalogingFactoryConfig, c pkg.Cataloger, tags ...string
|
||||
t.SetCompleted()
|
||||
log.WithFields("name", catalogerName).Trace("package cataloger completed")
|
||||
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
tags = append(tags, pkgcataloging.PackageTag)
|
||||
|
||||
return NewTask(c.Name(), fn, tags...)
|
||||
}
|
||||
|
||||
func finalizePkgCatalogerResults(cfg CatalogingFactoryConfig, resolver file.PathResolver, catalogerName string, pkgs []pkg.Package, relationships []artifact.Relationship) ([]pkg.Package, []artifact.Relationship) {
|
||||
for i, p := range pkgs {
|
||||
if p.FoundBy == "" {
|
||||
p.FoundBy = catalogerName
|
||||
}
|
||||
|
||||
if cfg.DataGenerationConfig.GenerateCPEs && !hasAuthoritativeCPE(p.CPEs) {
|
||||
// generate CPEs (note: this is excluded from package ID, so is safe to mutate)
|
||||
// we might have binary classified CPE already with the package so we want to append here
|
||||
dictionaryCPEs, ok := cpeutils.DictionaryFind(p)
|
||||
if ok {
|
||||
log.Tracef("used CPE dictionary to find CPEs for %s package %q: %s", p.Type, p.Name, dictionaryCPEs)
|
||||
p.CPEs = append(p.CPEs, dictionaryCPEs...)
|
||||
} else {
|
||||
p.CPEs = append(p.CPEs, cpeutils.Generate(p)...)
|
||||
}
|
||||
}
|
||||
|
||||
// if we were not able to identify the language we have an opportunity
|
||||
// to try and get this value from the PURL. Worst case we assert that
|
||||
// we could not identify the language at either stage and set UnknownLanguage
|
||||
if p.Language == "" {
|
||||
p.Language = pkg.LanguageFromPURL(p.PURL)
|
||||
}
|
||||
|
||||
if cfg.RelationshipsConfig.PackageFileOwnership {
|
||||
// create file-to-package relationships for files owned by the package
|
||||
owningRelationships, err := packageFileOwnershipRelationships(p, resolver)
|
||||
if err != nil {
|
||||
log.Warnf("unable to create any package-file relationships for package name=%q type=%q: %v", p.Name, p.Type, err)
|
||||
} else {
|
||||
relationships = append(relationships, owningRelationships...)
|
||||
}
|
||||
}
|
||||
|
||||
pkgs[i] = p
|
||||
}
|
||||
return pkgs, relationships
|
||||
}
|
||||
|
||||
type packageReplacement struct {
|
||||
original artifact.ID
|
||||
pkg pkg.Package
|
||||
}
|
||||
|
||||
func applyCompliance(cfg cataloging.ComplianceConfig, pkgs []pkg.Package, relationships []artifact.Relationship) ([]pkg.Package, []artifact.Relationship) {
|
||||
remainingPkgs, droppedPkgs, replacements := filterNonCompliantPackages(pkgs, cfg)
|
||||
|
||||
relIdx := relationship.NewIndex(relationships...)
|
||||
for _, p := range droppedPkgs {
|
||||
relIdx.Remove(p.ID())
|
||||
}
|
||||
|
||||
for _, replacement := range replacements {
|
||||
relIdx.Replace(replacement.original, replacement.pkg)
|
||||
}
|
||||
|
||||
return remainingPkgs, relIdx.All()
|
||||
}
|
||||
|
||||
func filterNonCompliantPackages(pkgs []pkg.Package, cfg cataloging.ComplianceConfig) ([]pkg.Package, []pkg.Package, []packageReplacement) {
|
||||
var remainingPkgs, droppedPkgs []pkg.Package
|
||||
var replacements []packageReplacement
|
||||
for _, p := range pkgs {
|
||||
keep, replacement := applyComplianceRules(&p, cfg)
|
||||
if keep {
|
||||
remainingPkgs = append(remainingPkgs, p)
|
||||
} else {
|
||||
droppedPkgs = append(droppedPkgs, p)
|
||||
}
|
||||
if replacement != nil {
|
||||
replacements = append(replacements, *replacement)
|
||||
}
|
||||
}
|
||||
|
||||
return remainingPkgs, droppedPkgs, replacements
|
||||
}
|
||||
|
||||
func applyComplianceRules(p *pkg.Package, cfg cataloging.ComplianceConfig) (bool, *packageReplacement) {
|
||||
var drop bool
|
||||
var replacement *packageReplacement
|
||||
|
||||
applyComplianceRule := func(value, fieldName string, action cataloging.ComplianceAction) bool {
|
||||
if strings.TrimSpace(value) != "" {
|
||||
return false
|
||||
}
|
||||
|
||||
loc := "unknown"
|
||||
locs := p.Locations.ToSlice()
|
||||
if len(locs) > 0 {
|
||||
loc = locs[0].Path()
|
||||
}
|
||||
switch action {
|
||||
case cataloging.ComplianceActionDrop:
|
||||
log.WithFields("pkg", p.String(), "location", loc).Debugf("package with missing %s, dropping", fieldName)
|
||||
drop = true
|
||||
|
||||
case cataloging.ComplianceActionStub:
|
||||
log.WithFields("pkg", p.String(), "location", loc).Debugf("package with missing %s, stubbing with default value", fieldName)
|
||||
return true
|
||||
|
||||
case cataloging.ComplianceActionKeep:
|
||||
log.WithFields("pkg", p.String(), "location", loc).Tracef("package with missing %s, taking no action", fieldName)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
ogID := p.ID()
|
||||
|
||||
if applyComplianceRule(p.Name, "name", cfg.MissingName) {
|
||||
p.Name = cataloging.UnknownStubValue
|
||||
p.SetID()
|
||||
}
|
||||
|
||||
if applyComplianceRule(p.Version, "version", cfg.MissingVersion) {
|
||||
p.Version = cataloging.UnknownStubValue
|
||||
p.SetID()
|
||||
}
|
||||
|
||||
newID := p.ID()
|
||||
if newID != ogID {
|
||||
replacement = &packageReplacement{
|
||||
original: ogID,
|
||||
pkg: *p,
|
||||
}
|
||||
}
|
||||
|
||||
return !drop, replacement
|
||||
}
|
||||
|
||||
func hasAuthoritativeCPE(cpes []cpe.CPE) bool {
|
||||
for _, c := range cpes {
|
||||
if c.Source != cpe.GeneratedSource {
|
||||
|
||||
@@ -4,8 +4,13 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/cataloging"
|
||||
"github.com/anchore/syft/syft/cpe"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
func Test_hasAuthoritativeCPE(t *testing.T) {
|
||||
@@ -53,3 +58,83 @@ func Test_hasAuthoritativeCPE(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyCompliance(t *testing.T) {
|
||||
p1 := pkg.Package{Name: "pkg-1", Version: "1.0"}
|
||||
p2 := pkg.Package{Name: "", Version: "1.0"} // missing name
|
||||
p3 := pkg.Package{Name: "pkg-3", Version: ""} // missing version
|
||||
p4 := pkg.Package{Name: "pkg-4", Version: ""} // missing version
|
||||
c1 := file.Coordinates{RealPath: "/coords/1"}
|
||||
c2 := file.Coordinates{RealPath: "/coords/2"}
|
||||
|
||||
for _, p := range []*pkg.Package{&p1, &p2, &p3, &p4} {
|
||||
p.SetID()
|
||||
}
|
||||
|
||||
r1 := artifact.Relationship{
|
||||
From: p1,
|
||||
To: c1,
|
||||
Type: artifact.ContainsRelationship,
|
||||
}
|
||||
r2 := artifact.Relationship{
|
||||
From: p2,
|
||||
To: c2,
|
||||
Type: artifact.ContainsRelationship,
|
||||
}
|
||||
|
||||
cfg := cataloging.ComplianceConfig{
|
||||
MissingName: cataloging.ComplianceActionDrop,
|
||||
MissingVersion: cataloging.ComplianceActionStub,
|
||||
}
|
||||
|
||||
remainingPkgs, remainingRels := applyCompliance(cfg, []pkg.Package{p1, p2, p3, p4}, []artifact.Relationship{r1, r2})
|
||||
|
||||
// p2 should be dropped because it has a missing name, p3 and p4 should pass with a warning for the missing version
|
||||
assert.Len(t, remainingPkgs, 3) // p1, p3, p4 should remain
|
||||
assert.Len(t, remainingRels, 1) // only r1 should remain (relationship involving p1)
|
||||
}
|
||||
|
||||
func TestFilterNonCompliantPackages(t *testing.T) {
|
||||
p1 := pkg.Package{Name: "pkg-1", Version: "1.0"}
|
||||
p2 := pkg.Package{Name: "", Version: "1.0"} // missing name
|
||||
p3 := pkg.Package{Name: "pkg-3", Version: ""} // missing version
|
||||
|
||||
for _, p := range []*pkg.Package{&p1, &p2, &p3} {
|
||||
p.SetID()
|
||||
}
|
||||
|
||||
cfg := cataloging.ComplianceConfig{
|
||||
MissingName: cataloging.ComplianceActionDrop,
|
||||
MissingVersion: cataloging.ComplianceActionKeep,
|
||||
}
|
||||
|
||||
remainingPkgs, droppedPkgs, replacement := filterNonCompliantPackages([]pkg.Package{p1, p2, p3}, cfg)
|
||||
require.Nil(t, replacement)
|
||||
|
||||
// p2 should be dropped because it has a missing name
|
||||
assert.Len(t, remainingPkgs, 2)
|
||||
assert.Len(t, droppedPkgs, 1)
|
||||
assert.Equal(t, p2, droppedPkgs[0])
|
||||
}
|
||||
|
||||
func TestApplyComplianceRules_DropAndStub(t *testing.T) {
|
||||
p := pkg.Package{Name: "", Version: ""}
|
||||
p.SetID()
|
||||
ogID := p.ID()
|
||||
|
||||
cfg := cataloging.ComplianceConfig{
|
||||
MissingName: cataloging.ComplianceActionDrop,
|
||||
MissingVersion: cataloging.ComplianceActionStub,
|
||||
}
|
||||
|
||||
isCompliant, replacement := applyComplianceRules(&p, cfg)
|
||||
require.NotNil(t, replacement)
|
||||
assert.Equal(t, packageReplacement{
|
||||
original: ogID,
|
||||
pkg: p,
|
||||
}, *replacement)
|
||||
|
||||
// the package should be dropped due to missing name (drop action) and its version should be stubbed
|
||||
assert.False(t, isCompliant)
|
||||
assert.Equal(t, cataloging.UnknownStubValue, p.Version)
|
||||
}
|
||||
|
||||
@@ -34,6 +34,21 @@ import (
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/wordpress"
|
||||
)
|
||||
|
||||
const (
|
||||
// Java ecosystem labels
|
||||
Java = "java"
|
||||
Maven = "maven"
|
||||
|
||||
// Go ecosystem labels
|
||||
Go = "go"
|
||||
Golang = "golang"
|
||||
|
||||
// JavaScript ecosystem labels
|
||||
JavaScript = "javascript"
|
||||
Node = "node"
|
||||
NPM = "npm"
|
||||
)
|
||||
|
||||
//nolint:funlen
|
||||
func DefaultPackageTaskFactories() PackageTaskFactories {
|
||||
return []packageTaskFactory{
|
||||
@@ -49,7 +64,7 @@ func DefaultPackageTaskFactories() PackageTaskFactories {
|
||||
|
||||
// language-specific package installed catalogers ///////////////////////////////////////////////////////////////////////////
|
||||
newSimplePackageTaskFactory(cpp.NewConanInfoCataloger, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, pkgcataloging.LanguageTag, "cpp", "conan"),
|
||||
newSimplePackageTaskFactory(javascript.NewPackageCataloger, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, pkgcataloging.LanguageTag, "javascript", "node"),
|
||||
newSimplePackageTaskFactory(javascript.NewPackageCataloger, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, pkgcataloging.LanguageTag, JavaScript, Node),
|
||||
newSimplePackageTaskFactory(php.NewComposerInstalledCataloger, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, pkgcataloging.LanguageTag, "php", "composer"),
|
||||
newSimplePackageTaskFactory(r.NewPackageCataloger, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, pkgcataloging.LanguageTag, "r"),
|
||||
newSimplePackageTaskFactory(ruby.NewInstalledGemSpecCataloger, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, pkgcataloging.LanguageTag, "ruby", "gem", "gemspec"),
|
||||
@@ -67,20 +82,20 @@ func DefaultPackageTaskFactories() PackageTaskFactories {
|
||||
func(cfg CatalogingFactoryConfig) pkg.Cataloger {
|
||||
return golang.NewGoModuleFileCataloger(cfg.PackagesConfig.Golang)
|
||||
},
|
||||
pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "go", "golang", "gomod",
|
||||
pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, Go, Golang, "gomod",
|
||||
),
|
||||
newSimplePackageTaskFactory(java.NewGradleLockfileCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "java", "gradle"),
|
||||
newSimplePackageTaskFactory(java.NewGradleLockfileCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, Java, "gradle"),
|
||||
newPackageTaskFactory(
|
||||
func(cfg CatalogingFactoryConfig) pkg.Cataloger {
|
||||
return java.NewPomCataloger(cfg.PackagesConfig.JavaArchive)
|
||||
},
|
||||
pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "java", "maven",
|
||||
pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, Java, Maven,
|
||||
),
|
||||
newPackageTaskFactory(
|
||||
func(cfg CatalogingFactoryConfig) pkg.Cataloger {
|
||||
return javascript.NewLockCataloger(cfg.PackagesConfig.JavaScript)
|
||||
},
|
||||
pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "javascript", "node", "npm",
|
||||
pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, JavaScript, Node, NPM,
|
||||
),
|
||||
newSimplePackageTaskFactory(php.NewComposerLockCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "php", "composer"),
|
||||
newSimplePackageTaskFactory(php.NewPeclCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, pkgcataloging.ImageTag, "php", "pecl"),
|
||||
@@ -105,15 +120,15 @@ func DefaultPackageTaskFactories() PackageTaskFactories {
|
||||
func(cfg CatalogingFactoryConfig) pkg.Cataloger {
|
||||
return golang.NewGoModuleBinaryCataloger(cfg.PackagesConfig.Golang)
|
||||
},
|
||||
pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, pkgcataloging.LanguageTag, "go", "golang", "gomod", "binary",
|
||||
pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, pkgcataloging.LanguageTag, Go, Golang, "gomod", "binary",
|
||||
),
|
||||
newPackageTaskFactory(
|
||||
func(cfg CatalogingFactoryConfig) pkg.Cataloger {
|
||||
return java.NewArchiveCataloger(cfg.PackagesConfig.JavaArchive)
|
||||
},
|
||||
pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, pkgcataloging.LanguageTag, "java", "maven",
|
||||
pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, pkgcataloging.LanguageTag, Java, Maven,
|
||||
),
|
||||
newSimplePackageTaskFactory(java.NewNativeImageCataloger, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, pkgcataloging.LanguageTag, "java"),
|
||||
newSimplePackageTaskFactory(java.NewNativeImageCataloger, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, pkgcataloging.LanguageTag, Java),
|
||||
newSimplePackageTaskFactory(nix.NewStoreCataloger, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, pkgcataloging.LanguageTag, "nix"),
|
||||
newSimplePackageTaskFactory(lua.NewPackageCataloger, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, pkgcataloging.LanguageTag, "lua"),
|
||||
|
||||
@@ -127,6 +142,7 @@ func DefaultPackageTaskFactories() PackageTaskFactories {
|
||||
newSimplePackageTaskFactory(binary.NewELFPackageCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, "binary", "elf-package"),
|
||||
newSimplePackageTaskFactory(githubactions.NewActionUsageCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, "github", "github-actions"),
|
||||
newSimplePackageTaskFactory(githubactions.NewWorkflowUsageCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, "github", "github-actions"),
|
||||
newSimplePackageTaskFactory(java.NewJvmDistributionCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, "java", "jvm", "jdk", "jre"),
|
||||
newPackageTaskFactory(
|
||||
func(cfg CatalogingFactoryConfig) pkg.Cataloger {
|
||||
return kernel.NewLinuxKernelCataloger(cfg.PackagesConfig.LinuxKernel)
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/mholt/archiver/v3"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/sbomsync"
|
||||
"github.com/anchore/syft/syft/cataloging"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
)
|
||||
|
||||
const unknownsLabelerTaskName = "unknowns-labeler"
|
||||
|
||||
func NewUnknownsLabelerTask(cfg cataloging.UnknownsConfig) Task {
|
||||
return NewTask(unknownsLabelerTaskName, unknownsLabelerTask{cfg}.processUnknowns)
|
||||
}
|
||||
|
||||
type unknownsLabelerTask struct {
|
||||
cataloging.UnknownsConfig
|
||||
}
|
||||
|
||||
// processUnknowns removes unknown entries that have valid packages reported for the locations
|
||||
func (c unknownsLabelerTask) processUnknowns(_ context.Context, resolver file.Resolver, builder sbomsync.Builder) error {
|
||||
accessor := builder.(sbomsync.Accessor)
|
||||
accessor.WriteToSBOM(func(s *sbom.SBOM) {
|
||||
c.finalize(resolver, s)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c unknownsLabelerTask) finalize(resolver file.Resolver, s *sbom.SBOM) {
|
||||
hasPackageReference := coordinateReferenceLookup(resolver, s)
|
||||
|
||||
if c.RemoveWhenPackagesDefined {
|
||||
for coords := range s.Artifacts.Unknowns {
|
||||
if !hasPackageReference(coords) {
|
||||
continue
|
||||
}
|
||||
delete(s.Artifacts.Unknowns, coords)
|
||||
}
|
||||
}
|
||||
|
||||
if s.Artifacts.Unknowns == nil {
|
||||
s.Artifacts.Unknowns = map[file.Coordinates][]string{}
|
||||
}
|
||||
|
||||
if c.IncludeExecutablesWithoutPackages {
|
||||
for coords := range s.Artifacts.Executables {
|
||||
if !hasPackageReference(coords) {
|
||||
s.Artifacts.Unknowns[coords] = append(s.Artifacts.Unknowns[coords], formatUnknown("no package identified in executable file", unknownsLabelerTaskName))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if c.IncludeUnexpandedArchives {
|
||||
for coords := range s.Artifacts.FileMetadata {
|
||||
unarchiver, notArchiveErr := archiver.ByExtension(coords.RealPath)
|
||||
if unarchiver != nil && notArchiveErr == nil && !hasPackageReference(coords) {
|
||||
s.Artifacts.Unknowns[coords] = append(s.Artifacts.Unknowns[coords], "archive not cataloged")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func formatUnknown(err string, task ...string) string {
|
||||
return strings.Join(task, "/") + ": " + err
|
||||
}
|
||||
|
||||
func coordinateReferenceLookup(resolver file.Resolver, s *sbom.SBOM) func(coords file.Coordinates) bool {
|
||||
allPackageCoords := file.NewCoordinateSet()
|
||||
|
||||
// include all directly included locations that result in packages
|
||||
for p := range s.Artifacts.Packages.Enumerate() {
|
||||
allPackageCoords.Add(p.Locations.CoordinateSet().ToSlice()...)
|
||||
}
|
||||
|
||||
// include owned files, for example specified by package managers.
|
||||
// relationships for these owned files may be disabled, but we always want to include them
|
||||
for p := range s.Artifacts.Packages.Enumerate() {
|
||||
if f, ok := p.Metadata.(pkg.FileOwner); ok {
|
||||
for _, ownedFilePath := range f.OwnedFiles() {
|
||||
// resolve these owned files, as they may have symlinks
|
||||
// but coordinates we will test against are always absolute paths
|
||||
locations, err := resolver.FilesByPath(ownedFilePath)
|
||||
if err != nil {
|
||||
log.Debugf("unable to resolve owned file '%s': %v", ownedFilePath, err)
|
||||
}
|
||||
for _, loc := range locations {
|
||||
allPackageCoords.Add(loc.Coordinates)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// include relationships
|
||||
for _, r := range s.Relationships {
|
||||
_, fromPkgOk := r.From.(pkg.Package)
|
||||
fromFile, fromFileOk := r.From.(file.Coordinates)
|
||||
_, toPkgOk := r.To.(pkg.Package)
|
||||
toFile, toFileOk := r.To.(file.Coordinates)
|
||||
if fromPkgOk && toFileOk {
|
||||
allPackageCoords.Add(toFile)
|
||||
} else if fromFileOk && toPkgOk {
|
||||
allPackageCoords.Add(fromFile)
|
||||
}
|
||||
}
|
||||
|
||||
return allPackageCoords.Contains
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
package unknown
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
)
|
||||
|
||||
type hasCoordinates interface {
|
||||
GetCoordinates() file.Coordinates
|
||||
}
|
||||
|
||||
type CoordinateError struct {
|
||||
Coordinates file.Coordinates
|
||||
Reason error
|
||||
}
|
||||
|
||||
var _ error = (*CoordinateError)(nil)
|
||||
|
||||
func (u *CoordinateError) Error() string {
|
||||
if u.Coordinates.FileSystemID == "" {
|
||||
return fmt.Sprintf("%s: %v", u.Coordinates.RealPath, u.Reason)
|
||||
}
|
||||
return fmt.Sprintf("%s (%s): %v", u.Coordinates.RealPath, u.Coordinates.FileSystemID, u.Reason)
|
||||
}
|
||||
|
||||
// New returns a new CoordinateError unless the reason is a CoordinateError itself, in which case
|
||||
// reason will be returned directly or if reason is nil, nil will be returned
|
||||
func New(coords hasCoordinates, reason error) *CoordinateError {
|
||||
if reason == nil {
|
||||
return nil
|
||||
}
|
||||
coordinates := coords.GetCoordinates()
|
||||
reasonCoordinateError := &CoordinateError{}
|
||||
if errors.As(reason, &reasonCoordinateError) {
|
||||
// if the reason is already a coordinate error, it is potentially for a different location,
|
||||
// so we do not want to surface this location having an error
|
||||
return reasonCoordinateError
|
||||
}
|
||||
return &CoordinateError{
|
||||
Coordinates: coordinates,
|
||||
Reason: reason,
|
||||
}
|
||||
}
|
||||
|
||||
// Newf returns a new CoordinateError with a reason of an error created from given format and args
|
||||
func Newf(coords hasCoordinates, format string, args ...any) *CoordinateError {
|
||||
return New(coords, fmt.Errorf(format, args...))
|
||||
}
|
||||
|
||||
// Append returns an error joined to the first error/set of errors, with a new CoordinateError appended to the end
|
||||
func Append(errs error, coords hasCoordinates, reason error) error {
|
||||
return Join(errs, New(coords, reason))
|
||||
}
|
||||
|
||||
// Appendf returns an error joined to the first error/set of errors, with a new CoordinateError appended to the end,
|
||||
// created from the given reason and args
|
||||
func Appendf(errs error, coords hasCoordinates, format string, args ...any) error {
|
||||
return Append(errs, coords, fmt.Errorf(format, args...))
|
||||
}
|
||||
|
||||
// Join joins the provided sets of errors together in a flattened manner, taking into account nested errors created
|
||||
// from other sources, including errors.Join, multierror.Append, and unknown.Join
|
||||
func Join(errs ...error) error {
|
||||
var out []error
|
||||
for _, err := range errs {
|
||||
// append errors, de-duplicated
|
||||
for _, e := range flatten(err) {
|
||||
if containsErr(out, e) {
|
||||
continue
|
||||
}
|
||||
out = append(out, e)
|
||||
}
|
||||
}
|
||||
if len(out) == 1 {
|
||||
return out[0]
|
||||
}
|
||||
if len(out) == 0 {
|
||||
return nil
|
||||
}
|
||||
return errors.Join(out...)
|
||||
}
|
||||
|
||||
// Joinf joins the provided sets of errors together in a flattened manner, taking into account nested errors created
|
||||
// from other sources, including errors.Join, multierror.Append, and unknown.Join and appending a new error,
|
||||
// created from the format and args provided -- the error is NOT a CoordinateError
|
||||
func Joinf(errs error, format string, args ...any) error {
|
||||
return Join(errs, fmt.Errorf(format, args...))
|
||||
}
|
||||
|
||||
// IfEmptyf returns a new Errorf-formatted error, only when the provided slice is empty or nil when
|
||||
// the slice has entries
|
||||
func IfEmptyf[T any](emptyTest []T, format string, args ...any) error {
|
||||
if len(emptyTest) == 0 {
|
||||
return fmt.Errorf(format, args...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExtractCoordinateErrors extracts all coordinate errors returned, and any _additional_ errors in the graph
|
||||
// are encapsulated in the second, error return parameter
|
||||
func ExtractCoordinateErrors(err error) (coordinateErrors []CoordinateError, remainingErrors error) {
|
||||
remainingErrors = visitErrors(err, func(e error) error {
|
||||
if coordinateError, _ := e.(*CoordinateError); coordinateError != nil {
|
||||
coordinateErrors = append(coordinateErrors, *coordinateError)
|
||||
return nil
|
||||
}
|
||||
return e
|
||||
})
|
||||
return coordinateErrors, remainingErrors
|
||||
}
|
||||
|
||||
func flatten(errs ...error) []error {
|
||||
var out []error
|
||||
for _, err := range errs {
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
// turn all errors nested under a coordinate error to individual coordinate errors
|
||||
if e, ok := err.(*CoordinateError); ok {
|
||||
if e == nil {
|
||||
continue
|
||||
}
|
||||
for _, r := range flatten(e.Reason) {
|
||||
out = append(out, New(e.Coordinates, r))
|
||||
}
|
||||
} else
|
||||
// from multierror.Append
|
||||
if e, ok := err.(interface{ WrappedErrors() []error }); ok {
|
||||
if e == nil {
|
||||
continue
|
||||
}
|
||||
out = append(out, flatten(e.WrappedErrors()...)...)
|
||||
} else
|
||||
// from errors.Join
|
||||
if e, ok := err.(interface{ Unwrap() []error }); ok {
|
||||
if e == nil {
|
||||
continue
|
||||
}
|
||||
out = append(out, flatten(e.Unwrap()...)...)
|
||||
} else {
|
||||
out = append(out, err)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// containsErr returns true if a duplicate error is found
|
||||
func containsErr(out []error, err error) bool {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
log.Tracef("error comparing errors: %v", err)
|
||||
}
|
||||
}()
|
||||
for _, e := range out {
|
||||
if e == err {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// visitErrors visits every wrapped error. the returned error replaces the provided error, null errors are omitted from
|
||||
// the object graph
|
||||
func visitErrors(err error, fn func(error) error) error {
|
||||
// unwrap errors from errors.Join
|
||||
if errs, ok := err.(interface{ Unwrap() []error }); ok {
|
||||
var out []error
|
||||
for _, e := range errs.Unwrap() {
|
||||
out = append(out, visitErrors(e, fn))
|
||||
}
|
||||
// errors.Join omits nil errors and will return nil if all passed errors are nil
|
||||
return errors.Join(out...)
|
||||
}
|
||||
// unwrap errors from multierror.Append -- these also implement Unwrap() error, so check this first
|
||||
if errs, ok := err.(interface{ WrappedErrors() []error }); ok {
|
||||
var out []error
|
||||
for _, e := range errs.WrappedErrors() {
|
||||
out = append(out, visitErrors(e, fn))
|
||||
}
|
||||
// errors.Join omits nil errors and will return nil if all passed errors are nil
|
||||
return errors.Join(out...)
|
||||
}
|
||||
// unwrap singly wrapped errors
|
||||
if e, ok := err.(interface{ Unwrap() error }); ok {
|
||||
wrapped := e.Unwrap()
|
||||
got := visitErrors(wrapped, fn)
|
||||
if got == nil {
|
||||
return nil
|
||||
}
|
||||
if wrapped.Error() != got.Error() {
|
||||
prefix := strings.TrimSuffix(err.Error(), wrapped.Error())
|
||||
return fmt.Errorf("%s%w", prefix, got)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return fn(err)
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
package unknown
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/syft/file"
|
||||
)
|
||||
|
||||
func Test_visitErrors(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
in error
|
||||
transform func(error) error
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "return",
|
||||
in: fmt.Errorf("err1"),
|
||||
transform: func(e error) error {
|
||||
return e
|
||||
},
|
||||
expected: "err1",
|
||||
},
|
||||
{
|
||||
name: "omit",
|
||||
in: fmt.Errorf("err1"),
|
||||
transform: func(_ error) error {
|
||||
return nil
|
||||
},
|
||||
expected: "<nil>",
|
||||
},
|
||||
{
|
||||
name: "wrapped return",
|
||||
in: fmt.Errorf("wrapped: %w", fmt.Errorf("err1")),
|
||||
transform: func(e error) error {
|
||||
return e
|
||||
},
|
||||
expected: "wrapped: err1",
|
||||
},
|
||||
{
|
||||
name: "wrapped omit",
|
||||
in: fmt.Errorf("wrapped: %w", fmt.Errorf("err1")),
|
||||
transform: func(e error) error {
|
||||
if e.Error() == "err1" {
|
||||
return nil
|
||||
}
|
||||
return e
|
||||
},
|
||||
expected: "<nil>",
|
||||
},
|
||||
{
|
||||
name: "joined return",
|
||||
in: errors.Join(fmt.Errorf("err1"), fmt.Errorf("err2")),
|
||||
transform: func(e error) error {
|
||||
return e
|
||||
},
|
||||
expected: "err1\nerr2",
|
||||
},
|
||||
{
|
||||
name: "joined omit",
|
||||
in: errors.Join(fmt.Errorf("err1"), fmt.Errorf("err2")),
|
||||
transform: func(_ error) error {
|
||||
return nil
|
||||
},
|
||||
expected: "<nil>",
|
||||
},
|
||||
{
|
||||
name: "joined omit first",
|
||||
in: errors.Join(fmt.Errorf("err1"), fmt.Errorf("err2")),
|
||||
transform: func(e error) error {
|
||||
if e.Error() == "err1" {
|
||||
return nil
|
||||
}
|
||||
return e
|
||||
},
|
||||
expected: "err2",
|
||||
},
|
||||
{
|
||||
name: "joined wrapped return",
|
||||
in: errors.Join(fmt.Errorf("wrapped: %w", fmt.Errorf("err1")), fmt.Errorf("err2")),
|
||||
transform: func(e error) error {
|
||||
return e
|
||||
},
|
||||
expected: "wrapped: err1\nerr2",
|
||||
},
|
||||
{
|
||||
name: "joined wrapped omit first",
|
||||
in: errors.Join(fmt.Errorf("wrapped: %w", fmt.Errorf("err1")), fmt.Errorf("err2")),
|
||||
transform: func(e error) error {
|
||||
if e.Error() == "err1" {
|
||||
return nil
|
||||
}
|
||||
return e
|
||||
},
|
||||
expected: "err2",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
gotErr := visitErrors(test.in, test.transform)
|
||||
got := fmt.Sprintf("%v", gotErr)
|
||||
require.Equal(t, test.expected, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Join(t *testing.T) {
|
||||
err1 := fmt.Errorf("err1")
|
||||
err2 := fmt.Errorf("err2")
|
||||
|
||||
tests := []struct {
|
||||
name string ``
|
||||
in []error
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "basic",
|
||||
in: []error{fmt.Errorf("err")},
|
||||
expected: "err",
|
||||
},
|
||||
{
|
||||
name: "wrapped",
|
||||
in: []error{fmt.Errorf("outer: %w", fmt.Errorf("err"))},
|
||||
expected: "outer: err",
|
||||
},
|
||||
{
|
||||
name: "wrapped joined",
|
||||
in: []error{errors.Join(fmt.Errorf("outer: %w", fmt.Errorf("err1")), fmt.Errorf("err2"))},
|
||||
expected: "outer: err1\nerr2",
|
||||
},
|
||||
{
|
||||
name: "duplicates",
|
||||
in: []error{err1, err1, err2},
|
||||
expected: "err1\nerr2",
|
||||
},
|
||||
{
|
||||
name: "nested duplicates",
|
||||
in: []error{errors.Join(err1, err2), err1, err2},
|
||||
expected: "err1\nerr2",
|
||||
},
|
||||
{
|
||||
name: "nested duplicates coords",
|
||||
in: []error{New(file.NewLocation("l1"), errors.Join(fmt.Errorf("err1"), fmt.Errorf("err2"))), fmt.Errorf("err1"), fmt.Errorf("err2")},
|
||||
expected: "l1: err1\nl1: err2\nerr1\nerr2",
|
||||
},
|
||||
{
|
||||
name: "all nil",
|
||||
in: []error{nil, nil, nil},
|
||||
expected: "",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
got := Join(test.in...)
|
||||
if test.expected == "" {
|
||||
require.Nil(t, got)
|
||||
return
|
||||
}
|
||||
require.NotNil(t, got)
|
||||
require.Equal(t, test.expected, got.Error())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_flatten(t *testing.T) {
|
||||
coords := file.Coordinates{
|
||||
RealPath: "real/path",
|
||||
}
|
||||
e1 := fmt.Errorf("e1")
|
||||
e2 := fmt.Errorf("e2")
|
||||
c1 := New(coords, fmt.Errorf("c1"))
|
||||
c2 := New(coords, fmt.Errorf("c2"))
|
||||
tests := []struct {
|
||||
name string ``
|
||||
in error
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "basic",
|
||||
in: errors.Join(e1, e2),
|
||||
expected: "e1//e2",
|
||||
},
|
||||
{
|
||||
name: "coords",
|
||||
in: New(coords, e1),
|
||||
expected: "real/path: e1",
|
||||
},
|
||||
{
|
||||
name: "coords with joined children",
|
||||
in: New(coords, errors.Join(e1, e2)),
|
||||
expected: "real/path: e1//real/path: e2",
|
||||
},
|
||||
{
|
||||
name: "very nested",
|
||||
in: errors.Join(errors.Join(errors.Join(errors.Join(e1, c1), e2), c2), e2),
|
||||
expected: "e1//real/path: c1//e2//real/path: c2//e2",
|
||||
},
|
||||
}
|
||||
toString := func(errs ...error) string {
|
||||
var parts []string
|
||||
for _, e := range errs {
|
||||
parts = append(parts, e.Error())
|
||||
}
|
||||
return strings.Join(parts, "//")
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
got := flatten(test.in)
|
||||
require.NotNil(t, got)
|
||||
require.Equal(t, test.expected, toString(got...))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Append(t *testing.T) {
|
||||
e1 := New(file.NewLocation("l1"), fmt.Errorf("e1"))
|
||||
e2 := Append(e1, file.NewLocation("l2"), fmt.Errorf("e2"))
|
||||
e3 := Appendf(e2, file.NewLocation("l3"), "%s", "e3")
|
||||
require.Equal(t, "l1: e1\nl2: e2\nl3: e3", e3.Error())
|
||||
|
||||
e1 = New(file.NewLocation("l1"), nil)
|
||||
require.Nil(t, e1)
|
||||
e2 = Append(e1, file.NewLocation("l2"), fmt.Errorf("e2"))
|
||||
e3 = Appendf(e2, file.NewLocation("l3"), "%s", "e3")
|
||||
require.Equal(t, "l2: e2\nl3: e3", e3.Error())
|
||||
|
||||
e1 = New(file.NewLocation("l1"), fmt.Errorf("e1"))
|
||||
e2 = Append(e1, file.NewLocation("l2"), nil)
|
||||
e3 = Appendf(e2, file.NewLocation("l3"), "%s", "e3")
|
||||
require.Equal(t, "l1: e1\nl3: e3", e3.Error())
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "anchore.io/schema/syft/json/16.0.16/document",
|
||||
"$id": "anchore.io/schema/syft/json/16.0.18/document",
|
||||
"$ref": "#/$defs/Document",
|
||||
"$defs": {
|
||||
"AlpmDbEntry": {
|
||||
@@ -777,6 +777,12 @@
|
||||
},
|
||||
"executable": {
|
||||
"$ref": "#/$defs/Executable"
|
||||
},
|
||||
"unknowns": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
@@ -955,6 +961,24 @@
|
||||
"virtualPath"
|
||||
]
|
||||
},
|
||||
"JavaJvmInstallation": {
|
||||
"properties": {
|
||||
"release": {
|
||||
"$ref": "#/$defs/JavaVMRelease"
|
||||
},
|
||||
"files": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"release",
|
||||
"files"
|
||||
]
|
||||
},
|
||||
"JavaManifest": {
|
||||
"properties": {
|
||||
"main": {
|
||||
@@ -1062,6 +1086,77 @@
|
||||
"version"
|
||||
]
|
||||
},
|
||||
"JavaVMRelease": {
|
||||
"properties": {
|
||||
"implementor": {
|
||||
"type": "string"
|
||||
},
|
||||
"implementorVersion": {
|
||||
"type": "string"
|
||||
},
|
||||
"javaRuntimeVersion": {
|
||||
"type": "string"
|
||||
},
|
||||
"javaVersion": {
|
||||
"type": "string"
|
||||
},
|
||||
"javaVersionDate": {
|
||||
"type": "string"
|
||||
},
|
||||
"libc": {
|
||||
"type": "string"
|
||||
},
|
||||
"modules": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"osArch": {
|
||||
"type": "string"
|
||||
},
|
||||
"osName": {
|
||||
"type": "string"
|
||||
},
|
||||
"osVersion": {
|
||||
"type": "string"
|
||||
},
|
||||
"source": {
|
||||
"type": "string"
|
||||
},
|
||||
"buildSource": {
|
||||
"type": "string"
|
||||
},
|
||||
"buildSourceRepo": {
|
||||
"type": "string"
|
||||
},
|
||||
"sourceRepo": {
|
||||
"type": "string"
|
||||
},
|
||||
"fullVersion": {
|
||||
"type": "string"
|
||||
},
|
||||
"semanticVersion": {
|
||||
"type": "string"
|
||||
},
|
||||
"buildInfo": {
|
||||
"type": "string"
|
||||
},
|
||||
"jvmVariant": {
|
||||
"type": "string"
|
||||
},
|
||||
"jvmVersion": {
|
||||
"type": "string"
|
||||
},
|
||||
"imageType": {
|
||||
"type": "string"
|
||||
},
|
||||
"buildType": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"JavascriptNpmPackage": {
|
||||
"properties": {
|
||||
"name": {
|
||||
@@ -1583,6 +1678,9 @@
|
||||
{
|
||||
"$ref": "#/$defs/JavaArchive"
|
||||
},
|
||||
{
|
||||
"$ref": "#/$defs/JavaJvmInstallation"
|
||||
},
|
||||
{
|
||||
"$ref": "#/$defs/JavascriptNpmPackage"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package cataloging
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
ComplianceActionKeep ComplianceAction = "keep"
|
||||
ComplianceActionDrop ComplianceAction = "drop"
|
||||
ComplianceActionStub ComplianceAction = "stub"
|
||||
)
|
||||
|
||||
const UnknownStubValue = "UNKNOWN"
|
||||
|
||||
type ComplianceAction string
|
||||
|
||||
type ComplianceConfig struct {
|
||||
MissingName ComplianceAction `yaml:"missing-name" json:"missing-name" mapstructure:"missing-name"`
|
||||
MissingVersion ComplianceAction `yaml:"missing-version" json:"missing-version" mapstructure:"missing-version"`
|
||||
}
|
||||
|
||||
func DefaultComplianceConfig() ComplianceConfig {
|
||||
// Note: name and version are required minimum SBOM elements by NTIA, thus should be the API default
|
||||
return ComplianceConfig{
|
||||
MissingName: ComplianceActionDrop,
|
||||
MissingVersion: ComplianceActionStub,
|
||||
}
|
||||
}
|
||||
|
||||
func (c ComplianceConfig) Parse() ComplianceConfig {
|
||||
return ComplianceConfig{
|
||||
MissingName: c.MissingName.Parse(),
|
||||
MissingVersion: c.MissingVersion.Parse(),
|
||||
}
|
||||
}
|
||||
|
||||
func (c ComplianceAction) Parse() ComplianceAction {
|
||||
switch strings.ToLower(string(c)) {
|
||||
case string(ComplianceActionKeep), "include":
|
||||
return ComplianceActionKeep
|
||||
case string(ComplianceActionDrop), "exclude":
|
||||
return ComplianceActionDrop
|
||||
case string(ComplianceActionStub), "replace":
|
||||
return ComplianceActionStub
|
||||
}
|
||||
return ComplianceActionKeep
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package cataloging
|
||||
|
||||
type UnknownsConfig struct {
|
||||
RemoveWhenPackagesDefined bool
|
||||
IncludeExecutablesWithoutPackages bool
|
||||
IncludeUnexpandedArchives bool
|
||||
}
|
||||
|
||||
func DefaultUnknownsConfig() UnknownsConfig {
|
||||
return UnknownsConfig{
|
||||
RemoveWhenPackagesDefined: true,
|
||||
IncludeExecutablesWithoutPackages: true,
|
||||
IncludeUnexpandedArchives: true,
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -95,7 +95,7 @@ func NewAttributes(cpeStr string) (Attributes, error) {
|
||||
}
|
||||
|
||||
// ensure that this Attributes can be validated after being fully sanitized
|
||||
if ValidateString(c.String()) != nil {
|
||||
if err = ValidateString(c.String()); err != nil {
|
||||
return Attributes{}, err
|
||||
}
|
||||
|
||||
|
||||
+10
-2
@@ -17,6 +17,7 @@ func Test_NewAttributes(t *testing.T) {
|
||||
name string
|
||||
input string
|
||||
expected Attributes
|
||||
wantErr require.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "gocase",
|
||||
@@ -33,14 +34,21 @@ func Test_NewAttributes(t *testing.T) {
|
||||
input: `cpe:/a:%240.99_kindle_books_project:%240.99_kindle_books:6::~~~android~~`,
|
||||
expected: MustAttributes(`cpe:2.3:a:\$0.99_kindle_books_project:\$0.99_kindle_books:6:*:*:*:*:android:*:*`),
|
||||
},
|
||||
{
|
||||
name: "null byte in version for some reason",
|
||||
input: "cpe:2.3:a:oracle:openjdk:11.0.22+7\u0000-J-ms8m:*:*:*:*:*:*:*",
|
||||
wantErr: require.Error,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
actual, err := NewAttributes(test.input)
|
||||
if err != nil {
|
||||
t.Fatalf("got an error while creating Attributes: %+v", err)
|
||||
if test.wantErr != nil {
|
||||
test.wantErr(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
if d := cmp.Diff(actual, test.expected); d != "" {
|
||||
t.Errorf("Attributes mismatch (-want +got):\n%s", d)
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/scylladb/go-set/strset"
|
||||
|
||||
"github.com/anchore/syft/internal/bus"
|
||||
"github.com/anchore/syft/internal/licenses"
|
||||
"github.com/anchore/syft/internal/sbomsync"
|
||||
"github.com/anchore/syft/internal/task"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
@@ -60,6 +61,9 @@ func CreateSBOM(ctx context.Context, src source.Source, cfg *CreateSBOMConfig) (
|
||||
},
|
||||
}
|
||||
|
||||
// inject a single license scanner for all package cataloging tasks into context
|
||||
ctx = licenses.SetContextLicenseScanner(ctx, licenses.NewDefaultScanner())
|
||||
|
||||
catalogingProgress := monitorCatalogingTask(src.ID(), taskGroups)
|
||||
packageCatalogingProgress := monitorPackageCatalogingTask()
|
||||
|
||||
|
||||
@@ -19,8 +19,10 @@ import (
|
||||
// CreateSBOMConfig specifies all parameters needed for creating an SBOM.
|
||||
type CreateSBOMConfig struct {
|
||||
// required configuration input to specify how cataloging should be performed
|
||||
Compliance cataloging.ComplianceConfig
|
||||
Search cataloging.SearchConfig
|
||||
Relationships cataloging.RelationshipsConfig
|
||||
Unknowns cataloging.UnknownsConfig
|
||||
DataGeneration cataloging.DataGenerationConfig
|
||||
Packages pkgcataloging.Config
|
||||
Files filecataloging.Config
|
||||
@@ -38,6 +40,7 @@ type CreateSBOMConfig struct {
|
||||
|
||||
func DefaultCreateSBOMConfig() *CreateSBOMConfig {
|
||||
return &CreateSBOMConfig{
|
||||
Compliance: cataloging.DefaultComplianceConfig(),
|
||||
Search: cataloging.DefaultSearchConfig(),
|
||||
Relationships: cataloging.DefaultRelationshipsConfig(),
|
||||
DataGeneration: cataloging.DefaultDataGenerationConfig(),
|
||||
@@ -93,6 +96,12 @@ func (c *CreateSBOMConfig) WithParallelism(p int) *CreateSBOMConfig {
|
||||
return c
|
||||
}
|
||||
|
||||
// WithComplianceConfig allows for setting the specific compliance configuration for cataloging.
|
||||
func (c *CreateSBOMConfig) WithComplianceConfig(cfg cataloging.ComplianceConfig) *CreateSBOMConfig {
|
||||
c.Compliance = cfg
|
||||
return c
|
||||
}
|
||||
|
||||
// WithSearchConfig allows for setting the specific search configuration for cataloging.
|
||||
func (c *CreateSBOMConfig) WithSearchConfig(cfg cataloging.SearchConfig) *CreateSBOMConfig {
|
||||
c.Search = cfg
|
||||
@@ -105,6 +114,12 @@ func (c *CreateSBOMConfig) WithRelationshipsConfig(cfg cataloging.RelationshipsC
|
||||
return c
|
||||
}
|
||||
|
||||
// WithUnknownsConfig allows for defining the specific behavior dealing with unknowns
|
||||
func (c *CreateSBOMConfig) WithUnknownsConfig(cfg cataloging.UnknownsConfig) *CreateSBOMConfig {
|
||||
c.Unknowns = cfg
|
||||
return c
|
||||
}
|
||||
|
||||
// WithDataGenerationConfig allows for defining what data elements that cannot be discovered from the underlying
|
||||
// target being scanned that should be generated after package creation.
|
||||
func (c *CreateSBOMConfig) WithDataGenerationConfig(cfg cataloging.DataGenerationConfig) *CreateSBOMConfig {
|
||||
@@ -165,6 +180,7 @@ func (c *CreateSBOMConfig) makeTaskGroups(src source.Description) ([][]task.Task
|
||||
// generate package and file tasks based on the configuration
|
||||
environmentTasks := c.environmentTasks()
|
||||
relationshipsTasks := c.relationshipTasks(src)
|
||||
unknownTasks := c.unknownsTasks()
|
||||
fileTasks := c.fileTasks()
|
||||
pkgTasks, selectionEvidence, err := c.packageTasks(src)
|
||||
if err != nil {
|
||||
@@ -184,6 +200,11 @@ func (c *CreateSBOMConfig) makeTaskGroups(src source.Description) ([][]task.Task
|
||||
taskGroups = append(taskGroups, relationshipsTasks)
|
||||
}
|
||||
|
||||
// all unknowns tasks should happen after all scanning is complete
|
||||
if len(unknownTasks) > 0 {
|
||||
taskGroups = append(taskGroups, unknownTasks)
|
||||
}
|
||||
|
||||
// identifying the environment (i.e. the linux release) must be done first as this is required for package cataloging
|
||||
taskGroups = append(
|
||||
[][]task.Task{
|
||||
@@ -225,6 +246,7 @@ func (c *CreateSBOMConfig) packageTasks(src source.Description) ([]task.Task, *t
|
||||
RelationshipsConfig: c.Relationships,
|
||||
DataGenerationConfig: c.DataGeneration,
|
||||
PackagesConfig: c.Packages,
|
||||
ComplianceConfig: c.Compliance,
|
||||
}
|
||||
|
||||
persistentTasks, selectableTasks, err := c.allPackageTasks(cfg)
|
||||
@@ -329,6 +351,18 @@ func (c *CreateSBOMConfig) environmentTasks() []task.Task {
|
||||
return tsks
|
||||
}
|
||||
|
||||
// unknownsTasks returns a set of tasks that perform any necessary post-processing
|
||||
// to identify SBOM elements as unknowns
|
||||
func (c *CreateSBOMConfig) unknownsTasks() []task.Task {
|
||||
var tasks []task.Task
|
||||
|
||||
if t := task.NewUnknownsLabelerTask(c.Unknowns); t != nil {
|
||||
tasks = append(tasks, t)
|
||||
}
|
||||
|
||||
return tasks
|
||||
}
|
||||
|
||||
func (c *CreateSBOMConfig) validate() error {
|
||||
if c.Relationships.ExcludeBinaryPackagesWithFileOwnershipOverlap {
|
||||
if !c.Relationships.PackageFileOwnershipOverlap {
|
||||
|
||||
@@ -90,6 +90,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
||||
pkgCatalogerNamesWithTagOrName(t, "image"),
|
||||
fileCatalogerNames(true, true, true),
|
||||
relationshipCatalogerNames(),
|
||||
unknownsTaskNames(),
|
||||
},
|
||||
wantManifest: &catalogerManifest{
|
||||
Requested: pkgcataloging.SelectionRequest{
|
||||
@@ -108,6 +109,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
||||
pkgCatalogerNamesWithTagOrName(t, "directory"),
|
||||
fileCatalogerNames(true, true, true),
|
||||
relationshipCatalogerNames(),
|
||||
unknownsTaskNames(),
|
||||
},
|
||||
wantManifest: &catalogerManifest{
|
||||
Requested: pkgcataloging.SelectionRequest{
|
||||
@@ -127,6 +129,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
||||
pkgCatalogerNamesWithTagOrName(t, "directory"),
|
||||
fileCatalogerNames(true, true, true),
|
||||
relationshipCatalogerNames(),
|
||||
unknownsTaskNames(),
|
||||
},
|
||||
wantManifest: &catalogerManifest{
|
||||
Requested: pkgcataloging.SelectionRequest{
|
||||
@@ -145,6 +148,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
||||
pkgCatalogerNamesWithTagOrName(t, "image"),
|
||||
fileCatalogerNames(false, true, true), // note: the digest cataloger is not included
|
||||
relationshipCatalogerNames(),
|
||||
unknownsTaskNames(),
|
||||
},
|
||||
wantManifest: &catalogerManifest{
|
||||
Requested: pkgcataloging.SelectionRequest{
|
||||
@@ -163,6 +167,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
||||
pkgCatalogerNamesWithTagOrName(t, "image"),
|
||||
// note: there are no file catalogers in their own group
|
||||
relationshipCatalogerNames(),
|
||||
unknownsTaskNames(),
|
||||
},
|
||||
wantManifest: &catalogerManifest{
|
||||
Requested: pkgcataloging.SelectionRequest{
|
||||
@@ -184,6 +189,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
||||
fileCatalogerNames(true, true, true)...,
|
||||
),
|
||||
relationshipCatalogerNames(),
|
||||
unknownsTaskNames(),
|
||||
},
|
||||
wantManifest: &catalogerManifest{
|
||||
Requested: pkgcataloging.SelectionRequest{
|
||||
@@ -204,6 +210,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
||||
addTo(pkgCatalogerNamesWithTagOrName(t, "image"), "persistent"),
|
||||
fileCatalogerNames(true, true, true),
|
||||
relationshipCatalogerNames(),
|
||||
unknownsTaskNames(),
|
||||
},
|
||||
wantManifest: &catalogerManifest{
|
||||
Requested: pkgcataloging.SelectionRequest{
|
||||
@@ -224,6 +231,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
||||
addTo(pkgCatalogerNamesWithTagOrName(t, "directory"), "persistent"),
|
||||
fileCatalogerNames(true, true, true),
|
||||
relationshipCatalogerNames(),
|
||||
unknownsTaskNames(),
|
||||
},
|
||||
wantManifest: &catalogerManifest{
|
||||
Requested: pkgcataloging.SelectionRequest{
|
||||
@@ -244,6 +252,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
||||
addTo(pkgIntersect("image", "javascript"), "persistent"),
|
||||
fileCatalogerNames(true, true, true),
|
||||
relationshipCatalogerNames(),
|
||||
unknownsTaskNames(),
|
||||
},
|
||||
wantManifest: &catalogerManifest{
|
||||
Requested: pkgcataloging.SelectionRequest{
|
||||
@@ -265,6 +274,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
||||
addTo(pkgCatalogerNamesWithTagOrName(t, "image"), "user-provided"),
|
||||
fileCatalogerNames(true, true, true),
|
||||
relationshipCatalogerNames(),
|
||||
unknownsTaskNames(),
|
||||
},
|
||||
wantManifest: &catalogerManifest{
|
||||
Requested: pkgcataloging.SelectionRequest{
|
||||
@@ -285,6 +295,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
||||
pkgCatalogerNamesWithTagOrName(t, "image"),
|
||||
fileCatalogerNames(true, true, true),
|
||||
relationshipCatalogerNames(),
|
||||
unknownsTaskNames(),
|
||||
},
|
||||
wantManifest: &catalogerManifest{
|
||||
Requested: pkgcataloging.SelectionRequest{
|
||||
@@ -385,6 +396,10 @@ func relationshipCatalogerNames() []string {
|
||||
return []string{"relationships-cataloger"}
|
||||
}
|
||||
|
||||
func unknownsTaskNames() []string {
|
||||
return []string{"unknowns-labeler"}
|
||||
}
|
||||
|
||||
func environmentCatalogerNames() []string {
|
||||
return []string{"environment-cataloger"}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/anchore/syft/internal/bus"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/mimetype"
|
||||
"github.com/anchore/syft/internal/unknown"
|
||||
"github.com/anchore/syft/syft/event/monitor"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/internal/unionreader"
|
||||
@@ -45,6 +46,8 @@ func NewCataloger(cfg Config) *Cataloger {
|
||||
}
|
||||
|
||||
func (i *Cataloger) Catalog(resolver file.Resolver) (map[file.Coordinates]file.Executable, error) {
|
||||
var errs error
|
||||
|
||||
locs, err := resolver.FilesByMIMEType(i.config.MIMETypes...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get file locations for binaries: %w", err)
|
||||
@@ -61,7 +64,10 @@ func (i *Cataloger) Catalog(resolver file.Resolver) (map[file.Coordinates]file.E
|
||||
for _, loc := range locs {
|
||||
prog.AtomicStage.Set(loc.Path())
|
||||
|
||||
exec := processExecutableLocation(loc, resolver)
|
||||
exec, err := processExecutableLocation(loc, resolver)
|
||||
if err != nil {
|
||||
errs = unknown.Append(errs, loc, err)
|
||||
}
|
||||
|
||||
if exec != nil {
|
||||
prog.Increment()
|
||||
@@ -74,30 +80,24 @@ func (i *Cataloger) Catalog(resolver file.Resolver) (map[file.Coordinates]file.E
|
||||
prog.AtomicStage.Set(fmt.Sprintf("%s executables", humanize.Comma(prog.Current())))
|
||||
prog.SetCompleted()
|
||||
|
||||
return results, nil
|
||||
return results, errs
|
||||
}
|
||||
|
||||
func processExecutableLocation(loc file.Location, resolver file.Resolver) *file.Executable {
|
||||
func processExecutableLocation(loc file.Location, resolver file.Resolver) (*file.Executable, error) {
|
||||
reader, err := resolver.FileContentsByLocation(loc)
|
||||
if err != nil {
|
||||
// TODO: known-unknowns
|
||||
log.WithFields("error", err).Warnf("unable to get file contents for %q", loc.RealPath)
|
||||
return nil
|
||||
return nil, fmt.Errorf("unable to get file contents: %w", err)
|
||||
}
|
||||
defer internal.CloseAndLogError(reader, loc.RealPath)
|
||||
|
||||
uReader, err := unionreader.GetUnionReader(reader)
|
||||
if err != nil {
|
||||
// TODO: known-unknowns
|
||||
log.WithFields("error", err).Warnf("unable to get union reader for %q", loc.RealPath)
|
||||
return nil
|
||||
return nil, fmt.Errorf("unable to get union reader: %w", err)
|
||||
}
|
||||
|
||||
exec, err := processExecutable(loc, uReader)
|
||||
if err != nil {
|
||||
log.WithFields("error", err).Warnf("unable to process executable %q", loc.RealPath)
|
||||
}
|
||||
return exec
|
||||
return processExecutable(loc, uReader)
|
||||
}
|
||||
|
||||
func catalogingProgress(locations int64) *monitor.CatalogerTaskProgress {
|
||||
@@ -153,10 +153,12 @@ func processExecutable(loc file.Location, reader unionreader.UnionReader) (*file
|
||||
|
||||
format, err := findExecutableFormat(reader)
|
||||
if err != nil {
|
||||
log.Debugf("unable to determine executable kind for %v: %v", loc.RealPath, err)
|
||||
return nil, fmt.Errorf("unable to determine executable kind: %w", err)
|
||||
}
|
||||
|
||||
if format == "" {
|
||||
// this is not an "unknown", so just log -- this binary does not have parseable data in it
|
||||
log.Debugf("unable to determine executable format for %q", loc.RealPath)
|
||||
return nil, nil
|
||||
}
|
||||
@@ -165,16 +167,19 @@ func processExecutable(loc file.Location, reader unionreader.UnionReader) (*file
|
||||
|
||||
switch format {
|
||||
case file.ELF:
|
||||
if err := findELFFeatures(&data, reader); err != nil {
|
||||
if err = findELFFeatures(&data, reader); err != nil {
|
||||
log.WithFields("error", err).Tracef("unable to determine ELF features for %q", loc.RealPath)
|
||||
err = fmt.Errorf("unable to determine ELF features: %w", err)
|
||||
}
|
||||
case file.PE:
|
||||
if err := findPEFeatures(&data, reader); err != nil {
|
||||
if err = findPEFeatures(&data, reader); err != nil {
|
||||
log.WithFields("error", err).Tracef("unable to determine PE features for %q", loc.RealPath)
|
||||
err = fmt.Errorf("unable to determine PE features: %w", err)
|
||||
}
|
||||
case file.MachO:
|
||||
if err := findMachoFeatures(&data, reader); err != nil {
|
||||
if err = findMachoFeatures(&data, reader); err != nil {
|
||||
log.WithFields("error", err).Tracef("unable to determine Macho features for %q", loc.RealPath)
|
||||
err = fmt.Errorf("unable to determine Macho features: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,7 +188,7 @@ func processExecutable(loc file.Location, reader unionreader.UnionReader) (*file
|
||||
data.ImportedLibraries = []string{}
|
||||
}
|
||||
|
||||
return &data, nil
|
||||
return &data, err
|
||||
}
|
||||
|
||||
func findExecutableFormat(reader unionreader.UnionReader) (file.ExecutableFormat, error) {
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/scylladb/go-set/strset"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/unknown"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/internal/unionreader"
|
||||
)
|
||||
@@ -20,8 +21,8 @@ func findELFFeatures(data *file.Executable, reader unionreader.UnionReader) erro
|
||||
|
||||
libs, err := f.ImportedLibraries()
|
||||
if err != nil {
|
||||
// TODO: known-unknowns
|
||||
log.WithFields("error", err).Trace("unable to read imported libraries from elf file")
|
||||
err = unknown.Joinf(err, "unable to read imported libraries from elf file: %w", err)
|
||||
libs = nil
|
||||
}
|
||||
|
||||
@@ -34,7 +35,7 @@ func findELFFeatures(data *file.Executable, reader unionreader.UnionReader) erro
|
||||
data.HasEntrypoint = elfHasEntrypoint(f)
|
||||
data.HasExports = elfHasExports(f)
|
||||
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
func findELFSecurityFeatures(f *elf.File) *file.ELFSecurityFeatures {
|
||||
@@ -62,7 +63,6 @@ func checkElfStackCanary(file *elf.File) *bool {
|
||||
func hasAnyDynamicSymbols(file *elf.File, symbolNames ...string) *bool {
|
||||
dynSyms, err := file.DynamicSymbols()
|
||||
if err != nil {
|
||||
// TODO: known-unknowns
|
||||
log.WithFields("error", err).Trace("unable to read dynamic symbols from elf file")
|
||||
return nil
|
||||
}
|
||||
@@ -129,7 +129,6 @@ func hasBindNowDynTagOrFlag(f *elf.File) bool {
|
||||
func hasElfDynFlag(f *elf.File, flag elf.DynFlag) bool {
|
||||
vals, err := f.DynValue(elf.DT_FLAGS)
|
||||
if err != nil {
|
||||
// TODO: known-unknowns
|
||||
log.WithFields("error", err).Trace("unable to read DT_FLAGS from elf file")
|
||||
return false
|
||||
}
|
||||
@@ -144,7 +143,6 @@ func hasElfDynFlag(f *elf.File, flag elf.DynFlag) bool {
|
||||
func hasElfDynFlag1(f *elf.File, flag elf.DynFlag1) bool {
|
||||
vals, err := f.DynValue(elf.DT_FLAGS_1)
|
||||
if err != nil {
|
||||
// TODO: known-unknowns
|
||||
log.WithFields("error", err).Trace("unable to read DT_FLAGS_1 from elf file")
|
||||
return false
|
||||
}
|
||||
@@ -175,7 +173,7 @@ func hasElfDynTag(f *elf.File, tag elf.DynTag) bool {
|
||||
t = elf.DynTag(f.ByteOrder.Uint32(d[0:4]))
|
||||
d = d[8:]
|
||||
case elf.ELFCLASS64:
|
||||
t = elf.DynTag(f.ByteOrder.Uint64(d[0:8])) //nolint:gosec
|
||||
t = elf.DynTag(f.ByteOrder.Uint64(d[0:8]))
|
||||
d = d[16:]
|
||||
}
|
||||
if t == tag {
|
||||
@@ -203,7 +201,6 @@ func checkLLVMControlFlowIntegrity(file *elf.File) *bool {
|
||||
// look for any symbols that are functions and end with ".cfi"
|
||||
dynSyms, err := file.Symbols()
|
||||
if err != nil {
|
||||
// TODO: known-unknowns
|
||||
log.WithFields("error", err).Trace("unable to read symbols from elf file")
|
||||
return nil
|
||||
}
|
||||
@@ -225,7 +222,6 @@ var fortifyPattern = regexp.MustCompile(`__\w+_chk@.+`)
|
||||
func checkClangFortifySource(file *elf.File) *bool {
|
||||
dynSyms, err := file.Symbols()
|
||||
if err != nil {
|
||||
// TODO: known-unknowns
|
||||
log.WithFields("error", err).Trace("unable to read symbols from elf file")
|
||||
return nil
|
||||
}
|
||||
@@ -254,7 +250,7 @@ func elfHasExports(f *elf.File) bool {
|
||||
// really anything that is not marked with 'U' (undefined) is considered an export.
|
||||
symbols, err := f.DynamicSymbols()
|
||||
if err != nil {
|
||||
// TODO: known-unknowns?
|
||||
log.WithFields("error", err).Trace("unable to get ELF dynamic symbols")
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ func Test_findELFSecurityFeatures(t *testing.T) {
|
||||
name string
|
||||
fixture string
|
||||
want *file.ELFSecurityFeatures
|
||||
wantErr require.ErrorAssertionFunc
|
||||
wantStripped bool
|
||||
}{
|
||||
{
|
||||
@@ -221,6 +222,7 @@ func Test_elfHasExports(t *testing.T) {
|
||||
f, err := elf.NewFile(readerForFixture(t, tt.fixture))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tt.want, elfHasExports(f))
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/anchore/syft/internal/bus"
|
||||
intFile "github.com/anchore/syft/internal/file"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/unknown"
|
||||
"github.com/anchore/syft/syft/event/monitor"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
)
|
||||
@@ -46,6 +47,7 @@ func NewCataloger(cfg Config) *Cataloger {
|
||||
func (i *Cataloger) Catalog(_ context.Context, resolver file.Resolver) (map[file.Coordinates]string, error) {
|
||||
results := make(map[file.Coordinates]string)
|
||||
var locations []file.Location
|
||||
var errs error
|
||||
|
||||
locations, err := resolver.FilesByGlob(i.globs...)
|
||||
if err != nil {
|
||||
@@ -59,8 +61,9 @@ func (i *Cataloger) Catalog(_ context.Context, resolver file.Resolver) (map[file
|
||||
|
||||
metadata, err := resolver.FileMetadataByLocation(location)
|
||||
if err != nil {
|
||||
errs = unknown.Append(errs, location, err)
|
||||
prog.SetError(err)
|
||||
return nil, err
|
||||
continue
|
||||
}
|
||||
|
||||
if i.skipFilesAboveSizeInBytes > 0 && metadata.Size() > i.skipFilesAboveSizeInBytes {
|
||||
@@ -69,12 +72,12 @@ func (i *Cataloger) Catalog(_ context.Context, resolver file.Resolver) (map[file
|
||||
|
||||
result, err := i.catalogLocation(resolver, location)
|
||||
if internal.IsErrPathPermission(err) {
|
||||
log.Debugf("file contents cataloger skipping - %+v", err)
|
||||
errs = unknown.Append(errs, location, fmt.Errorf("permission error reading file contents: %w", err))
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
prog.SetError(err)
|
||||
return nil, err
|
||||
errs = unknown.Append(errs, location, err)
|
||||
continue
|
||||
}
|
||||
|
||||
prog.Increment()
|
||||
@@ -87,7 +90,7 @@ func (i *Cataloger) Catalog(_ context.Context, resolver file.Resolver) (map[file
|
||||
prog.AtomicStage.Set(fmt.Sprintf("%s files", humanize.Comma(prog.Current())))
|
||||
prog.SetCompleted()
|
||||
|
||||
return results, nil
|
||||
return results, errs
|
||||
}
|
||||
|
||||
func (i *Cataloger) catalogLocation(resolver file.Resolver, location file.Location) (string, error) {
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/anchore/syft/internal/bus"
|
||||
intFile "github.com/anchore/syft/internal/file"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/unknown"
|
||||
"github.com/anchore/syft/syft/event/monitor"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
intCataloger "github.com/anchore/syft/syft/file/cataloger/internal"
|
||||
@@ -33,6 +34,7 @@ func NewCataloger(hashes []crypto.Hash) *Cataloger {
|
||||
func (i *Cataloger) Catalog(ctx context.Context, resolver file.Resolver, coordinates ...file.Coordinates) (map[file.Coordinates][]file.Digest, error) {
|
||||
results := make(map[file.Coordinates][]file.Digest)
|
||||
var locations []file.Location
|
||||
var errs error
|
||||
|
||||
if len(coordinates) == 0 {
|
||||
locations = intCataloger.AllRegularFiles(ctx, resolver)
|
||||
@@ -58,12 +60,14 @@ func (i *Cataloger) Catalog(ctx context.Context, resolver file.Resolver, coordin
|
||||
|
||||
if internal.IsErrPathPermission(err) {
|
||||
log.Debugf("file digests cataloger skipping %q: %+v", location.RealPath, err)
|
||||
errs = unknown.Append(errs, location, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
prog.SetError(err)
|
||||
return nil, fmt.Errorf("failed to process file %q: %w", location.RealPath, err)
|
||||
errs = unknown.Append(errs, location, err)
|
||||
continue
|
||||
}
|
||||
|
||||
prog.Increment()
|
||||
@@ -76,7 +80,7 @@ func (i *Cataloger) Catalog(ctx context.Context, resolver file.Resolver, coordin
|
||||
prog.AtomicStage.Set(fmt.Sprintf("%s files", humanize.Comma(prog.Current())))
|
||||
prog.SetCompleted()
|
||||
|
||||
return results, nil
|
||||
return results, errs
|
||||
}
|
||||
|
||||
func (i *Cataloger) catalogLocation(resolver file.Resolver, location file.Location) ([]file.Digest, error) {
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/anchore/syft/internal/bus"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/unknown"
|
||||
"github.com/anchore/syft/syft/event/monitor"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
)
|
||||
@@ -20,6 +21,7 @@ func NewCataloger() *Cataloger {
|
||||
}
|
||||
|
||||
func (i *Cataloger) Catalog(ctx context.Context, resolver file.Resolver, coordinates ...file.Coordinates) (map[file.Coordinates]file.Metadata, error) {
|
||||
var errs error
|
||||
results := make(map[file.Coordinates]file.Metadata)
|
||||
var locations <-chan file.Location
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
@@ -34,7 +36,7 @@ func (i *Cataloger) Catalog(ctx context.Context, resolver file.Resolver, coordin
|
||||
for _, c := range coordinates {
|
||||
locs, err := resolver.FilesByPath(c.RealPath)
|
||||
if err != nil {
|
||||
log.Warn("unable to get file locations for path %q: %w", c.RealPath, err)
|
||||
errs = unknown.Append(errs, c, err)
|
||||
continue
|
||||
}
|
||||
for _, loc := range locs {
|
||||
@@ -71,7 +73,7 @@ func (i *Cataloger) Catalog(ctx context.Context, resolver file.Resolver, coordin
|
||||
prog.AtomicStage.Set(fmt.Sprintf("%s locations", humanize.Comma(prog.Current())))
|
||||
prog.SetCompleted()
|
||||
|
||||
return results, nil
|
||||
return results, errs
|
||||
}
|
||||
|
||||
func catalogingProgress(locations int64) *monitor.CatalogerTaskProgress {
|
||||
|
||||
@@ -39,3 +39,7 @@ func (c Coordinates) String() string {
|
||||
}
|
||||
return fmt.Sprintf("Location<%s>", str)
|
||||
}
|
||||
|
||||
func (c Coordinates) GetCoordinates() Coordinates {
|
||||
return c
|
||||
}
|
||||
|
||||
@@ -62,6 +62,10 @@ func Originator(p pkg.Package) (typ string, author string) { //nolint: funlen
|
||||
}
|
||||
}
|
||||
|
||||
case pkg.JavaVMInstallation:
|
||||
typ = orgType
|
||||
author = metadata.Release.Implementor
|
||||
|
||||
case pkg.LinuxKernelModule:
|
||||
author = metadata.Author
|
||||
|
||||
|
||||
@@ -178,6 +178,18 @@ func Test_OriginatorSupplier(t *testing.T) {
|
||||
},
|
||||
// note: empty!
|
||||
},
|
||||
{
|
||||
name: "from java -- jvm installation",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.JavaVMInstallation{
|
||||
Release: pkg.JavaVMRelease{
|
||||
Implementor: "Oracle",
|
||||
},
|
||||
},
|
||||
},
|
||||
originator: "Organization: Oracle",
|
||||
supplier: "Organization: Oracle",
|
||||
},
|
||||
{
|
||||
name: "from linux kernel module",
|
||||
input: pkg.Package{
|
||||
|
||||
@@ -221,6 +221,7 @@ func Test_encodeDecodeFileMetadata(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
Unknowns: map[file.Coordinates][]string{},
|
||||
Executables: map[file.Coordinates]file.Executable{
|
||||
c: {
|
||||
Format: file.ELF,
|
||||
|
||||
@@ -13,6 +13,7 @@ type File struct {
|
||||
Digests []file.Digest `json:"digests,omitempty"`
|
||||
Licenses []FileLicense `json:"licenses,omitempty"`
|
||||
Executable *file.Executable `json:"executable,omitempty"`
|
||||
Unknowns []string `json:"unknowns,omitempty"`
|
||||
}
|
||||
|
||||
type FileMetadataEntry struct {
|
||||
|
||||
@@ -101,6 +101,11 @@ func toFile(s sbom.SBOM) []model.File {
|
||||
contents = contentsForLocation
|
||||
}
|
||||
|
||||
var unknowns []string
|
||||
if unknownsForLocation, exists := artifacts.Unknowns[coordinates]; exists {
|
||||
unknowns = unknownsForLocation
|
||||
}
|
||||
|
||||
var licenses []model.FileLicense
|
||||
for _, l := range artifacts.FileLicenses[coordinates] {
|
||||
var evidence *model.FileLicenseEvidence
|
||||
@@ -132,6 +137,7 @@ func toFile(s sbom.SBOM) []model.File {
|
||||
Contents: contents,
|
||||
Licenses: licenses,
|
||||
Executable: executable,
|
||||
Unknowns: unknowns,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ func toSyftModel(doc model.Document) *sbom.SBOM {
|
||||
FileContents: fileArtifacts.FileContents,
|
||||
FileLicenses: fileArtifacts.FileLicenses,
|
||||
Executables: fileArtifacts.Executables,
|
||||
Unknowns: fileArtifacts.Unknowns,
|
||||
LinuxDistribution: toSyftLinuxRelease(doc.Distro),
|
||||
},
|
||||
Source: *toSyftSourceData(doc.Source),
|
||||
@@ -66,6 +67,7 @@ func deduplicateErrors(errors []error) []string {
|
||||
return errorMessages
|
||||
}
|
||||
|
||||
//nolint:funlen
|
||||
func toSyftFiles(files []model.File) sbom.Artifacts {
|
||||
ret := sbom.Artifacts{
|
||||
FileMetadata: make(map[file.Coordinates]file.Metadata),
|
||||
@@ -73,6 +75,7 @@ func toSyftFiles(files []model.File) sbom.Artifacts {
|
||||
FileContents: make(map[file.Coordinates]string),
|
||||
FileLicenses: make(map[file.Coordinates][]file.License),
|
||||
Executables: make(map[file.Coordinates]file.Executable),
|
||||
Unknowns: make(map[file.Coordinates][]string),
|
||||
}
|
||||
|
||||
for _, f := range files {
|
||||
@@ -130,6 +133,10 @@ func toSyftFiles(files []model.File) sbom.Artifacts {
|
||||
if f.Executable != nil {
|
||||
ret.Executables[coord] = *f.Executable
|
||||
}
|
||||
|
||||
if len(f.Unknowns) > 0 {
|
||||
ret.Unknowns[coord] = f.Unknowns
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
@@ -146,7 +153,7 @@ func safeFileModeConvert(val int) (fs.FileMode, error) {
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return os.FileMode(mode), nil //nolint:gosec
|
||||
return os.FileMode(mode), nil
|
||||
}
|
||||
|
||||
func toSyftLicenses(m []model.License) (p []pkg.License) {
|
||||
|
||||
@@ -234,6 +234,7 @@ func Test_toSyftFiles(t *testing.T) {
|
||||
FileMetadata: map[file.Coordinates]file.Metadata{},
|
||||
FileDigests: map[file.Coordinates][]file.Digest{},
|
||||
Executables: map[file.Coordinates]file.Executable{},
|
||||
Unknowns: make(map[file.Coordinates][]string),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -349,6 +350,7 @@ func Test_toSyftFiles(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tt.want.FileContents = make(map[file.Coordinates]string)
|
||||
tt.want.FileLicenses = make(map[file.Coordinates][]file.License)
|
||||
tt.want.Unknowns = make(map[file.Coordinates][]string)
|
||||
assert.Equal(t, tt.want, toSyftFiles(tt.files))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -140,6 +140,40 @@ func (r ChrootContext) ToNativePath(chrootPath string) (string, error) {
|
||||
return responsePath, nil
|
||||
}
|
||||
|
||||
func (r ChrootContext) ToNativeGlob(chrootPath string) (string, error) {
|
||||
// split on any *
|
||||
parts := strings.Split(chrootPath, "*")
|
||||
if len(parts) == 0 || parts[0] == "" {
|
||||
// either this is an empty string or a path that starts with * so there is nothing we can do
|
||||
return chrootPath, nil
|
||||
}
|
||||
|
||||
if len(parts) == 1 {
|
||||
// this has no glob, treat it like a path
|
||||
return r.ToNativePath(chrootPath)
|
||||
}
|
||||
|
||||
responsePath := parts[0]
|
||||
|
||||
if filepath.IsAbs(responsePath) {
|
||||
// don't allow input to potentially hop above root path
|
||||
responsePath = path.Join(r.root, responsePath)
|
||||
} else {
|
||||
// ensure we take into account any relative difference between the root path and the CWD for relative requests
|
||||
responsePath = path.Join(r.cwdRelativeToRoot, responsePath)
|
||||
}
|
||||
|
||||
var err error
|
||||
responsePath, err = filepath.Abs(responsePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
parts[0] = strings.TrimRight(responsePath, "/") + "/"
|
||||
|
||||
return strings.Join(parts, "*"), nil
|
||||
}
|
||||
|
||||
// ToChrootPath takes a path from the underlying fs domain and converts it to a path that is relative to the current root context.
|
||||
func (r ChrootContext) ToChrootPath(nativePath string) string {
|
||||
responsePath := nativePath
|
||||
|
||||
@@ -479,3 +479,98 @@ func Test_ChrootContext_RequestResponse(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestToNativeGlob(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
chrootContext ChrootContext
|
||||
chrootPath string
|
||||
expectedResult string
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "ignore empty path",
|
||||
chrootContext: ChrootContext{
|
||||
root: "/root",
|
||||
cwdRelativeToRoot: "/cwd",
|
||||
},
|
||||
chrootPath: "",
|
||||
expectedResult: "",
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "ignore if just a path",
|
||||
chrootContext: ChrootContext{
|
||||
root: "/root",
|
||||
cwdRelativeToRoot: "/cwd",
|
||||
},
|
||||
chrootPath: "/some/path/file.txt",
|
||||
expectedResult: "/root/some/path/file.txt",
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "ignore starting with glob",
|
||||
chrootContext: ChrootContext{
|
||||
root: "/root",
|
||||
cwdRelativeToRoot: "/cwd",
|
||||
},
|
||||
chrootPath: "*/relative/path/*",
|
||||
expectedResult: "*/relative/path/*",
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "absolute path with glob",
|
||||
chrootContext: ChrootContext{
|
||||
root: "/root",
|
||||
cwdRelativeToRoot: "/cwd",
|
||||
},
|
||||
chrootPath: "/some/path/*",
|
||||
expectedResult: "/root/some/path/*",
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "relative path with glob",
|
||||
chrootContext: ChrootContext{
|
||||
root: "/root",
|
||||
cwdRelativeToRoot: "/cwd",
|
||||
},
|
||||
chrootPath: "relative/path/*",
|
||||
expectedResult: "/cwd/relative/path/*",
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "relative path with no root",
|
||||
chrootContext: ChrootContext{
|
||||
root: "",
|
||||
cwdRelativeToRoot: "/cwd",
|
||||
},
|
||||
chrootPath: "relative/path/*",
|
||||
expectedResult: "/cwd/relative/path/*",
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "globs everywhere",
|
||||
chrootContext: ChrootContext{
|
||||
root: "/root",
|
||||
cwdRelativeToRoot: "/cwd",
|
||||
},
|
||||
chrootPath: "relative/path/**/file*.txt",
|
||||
expectedResult: "/cwd/relative/path/**/file*.txt",
|
||||
expectedError: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result, err := tt.chrootContext.ToNativeGlob(tt.chrootPath)
|
||||
|
||||
if tt.expectedError != nil {
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, tt.expectedError, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.expectedResult, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,13 +145,21 @@ func (r Directory) FilesByPath(userPaths ...string) ([]file.Location, error) {
|
||||
return references, nil
|
||||
}
|
||||
|
||||
func (r Directory) requestGlob(pattern string) (string, error) {
|
||||
return r.chroot.ToNativeGlob(pattern)
|
||||
}
|
||||
|
||||
// FilesByGlob returns all file.References that match the given path glob pattern from any layer in the image.
|
||||
func (r Directory) FilesByGlob(patterns ...string) ([]file.Location, error) {
|
||||
uniqueFileIDs := stereoscopeFile.NewFileReferenceSet()
|
||||
uniqueLocations := make([]file.Location, 0)
|
||||
|
||||
for _, pattern := range patterns {
|
||||
refVias, err := r.searchContext.SearchByGlob(pattern, filetree.FollowBasenameLinks)
|
||||
requestGlob, err := r.requestGlob(pattern)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
refVias, err := r.searchContext.SearchByGlob(requestGlob, filetree.FollowBasenameLinks)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -93,12 +93,13 @@ func (ps pathSkipper) pathIndexVisitor(_ string, givenPath string, _ os.FileInfo
|
||||
for _, mi := range ps.mounts {
|
||||
conditionalPaths, ignorable := ps.ignorableMountTypes[mi.FSType]
|
||||
|
||||
if len(conditionalPaths) == 0 {
|
||||
// Rule 1: ignore any path within a mount point that is of the given filesystem type unconditionally
|
||||
if !containsPath(givenPath, mi.Mountpoint) {
|
||||
continue
|
||||
}
|
||||
// Rule 0: Make sure the given path is within the mount point; if not let the scan continue
|
||||
if !containsPath(givenPath, mi.Mountpoint) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Rule 1: ignore any path within a mount point that is of the given filesystem type unconditionally
|
||||
if len(conditionalPaths) == 0 {
|
||||
if !ignorable {
|
||||
// we've matched on the most specific path at this point, which means we should stop searching
|
||||
// mount points for this path
|
||||
@@ -151,7 +152,7 @@ func simpleClean(p string) string {
|
||||
return "."
|
||||
}
|
||||
if p == "/" {
|
||||
return "/"
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSuffix(p, "/")
|
||||
}
|
||||
|
||||
@@ -358,6 +358,43 @@ func Test_newPathSkipper(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "buildkit github ubuntu 22.04",
|
||||
root: "/run/src/core/sbom",
|
||||
mounts: []*mountinfo.Info{
|
||||
{Mountpoint: "/", FSType: "overlay"},
|
||||
{Mountpoint: "/proc", FSType: "proc"},
|
||||
{Mountpoint: "/dev", FSType: "tmpfs"},
|
||||
{Mountpoint: "/dev/pts", FSType: "devpts"},
|
||||
{Mountpoint: "/dev/shm", FSType: "tmpfs"},
|
||||
{Mountpoint: "/dev/mqueue", FSType: "mqueue"},
|
||||
{Mountpoint: "/sys", FSType: "sysfs"},
|
||||
{Mountpoint: "/etc/resolv.conf", FSType: "ext4"},
|
||||
{Mountpoint: "/etc/hosts", FSType: "ext4"},
|
||||
{Mountpoint: "/sys/fs/cgroup", FSType: "cgroup2"},
|
||||
{Mountpoint: "/run/out", FSType: "ext4"},
|
||||
{Mountpoint: "/run/src/core/sbom", FSType: "overlay"},
|
||||
{Mountpoint: "/tmp", FSType: "tmpfs"},
|
||||
{Mountpoint: "/dev/otel-grpc.sock", FSType: "overlay"},
|
||||
{Mountpoint: "/proc/bus", FSType: "proc"},
|
||||
{Mountpoint: "/proc/fs", FSType: "proc"},
|
||||
{Mountpoint: "/proc/irq", FSType: "proc"},
|
||||
{Mountpoint: "/proc/sys", FSType: "proc"},
|
||||
{Mountpoint: "/proc/sysrq-trigger", FSType: "proc"},
|
||||
{Mountpoint: "/proc/acpi", FSType: "tmpfs"},
|
||||
{Mountpoint: "/proc/kcore", FSType: "tmpfs"},
|
||||
{Mountpoint: "/proc/keys", FSType: "tmpfs"},
|
||||
{Mountpoint: "/proc/latency_stats", FSType: "tmpfs"},
|
||||
{Mountpoint: "/proc/timer_list", FSType: "tmpfs"},
|
||||
{Mountpoint: "/sys/firmware", FSType: "tmpfs"},
|
||||
{Mountpoint: "/proc/scsi", FSType: "tmpfs"},
|
||||
},
|
||||
want: []expect{
|
||||
{
|
||||
path: "/run/src/core/sbom",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
@@ -27,6 +27,7 @@ func AllTypes() []any {
|
||||
pkg.HackageStackYamlEntry{},
|
||||
pkg.HackageStackYamlLockEntry{},
|
||||
pkg.JavaArchive{},
|
||||
pkg.JavaVMInstallation{},
|
||||
pkg.LinuxKernel{},
|
||||
pkg.LinuxKernelModule{},
|
||||
pkg.LuaRocksPackage{},
|
||||
|
||||
@@ -81,6 +81,7 @@ var jsonTypes = makeJSONTypes(
|
||||
jsonNames(pkg.HackageStackYamlLockEntry{}, "haskell-hackage-stack-lock-entry", "HackageMetadataType"),
|
||||
jsonNamesWithoutLookup(pkg.HackageStackYamlEntry{}, "haskell-hackage-stack-entry", "HackageMetadataType"), // the legacy value is split into two types, where the other is preferred
|
||||
jsonNames(pkg.JavaArchive{}, "java-archive", "JavaMetadata"),
|
||||
jsonNames(pkg.JavaVMInstallation{}, "java-jvm-installation"),
|
||||
jsonNames(pkg.MicrosoftKbPatch{}, "microsoft-kb-patch", "KbPatchMetadata"),
|
||||
jsonNames(pkg.LinuxKernel{}, "linux-kernel-archive", "LinuxKernel"),
|
||||
jsonNames(pkg.LinuxKernelModule{}, "linux-kernel-module", "LinuxKernelModule"),
|
||||
|
||||
@@ -190,6 +190,14 @@ func TestApkDBCataloger(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func Test_corruptDb(t *testing.T) {
|
||||
pkgtest.NewCatalogTester().
|
||||
FromDirectory(t, "test-fixtures/corrupt").
|
||||
WithCompareOptions(cmpopts.IgnoreFields(pkg.ApkDBEntry{}, "Files", "GitCommit", "Checksum")).
|
||||
WithError().
|
||||
TestCataloger(t, NewDBCataloger())
|
||||
}
|
||||
|
||||
func TestCatalogerDependencyTree(t *testing.T) {
|
||||
assertion := func(t *testing.T, pkgs []pkg.Package, relationships []artifact.Relationship) {
|
||||
expected := map[string][]string{
|
||||
|
||||
@@ -112,7 +112,7 @@ func Test_PackageURL(t *testing.T) {
|
||||
ID: "alpine",
|
||||
VersionID: "3.4.6",
|
||||
},
|
||||
expected: "pkg:apk/alpine/p@v?arch=a&upstream=origin&distro=alpine-3.4.6",
|
||||
expected: "pkg:apk/alpine/p@v?arch=a&distro=alpine-3.4.6&upstream=origin",
|
||||
},
|
||||
{
|
||||
name: "wolfi distro",
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/unknown"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
@@ -38,6 +39,7 @@ type parsedData struct {
|
||||
func parseApkDB(_ context.Context, resolver file.Resolver, env *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
scanner := bufio.NewScanner(reader)
|
||||
|
||||
var errs error
|
||||
var apks []parsedData
|
||||
var currentEntry parsedData
|
||||
entryParsingInProgress := false
|
||||
@@ -81,10 +83,12 @@ func parseApkDB(_ context.Context, resolver file.Resolver, env *generic.Environm
|
||||
field := parseApkField(line)
|
||||
if field == nil {
|
||||
log.Warnf("unable to parse field data from line %q", line)
|
||||
errs = unknown.Appendf(errs, reader, "unable to parse field data from line %q", line)
|
||||
continue
|
||||
}
|
||||
if len(field.name) == 0 {
|
||||
log.Warnf("failed to parse field name from line %q", line)
|
||||
errs = unknown.Appendf(errs, reader, "failed to parse field name from line %q", line)
|
||||
continue
|
||||
}
|
||||
if len(field.value) == 0 {
|
||||
@@ -131,7 +135,7 @@ func parseApkDB(_ context.Context, resolver file.Resolver, env *generic.Environm
|
||||
pkgs = append(pkgs, newPackage(apk, r, reader.Location))
|
||||
}
|
||||
|
||||
return pkgs, nil, nil
|
||||
return pkgs, nil, errs
|
||||
}
|
||||
|
||||
func findReleases(resolver file.Resolver, dbPath string) []linux.Release {
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
this is a corrupt db
|
||||
|
||||
C:Q1v4QhLje3kWlC8DJj+ZfJTjlJRSU=
|
||||
P:alpine-baselayout-data
|
||||
V:3.2.0-r22
|
||||
A:x86_64
|
||||
S:11435
|
||||
I:73728
|
||||
o:alpine-baselayout
|
||||
t:1655134784
|
||||
c:cb70ca5c6d6db0399d2dd09189c5d57827bce5cd
|
||||
r:alpine-baselayout
|
||||
F:etc
|
||||
R:fstab
|
||||
Z:Q11Q7hNe8QpDS531guqCdrXBzoA/o=
|
||||
R:group
|
||||
Z:Q13K+olJg5ayzHSVNUkggZJXuB+9Y=
|
||||
R:hostname
|
||||
Z:Q16nVwYVXP/tChvUPdukVD2ifXOmc=
|
||||
R:hosts
|
||||
Z:Q1BD6zJKZTRWyqGnPi4tSfd3krsMU=
|
||||
R:inittab
|
||||
Z:Q1TsthbhW7QzWRe1E/NKwTOuD4pHc=
|
||||
R:modules
|
||||
Z:Q1toogjUipHGcMgECgPJX64SwUT1M=
|
||||
R:mtab
|
||||
a:0:0:777
|
||||
Z:Q1kiljhXXH1LlQroHsEJIkPZg2eiw=
|
||||
R:passwd
|
||||
Z:Q1TchuuLUfur0izvfZQZxgN/LJhB8=
|
||||
R:profile
|
||||
Z:Q1F3DgXUP+jNZDknmQPPb5t9FSfDg=
|
||||
R:protocols
|
||||
Z:Q1omKlp3vgGq2ZqYzyD/KHNdo8rDc=
|
||||
R:services
|
||||
Z:Q19WLCv5ItKg4MH7RWfNRh1I7byQc=
|
||||
R:shadow
|
||||
a:0:42:640
|
||||
Z:Q1ltrPIAW2zHeDiajsex2Bdmq3uqA=
|
||||
R:shells
|
||||
Z:Q1ojm2YdpCJ6B/apGDaZ/Sdb2xJkA=
|
||||
R:sysctl.conf
|
||||
Z:Q14upz3tfnNxZkIEsUhWn7Xoiw96g=
|
||||
@@ -11,6 +11,14 @@ import (
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
||||
)
|
||||
|
||||
func TestAlpmUnknowns(t *testing.T) {
|
||||
pkgtest.NewCatalogTester().
|
||||
FromDirectory(t, "test-fixtures/installed").
|
||||
WithCompareOptions(cmpopts.IgnoreFields(pkg.AlpmFileRecord{}, "Time")).
|
||||
WithError().
|
||||
TestCataloger(t, NewDBCataloger())
|
||||
}
|
||||
|
||||
func TestAlpmCataloger(t *testing.T) {
|
||||
gmpDbLocation := file.NewLocation("var/lib/pacman/local/gmp-6.2.1-2/desc")
|
||||
treeSitterDbLocation := file.NewLocation("var/lib/pacman/local/tree-sitter-0.22.6-1/desc")
|
||||
|
||||
@@ -108,7 +108,7 @@ func Test_PackageURL(t *testing.T) {
|
||||
ID: "arch",
|
||||
BuildID: "rolling",
|
||||
},
|
||||
expected: "pkg:alpm/arch/p@v?arch=a&upstream=origin&distro=arch-rolling",
|
||||
expected: "pkg:alpm/arch/p@v?arch=a&distro=arch-rolling&upstream=origin",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/unknown"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
@@ -41,6 +42,8 @@ type parsedData struct {
|
||||
|
||||
// parseAlpmDB parses the arch linux pacman database flat-files and returns the packages and relationships found within.
|
||||
func parseAlpmDB(_ context.Context, resolver file.Resolver, env *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
var errs error
|
||||
|
||||
data, err := parseAlpmDBEntry(reader)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -52,24 +55,25 @@ func parseAlpmDB(_ context.Context, resolver file.Resolver, env *generic.Environ
|
||||
|
||||
base := path.Dir(reader.RealPath)
|
||||
|
||||
var locs []file.Location
|
||||
|
||||
// replace the files found the pacman database with the files from the mtree These contain more metadata and
|
||||
// thus more useful.
|
||||
files, fileLoc := fetchPkgFiles(base, resolver)
|
||||
backups, backupLoc := fetchBackupFiles(base, resolver)
|
||||
|
||||
var locs []file.Location
|
||||
if fileLoc != nil {
|
||||
files, fileLoc, err := fetchPkgFiles(base, resolver)
|
||||
errs = unknown.Join(errs, err)
|
||||
if err == nil {
|
||||
locs = append(locs, fileLoc.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.SupportingEvidenceAnnotation))
|
||||
data.Files = files
|
||||
}
|
||||
|
||||
if backupLoc != nil {
|
||||
backups, backupLoc, err := fetchBackupFiles(base, resolver)
|
||||
errs = unknown.Join(errs, err)
|
||||
if err == nil {
|
||||
locs = append(locs, backupLoc.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.SupportingEvidenceAnnotation))
|
||||
data.Backup = backups
|
||||
}
|
||||
|
||||
if data.Package == "" {
|
||||
return nil, nil, nil
|
||||
return nil, nil, errs
|
||||
}
|
||||
|
||||
return []pkg.Package{
|
||||
@@ -79,63 +83,56 @@ func parseAlpmDB(_ context.Context, resolver file.Resolver, env *generic.Environ
|
||||
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
locs...,
|
||||
),
|
||||
}, nil, nil
|
||||
}, nil, errs
|
||||
}
|
||||
|
||||
func fetchPkgFiles(base string, resolver file.Resolver) ([]pkg.AlpmFileRecord, *file.Location) {
|
||||
func fetchPkgFiles(base string, resolver file.Resolver) ([]pkg.AlpmFileRecord, file.Location, error) {
|
||||
// TODO: probably want to use MTREE and PKGINFO here
|
||||
target := path.Join(base, "mtree")
|
||||
|
||||
loc, err := getLocation(target, resolver)
|
||||
if err != nil {
|
||||
log.WithFields("error", err, "path", target).Trace("failed to find mtree file")
|
||||
return []pkg.AlpmFileRecord{}, nil
|
||||
return []pkg.AlpmFileRecord{}, loc, unknown.New(loc, fmt.Errorf("failed to find mtree file: %w", err))
|
||||
}
|
||||
if loc == nil {
|
||||
return []pkg.AlpmFileRecord{}, nil
|
||||
}
|
||||
|
||||
reader, err := resolver.FileContentsByLocation(*loc)
|
||||
reader, err := resolver.FileContentsByLocation(loc)
|
||||
if err != nil {
|
||||
return []pkg.AlpmFileRecord{}, nil
|
||||
return []pkg.AlpmFileRecord{}, loc, unknown.New(loc, fmt.Errorf("failed to get contents: %w", err))
|
||||
}
|
||||
defer internal.CloseAndLogError(reader, loc.RealPath)
|
||||
|
||||
pkgFiles, err := parseMtree(reader)
|
||||
if err != nil {
|
||||
log.WithFields("error", err, "path", target).Trace("failed to parse mtree file")
|
||||
return []pkg.AlpmFileRecord{}, nil
|
||||
return []pkg.AlpmFileRecord{}, loc, unknown.New(loc, fmt.Errorf("failed to parse mtree: %w", err))
|
||||
}
|
||||
return pkgFiles, loc
|
||||
return pkgFiles, loc, nil
|
||||
}
|
||||
|
||||
func fetchBackupFiles(base string, resolver file.Resolver) ([]pkg.AlpmFileRecord, *file.Location) {
|
||||
func fetchBackupFiles(base string, resolver file.Resolver) ([]pkg.AlpmFileRecord, file.Location, error) {
|
||||
// We only really do this to get any backup database entries from the files database
|
||||
target := filepath.Join(base, "files")
|
||||
|
||||
loc, err := getLocation(target, resolver)
|
||||
if err != nil {
|
||||
log.WithFields("error", err, "path", target).Trace("failed to find alpm files")
|
||||
return []pkg.AlpmFileRecord{}, nil
|
||||
}
|
||||
if loc == nil {
|
||||
return []pkg.AlpmFileRecord{}, nil
|
||||
return []pkg.AlpmFileRecord{}, loc, unknown.New(loc, fmt.Errorf("failed to find alpm files: %w", err))
|
||||
}
|
||||
|
||||
reader, err := resolver.FileContentsByLocation(*loc)
|
||||
reader, err := resolver.FileContentsByLocation(loc)
|
||||
if err != nil {
|
||||
return []pkg.AlpmFileRecord{}, nil
|
||||
return []pkg.AlpmFileRecord{}, loc, unknown.New(loc, fmt.Errorf("failed to get contents: %w", err))
|
||||
}
|
||||
defer internal.CloseAndLogError(reader, loc.RealPath)
|
||||
|
||||
filesMetadata, err := parseAlpmDBEntry(reader)
|
||||
if err != nil {
|
||||
return []pkg.AlpmFileRecord{}, nil
|
||||
return []pkg.AlpmFileRecord{}, loc, unknown.New(loc, fmt.Errorf("failed to parse alpm db entry: %w", err))
|
||||
}
|
||||
if filesMetadata != nil {
|
||||
return filesMetadata.Backup, loc
|
||||
return filesMetadata.Backup, loc, nil
|
||||
}
|
||||
return []pkg.AlpmFileRecord{}, loc
|
||||
return []pkg.AlpmFileRecord{}, loc, nil
|
||||
}
|
||||
|
||||
func parseAlpmDBEntry(reader io.Reader) (*parsedData, error) {
|
||||
@@ -171,20 +168,21 @@ func newScanner(reader io.Reader) *bufio.Scanner {
|
||||
return scanner
|
||||
}
|
||||
|
||||
func getLocation(path string, resolver file.Resolver) (*file.Location, error) {
|
||||
func getLocation(path string, resolver file.Resolver) (file.Location, error) {
|
||||
loc := file.NewLocation(path)
|
||||
locs, err := resolver.FilesByPath(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return loc, err
|
||||
}
|
||||
|
||||
if len(locs) == 0 {
|
||||
return nil, fmt.Errorf("could not find file: %s", path)
|
||||
return loc, fmt.Errorf("could not find file: %s", path)
|
||||
}
|
||||
|
||||
if len(locs) > 1 {
|
||||
log.WithFields("path", path).Trace("multiple files found for path, using first path")
|
||||
}
|
||||
return &locs[0], nil
|
||||
return locs[0], nil
|
||||
}
|
||||
|
||||
func parseDatabase(b *bufio.Scanner) (*parsedData, error) {
|
||||
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
%NME%
|
||||
tree-sitter
|
||||
|
||||
%VER.6-1
|
||||
|
||||
%BASE%
|
||||
tree-sitter
|
||||
|
||||
%DESC%
|
||||
Incremental parsing library
|
||||
|
||||
|
||||
%BUILDDATE%
|
||||
1714945746
|
||||
|
||||
%INSTALLDATE%
|
||||
1715026360
|
||||
|
||||
%PACKA@archlinux.org>
|
||||
|
||||
%SIZE%
|
||||
223539
|
||||
|
||||
%REASON%
|
||||
1
|
||||
|
||||
%LICENSE%
|
||||
MIT
|
||||
|
||||
%VALIDATION%
|
||||
pgp
|
||||
|
||||
%PROVIDE
|
||||
libtree-sitter.so=0-64
|
||||
@@ -6,8 +6,10 @@ package binary
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/unknown"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
@@ -59,12 +61,14 @@ func (c cataloger) Name() string {
|
||||
func (c cataloger) Catalog(_ context.Context, resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
var packages []pkg.Package
|
||||
var relationships []artifact.Relationship
|
||||
var errs error
|
||||
|
||||
for _, cls := range c.classifiers {
|
||||
log.WithFields("classifier", cls.Class).Trace("cataloging binaries")
|
||||
newPkgs, err := catalog(resolver, cls)
|
||||
if err != nil {
|
||||
log.WithFields("error", err, "classifier", cls.Class).Warn("unable to catalog binary package: %w", err)
|
||||
log.WithFields("error", err, "classifier", cls.Class).Debugf("unable to catalog binary package: %v", err)
|
||||
errs = unknown.Join(errs, fmt.Errorf("%s: %w", cls.Class, err))
|
||||
continue
|
||||
}
|
||||
newPackages:
|
||||
@@ -82,7 +86,7 @@ func (c cataloger) Catalog(_ context.Context, resolver file.Resolver) ([]pkg.Pac
|
||||
}
|
||||
}
|
||||
|
||||
return packages, relationships, nil
|
||||
return packages, relationships, errs
|
||||
}
|
||||
|
||||
// mergePackages merges information from the extra package into the target package
|
||||
@@ -98,6 +102,7 @@ func mergePackages(target *pkg.Package, extra *pkg.Package) {
|
||||
}
|
||||
|
||||
func catalog(resolver file.Resolver, cls Classifier) (packages []pkg.Package, err error) {
|
||||
var errs error
|
||||
locations, err := resolver.FilesByGlob(cls.FileGlob)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -105,11 +110,12 @@ func catalog(resolver file.Resolver, cls Classifier) (packages []pkg.Package, er
|
||||
for _, location := range locations {
|
||||
pkgs, err := cls.EvidenceMatcher(cls, matcherContext{resolver: resolver, location: location})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
errs = unknown.Append(errs, location, err)
|
||||
continue
|
||||
}
|
||||
packages = append(packages, pkgs...)
|
||||
}
|
||||
return packages, nil
|
||||
return packages, errs
|
||||
}
|
||||
|
||||
// packagesMatch returns true if the binary packages "match" based on basic criteria
|
||||
|
||||
@@ -348,6 +348,17 @@ func Test_Cataloger_PositiveCases(t *testing.T) {
|
||||
Metadata: metadata("haproxy-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "haproxy/2.0.0/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
Name: "haproxy",
|
||||
Version: "2.0.0",
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/haproxy@2.0.0",
|
||||
Locations: locations("haproxy"),
|
||||
Metadata: metadata("haproxy-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "haproxy/2.7.3/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
@@ -485,6 +496,17 @@ func Test_Cataloger_PositiveCases(t *testing.T) {
|
||||
Metadata: metadata("redis-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "redis-server/7.2.5/linux-386",
|
||||
expected: pkg.Package{
|
||||
Name: "redis",
|
||||
Version: "7.2.5",
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/redis@7.2.5",
|
||||
Locations: locations("redis-server"),
|
||||
Metadata: metadata("redis-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "python-shared-lib/3.7.4/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
@@ -625,6 +647,36 @@ func Test_Cataloger_PositiveCases(t *testing.T) {
|
||||
Metadata: metadata("go-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "node/0.10.48/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
Name: "node",
|
||||
Version: "0.10.48",
|
||||
PURL: "pkg:generic/node@0.10.48",
|
||||
Locations: locations("node"),
|
||||
Metadata: metadata("nodejs-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "node/0.12.18/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
Name: "node",
|
||||
Version: "0.12.18",
|
||||
PURL: "pkg:generic/node@0.12.18",
|
||||
Locations: locations("node"),
|
||||
Metadata: metadata("nodejs-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "node/4.9.1/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
Name: "node",
|
||||
Version: "4.9.1",
|
||||
PURL: "pkg:generic/node@4.9.1",
|
||||
Locations: locations("node"),
|
||||
Metadata: metadata("nodejs-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "node/19.2.0/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
@@ -850,6 +902,60 @@ func Test_Cataloger_PositiveCases(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// note: dynamic (non-snippet) test case
|
||||
name: "positive-ruby-3.4.0-dev",
|
||||
logicalFixture: "ruby-shared-libs/3.4.0-dev/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
Name: "ruby",
|
||||
Version: "3.4.0dev",
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/ruby@3.4.0dev",
|
||||
Locations: locations("ruby", "libruby.so.3.4.0"),
|
||||
Metadata: pkg.BinarySignature{
|
||||
Matches: []pkg.ClassifierMatch{
|
||||
match("ruby-binary", "ruby"),
|
||||
match("ruby-binary", "libruby.so.3.4.0"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// note: dynamic (non-snippet) test case
|
||||
name: "positive-ruby-3.4.0-preview1",
|
||||
logicalFixture: "ruby-shared-libs/3.4.0-preview1/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
Name: "ruby",
|
||||
Version: "3.4.0preview1",
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/ruby@3.4.0preview1",
|
||||
Locations: locations("ruby", "libruby.so.3.4.0"),
|
||||
Metadata: pkg.BinarySignature{
|
||||
Matches: []pkg.ClassifierMatch{
|
||||
match("ruby-binary", "ruby"),
|
||||
match("ruby-binary", "libruby.so.3.4.0"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// note: dynamic (non-snippet) test case
|
||||
name: "positive-ruby-3.3.0-rc1",
|
||||
logicalFixture: "ruby-shared-libs/3.3.0-rc1/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
Name: "ruby",
|
||||
Version: "3.3.0rc1",
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/ruby@3.3.0rc1",
|
||||
Locations: locations("ruby", "libruby.so.3.3.0"),
|
||||
Metadata: pkg.BinarySignature{
|
||||
Matches: []pkg.ClassifierMatch{
|
||||
match("ruby-binary", "ruby"),
|
||||
match("ruby-binary", "libruby.so.3.3.0"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// note: dynamic (non-snippet) test case
|
||||
logicalFixture: "ruby-bullseye-shared-libs/2.7.7/linux-amd64",
|
||||
@@ -961,6 +1067,28 @@ func Test_Cataloger_PositiveCases(t *testing.T) {
|
||||
Metadata: metadata("swipl-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "dart/3.5.2/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
Name: "dart",
|
||||
Version: "3.5.2",
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/dart@3.5.2",
|
||||
Locations: locations("dart"),
|
||||
Metadata: metadata("dart-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "dart/3.6.0-216.1.beta/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
Name: "dart",
|
||||
Version: "3.6.0-216.1.beta",
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/dart@3.6.0-216.1.beta",
|
||||
Locations: locations("dart"),
|
||||
Metadata: metadata("dart-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "haskell-ghc/9.6.5/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
@@ -1082,6 +1210,94 @@ func Test_Cataloger_PositiveCases(t *testing.T) {
|
||||
Metadata: metadata("wordpress-cli-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "lighttpd/1.4.76/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
Name: "lighttpd",
|
||||
Version: "1.4.76",
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/lighttpd@1.4.76",
|
||||
Locations: locations("lighttpd"),
|
||||
Metadata: metadata("lighttpd-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "proftpd/1.3.8b/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
Name: "proftpd",
|
||||
Version: "1.3.8b",
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/proftpd@1.3.8b",
|
||||
Locations: locations("proftpd"),
|
||||
Metadata: metadata("proftpd-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "zstd/1.5.6/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
Name: "zstd",
|
||||
Version: "1.5.6",
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/zstd@1.5.6",
|
||||
Locations: locations("zstd"),
|
||||
Metadata: metadata("zstd-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "zstd/1.5.6/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
Name: "zstd",
|
||||
Version: "1.5.6",
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/zstd@1.5.6",
|
||||
Locations: locations("zstd"),
|
||||
Metadata: metadata("zstd-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "xz/5.6.2/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
Name: "xz",
|
||||
Version: "5.6.2",
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/xz@5.6.2",
|
||||
Locations: locations("xz"),
|
||||
Metadata: metadata("xz-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "gzip/1.12/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
Name: "gzip",
|
||||
Version: "1.12",
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/gzip@1.12",
|
||||
Locations: locations("gzip"),
|
||||
Metadata: metadata("gzip-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "sqlcipher/4.5.5/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
Name: "sqlcipher",
|
||||
Version: "4.5.5",
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/sqlcipher@4.5.5",
|
||||
Locations: locations("sqlcipher"),
|
||||
Metadata: metadata("sqlcipher-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
logicalFixture: "jq/1.7.1/linux-amd64",
|
||||
expected: pkg.Package{
|
||||
Name: "jq",
|
||||
Version: "1.7.1",
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/jq@1.7.1",
|
||||
Locations: locations("jq"),
|
||||
Metadata: metadata("jq-binary"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
@@ -1452,7 +1668,7 @@ func Test_Cataloger_ResilientToErrors(t *testing.T) {
|
||||
|
||||
resolver := &panicyResolver{}
|
||||
_, _, err := c.Catalog(context.Background(), resolver)
|
||||
assert.NoError(t, err)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, resolver.searchCalled)
|
||||
}
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ func DefaultClassifiers() []Classifier {
|
||||
FileGlob: "**/redis-server",
|
||||
EvidenceMatcher: evidenceMatchers(
|
||||
FileContentsVersionMatcher(`(?s)payload %5.*?(?P<version>\d.\d\.\d\d*)[a-z0-9]{12,15}-[0-9]{19}`),
|
||||
FileContentsVersionMatcher(`(?s)\x00(?P<version>\d.\d\.\d\d*)[a-z0-9]{12}-[0-9]{19}\x00.*?payload %5`),
|
||||
FileContentsVersionMatcher(`(?s)\x00(?P<version>\d.\d\.\d\d*)[a-z0-9]{12,15}-[0-9]{19}\x00.*?payload %5`),
|
||||
),
|
||||
Package: "redis",
|
||||
PURL: mustPURL("pkg:generic/redis@version"),
|
||||
@@ -153,8 +153,14 @@ func DefaultClassifiers() []Classifier {
|
||||
{
|
||||
Class: "nodejs-binary",
|
||||
FileGlob: "**/node",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
`(?m)node\.js\/v(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
|
||||
EvidenceMatcher: evidenceMatchers(
|
||||
// [NUL]node v0.10.48[NUL]
|
||||
// [NUL]v0.12.18[NUL]
|
||||
// [NUL]v4.9.1[NUL]
|
||||
// node.js/v22.9.0
|
||||
FileContentsVersionMatcher(`(?m)\x00(node )?v(?P<version>(0|4|5)\.[0-9]+\.[0-9]+)\x00`),
|
||||
FileContentsVersionMatcher(`(?m)node\.js\/v(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
|
||||
),
|
||||
Package: "node",
|
||||
PURL: mustPURL("pkg:generic/node@version"),
|
||||
CPEs: singleCPE("cpe:2.3:a:nodejs:node.js:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
|
||||
@@ -190,6 +196,7 @@ func DefaultClassifiers() []Classifier {
|
||||
Class: "haproxy-binary",
|
||||
FileGlob: "**/haproxy",
|
||||
EvidenceMatcher: evidenceMatchers(
|
||||
FileContentsVersionMatcher(`(?m)version (?P<version>[0-9]+\.[0-9]+(\.|-dev|-rc)[0-9]+)(-[a-z0-9]{7})?, released 20`),
|
||||
FileContentsVersionMatcher(`(?m)HA-Proxy version (?P<version>[0-9]+\.[0-9]+(\.|-dev)[0-9]+)`),
|
||||
FileContentsVersionMatcher(`(?m)(?P<version>[0-9]+\.[0-9]+(\.|-dev)[0-9]+)-[0-9a-zA-Z]{7}.+HAProxy version`),
|
||||
),
|
||||
@@ -344,7 +351,7 @@ func DefaultClassifiers() []Classifier {
|
||||
},
|
||||
{
|
||||
Class: "mariadb-binary",
|
||||
FileGlob: "**/mariadb",
|
||||
FileGlob: "**/{mariadb,mysql}",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
// 10.6.15-MariaDB
|
||||
`(?m)(?P<version>[0-9]+(\.[0-9]+)?(\.[0-9]+)?(alpha[0-9]|beta[0-9]|rc[0-9])?)-MariaDB`),
|
||||
@@ -447,6 +454,16 @@ func DefaultClassifiers() []Classifier {
|
||||
PURL: mustPURL("pkg:generic/swipl@version"),
|
||||
CPEs: singleCPE("cpe:2.3:a:erlang:erlang\\/otp:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
|
||||
},
|
||||
{
|
||||
Class: "dart-binary",
|
||||
FileGlob: "**/dart",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
`(?m)Dart,GC"\x00(?P<version>[0-9]+\.[0-9]+\.[0-9]+(-[0-9]+(\.[0-9]+)?\.beta)?) `,
|
||||
),
|
||||
Package: "dart",
|
||||
PURL: mustPURL("pkg:generic/dart@version"),
|
||||
CPEs: singleCPE("cpe:2.3:a:dart:dart_software_development_kit:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
|
||||
},
|
||||
{
|
||||
Class: "haskell-ghc-binary",
|
||||
FileGlob: "**/ghc*",
|
||||
@@ -574,6 +591,76 @@ func DefaultClassifiers() []Classifier {
|
||||
PURL: mustPURL("pkg:generic/curl@version"),
|
||||
CPEs: singleCPE("cpe:2.3:a:haxx:curl:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
|
||||
},
|
||||
{
|
||||
Class: "lighttpd-binary",
|
||||
FileGlob: "**/lighttpd",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
`\x00lighttpd/(?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00`,
|
||||
),
|
||||
Package: "lighttpd",
|
||||
PURL: mustPURL("pkg:generic/lighttpd@version"),
|
||||
CPEs: singleCPE("cpe:2.3:a:lighttpd:lighttpd:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
|
||||
},
|
||||
{
|
||||
Class: "proftpd-binary",
|
||||
FileGlob: "**/proftpd",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
`\x00ProFTPD Version (?P<version>[0-9]+\.[0-9]+\.[0-9]+[a-z]?)\x00`,
|
||||
),
|
||||
Package: "proftpd",
|
||||
PURL: mustPURL("pkg:generic/proftpd@version"),
|
||||
CPEs: singleCPE("cpe:2.3:a:proftpd:proftpd:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
|
||||
},
|
||||
{
|
||||
Class: "zstd-binary",
|
||||
FileGlob: "**/zstd",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
`\x00v(?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00`,
|
||||
),
|
||||
Package: "zstd",
|
||||
PURL: mustPURL("pkg:generic/zstd@version"),
|
||||
CPEs: singleCPE("cpe:2.3:a:facebook:zstandard:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
|
||||
},
|
||||
{
|
||||
Class: "xz-binary",
|
||||
FileGlob: "**/xz",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
`\x00xz \(XZ Utils\) (?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00`,
|
||||
),
|
||||
Package: "xz",
|
||||
PURL: mustPURL("pkg:generic/xz@version"),
|
||||
CPEs: singleCPE("cpe:2.3:a:tukaani:xz:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
|
||||
},
|
||||
{
|
||||
Class: "gzip-binary",
|
||||
FileGlob: "**/gzip",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
`\x00(?P<version>[0-9]+\.[0-9]+)\x00`,
|
||||
),
|
||||
Package: "gzip",
|
||||
PURL: mustPURL("pkg:generic/gzip@version"),
|
||||
CPEs: singleCPE("cpe:2.3:a:gnu:gzip:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
|
||||
},
|
||||
{
|
||||
Class: "sqlcipher-binary",
|
||||
FileGlob: "**/sqlcipher",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
`[^0-9]\x00(?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00`,
|
||||
),
|
||||
Package: "sqlcipher",
|
||||
PURL: mustPURL("pkg:generic/sqlcipher@version"),
|
||||
CPEs: singleCPE("cpe:2.3:a:zetetic:sqlcipher:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
|
||||
},
|
||||
{
|
||||
Class: "jq-binary",
|
||||
FileGlob: "**/jq",
|
||||
EvidenceMatcher: FileContentsVersionMatcher(
|
||||
`\x00(?P<version>[0-9]{1,3}\.[0-9]{1,3}(\.[0-9]+)?)\x00`,
|
||||
),
|
||||
Package: "jq",
|
||||
PURL: mustPURL("pkg:generic/jq@version"),
|
||||
CPEs: singleCPE("cpe:2.3:a:jqlang:jq:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -586,6 +673,9 @@ var libpythonMatcher = fileNameTemplateVersionMatcher(
|
||||
)
|
||||
|
||||
var rubyMatcher = FileContentsVersionMatcher(
|
||||
// ruby 3.4.0dev (2024-09-15T01:06:11Z master 532af89e3b) [x86_64-linux]
|
||||
// ruby 3.4.0preview1 (2024-05-16 master 9d69619623) [x86_64-linux]
|
||||
// ruby 3.3.0rc1 (2023-12-11 master a49643340e) [x86_64-linux]
|
||||
// ruby 3.2.1 (2023-02-08 revision 31819e82c8) [x86_64-linux]
|
||||
// ruby 2.7.7p221 (2022-11-24 revision 168ec2b1e5) [x86_64-linux]
|
||||
`(?m)ruby (?P<version>[0-9]+\.[0-9]+\.[0-9]+(p[0-9]+)?) `)
|
||||
`(?m)ruby (?P<version>[0-9]+\.[0-9]+\.[0-9]+((p|preview|rc|dev)[0-9]*)?) `)
|
||||
|
||||
@@ -2,6 +2,7 @@ package binary
|
||||
|
||||
import (
|
||||
"github.com/anchore/packageurl-go"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/cpe"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
@@ -26,17 +27,7 @@ func newELFPackage(metadata elfBinaryPackageNotes, locations file.LocationSet) p
|
||||
func packageURL(metadata elfBinaryPackageNotes) string {
|
||||
var qualifiers []packageurl.Qualifier
|
||||
|
||||
os := metadata.OS
|
||||
osVersion := metadata.OSVersion
|
||||
|
||||
atts, err := cpe.NewAttributes(metadata.OSCPE)
|
||||
if err == nil {
|
||||
// only "upgrade" the OS information if there is something more specific to use in it's place
|
||||
if os == "" && osVersion == "" || os == "" && atts.Version != "" || atts.Product != "" && osVersion == "" {
|
||||
os = atts.Product
|
||||
osVersion = atts.Version
|
||||
}
|
||||
}
|
||||
os, osVersion := osNameAndVersionFromMetadata(metadata)
|
||||
|
||||
if os != "" {
|
||||
osQualifier := os
|
||||
@@ -67,6 +58,26 @@ func packageURL(metadata elfBinaryPackageNotes) string {
|
||||
).ToString()
|
||||
}
|
||||
|
||||
func osNameAndVersionFromMetadata(metadata elfBinaryPackageNotes) (string, string) {
|
||||
os := metadata.OS
|
||||
osVersion := metadata.OSVersion
|
||||
|
||||
if os != "" && osVersion != "" {
|
||||
return os, osVersion
|
||||
}
|
||||
|
||||
if metadata.OSCPE == "" {
|
||||
return "", ""
|
||||
}
|
||||
|
||||
attrs, err := cpe.NewAttributes(metadata.OSCPE)
|
||||
if err != nil {
|
||||
log.WithFields("error", err).Trace("unable to parse cpe attributes for elf binary package")
|
||||
return "", ""
|
||||
}
|
||||
return attrs.Product, attrs.Version
|
||||
}
|
||||
|
||||
const alpmType = "alpm"
|
||||
|
||||
func purlDistroType(ty string) string {
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/mimetype"
|
||||
"github.com/anchore/syft/internal/unknown"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/internal/unionreader"
|
||||
@@ -52,6 +53,7 @@ func (c *elfPackageCataloger) Name() string {
|
||||
}
|
||||
|
||||
func (c *elfPackageCataloger) Catalog(_ context.Context, resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
var errs error
|
||||
locations, err := resolver.FilesByMIMEType(mimetype.ExecutableMIMETypeSet.List()...)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to get binary files by mime type: %w", err)
|
||||
@@ -62,7 +64,8 @@ func (c *elfPackageCataloger) Catalog(_ context.Context, resolver file.Resolver)
|
||||
for _, location := range locations {
|
||||
notes, key, err := parseElfPackageNotes(resolver, location, c)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
errs = unknown.Append(errs, location, err)
|
||||
continue
|
||||
}
|
||||
if notes == nil {
|
||||
continue
|
||||
@@ -87,7 +90,7 @@ func (c *elfPackageCataloger) Catalog(_ context.Context, resolver file.Resolver)
|
||||
// why not return relationships? We have an executable cataloger that will note the dynamic libraries imported by
|
||||
// each binary. After all files and packages are processed there is a final task that creates package-to-package
|
||||
// and package-to-file relationships based on the dynamic libraries imported by each binary.
|
||||
return pkgs, nil, nil
|
||||
return pkgs, nil, errs
|
||||
}
|
||||
|
||||
func parseElfPackageNotes(resolver file.Resolver, location file.Location, c *elfPackageCataloger) (*elfBinaryPackageNotes, elfPackageKey, error) {
|
||||
@@ -104,7 +107,7 @@ func parseElfPackageNotes(resolver file.Resolver, location file.Location, c *elf
|
||||
|
||||
if err != nil {
|
||||
log.WithFields("file", location.Path(), "error", err).Trace("unable to parse ELF notes")
|
||||
return nil, elfPackageKey{}, nil
|
||||
return nil, elfPackageKey{}, err
|
||||
}
|
||||
|
||||
if notes == nil {
|
||||
@@ -173,7 +176,7 @@ func getELFNotes(r file.LocationReadCloser) (*elfBinaryPackageNotes, error) {
|
||||
if len(notes) > headerSize {
|
||||
var metadata elfBinaryPackageNotes
|
||||
newPayload := bytes.TrimRight(notes[headerSize:], "\x00")
|
||||
if err := json.Unmarshal(newPayload, &metadata); err == nil {
|
||||
if err = json.Unmarshal(newPayload, &metadata); err == nil {
|
||||
return &metadata, nil
|
||||
}
|
||||
log.WithFields("file", r.Location.Path(), "error", err).Trace("unable to unmarshal ELF package notes as JSON")
|
||||
|
||||
@@ -3,6 +3,8 @@ package binary
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
||||
@@ -14,6 +16,7 @@ func Test_ELF_Package_Cataloger(t *testing.T) {
|
||||
name string
|
||||
fixture string
|
||||
expected []pkg.Package
|
||||
wantErr require.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "go case",
|
||||
@@ -63,6 +66,7 @@ func Test_ELF_Package_Cataloger(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: require.Error,
|
||||
},
|
||||
{
|
||||
name: "fedora 64 bit binaries",
|
||||
@@ -116,6 +120,7 @@ func Test_ELF_Package_Cataloger(t *testing.T) {
|
||||
WithImageResolver(t, v.fixture).
|
||||
IgnoreLocationLayer(). // this fixture can be rebuilt, thus the layer ID will change
|
||||
Expects(v.expected, nil).
|
||||
WithErrorAssertion(v.wantErr).
|
||||
TestCataloger(t, NewELFPackageCataloger())
|
||||
})
|
||||
}
|
||||
|
||||
@@ -113,6 +113,18 @@ func Test_packageURL(t *testing.T) {
|
||||
},
|
||||
want: "pkg:generic/system/test@1.0",
|
||||
},
|
||||
{
|
||||
name: "bad or missing OSCPE data cannot be parsed allows for correct string",
|
||||
metadata: elfBinaryPackageNotes{
|
||||
Name: "test",
|
||||
Version: "1.0",
|
||||
ELFBinaryPackageNoteJSONPayload: pkg.ELFBinaryPackageNoteJSONPayload{
|
||||
System: "system",
|
||||
OSCPE: "%$#*(#*@&$(",
|
||||
},
|
||||
},
|
||||
want: "pkg:generic/system/test@1.0",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
||||
@@ -69,6 +69,11 @@ if ! command -v xxd &> /dev/null; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# check if xargs is even installed
|
||||
if ! command -v xargs &> /dev/null; then
|
||||
echo "xargs not found. Please install xargs."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PATTERN=${SEARCH_FOR:-$VERSION}
|
||||
|
||||
@@ -116,7 +121,7 @@ while $CONTINUE_LOOP; do
|
||||
fi
|
||||
|
||||
# search for the pattern in the binary file and capture the offset
|
||||
OFFSET=$(echo "${SELECTED_RESULT}" | cut -d ' ' -f 1)
|
||||
OFFSET=$(echo "${SELECTED_RESULT}" | xargs | cut -d ' ' -f 1)
|
||||
|
||||
if [ -z "$OFFSET" ]; then
|
||||
echo "Pattern not found."
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user