mirror of
https://github.com/anchore/syft.git
synced 2026-10-12 06:07:20 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
244b797a19 | ||
|
|
95176d7e0c | ||
|
|
5a7bab972c | ||
|
|
b69259534d | ||
|
|
44422853be | ||
|
|
cc731c7b19 | ||
|
|
5d156b8241 | ||
|
|
05715489c4 | ||
|
|
661d256b85 | ||
|
|
dd30c99bc2 | ||
|
|
16ebcb2455 | ||
|
|
305838582b | ||
|
|
a260fb2774 | ||
|
|
f83cae35f2 | ||
|
|
0fed17f1c8 | ||
|
|
da44db92e9 | ||
|
|
4a499c946e | ||
|
|
99c28a94a4 | ||
|
|
f7ac4e98af | ||
|
|
394ec8d215 | ||
|
|
7845381331 | ||
|
|
7464079a09 | ||
|
|
8a574c9ed9 | ||
|
|
681d250fdc | ||
|
|
2022ffa0e5 | ||
|
|
dfcc07e512 | ||
|
|
2fa238af7c | ||
|
|
63bbd1e3ed | ||
|
|
81b87dd108 | ||
|
|
f473bb75a8 | ||
|
|
539bc2afcb | ||
|
|
9fd532246a | ||
|
|
11e926ab2f | ||
|
|
168c5aed51 | ||
|
|
d02c56aa5f | ||
|
|
829a71cd92 | ||
|
|
34ace36a9e | ||
|
|
100cf1003d | ||
|
|
f11a7b5e9f | ||
|
|
434aa7fd46 | ||
|
|
5fb0423b72 | ||
|
|
7998520848 | ||
|
|
d05000ff21 | ||
|
|
f66e77e2c6 | ||
|
|
928c4a55ff | ||
|
|
1899eb50d0 | ||
|
|
b5ec4d4f08 | ||
|
|
61362c04fa | ||
|
|
e3140063d4 | ||
|
|
1d9ef34ec7 | ||
|
|
302735097e | ||
|
|
cc0a376aba | ||
|
|
b379dd9f27 | ||
|
|
a81e0c8008 | ||
|
|
41cbbe09b2 | ||
|
|
7714bc0521 | ||
|
|
096d2b7bff | ||
|
|
7cfdffab5f | ||
|
|
f43953d225 | ||
|
|
eea1b48cbb | ||
|
|
a063cf300b | ||
|
|
b73903519c | ||
|
|
304be4a5a1 | ||
|
|
c4cbe211a3 | ||
|
|
01230aa766 | ||
|
|
5f90d03718 | ||
|
|
e2ebc9769f | ||
|
|
2e6e3b0c74 | ||
|
|
24584a4d27 | ||
|
|
8e1205f7ab | ||
|
|
e92b0fa629 | ||
|
|
bcc0751a40 | ||
|
|
f1169e56fc | ||
|
|
98e737fc27 | ||
|
|
ff34594284 | ||
|
|
9e953b1da3 | ||
|
|
fbda21f4f4 | ||
|
|
d23b4d4cbd | ||
|
|
f3acff81f3 | ||
|
|
fa0a9fe8f9 | ||
|
|
284bae9d5f | ||
|
|
3ee1af0dc6 | ||
|
|
0c5f03235e | ||
|
|
5e8aa4da5e | ||
|
|
abfec62219 | ||
|
|
0c05855131 | ||
|
|
1150772d06 | ||
|
|
669fee84d5 | ||
|
|
aa151da5fe | ||
|
|
4bf677d555 | ||
|
|
f5e20521e0 | ||
|
|
8f6a317fef | ||
|
|
d339ffdcb5 | ||
|
|
e84ffc6003 | ||
|
|
bb52a25c8a | ||
|
|
4b7b709473 | ||
|
|
2ef2eed382 | ||
|
|
f6716092af | ||
|
|
0076b19893 | ||
|
|
2642a36161 | ||
|
|
1981b249f1 | ||
|
|
a3ebb31e3c | ||
|
|
9b9a7d6c98 | ||
|
|
57a13ae355 | ||
|
|
890fb3f0e8 | ||
|
|
e236054668 | ||
|
|
3013c8b691 | ||
|
|
88c81d33ed | ||
|
|
988041ba6d | ||
|
|
550e2fc7c3 | ||
|
|
43b53ac173 | ||
|
|
08804842fa | ||
|
|
284814153d | ||
|
|
8847ba5d0b | ||
|
|
48528efff3 | ||
|
|
8d856a7c7b | ||
|
|
38a090c218 | ||
|
|
deb7052f41 | ||
|
|
9650473298 | ||
|
|
95201840d2 | ||
|
|
6ba595344a | ||
|
|
9995950c70 | ||
|
|
b6a496f18c | ||
|
|
0853a50f4d | ||
|
|
a1b82c9664 | ||
|
|
ad8604c223 | ||
|
|
d80ee966dc | ||
|
|
1530ef354f | ||
|
|
cdac2245b5 | ||
|
|
8dba4c33dd | ||
|
|
b81c9805dc | ||
|
|
ba55963104 | ||
|
|
8c91605541 | ||
|
|
4c0aef09b8 | ||
|
|
02fb757c21 | ||
|
|
674a54512c | ||
|
|
21ba5d0806 | ||
|
|
0ba57a5936 | ||
|
|
3269bc98d4 | ||
|
|
7f3382f7eb | ||
|
|
65e5ff63f0 | ||
|
|
d287c22b69 | ||
|
|
e8be93a8eb |
@@ -7,12 +7,12 @@ assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Please provide a set of steps on how to reproduce the issue**
|
||||
|
||||
**What happened**:
|
||||
|
||||
**What you expected to happen**:
|
||||
|
||||
**Steps to reproduce the issue**:
|
||||
|
||||
**Anything else we need to know?**:
|
||||
|
||||
**Environment**:
|
||||
|
||||
@@ -4,7 +4,7 @@ inputs:
|
||||
go-version:
|
||||
description: "Go version to install"
|
||||
required: true
|
||||
default: "1.18.x"
|
||||
default: "1.19.x"
|
||||
use-go-cache:
|
||||
description: "Restore go cache"
|
||||
required: true
|
||||
|
||||
@@ -6,6 +6,6 @@ normal=$(tput sgr0)
|
||||
|
||||
# assert we are running in CI (or die!)
|
||||
if [[ -z "$CI" ]]; then
|
||||
echo "${bold}${red}This script should ONLY be run in CI. Exiting...${normal}"
|
||||
echo "${bold}${red}This step should ONLY be run in CI. Exiting...${normal}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -4,7 +4,7 @@ set -eu
|
||||
ORIGINAL_STATE_DIR=$(mktemp -d "TEMP-original-state-XXXXXXXXX")
|
||||
TIDY_STATE_DIR=$(mktemp -d "TEMP-tidy-state-XXXXXXXXX")
|
||||
|
||||
trap "cp ${ORIGINAL_STATE_DIR}/* ./ && rm -fR ${ORIGINAL_STATE_DIR} ${TIDY_STATE_DIR}" EXIT
|
||||
trap "cp -p ${ORIGINAL_STATE_DIR}/* ./ && git update-index -q --refresh && rm -fR ${ORIGINAL_STATE_DIR} ${TIDY_STATE_DIR}" EXIT
|
||||
|
||||
# capturing original state of files...
|
||||
cp go.mod go.sum "${ORIGINAL_STATE_DIR}"
|
||||
|
||||
Executable
+27
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env bash
|
||||
set -u
|
||||
|
||||
if ! git diff-index --quiet HEAD --; then
|
||||
git diff-index HEAD --
|
||||
git --no-pager diff
|
||||
echo "there are uncommitted changes, please commit them before running this check"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
success=true
|
||||
|
||||
if ! make generate-json-schema; then
|
||||
echo "Generating json schema failed"
|
||||
success=false
|
||||
fi
|
||||
|
||||
if ! git diff-index --quiet HEAD --; then
|
||||
git diff-index HEAD --
|
||||
git --no-pager diff
|
||||
echo "JSON schema drift detected!"
|
||||
success=false
|
||||
fi
|
||||
|
||||
if ! $success; then
|
||||
exit 1
|
||||
fi
|
||||
Executable
+50
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eu
|
||||
|
||||
bold=$(tput bold)
|
||||
normal=$(tput sgr0)
|
||||
|
||||
if ! [ -x "$(command -v gh)" ]; then
|
||||
echo "The GitHub CLI could not be found. To continue follow the instructions at https://github.com/cli/cli#installation"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
gh auth status
|
||||
|
||||
# we need all of the git state to determine the next version. Since tagging is done by
|
||||
# the release pipeline it is possible to not have all of the tags from previous releases.
|
||||
git fetch --tags
|
||||
|
||||
# populates the CHANGELOG.md and VERSION files
|
||||
echo "${bold}Generating changelog...${normal}"
|
||||
make changelog 2> /dev/null
|
||||
|
||||
NEXT_VERSION=$(cat VERSION)
|
||||
|
||||
if [[ "$NEXT_VERSION" == "" || "${NEXT_VERSION}" == "(Unreleased)" ]]; then
|
||||
echo "Could not determine the next version to release. Exiting..."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
while true; do
|
||||
read -p "${bold}Do you want to trigger a release for version '${NEXT_VERSION}'?${normal} [y/n] " yn
|
||||
case $yn in
|
||||
[Yy]* ) echo; break;;
|
||||
[Nn]* ) echo; echo "Cancelling release..."; exit;;
|
||||
* ) echo "Please answer yes or no.";;
|
||||
esac
|
||||
done
|
||||
|
||||
echo "${bold}Kicking off release for ${NEXT_VERSION}${normal}..."
|
||||
echo
|
||||
gh workflow run release.yaml -f version=${NEXT_VERSION}
|
||||
|
||||
echo
|
||||
echo "${bold}Waiting for release to start...${normal}"
|
||||
sleep 10
|
||||
|
||||
set +e
|
||||
|
||||
echo "${bold}Head to the release workflow to monitor the release:${normal} $(gh run list --workflow=release.yaml --limit=1 --json url --jq '.[].url')"
|
||||
id=$(gh run list --workflow=release.yaml --limit=1 --json databaseId --jq '.[].databaseId')
|
||||
gh run watch $id --exit-status || (echo ; echo "${bold}Logs of failed step:${normal}" && GH_PAGER="" gh run view $id --log-failed)
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
OUTPUT="${OUTPUT//'%'/'%25'}" # URL encode all '%' characters
|
||||
OUTPUT="${OUTPUT//$'\n'/'%0A'}" # URL encode all '\n' characters
|
||||
OUTPUT="${OUTPUT//$'\r'/'%0D'}" # URL encode all '\r' characters
|
||||
echo "::set-output name=result::$OUTPUT"
|
||||
echo "result=$OUTPUT" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
name: "Release"
|
||||
on:
|
||||
push:
|
||||
# take no actions on push to any branch...
|
||||
branches-ignore:
|
||||
- "**"
|
||||
# ... only act on release tags
|
||||
tags:
|
||||
- "v*"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: tag the latest commit on main with the given version (prefixed with v)
|
||||
required: true
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.18.x"
|
||||
GO_VERSION: "1.19.x"
|
||||
|
||||
jobs:
|
||||
quality-gate:
|
||||
@@ -18,12 +16,11 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
# we don't want to release commits that have been pushed and tagged, but not necessarily merged onto main
|
||||
- name: Ensure tagged commit is on main
|
||||
- name: Check if tag already exists
|
||||
# note: this will fail if the tag already exists
|
||||
run: |
|
||||
echo "Tag: ${GITHUB_REF##*/}"
|
||||
git fetch origin main
|
||||
git merge-base --is-ancestor ${GITHUB_REF##*/} origin/main && echo "${GITHUB_REF##*/} is a commit on main!"
|
||||
[[ "${{ github.event.inputs.version }}" == v* ]] || (echo "version '${{ github.event.inputs.version }}' does not have a 'v' prefix" && exit 1)
|
||||
git tag ${{ github.event.inputs.version }}
|
||||
|
||||
- name: Check static analysis results
|
||||
uses: fountainhead/action-wait-for-check@v1.1.0
|
||||
@@ -120,8 +117,15 @@ jobs:
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Tag release
|
||||
run: |
|
||||
git tag ${{ github.event.inputs.version }}
|
||||
git push origin --tags
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build & publish release artifacts
|
||||
run: make release
|
||||
run: make ci-release
|
||||
env:
|
||||
# for mac signing and notarization...
|
||||
QUILL_SIGN_P12: ${{ secrets.ANCHORE_APPLE_DEVELOPER_ID_CERT_CHAIN }}
|
||||
@@ -147,12 +151,12 @@ jobs:
|
||||
with:
|
||||
status: ${{ job.status }}
|
||||
fields: repo,workflow,action,eventName
|
||||
text: "A new Syft release has been published: https://github.com/anchore/syft/releases/tag/${{ github.ref_name }}"
|
||||
text: "A new Syft release has been published: https://github.com/anchore/syft/releases/tag/${{ github.event.inputs.version }}"
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_TOOLBOX_WEBHOOK_URL }}
|
||||
if: ${{ success() }}
|
||||
|
||||
- uses: actions/upload-artifact@v2
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: artifacts
|
||||
path: dist/**/*
|
||||
|
||||
@@ -6,7 +6,7 @@ on:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.18.x"
|
||||
GO_VERSION: "1.19.x"
|
||||
GO_STABLE_VERSION: true
|
||||
|
||||
jobs:
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/setup-go@v3
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
stable: ${{ env.GO_STABLE_VERSION }}
|
||||
@@ -29,24 +29,27 @@ jobs:
|
||||
GOSIMPORTS_LATEST_VERSION=$(go list -m -json github.com/rinchsan/gosimports@latest 2>/dev/null | jq -r '.Version')
|
||||
YAJSV_LATEST_VERSION=$(go list -m -json github.com/neilpa/yajsv@latest 2>/dev/null | jq -r '.Version')
|
||||
COSIGN_LATEST_VERSION=$(go list -m -json github.com/sigstore/cosign@latest 2>/dev/null | jq -r '.Version')
|
||||
GLOW_LATEST_VERSION=$(go list -m -json github.com/charmbracelet/glow@latest 2>/dev/null | jq -r '.Version')
|
||||
|
||||
# update version variables in the Makefile
|
||||
sed -r -i -e 's/^(GOLANGCILINT_VERSION = ).*/\1'${GOLANGCILINT_LATEST_VERSION}'/' Makefile
|
||||
sed -r -i -e 's/^(BOUNCER_VERSION = ).*/\1'${BOUNCER_LATEST_VERSION}'/' Makefile
|
||||
sed -r -i -e 's/^(CHRONICLE_VERSION = ).*/\1'${CHRONICLE_LATEST_VERSION}'/' Makefile
|
||||
sed -r -i -e 's/^(GORELEASER_VERSION = ).*/\1'${GORELEASER_LATEST_VERSION}'/' Makefile
|
||||
sed -r -i -e 's/^(GOSIMPORTS_VERSION = ).*/\1'${GOSIMPORTS_LATEST_VERSION}'/' Makefile
|
||||
sed -r -i -e 's/^(YAJSV_VERSION = ).*/\1'${YAJSV_LATEST_VERSION}'/' Makefile
|
||||
sed -r -i -e 's/^(COSIGN_VERSION = ).*/\1'${COSIGN_LATEST_VERSION}'/' Makefile
|
||||
sed -r -i -e 's/^(GOLANGCILINT_VERSION := ).*/\1'${GOLANGCILINT_LATEST_VERSION}'/' Makefile
|
||||
sed -r -i -e 's/^(BOUNCER_VERSION := ).*/\1'${BOUNCER_LATEST_VERSION}'/' Makefile
|
||||
sed -r -i -e 's/^(CHRONICLE_VERSION := ).*/\1'${CHRONICLE_LATEST_VERSION}'/' Makefile
|
||||
sed -r -i -e 's/^(GORELEASER_VERSION := ).*/\1'${GORELEASER_LATEST_VERSION}'/' Makefile
|
||||
sed -r -i -e 's/^(GOSIMPORTS_VERSION := ).*/\1'${GOSIMPORTS_LATEST_VERSION}'/' Makefile
|
||||
sed -r -i -e 's/^(YAJSV_VERSION := ).*/\1'${YAJSV_LATEST_VERSION}'/' Makefile
|
||||
sed -r -i -e 's/^(COSIGN_VERSION := ).*/\1'${COSIGN_LATEST_VERSION}'/' Makefile
|
||||
sed -r -i -e 's/^(GLOW_VERSION := ).*/\1'${GLOW_LATEST_VERSION}'/' Makefile
|
||||
|
||||
# export the versions for use with create-pull-request
|
||||
echo "::set-output name=GOLANGCILINT::$GOLANGCILINT_LATEST_VERSION"
|
||||
echo "::set-output name=BOUNCER::$BOUNCER_LATEST_VERSION"
|
||||
echo "::set-output name=CHRONICLE::$CHRONICLE_LATEST_VERSION"
|
||||
echo "::set-output name=GORELEASER::$GORELEASER_LATEST_VERSION"
|
||||
echo "::set-output name=GOSIMPORTS::$GOSIMPORTS_LATEST_VERSION"
|
||||
echo "::set-output name=YAJSV::$YAJSV_LATEST_VERSION"
|
||||
echo "::set-output name=COSIGN::$COSIGN_LATEST_VERSION"
|
||||
echo "GOLANGCILINT=$GOLANGCILINT_LATEST_VERSION" >> $GITHUB_OUTPUT
|
||||
echo "BOUNCER=$BOUNCER_LATEST_VERSION" >> $GITHUB_OUTPUT
|
||||
echo "CHRONICLE=$CHRONICLE_LATEST_VERSION" >> $GITHUB_OUTPUT
|
||||
echo "GORELEASER=$GORELEASER_LATEST_VERSION" >> $GITHUB_OUTPUT
|
||||
echo "GOSIMPORTS=$GOSIMPORTS_LATEST_VERSION" >> $GITHUB_OUTPUT
|
||||
echo "YAJSV=$YAJSV_LATEST_VERSION" >> $GITHUB_OUTPUT
|
||||
echo "COSIGN=$COSIGN_LATEST_VERSION" >> $GITHUB_OUTPUT
|
||||
echo "GLOW=GLOW_LATEST_VERSION" >> $GITHUB_OUTPUT
|
||||
id: latest-versions
|
||||
|
||||
- uses: tibdex/github-app-token@v1
|
||||
@@ -55,14 +58,14 @@ jobs:
|
||||
app_id: ${{ secrets.TOKEN_APP_ID }}
|
||||
private_key: ${{ secrets.TOKEN_APP_PRIVATE_KEY }}
|
||||
|
||||
- uses: peter-evans/create-pull-request@v4
|
||||
- uses: peter-evans/create-pull-request@v5
|
||||
with:
|
||||
signoff: true
|
||||
delete-branch: true
|
||||
branch: auto/latest-bootstrap-tools
|
||||
labels: dependencies
|
||||
commit-message: 'Update syft bootstrap tools to latest versions.'
|
||||
title: 'Update syft bootstrap tools to latest versions.'
|
||||
commit-message: 'chore(deps): update bootstrap tools to latest versions'
|
||||
title: 'chore(deps): update bootstrap tools to latest versions'
|
||||
body: |
|
||||
- [golangci-lint ${{ steps.latest-versions.outputs.GOLANGCILINT }}](https://github.com/golangci/golangci-lint/releases/tag/${{ steps.latest-versions.outputs.GOLANGCILINT }})
|
||||
- [bouncer ${{ steps.latest-versions.outputs.BOUNCER }}](https://github.com/wagoodman/go-bouncer/releases/tag/${{ steps.latest-versions.outputs.BOUNCER }})
|
||||
@@ -71,5 +74,6 @@ jobs:
|
||||
- [gosimports ${{ steps.latest-versions.outputs.GOSIMPORTS }}](https://github.com/rinchsan/gosimports/releases/tag/${{ steps.latest-versions.outputs.GOSIMPORTS }})
|
||||
- [yajsv ${{ steps.latest-versions.outputs.YAJSV }}](https://github.com/neilpa/yajsv/releases/tag/${{ steps.latest-versions.outputs.YAJSV }})
|
||||
- [cosign ${{ steps.latest-versions.outputs.COSIGN }}](https://github.com/sigstore/cosign/releases/tag/${{ steps.latest-versions.outputs.COSIGN }})
|
||||
- [glow ${{ steps.latest-versions.outputs.GLOW }}](https://github.com/charmbracelet/glow/releases/tag/${{ steps.latest-versions.outputs.GLOW }})
|
||||
This is an auto-generated pull request to update all of the bootstrap tools to the latest versions.
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
|
||||
@@ -6,7 +6,7 @@ on:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.18.x"
|
||||
GO_VERSION: "1.19.x"
|
||||
GO_STABLE_VERSION: true
|
||||
|
||||
jobs:
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/setup-go@v3
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
stable: ${{ env.GO_STABLE_VERSION }}
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
go mod tidy
|
||||
|
||||
# export the version for use with create-pull-request
|
||||
echo "::set-output name=LATEST_VERSION::$LATEST_VERSION"
|
||||
echo "LATEST_VERSION=$LATEST_VERSION" >> $GITHUB_OUTPUT
|
||||
id: latest-version
|
||||
|
||||
- uses: tibdex/github-app-token@v1
|
||||
@@ -38,14 +38,14 @@ jobs:
|
||||
app_id: ${{ secrets.TOKEN_APP_ID }}
|
||||
private_key: ${{ secrets.TOKEN_APP_PRIVATE_KEY }}
|
||||
|
||||
- uses: peter-evans/create-pull-request@v4
|
||||
- uses: peter-evans/create-pull-request@v5
|
||||
with:
|
||||
signoff: true
|
||||
delete-branch: true
|
||||
branch: auto/latest
|
||||
branch: auto/latest-stereoscope
|
||||
labels: dependencies
|
||||
commit-message: "Update Stereoscope to ${{ steps.latest-version.outputs.LATEST_VERSION }}"
|
||||
title: "Update Stereoscope to ${{ steps.latest-version.outputs.LATEST_VERSION }}"
|
||||
commit-message: "chore(deps): update stereoscope to ${{ steps.latest-version.outputs.LATEST_VERSION }}"
|
||||
title: "chore(deps): update stereoscope to ${{ steps.latest-version.outputs.LATEST_VERSION }}"
|
||||
body: |
|
||||
Update Stereoscope to ${{ steps.latest-version.outputs.LATEST_VERSION }}
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
|
||||
@@ -34,26 +34,35 @@ jobs:
|
||||
uses: ./.github/actions/bootstrap
|
||||
|
||||
- name: Restore Java test-fixture cache
|
||||
id: unit-java-cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: syft/pkg/cataloger/java/test-fixtures/java-builds/packages
|
||||
key: ${{ runner.os }}-unit-java-cache-${{ hashFiles( 'syft/pkg/cataloger/java/test-fixtures/java-builds/packages.fingerprint' ) }}
|
||||
|
||||
- name: Restore RPM test-fixture cache
|
||||
id: unit-rpm-cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: syft/pkg/cataloger/rpm/test-fixtures/rpms
|
||||
key: ${{ runner.os }}-unit-rpm-cache-${{ hashFiles( 'syft/pkg/cataloger/rpm/test-fixtures/rpms.fingerprint' ) }}
|
||||
|
||||
- name: Restore go binary test-fixture cache
|
||||
id: unit-go-binary-cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: syft/pkg/cataloger/golang/test-fixtures/archs/binaries
|
||||
key: ${{ runner.os }}-unit-go-binaries-cache-${{ hashFiles( 'syft/pkg/cataloger/golang/test-fixtures/archs/binaries.fingerprint' ) }}
|
||||
|
||||
- name: Restore binary cataloger test-fixture cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: syft/pkg/cataloger/binary/test-fixtures/classifiers/dynamic
|
||||
key: ${{ runner.os }}-unit-binary-cataloger-cache-${{ hashFiles( 'syft/pkg/cataloger/binary/test-fixtures/cache.fingerprint' ) }}
|
||||
|
||||
- name: Restore Kernel test-fixture cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: syft/pkg/cataloger/kernel/test-fixtures/cache
|
||||
key: ${{ runner.os }}-unit-kernel-cache-${{ hashFiles( 'syft/pkg/cataloger/kernel/test-fixtures/cache.fingerprint' ) }}
|
||||
|
||||
- name: Run unit tests
|
||||
run: make unit
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
CHANGELOG.md
|
||||
VERSION
|
||||
/test/results
|
||||
/dist
|
||||
/snapshot
|
||||
|
||||
+13
-10
@@ -6,7 +6,6 @@ issues:
|
||||
# include:
|
||||
# - EXC0002 # disable excluding of issues about comments from golint
|
||||
|
||||
|
||||
linters:
|
||||
# inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint
|
||||
disable-all: true
|
||||
@@ -14,25 +13,24 @@ linters:
|
||||
- asciicheck
|
||||
- bodyclose
|
||||
- depguard
|
||||
- dogsled
|
||||
- dupl
|
||||
- errcheck
|
||||
- errorlint
|
||||
- exportloopref
|
||||
- forcetypeassert
|
||||
- funlen
|
||||
- gocognit
|
||||
- goconst
|
||||
- gocritic
|
||||
- gocyclo
|
||||
- gofmt
|
||||
- tparallel
|
||||
- importas
|
||||
- goimports
|
||||
- goprintffuncname
|
||||
- gosec
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
- misspell
|
||||
- nolintlint
|
||||
- nakedret
|
||||
- revive
|
||||
- staticcheck
|
||||
- stylecheck
|
||||
@@ -41,6 +39,7 @@ linters:
|
||||
- unparam
|
||||
- unused
|
||||
- whitespace
|
||||
|
||||
linters-settings:
|
||||
funlen:
|
||||
# Checks the number of lines in a function.
|
||||
@@ -57,7 +56,7 @@ run:
|
||||
timeout: 10m
|
||||
|
||||
# do not enable...
|
||||
# - dogsled # found to be to niche and ineffective
|
||||
# - deadcode # The owner seems to have abandoned the linter. Replaced by "unused".
|
||||
# - goprintffuncname # does not catch all cases and there are exceptions
|
||||
# - nakedret # does not catch all cases and should not fail a build
|
||||
# - gochecknoglobals
|
||||
@@ -73,7 +72,11 @@ run:
|
||||
# - lll # without a way to specify per-line exception cases, this is not usable
|
||||
# - maligned # this is an excellent linter, but tricky to optimize and we are not sensitive to memory layout optimizations
|
||||
# - nestif
|
||||
# - prealloc # following this rule isn't consistently a good idea, as it sometimes forces unnecessary allocations that result in less idiomatic code
|
||||
# - scopelint # deprecated
|
||||
# - nolintlint # as of go1.19 this conflicts with the behavior of gofmt, which is a deal-breaker (lint-fix will still fail when running lint)
|
||||
# - prealloc # following this rule isn't consistently a good idea, as it sometimes forces unnecessary allocations that result in less idiomatic code
|
||||
# - rowserrcheck # not in a repo with sql, so this is not useful
|
||||
# - scopelint # deprecated
|
||||
# - structcheck # The owner seems to have abandoned the linter. Replaced by "unused".
|
||||
# - testpackage
|
||||
# - wsl # this doens't have an auto-fixer yet and is pretty noisy (https://github.com/bombsimon/wsl/issues/90)
|
||||
# - varcheck # The owner seems to have abandoned the linter. Replaced by "unused".
|
||||
# - wsl # this doens't have an auto-fixer yet and is pretty noisy (https://github.com/bombsimon/wsl/issues/90)
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
# Contributing to Syft
|
||||
[#](#) Contributing to Syft
|
||||
|
||||
If you are looking to contribute to this project and want to open a GitHub pull request ("PR"), there are a few guidelines of what we are looking for in patches. Make sure you go through this document and ensure that your code proposal is aligned.
|
||||
|
||||
@@ -10,7 +10,7 @@ Before you can contribute to Syft, you need to configure your development enviro
|
||||
|
||||
You will need to install Go. The version on https://go.dev works best, using the system golang doesn't always work the way you might expect.
|
||||
|
||||
At the time of writing, Go 1.19 does not work correctly with Syft. Please use Go 1.18 for now.
|
||||
Refer to the go.mod file in the root of this repo for the recommended version of Go to install.
|
||||
|
||||
You will also need Docker. There's no reason the system packages shouldn't work, but we used the official Docker package. You can find instructions for installing Docker in Debian [here](https://docs.docker.com/engine/install/debian/).
|
||||
|
||||
|
||||
+31
-13
@@ -15,7 +15,7 @@ After cloning the following step can help you get setup:
|
||||
- this command `go run cmd/syft/main.go alpine:latest` will compile and run syft against `alpine:latest`
|
||||
5. view the README or syft help output for more output options
|
||||
|
||||
The main make tasks for common static analysis and testing are `lint`, `lint-fix`, `unit`, `integration`, and `cli`.
|
||||
The main make tasks for common static analysis and testing are `lint`, `format`, `lint-fix`, `unit`, `integration`, and `cli`.
|
||||
|
||||
See `make help` for all the current make tasks.
|
||||
|
||||
@@ -118,45 +118,63 @@ sequenceDiagram
|
||||
|
||||
Catalogers are the way in which syft is able to identify and construct packages given some amount of source metadata.
|
||||
For example, Syft can locate and process `package-lock.json` files when performing filesystem scans.
|
||||
See: [how to specify file globs](https://github.com/anchore/syft/blob/main/syft/pkg/cataloger/javascript/cataloger.go#L16-L21)
|
||||
and an implementation of the [package-lock.json parser](https://github.com/anchore/syft/blob/main/syft/pkg/cataloger/javascript/cataloger.go#L16-L21) fora quick review.
|
||||
See: [how to specify file globs](https://github.com/anchore/syft/tree/v0.70.0/syft/pkg/cataloger/javascript/cataloger.go#L16-L21)
|
||||
and an implementation of the [package-lock.json parser](https://github.com/anchore/syft/tree/v0.70.0/syft/pkg/cataloger/javascript/cataloger.go#L16-L21) for a quick review.
|
||||
|
||||
From a high level catalogers have the following properties:
|
||||
|
||||
- They are independent from one another. The java cataloger has no idea of the processes, assumptions, or results of the python cataloger, for example.
|
||||
|
||||
- They do not know what source is being analyzed. Are we analyzing a local directory? an image? if so, the squashed representation or all layers? The catalogers do not know the answers to these questions. Only that there is an interface to query for file paths and contents from an underlying "source" being scanned.
|
||||
|
||||
- Packages created by the cataloger should not be mutated after they are created. There is one exception made for adding CPEs to a package after the cataloging phase, but that will most likely be moved back into the cataloger in the future.
|
||||
|
||||
#### Building a new Cataloger
|
||||
|
||||
Catalogers must fulfill the interface [found here](https://github.com/anchore/syft/blob/main/syft/pkg/cataloger.go).
|
||||
Catalogers must fulfill the interface [found here](https://github.com/anchore/syft/tree/v0.70.0/syft/pkg/cataloger.go).
|
||||
This means that when building a new cataloger, the new struct must implement both method signatures of `Catalog` and `Name`.
|
||||
|
||||
A top level view of the functions that construct all the catalogers can be found [here](https://github.com/anchore/syft/blob/main/syft/pkg/cataloger/cataloger.go).
|
||||
A top level view of the functions that construct all the catalogers can be found [here](https://github.com/anchore/syft/tree/v0.70.0/syft/pkg/cataloger/cataloger.go).
|
||||
When an author has finished writing a new cataloger this is the spot to plug in the new catalog constructor.
|
||||
|
||||
For a top level view of how the catalogers are used see [this function](https://github.com/anchore/syft/blob/main/syft/pkg/cataloger/catalog.go#L41-L100) as a reference. It ranges over all catalogers passed as an argument and invokes the `Catalog` method:
|
||||
For a top level view of how the catalogers are used see [this function](https://github.com/anchore/syft/tree/v0.70.0/syft/pkg/cataloger/catalog.go#L41-L100) as a reference. It ranges over all catalogers passed as an argument and invokes the `Catalog` method:
|
||||
|
||||
Each cataloger has its own `Catalog` method, but this does not mean that they are all vastly different.
|
||||
Take a look at the `apkdb` cataloger for alpine to see how it [constructs a generic.NewCataloger](https://github.com/anchore/syft/blob/main/syft/pkg/cataloger/apkdb/cataloger.go).
|
||||
Take a look at the `apkdb` cataloger for alpine to see how it [constructs a generic.NewCataloger](https://github.com/anchore/syft/tree/v0.70.0/syft/pkg/cataloger/apkdb/cataloger.go).
|
||||
|
||||
`generic.NewCataloger` is an abstraction syft uses to make writing common components easier. First, it takes the `catalogerName` to identify the cataloger.
|
||||
On the other side of the call it uses two key pieces which inform the cataloger how to identify and return packages, the `globPatterns` and the `parseFunction`:
|
||||
- The first piece is a `parseByGlob` matching pattern used to identify the files that contain the package metadata.
|
||||
See [here for the APK example](https://github.com/anchore/syft/blob/main/syft/pkg/apk_metadata.go#L16-L41).
|
||||
See [here for the APK example](https://github.com/anchore/syft/tree/v0.70.0/syft/pkg/apk_metadata.go#L16-L41).
|
||||
- The other is a `parseFunction` which informs the cataloger what to do when it has found one of the above matches files.
|
||||
See this [link for an example](https://github.com/anchore/syft/blob/main/syft/pkg/cataloger/apkdb/parse_apk_db.go#L22-L102).
|
||||
See this [link for an example](https://github.com/anchore/syft/tree/v0.70.0/syft/pkg/cataloger/apkdb/parse_apk_db.go#L22-L102).
|
||||
|
||||
If you're unsure about using the `Generic Cataloger` and think the use case being filled requires something more custom
|
||||
just file an issue or ask in our slack, and we'd be more than happy to help on the design.
|
||||
|
||||
Identified packages share a common struct so be sure that when the new cataloger is constructing a new package it is using the [`Package` struct](https://github.com/anchore/syft/blob/main/syft/pkg/package.go#L16-L31).
|
||||
Identified packages share a common struct so be sure that when the new cataloger is constructing a new package it is using the [`Package` struct](https://github.com/anchore/syft/tree/v0.70.0/syft/pkg/package.go#L16-L31).
|
||||
|
||||
Metadata Note: Identified packages are also assigned specific metadata that can be unique to their environment.
|
||||
See [this folder](https://github.com/anchore/syft/tree/main/syft/pkg) for examples of the different metadata types.
|
||||
See [this folder](https://github.com/anchore/syft/tree/v0.70.0/syft/pkg) for examples of the different metadata types.
|
||||
These are plugged into the `MetadataType` and `Metadata` fields in the above struct. `MetadataType` informs which type is being used. `Metadata` is an interface converted to that type.
|
||||
|
||||
Finally, here is an example of where the package construction is done in the apk cataloger. The first link is where `newPackage` is called in the `parseFunction`. The second link shows the package construction:
|
||||
- [Call for new package](https://github.com/anchore/syft/blob/6a7d6e6071829c7ce2943266c0e187b27c0b325c/syft/pkg/cataloger/apkdb/parse_apk_db.go#L96-L99)
|
||||
- [APK Package Constructor](https://github.com/anchore/syft/blob/6a7d6e6071829c7ce2943266c0e187b27c0b325c/syft/pkg/cataloger/apkdb/package.go#L12-L27)
|
||||
- [Call for new package](https://github.com/anchore/syft/blob/v0.70.0/syft/pkg/cataloger/apkdb/parse_apk_db.go#L106)
|
||||
- [APK Package Constructor](https://github.com/anchore/syft/tree/v0.70.0/syft/pkg/cataloger/apkdb/package.go#L12-L27)
|
||||
|
||||
If you have more questions about implementing a cataloger or questions about one you might be currently working
|
||||
always feel free to file an issue or reach out to us [on slack](https://anchore.com/slack).
|
||||
|
||||
#### Searching for files
|
||||
|
||||
All catalogers are provided an instance of the [`source.FileResolver`](https://github.com/anchore/syft/blob/v0.70.0/syft/source/file_resolver.go#L8) to interface with the image and search for files. The implementations for these
|
||||
abstractions leverage [`stereoscope`](https://github.com/anchore/stereoscope) in order to perform searching. Here is a
|
||||
rough outline how that works:
|
||||
|
||||
1. a stereoscope `file.Index` is searched based on the input given (a path, glob, or MIME type). The index is relatively fast to search, but requires results to be filtered down to the files that exist in the specific layer(s) of interest. This is done automatically by the `filetree.Searcher` abstraction. This abstraction will fallback to searching directly against the raw `filetree.FileTree` if the index does not contain the file(s) of interest. Note: the `filetree.Searcher` is used by the `source.FileResolver` abstraction.
|
||||
2. Once the set of files are returned from the `filetree.Searcher` the results are filtered down further to return the most unique file results. For example, you may have requested for files by a glob that returns multiple results. These results are filtered down to deduplicate by real files, so if a result contains two references to the same file, say one accessed via symlink and one accessed via the real path, then the real path reference is returned and the symlink reference is filtered out. If both were accessed by symlink then the first (by lexical order) is returned. This is done automatically by the `source.FileResolver` abstraction.
|
||||
3. By the time results reach the `pkg.Cataloger` you are guaranteed to have a set of unique files that exist in the layer(s) of interest (relative to what the resolver supports).
|
||||
|
||||
## Testing
|
||||
|
||||
### Levels of testing
|
||||
|
||||
@@ -4,18 +4,21 @@ TEMP_DIR := ./.tmp
|
||||
# Command templates #################################
|
||||
LINT_CMD := $(TEMP_DIR)/golangci-lint run --tests=false
|
||||
GOIMPORTS_CMD := $(TEMP_DIR)/gosimports -local github.com/anchore
|
||||
RELEASE_CMD := $(TEMP_DIR)/goreleaser release --rm-dist
|
||||
RELEASE_CMD := $(TEMP_DIR)/goreleaser release --clean
|
||||
SNAPSHOT_CMD := $(RELEASE_CMD) --skip-publish --skip-sign --snapshot
|
||||
CHRONICLE_CMD = $(TEMP_DIR)/chronicle
|
||||
GLOW_CMD = $(TEMP_DIR)/glow
|
||||
|
||||
# Tool versions #################################
|
||||
GOLANGCILINT_VERSION := v1.50.1
|
||||
GOSIMPORTS_VERSION := v0.3.5
|
||||
GOLANGCILINT_VERSION := v1.52.2
|
||||
GOSIMPORTS_VERSION := v0.3.8
|
||||
BOUNCER_VERSION := v0.4.0
|
||||
CHRONICLE_VERSION := v0.4.2
|
||||
GORELEASER_VERSION := v1.14.1
|
||||
CHRONICLE_VERSION := v0.6.0
|
||||
GORELEASER_VERSION := v1.17.1
|
||||
YAJSV_VERSION := v1.4.1
|
||||
COSIGN_VERSION := v1.13.1
|
||||
QUILL_VERSION := v0.2.0
|
||||
GLOW_VERSION := v1.5.0
|
||||
|
||||
# Formatting variables #################################
|
||||
BOLD := $(shell tput -T linux bold)
|
||||
@@ -88,6 +91,7 @@ bootstrap-tools: $(TEMP_DIR)
|
||||
GOBIN="$(realpath $(TEMP_DIR))" go install github.com/rinchsan/gosimports/cmd/gosimports@$(GOSIMPORTS_VERSION)
|
||||
GOBIN="$(realpath $(TEMP_DIR))" go install github.com/neilpa/yajsv@$(YAJSV_VERSION)
|
||||
GOBIN="$(realpath $(TEMP_DIR))" go install github.com/sigstore/cosign/cmd/cosign@$(COSIGN_VERSION)
|
||||
GOBIN="$(realpath $(TEMP_DIR))" go install github.com/charmbracelet/glow@$(GLOW_VERSION)
|
||||
|
||||
.PHONY: bootstrap-go
|
||||
bootstrap-go:
|
||||
@@ -114,14 +118,18 @@ lint: ## Run gofmt + golangci lint checks
|
||||
$(eval MALFORMED_FILENAMES := $(shell find . | grep -e ':'))
|
||||
@bash -c "[[ '$(MALFORMED_FILENAMES)' == '' ]] || (printf '\nfound unsupported filename characters:\n$(MALFORMED_FILENAMES)\n\n' && false)"
|
||||
|
||||
.PHONY: lint-fix
|
||||
lint-fix: ## Auto-format all source code + run golangci lint fixers
|
||||
$(call title,Running lint fixers)
|
||||
.PHONY: format
|
||||
format: ## Auto-format all source code
|
||||
$(call title,Running formatters)
|
||||
gofmt -w -s .
|
||||
$(GOIMPORTS_CMD) -w .
|
||||
$(LINT_CMD) --fix
|
||||
go mod tidy
|
||||
|
||||
.PHONY: lint-fix
|
||||
lint-fix: format ## Auto-format all source code + run golangci lint fixers
|
||||
$(call title,Running lint fixers)
|
||||
$(LINT_CMD) --fix
|
||||
|
||||
.PHONY: check-licenses
|
||||
check-licenses: ## Ensure transitive dependencies are compliant with the current license policy
|
||||
$(call title,Checking for license compliance)
|
||||
@@ -132,10 +140,7 @@ check-go-mod-tidy:
|
||||
|
||||
check-json-schema-drift:
|
||||
$(call title,Ensure there is no drift between the JSON schema and the code)
|
||||
@git diff-index --quiet HEAD -- || (echo "there are uncommitted changes, please commit them before running this check" && false)
|
||||
@make generate-json-schema || (echo "$(RED)$(BOLD)JSON schema drift detected!$(RESET)" && false)
|
||||
@git diff-index --quiet HEAD -- || (echo "$(RED)$(BOLD)JSON schema drift detected!$(RESET)" && false)
|
||||
|
||||
@.github/scripts/json-schema-drift-check.sh
|
||||
|
||||
## Testing targets #################################
|
||||
|
||||
@@ -188,6 +193,10 @@ fingerprints:
|
||||
cd test/integration/test-fixtures && \
|
||||
make cache.fingerprint
|
||||
|
||||
# for BINARY test fixtures
|
||||
cd syft/pkg/cataloger/binary/test-fixtures && \
|
||||
make cache.fingerprint
|
||||
|
||||
# for JAVA BUILD test fixtures
|
||||
cd syft/pkg/cataloger/java/test-fixtures/java-builds && \
|
||||
make packages.fingerprint
|
||||
@@ -200,6 +209,10 @@ fingerprints:
|
||||
cd syft/pkg/cataloger/rpm/test-fixtures && \
|
||||
make rpms.fingerprint
|
||||
|
||||
# for Kernel test fixtures
|
||||
cd syft/pkg/cataloger/kernel/test-fixtures && \
|
||||
make cache.fingerprint
|
||||
|
||||
# for INSTALL integration test fixtures
|
||||
cd test/install && \
|
||||
make cache.fingerprint
|
||||
@@ -213,6 +226,7 @@ fixtures:
|
||||
$(call title,Generating test fixtures)
|
||||
cd syft/pkg/cataloger/java/test-fixtures/java-builds && make
|
||||
cd syft/pkg/cataloger/rpm/test-fixtures && make
|
||||
cd syft/pkg/cataloger/binary/test-fixtures && make
|
||||
|
||||
.PHONY: show-test-image-cache
|
||||
show-test-image-cache: ## Show all docker and image tar cache
|
||||
@@ -246,6 +260,11 @@ install-test-ci-mac: $(SNAPSHOT_DIR)
|
||||
cd test/install && \
|
||||
make ci-test-mac
|
||||
|
||||
.PHONY: generate-compare-file
|
||||
generate-compare-file:
|
||||
$(call title,Generating compare test file)
|
||||
go run ./cmd/syft $(COMPARE_TEST_IMAGE) -o json > $(COMPARE_DIR)/test-fixtures/acceptance-centos-8.2.2004.json
|
||||
|
||||
# note: we cannot clean the snapshot directory since the pipeline builds the snapshot separately
|
||||
.PHONY: compare-mac
|
||||
compare-mac: $(TEMP_DIR) $(SNAPSHOT_DIR) ## Run compare tests on build snapshot binaries and packages (Mac)
|
||||
@@ -307,20 +326,20 @@ $(SNAPSHOT_DIR): ## Build snapshot release binaries and packages
|
||||
$(SNAPSHOT_CMD) --config $(TEMP_DIR)/goreleaser.yaml
|
||||
|
||||
.PHONY: changelog
|
||||
changelog: clean-changelog $(CHANGELOG) ## Generate and show the changelog for the current unreleased version
|
||||
@docker run -it --rm \
|
||||
-v $(shell pwd)/$(CHANGELOG):/$(CHANGELOG) \
|
||||
rawkode/mdv \
|
||||
-t 748.5989 \
|
||||
/$(CHANGELOG)
|
||||
changelog: clean-changelog ## Generate and show the changelog for the current unreleased version
|
||||
$(CHRONICLE_CMD) -vvv -n --version-file VERSION > $(CHANGELOG)
|
||||
@$(GLOW_CMD) $(CHANGELOG)
|
||||
|
||||
$(CHANGELOG):
|
||||
$(TEMP_DIR)/chronicle -vv > $(CHANGELOG)
|
||||
$(CHRONICLE_CMD) -vvv > $(CHANGELOG)
|
||||
|
||||
.PHONY: release
|
||||
release: clean-dist $(CHANGELOG)
|
||||
release:
|
||||
@.github/scripts/trigger-release.sh
|
||||
|
||||
.PHONY: ci-release
|
||||
ci-release: ci-check clean-dist $(CHANGELOG)
|
||||
$(call title,Publishing release artifacts)
|
||||
@.github/scripts/ci-check.sh
|
||||
|
||||
# create a config with the dist dir overridden
|
||||
echo "dist: $(DIST_DIR)" > $(TEMP_DIR)/goreleaser.yaml
|
||||
@@ -335,6 +354,10 @@ release: clean-dist $(CHANGELOG)
|
||||
# upload the version file that supports the application version update check (excluding pre-releases)
|
||||
.github/scripts/update-version-file.sh "$(DIST_DIR)" "$(VERSION)"
|
||||
|
||||
.PHONY: ci-check
|
||||
ci-check:
|
||||
@.github/scripts/ci-check.sh
|
||||
|
||||
## Cleanup targets #################################
|
||||
|
||||
.PHONY: clean
|
||||
@@ -353,7 +376,7 @@ clean-dist: clean-changelog
|
||||
|
||||
.PHONY: clean-changelog
|
||||
clean-changelog:
|
||||
rm -f $(CHANGELOG)
|
||||
rm -f $(CHANGELOG) VERSION
|
||||
|
||||
clean-test-image-cache: clean-test-image-tar-cache clean-test-image-docker-cache ## Clean test image cache
|
||||
|
||||
|
||||
@@ -42,9 +42,12 @@ For commercial support options with Syft or Grype, please [contact Anchore](http
|
||||
- Erlang (rebar3)
|
||||
- Go (go.mod, Go binaries)
|
||||
- Haskell (cabal, stack)
|
||||
- Java (jar, ear, war, par, sar, native-image)
|
||||
- Java (jar, ear, war, par, sar, nar, native-image)
|
||||
- JavaScript (npm, yarn)
|
||||
- Jenkins Plugins (jpi, hpi)
|
||||
- Linux kernel archives (vmlinz)
|
||||
- Linux kernel modules (ko)
|
||||
- Nix (outputs in /nix/store)
|
||||
- PHP (composer)
|
||||
- Python (wheel, egg, poetry, requirements.txt)
|
||||
- Red Hat (rpm)
|
||||
@@ -110,9 +113,7 @@ The above output includes only software that is visible in the container (i.e.,
|
||||
syft <image> --scope all-layers
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Supported sources
|
||||
### Supported sources
|
||||
|
||||
Syft can generate a SBOM from a variety of sources:
|
||||
|
||||
@@ -141,7 +142,13 @@ file:path/to/yourproject/file read directly from a path on disk (any
|
||||
registry:yourrepo/yourimage:tag pull image directly from a registry (no container runtime required)
|
||||
```
|
||||
|
||||
#### Default Cataloger Configuration by scan type
|
||||
If an image source is not provided and cannot be detected from the given reference it is assumed the image should be pulled from the Docker daemon.
|
||||
If docker is not present, then the Podman daemon is attempted next, followed by reaching out directly to the image registry last.
|
||||
|
||||
|
||||
This default behavior can be overridden with the `default-image-pull-source` configuration option (See [Configuration](https://github.com/anchore/syft#configuration) for more details).
|
||||
|
||||
### Default Cataloger Configuration by scan type
|
||||
|
||||
##### Image Scanning:
|
||||
- alpmdb
|
||||
@@ -179,7 +186,7 @@ registry:yourrepo/yourimage:tag pull image directly from a registry (no
|
||||
- conan
|
||||
- hackage
|
||||
|
||||
#### Non Default:
|
||||
##### Non Default:
|
||||
- cargo-auditable-binary
|
||||
|
||||
### Excluding file paths
|
||||
@@ -213,8 +220,10 @@ Where the `formats` available are:
|
||||
- `text`: A row-oriented, human-and-machine-friendly output.
|
||||
- `cyclonedx-xml`: A XML report conforming to the [CycloneDX 1.4 specification](https://cyclonedx.org/specification/overview/).
|
||||
- `cyclonedx-json`: A JSON report conforming to the [CycloneDX 1.4 specification](https://cyclonedx.org/specification/overview/).
|
||||
- `spdx-tag-value`: A tag-value formatted report conforming to the [SPDX 2.2 specification](https://spdx.github.io/spdx-spec/).
|
||||
- `spdx-json`: A JSON report conforming to the [SPDX 2.2 JSON Schema](https://github.com/spdx/spdx-spec/blob/v2.2/schemas/spdx-schema.json).
|
||||
- `spdx-tag-value`: A tag-value formatted report conforming to the [SPDX 2.3 specification](https://spdx.github.io/spdx-spec/v2.3/).
|
||||
- `spdx-tag-value@2.2`: A tag-value formatted report conforming to the [SPDX 2.2 specification](https://spdx.github.io/spdx-spec/v2.2.2/).
|
||||
- `spdx-json`: A JSON report conforming to the [SPDX 2.3 JSON Schema](https://github.com/spdx/spdx-spec/blob/v2.3/schemas/spdx-schema.json).
|
||||
- `spdx-json@2.2`: A JSON report conforming to the [SPDX 2.2 JSON Schema](https://github.com/spdx/spdx-spec/blob/v2.2/schemas/spdx-schema.json).
|
||||
- `github`: A JSON report conforming to GitHub's dependency snapshot format.
|
||||
- `table`: A columnar summary (default).
|
||||
- `template`: Lets the user specify the output format. See ["Using templates"](#using-templates) below.
|
||||
@@ -391,7 +400,7 @@ Certificate subject: test.email@testdomain.com
|
||||
Certificate issuer URL: https://accounts.google.com
|
||||
```
|
||||
|
||||
#### Local private key support
|
||||
### Local private key support
|
||||
|
||||
To generate an SBOM attestation for a container image using a local private key:
|
||||
```
|
||||
@@ -434,6 +443,10 @@ file: ""
|
||||
# same as SYFT_CHECK_FOR_APP_UPDATE env var
|
||||
check-for-app-update: true
|
||||
|
||||
# allows users to specify which image source should be used to generate the sbom
|
||||
# valid values are: registry, docker, podman
|
||||
default-image-pull-source: ""
|
||||
|
||||
# a list of globs to exclude from scanning. same as --exclude ; for example:
|
||||
# exclude:
|
||||
# - "/etc/**"
|
||||
@@ -492,6 +505,35 @@ package:
|
||||
# same as -s ; SYFT_PACKAGE_CATALOGER_SCOPE env var
|
||||
scope: "squashed"
|
||||
|
||||
golang:
|
||||
# search for go package licences in the GOPATH of the system running Syft, note that this is outside the
|
||||
# container filesystem and potentially outside the root of a local directory scan
|
||||
# SYFT_GOLANG_SEARCH_LOCAL_MOD_CACHE_LICENSES env var
|
||||
search-local-mod-cache-licenses: false
|
||||
|
||||
# specify an explicit go mod cache directory, if unset this defaults to $GOPATH/pkg/mod or $HOME/go/pkg/mod
|
||||
# SYFT_GOLANG_LOCAL_MOD_CACHE_DIR env var
|
||||
local-mod-cache-dir: ""
|
||||
|
||||
# search for go package licences by retrieving the package from a network proxy
|
||||
# SYFT_GOLANG_SEARCH_REMOTE_LICENSES env var
|
||||
search-remote-licenses: false
|
||||
|
||||
# remote proxy to use when retrieving go packages from the network,
|
||||
# if unset this defaults to $GOPROXY followed by https://proxy.golang.org
|
||||
# SYFT_GOLANG_PROXY env var
|
||||
proxy: ""
|
||||
|
||||
# specifies packages which should not be fetched by proxy
|
||||
# if unset this defaults to $GONOPROXY
|
||||
# SYFT_GOLANG_NOPROXY env var
|
||||
no-proxy: ""
|
||||
|
||||
linux-kernel:
|
||||
# whether to catalog linux kernel modules found within lib/modules/** directories
|
||||
# SYFT_LINUX_KERNEL_CATALOG_MODULES env var
|
||||
catalog-modules: true
|
||||
|
||||
# cataloging file contents is exposed through the power-user subcommand
|
||||
file-contents:
|
||||
cataloger:
|
||||
|
||||
+51
-43
@@ -1,14 +1,49 @@
|
||||
# Release Process
|
||||
# Release
|
||||
|
||||
## Creating a release
|
||||
|
||||
This release process itself should be as automated as possible, and has only a few steps:
|
||||
|
||||
1. **Trigger a new release with `make release`**. At this point you'll see a preview
|
||||
changelog in the terminal. If you're happy with the changelog, press `y` to continue, otherwise
|
||||
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.
|
||||
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:
|
||||
|
||||
- Deleting the GitHub Release
|
||||
- Untag the docker images in the `ghcr.io` and `docker.io` registries
|
||||
- Revert the brew formula in [`anchore/homebrew-syft`](https://github.com/anchore/homebrew-syft) to point to the previous release
|
||||
- Add a new `retract` entry in the go.mod for the versioned release
|
||||
|
||||
**Note**: do not delete release tags from the git repository since there may already be references to the release
|
||||
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. 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
|
||||
|
||||
### 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
|
||||
@@ -21,15 +56,13 @@ completed, would allow the release to be considered complete. A Milestone is onl
|
||||
|
||||
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). Beta releases
|
||||
and release candidates should not be independently planned from the non-beta release. That
|
||||
is, the features for a `v0.1-beta.1` release should be planned under the `v0.1` Milestone,
|
||||
not under a separate `v0.1-beta.1` Milestone.
|
||||
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
|
||||
|
||||
### 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
|
||||
@@ -42,9 +75,9 @@ Changelog line. Furthermore, there should be a place to see all released version
|
||||
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 repository while
|
||||
**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
|
||||
@@ -60,45 +93,20 @@ The above suggestions imply that we should:
|
||||
- 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.
|
||||
**With this approach as we cultivate good organization of PRs and issues we automatically
|
||||
get an equally good Changelog.**
|
||||
|
||||
## Major, minor, and patch releases
|
||||
|
||||
### 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
|
||||
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.
|
||||
|
||||
## Cutting a 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 2 or 4 weeks.
|
||||
|
||||
This release process itself should be as automated as possible, and have only a few steps:
|
||||
|
||||
1. Tag the main branch with a full semantic-version, prefixed with a `v`. If there is a
|
||||
milestone with a partial version, the full version should be used for the git tag (e.g.
|
||||
with a Milestone of `v0.1` the tag should be `v0.1.0`). You can determine the changes going
|
||||
into a release by running `make changelog-unreleased`. Use this change list to determine the
|
||||
release increment. After determining the release increment (major, minor, patch), create the tag.
|
||||
Given the above example the command to create the tag would be `git tag v0.1.0`.
|
||||
|
||||
1. Push the tag. Given the above example the command to push the tag would be `git push origin v0.1.0`.
|
||||
|
||||
1. A release admin must approve the release on the GitHub Actions release pipeline run page.
|
||||
Once approved, the release pipeline will generate all assets and draft a GitHub Release.
|
||||
|
||||
1. Navigate to the GitHub Release draft page to review the final changelog and publish the
|
||||
release. Once published, a release-follow-up pipeline will publish derivative artifacts
|
||||
(docker image to DockerHub, brew formula to the external homebrew git repo, etc).
|
||||
|
||||
1. If there is a release Milestone, close it.
|
||||
|
||||
@@ -20,8 +20,7 @@ const (
|
||||
attestHelp = attestExample + attestSchemeHelp
|
||||
)
|
||||
|
||||
//nolint:dupl
|
||||
func Attest(v *viper.Viper, app *config.Application, ro *options.RootOptions, po *options.PackagesOptions) *cobra.Command {
|
||||
func Attest(v *viper.Viper, app *config.Application, ro *options.RootOptions, po *options.PackagesOptions, ao *options.AttestOptions) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "attest --output [FORMAT] <IMAGE>",
|
||||
Short: "Generate an SBOM as an attestation for the given [SOURCE] container image",
|
||||
@@ -50,11 +49,17 @@ func Attest(v *viper.Viper, app *config.Application, ro *options.RootOptions, po
|
||||
},
|
||||
}
|
||||
|
||||
// syft attest is an enhancment of the packages command, so it should have the same flags
|
||||
// syft attest is an enhancement of the packages command, so it should have the same flags
|
||||
err := po.AddFlags(cmd, v)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// syft attest has its own options not included as part of the packages command
|
||||
err = ao.AddFlags(cmd, v)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/wagoodman/go-partybus"
|
||||
"github.com/wagoodman/go-progress"
|
||||
@@ -22,12 +23,11 @@ import (
|
||||
"github.com/anchore/syft/syft/event/monitor"
|
||||
"github.com/anchore/syft/syft/formats/syftjson"
|
||||
"github.com/anchore/syft/syft/formats/table"
|
||||
"github.com/anchore/syft/syft/formats/template"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func Run(ctx context.Context, app *config.Application, args []string) error {
|
||||
func Run(_ context.Context, app *config.Application, args []string) error {
|
||||
err := ValidateOutputOptions(app)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -47,7 +47,7 @@ func Run(ctx context.Context, app *config.Application, args []string) error {
|
||||
// could be an image or a directory, with or without a scheme
|
||||
// TODO: validate that source is image
|
||||
userInput := args[0]
|
||||
si, err := source.ParseInputWithName(userInput, app.Platform, true, app.Name)
|
||||
si, err := source.ParseInputWithName(userInput, app.Platform, app.Name, app.DefaultImagePullSource)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not generate source input for packages command: %w", err)
|
||||
}
|
||||
@@ -97,6 +97,7 @@ func buildSBOM(app *config.Application, si source.Input, writer sbom.Writer, err
|
||||
return sBytes, nil
|
||||
}
|
||||
|
||||
//nolint:funlen
|
||||
func execWorker(app *config.Application, si source.Input, writer sbom.Writer) <-chan error {
|
||||
errs := make(chan error)
|
||||
go func() {
|
||||
@@ -130,10 +131,33 @@ func execWorker(app *config.Application, si source.Input, writer sbom.Writer) <-
|
||||
return
|
||||
}
|
||||
|
||||
args := []string{"attest", si.UserInput, "--type", "custom", "--predicate", f.Name()}
|
||||
// Select Cosign predicate type based on defined output type
|
||||
// As orientation, check: https://github.com/sigstore/cosign/blob/main/pkg/cosign/attestation/attestation.go
|
||||
var predicateType string
|
||||
switch strings.ToLower(o) {
|
||||
case "cyclonedx-json":
|
||||
predicateType = "cyclonedx"
|
||||
case "spdx-tag-value":
|
||||
predicateType = "spdx"
|
||||
case "spdx-json":
|
||||
predicateType = "spdxjson"
|
||||
default:
|
||||
predicateType = "custom"
|
||||
}
|
||||
|
||||
args := []string{"attest", si.UserInput, "--predicate", f.Name(), "--type", predicateType}
|
||||
if app.Attest.Key != "" {
|
||||
args = append(args, "--key", app.Attest.Key)
|
||||
}
|
||||
|
||||
execCmd := exec.Command(cmd, args...)
|
||||
execCmd.Env = os.Environ()
|
||||
execCmd.Env = append(execCmd.Env, "COSIGN_EXPERIMENTAL=1")
|
||||
if app.Attest.Key != "" {
|
||||
execCmd.Env = append(execCmd.Env, fmt.Sprintf("COSIGN_PASSWORD=%s", app.Attest.Password))
|
||||
} else {
|
||||
// no key provided, use cosign's keyless mode
|
||||
execCmd.Env = append(execCmd.Env, "COSIGN_EXPERIMENTAL=1")
|
||||
}
|
||||
|
||||
// bus adapter for ui to hook into stdout via an os pipe
|
||||
r, w, err := os.Pipe()
|
||||
@@ -143,7 +167,7 @@ func execWorker(app *config.Application, si source.Input, writer sbom.Writer) <-
|
||||
}
|
||||
defer w.Close()
|
||||
|
||||
b := &busWriter{r: r, w: w, mon: &progress.Manual{N: -1}}
|
||||
b := &busWriter{r: r, w: w, mon: progress.NewManual(-1)}
|
||||
execCmd.Stdout = b
|
||||
execCmd.Stderr = b
|
||||
defer b.mon.SetCompleted()
|
||||
@@ -151,7 +175,7 @@ func execWorker(app *config.Application, si source.Input, writer sbom.Writer) <-
|
||||
// attest the SBOM
|
||||
err = execCmd.Run()
|
||||
if err != nil {
|
||||
b.mon.Err = err
|
||||
b.mon.SetError(err)
|
||||
errs <- fmt.Errorf("unable to attest SBOM: %w", err)
|
||||
return
|
||||
}
|
||||
@@ -166,16 +190,9 @@ func execWorker(app *config.Application, si source.Input, writer sbom.Writer) <-
|
||||
}
|
||||
|
||||
func ValidateOutputOptions(app *config.Application) error {
|
||||
var usesTemplateOutput bool
|
||||
for _, o := range app.Outputs {
|
||||
if o == template.ID.String() {
|
||||
usesTemplateOutput = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if usesTemplateOutput && app.OutputTemplatePath == "" {
|
||||
return fmt.Errorf(`must specify path to template file when using "template" output format`)
|
||||
err := packages.ValidateOutputOptions(app)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(app.Outputs) > 1 {
|
||||
|
||||
@@ -45,15 +45,17 @@ func New() (*cobra.Command, error) {
|
||||
// we also need the command to have information about the `root` options because of this alias
|
||||
ro := &options.RootOptions{}
|
||||
po := &options.PackagesOptions{}
|
||||
ao := &options.AttestOptions{}
|
||||
packagesCmd := Packages(v, app, ro, po)
|
||||
|
||||
// root options are also passed to the attestCmd so that a user provided config location can be discovered
|
||||
poweruserCmd := PowerUser(v, app, ro)
|
||||
convertCmd := Convert(v, app, ro, po)
|
||||
attestCmd := Attest(v, app, ro, po)
|
||||
attestCmd := Attest(v, app, ro, po, ao)
|
||||
|
||||
// rootCmd is currently an alias for the packages command
|
||||
rootCmd := &cobra.Command{
|
||||
Use: fmt.Sprintf("%s [SOURCE]", internal.ApplicationName),
|
||||
Short: packagesCmd.Short,
|
||||
Long: packagesCmd.Long,
|
||||
Args: packagesCmd.Args,
|
||||
|
||||
@@ -14,8 +14,9 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
convertExample = ` {{.appName}} {{.command}} img.syft.json -o spdx-json convert a syft SBOM to spdx-json, output goes to stdout in table format, by default
|
||||
{{.appName}} {{.command}} img.syft.json -o cyclonedx-json=img.cdx.json convert a syft SBOM to CycloneDX, output goes to a file named img.cdx.json
|
||||
convertExample = ` {{.appName}} {{.command}} img.syft.json -o spdx-json convert a syft SBOM to spdx-json, output goes to stdout
|
||||
{{.appName}} {{.command}} img.syft.json -o cyclonedx-json=img.cdx.json convert a syft SBOM to CycloneDX, output is written to the file "img.cdx.json""
|
||||
{{.appName}} {{.command}} - -o spdx-json convert an SBOM from STDIN to spdx-json
|
||||
`
|
||||
)
|
||||
|
||||
|
||||
@@ -3,17 +3,18 @@ package convert
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/anchore/syft/cmd/syft/cli/options"
|
||||
"github.com/anchore/syft/internal/config"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft"
|
||||
"github.com/anchore/syft/syft/formats"
|
||||
)
|
||||
|
||||
func Run(ctx context.Context, app *config.Application, args []string) error {
|
||||
func Run(_ context.Context, app *config.Application, args []string) error {
|
||||
log.Warn("convert is an experimental feature, run `syft convert -h` for help")
|
||||
writer, err := options.MakeWriter(app.Outputs, app.File, "")
|
||||
writer, err := options.MakeWriter(app.Outputs, app.File, app.OutputTemplatePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -26,13 +27,23 @@ func Run(ctx context.Context, app *config.Application, args []string) error {
|
||||
|
||||
// this can only be a SBOM file
|
||||
userInput := args[0]
|
||||
f, err := os.Open(userInput)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open SBOM file: %w", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
sbom, _, err := syft.Decode(f)
|
||||
var reader io.ReadCloser
|
||||
|
||||
if userInput == "-" {
|
||||
reader = os.Stdin
|
||||
} else {
|
||||
f, err := os.Open(userInput)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open SBOM file: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
_ = f.Close()
|
||||
}()
|
||||
reader = f
|
||||
}
|
||||
|
||||
sbom, _, err := formats.Decode(reader)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decode SBOM: %w", err)
|
||||
}
|
||||
|
||||
@@ -46,14 +46,11 @@ func generateCatalogPackagesTask(app *config.Application) (Task, error) {
|
||||
|
||||
task := func(results *sbom.Artifacts, src *source.Source) ([]artifact.Relationship, error) {
|
||||
packageCatalog, relationships, theDistro, err := syft.CatalogPackages(src, app.ToCatalogerConfig())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
results.PackageCatalog = packageCatalog
|
||||
results.LinuxDistribution = theDistro
|
||||
|
||||
return relationships, nil
|
||||
return relationships, err
|
||||
}
|
||||
|
||||
return task, nil
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package options
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type AttestOptions struct {
|
||||
Key string
|
||||
}
|
||||
|
||||
var _ Interface = (*AttestOptions)(nil)
|
||||
|
||||
func (o AttestOptions) AddFlags(cmd *cobra.Command, v *viper.Viper) error {
|
||||
cmd.Flags().StringVarP(&o.Key, "key", "k", "", "the key to use for the attestation")
|
||||
return bindAttestConfigOptions(cmd.Flags(), v)
|
||||
}
|
||||
|
||||
//nolint:revive
|
||||
func bindAttestConfigOptions(flags *pflag.FlagSet, v *viper.Viper) error {
|
||||
if err := v.BindPFlag("attest.key", flags.Lookup("key")); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package options
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/formats/cyclonedxjson"
|
||||
"github.com/anchore/syft/syft/formats/cyclonedxxml"
|
||||
"github.com/anchore/syft/syft/formats/github"
|
||||
"github.com/anchore/syft/syft/formats/spdxjson"
|
||||
"github.com/anchore/syft/syft/formats/spdxtagvalue"
|
||||
"github.com/anchore/syft/syft/formats/syftjson"
|
||||
"github.com/anchore/syft/syft/formats/table"
|
||||
"github.com/anchore/syft/syft/formats/text"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
)
|
||||
|
||||
func FormatAliases(ids ...sbom.FormatID) (aliases []string) {
|
||||
for _, id := range ids {
|
||||
switch id {
|
||||
case syftjson.ID:
|
||||
aliases = append(aliases, "syft-json")
|
||||
case text.ID:
|
||||
aliases = append(aliases, "text")
|
||||
case table.ID:
|
||||
aliases = append(aliases, "table")
|
||||
case spdxjson.ID:
|
||||
aliases = append(aliases, "spdx-json")
|
||||
case spdxtagvalue.ID:
|
||||
aliases = append(aliases, "spdx-tag-value")
|
||||
case cyclonedxxml.ID:
|
||||
aliases = append(aliases, "cyclonedx-xml")
|
||||
case cyclonedxjson.ID:
|
||||
aliases = append(aliases, "cyclonedx-json")
|
||||
case github.ID:
|
||||
aliases = append(aliases, "github", "github-json")
|
||||
default:
|
||||
aliases = append(aliases, string(id))
|
||||
}
|
||||
}
|
||||
return aliases
|
||||
}
|
||||
@@ -31,6 +31,7 @@ func (o *FulcioOptions) AddFlags(cmd *cobra.Command, v *viper.Viper) error {
|
||||
return bindFulcioConfigOptions(cmd.Flags(), v)
|
||||
}
|
||||
|
||||
//nolint:revive
|
||||
func bindFulcioConfigOptions(flags *pflag.FlagSet, v *viper.Viper) error {
|
||||
if err := v.BindPFlag("attest.fulcio-url", flags.Lookup("fulcio-url")); err != nil {
|
||||
return err
|
||||
|
||||
@@ -31,6 +31,7 @@ func (o *OIDCOptions) AddFlags(cmd *cobra.Command, v *viper.Viper) error {
|
||||
return bindOIDCConfigOptions(cmd.Flags(), v)
|
||||
}
|
||||
|
||||
//nolint:revive
|
||||
func bindOIDCConfigOptions(flags *pflag.FlagSet, v *viper.Viper) error {
|
||||
if err := v.BindPFlag("attest.oidc-issuer", flags.Lookup("oidc-issuer")); err != nil {
|
||||
return err
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/anchore/syft/syft"
|
||||
"github.com/anchore/syft/syft/formats"
|
||||
"github.com/anchore/syft/syft/formats/table"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
@@ -30,8 +30,8 @@ func (o *PackagesOptions) AddFlags(cmd *cobra.Command, v *viper.Viper) error {
|
||||
cmd.Flags().StringVarP(&o.Scope, "scope", "s", cataloger.DefaultSearchConfig().Scope.String(),
|
||||
fmt.Sprintf("selection of layers to catalog, options=%v", source.AllScopes))
|
||||
|
||||
cmd.Flags().StringArrayVarP(&o.Output, "output", "o", FormatAliases(table.ID),
|
||||
fmt.Sprintf("report output format, options=%v", FormatAliases(syft.FormatIDs()...)))
|
||||
cmd.Flags().StringArrayVarP(&o.Output, "output", "o", []string{string(table.ID)},
|
||||
fmt.Sprintf("report output format, options=%v", formats.AllIDs()))
|
||||
|
||||
cmd.Flags().StringVarP(&o.File, "file", "", "",
|
||||
"file to write the default report output to (default is STDOUT)")
|
||||
@@ -54,6 +54,7 @@ func (o *PackagesOptions) AddFlags(cmd *cobra.Command, v *viper.Viper) error {
|
||||
return bindPackageConfigOptions(cmd.Flags(), v)
|
||||
}
|
||||
|
||||
//nolint:revive
|
||||
func bindPackageConfigOptions(flags *pflag.FlagSet, v *viper.Viper) error {
|
||||
// Formatting & Input options //////////////////////////////////////////////
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ func (o *RekorOptions) AddFlags(cmd *cobra.Command, v *viper.Viper) error {
|
||||
return bindRekorConfigOptions(cmd.Flags(), v)
|
||||
}
|
||||
|
||||
//nolint:revive
|
||||
func bindRekorConfigOptions(flags *pflag.FlagSet, v *viper.Viper) error {
|
||||
// TODO: config re-design
|
||||
if err := v.BindPFlag("attest.rekor-url", flags.Lookup("rekor-url")); err != nil {
|
||||
|
||||
@@ -22,6 +22,7 @@ func (o *RootOptions) AddFlags(cmd *cobra.Command, v *viper.Viper) error {
|
||||
return bindRootConfigOptions(cmd.PersistentFlags(), v)
|
||||
}
|
||||
|
||||
//nolint:revive
|
||||
func bindRootConfigOptions(flags *pflag.FlagSet, v *viper.Viper) error {
|
||||
if err := v.BindPFlag("config", flags.Lookup("config")); err != nil {
|
||||
return err
|
||||
|
||||
@@ -11,7 +11,7 @@ type VersionOptions struct {
|
||||
|
||||
var _ Interface = (*VersionOptions)(nil)
|
||||
|
||||
func (o *VersionOptions) AddFlags(cmd *cobra.Command, v *viper.Viper) error {
|
||||
func (o *VersionOptions) AddFlags(cmd *cobra.Command, _ *viper.Viper) error {
|
||||
cmd.Flags().StringVarP(&o.Output, "output", "o", "text", "format to show version information (available=[text, json])")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
|
||||
"github.com/anchore/syft/syft"
|
||||
"github.com/anchore/syft/syft/formats"
|
||||
"github.com/anchore/syft/syft/formats/table"
|
||||
"github.com/anchore/syft/syft/formats/template"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
@@ -32,7 +32,7 @@ func MakeWriter(outputs []string, defaultFile, templateFilePath string) (sbom.Wr
|
||||
func parseOutputs(outputs []string, defaultFile, templateFilePath string) (out []sbom.WriterOption, errs error) {
|
||||
// always should have one option -- we generally get the default of "table", but just make sure
|
||||
if len(outputs) == 0 {
|
||||
outputs = append(outputs, string(table.ID))
|
||||
outputs = append(outputs, table.ID.String())
|
||||
}
|
||||
|
||||
for _, name := range outputs {
|
||||
@@ -52,9 +52,9 @@ func parseOutputs(outputs []string, defaultFile, templateFilePath string) (out [
|
||||
file = parts[1]
|
||||
}
|
||||
|
||||
format := syft.FormatByName(name)
|
||||
format := formats.ByName(name)
|
||||
if format == nil {
|
||||
errs = multierror.Append(errs, fmt.Errorf(`unsupported output format "%s", supported formats are: %+v`, name, FormatAliases(syft.FormatIDs()...)))
|
||||
errs = multierror.Append(errs, fmt.Errorf(`unsupported output format "%s", supported formats are: %+v`, name, formats.AllIDs()))
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -18,8 +18,10 @@ const (
|
||||
{{.appName}} {{.command}} alpine:latest -o json show all possible cataloging details
|
||||
{{.appName}} {{.command}} alpine:latest -o cyclonedx show a CycloneDX formatted SBOM
|
||||
{{.appName}} {{.command}} alpine:latest -o cyclonedx-json show a CycloneDX JSON formatted SBOM
|
||||
{{.appName}} {{.command}} alpine:latest -o spdx show a SPDX 2.2 Tag-Value formatted SBOM
|
||||
{{.appName}} {{.command}} alpine:latest -o spdx-json show a SPDX 2.2 JSON formatted SBOM
|
||||
{{.appName}} {{.command}} alpine:latest -o spdx show a SPDX 2.3 Tag-Value formatted SBOM
|
||||
{{.appName}} {{.command}} alpine:latest -o spdx@2.2 show a SPDX 2.2 Tag-Value formatted SBOM
|
||||
{{.appName}} {{.command}} alpine:latest -o spdx-json show a SPDX 2.3 JSON formatted SBOM
|
||||
{{.appName}} {{.command}} alpine:latest -o spdx-json@2.2 show a SPDX 2.2 JSON formatted SBOM
|
||||
{{.appName}} {{.command}} alpine:latest -vv show verbose debug information
|
||||
{{.appName}} {{.command}} alpine:latest -o template -t my_format.tmpl show a SBOM formatted according to given template file
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ import (
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func Run(ctx context.Context, app *config.Application, args []string) error {
|
||||
func Run(_ context.Context, app *config.Application, args []string) error {
|
||||
err := ValidateOutputOptions(app)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -42,7 +42,7 @@ func Run(ctx context.Context, app *config.Application, args []string) error {
|
||||
|
||||
// could be an image or a directory, with or without a scheme
|
||||
userInput := args[0]
|
||||
si, err := source.ParseInputWithName(userInput, app.Platform, true, app.Name)
|
||||
si, err := source.ParseInputWithName(userInput, app.Platform, app.Name, app.DefaultImagePullSource)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not generate source input for packages command: %w", err)
|
||||
}
|
||||
|
||||
@@ -26,9 +26,10 @@ import (
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func Run(ctx context.Context, app *config.Application, args []string) error {
|
||||
func Run(_ context.Context, app *config.Application, args []string) error {
|
||||
f := syftjson.Format()
|
||||
writer, err := sbom.NewWriter(sbom.WriterOption{
|
||||
Format: syftjson.Format(),
|
||||
Format: f,
|
||||
Path: app.File,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -46,7 +47,7 @@ func Run(ctx context.Context, app *config.Application, args []string) error {
|
||||
}()
|
||||
|
||||
userInput := args[0]
|
||||
si, err := source.ParseInputWithName(userInput, app.Platform, true, app.Name)
|
||||
si, err := source.ParseInputWithName(userInput, app.Platform, app.Name, app.DefaultImagePullSource)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not generate source input for packages command: %w", err)
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"github.com/anchore/syft/internal/version"
|
||||
)
|
||||
|
||||
func Version(v *viper.Viper, app *config.Application) *cobra.Command {
|
||||
func Version(v *viper.Viper, _ *config.Application) *cobra.Command {
|
||||
o := &options.VersionOptions{}
|
||||
cmd := &cobra.Command{
|
||||
Use: "version",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module github.com/anchore/syft
|
||||
|
||||
go 1.18
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
|
||||
@@ -11,12 +11,12 @@ require (
|
||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501
|
||||
github.com/bmatcuk/doublestar/v4 v4.6.0
|
||||
github.com/dustin/go-humanize v1.0.0
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
github.com/facebookincubator/nvdtools v0.1.5
|
||||
github.com/go-test/deep v1.1.0
|
||||
github.com/google/go-cmp v0.5.9
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/gookit/color v1.5.2
|
||||
github.com/gookit/color v1.5.3
|
||||
github.com/hashicorp/go-multierror v1.1.1
|
||||
github.com/jinzhu/copier v0.3.5
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||
@@ -31,67 +31,80 @@ require (
|
||||
github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e
|
||||
github.com/sergi/go-diff v1.3.1
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
github.com/spdx/tools-golang v0.3.1-0.20221108182156-8a01147e6342
|
||||
github.com/spf13/afero v1.9.3
|
||||
github.com/spf13/cobra v1.6.1
|
||||
github.com/spdx/tools-golang v0.5.0
|
||||
github.com/spf13/afero v1.9.5
|
||||
github.com/spf13/cobra v1.7.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/spf13/viper v1.15.0
|
||||
github.com/stretchr/testify v1.8.1
|
||||
github.com/stretchr/testify v1.8.2
|
||||
github.com/vifraa/gopom v0.2.1
|
||||
github.com/wagoodman/go-partybus v0.0.0-20210627031916-db1f5573bbc5
|
||||
github.com/wagoodman/go-progress v0.0.0-20200731105512-1020f39e6240
|
||||
github.com/wagoodman/go-progress v0.0.0-20230301185719-21920a456ad5
|
||||
github.com/wagoodman/jotframe v0.0.0-20211129225309-56b0d0a4aebb
|
||||
github.com/xeipuuv/gojsonschema v1.2.0
|
||||
golang.org/x/mod v0.6.0
|
||||
golang.org/x/net v0.5.0
|
||||
golang.org/x/term v0.4.0
|
||||
golang.org/x/mod v0.10.0
|
||||
golang.org/x/net v0.9.0
|
||||
golang.org/x/term v0.7.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/CycloneDX/cyclonedx-go v0.7.1-0.20221222100750-41a1ac565cce
|
||||
github.com/Masterminds/sprig/v3 v3.2.2
|
||||
github.com/Masterminds/sprig/v3 v3.2.3
|
||||
github.com/anchore/go-logger v0.0.0-20220728155337-03b66a5207d8
|
||||
github.com/anchore/stereoscope v0.0.0-20221208011002-c5ff155d72f1
|
||||
github.com/docker/docker v20.10.20+incompatible
|
||||
github.com/google/go-containerregistry v0.12.1
|
||||
github.com/anchore/stereoscope v0.0.0-20230406143206-e95d60a265e3
|
||||
github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da
|
||||
github.com/docker/docker v23.0.3+incompatible
|
||||
github.com/go-git/go-billy/v5 v5.4.1
|
||||
github.com/go-git/go-git/v5 v5.6.1
|
||||
github.com/google/go-containerregistry v0.14.0
|
||||
github.com/google/licensecheck v0.3.1
|
||||
github.com/invopop/jsonschema v0.7.0
|
||||
github.com/knqyf263/go-rpmdb v0.0.0-20221030135625-4082a22221ce
|
||||
github.com/opencontainers/go-digest v1.0.0
|
||||
github.com/sassoftware/go-rpmutils v0.2.0
|
||||
github.com/vbatts/go-mtree v0.5.2
|
||||
golang.org/x/exp v0.0.0-20220823124025-807a23277127
|
||||
github.com/vbatts/go-mtree v0.5.3
|
||||
golang.org/x/exp v0.0.0-20230202163644-54bba9f4231b
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/DataDog/zstd v1.4.5 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.2.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.0 // indirect
|
||||
github.com/containerd/containerd v1.6.12 // indirect
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.12.1 // indirect
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 // indirect
|
||||
github.com/acomagu/bufpipe v1.0.4 // indirect
|
||||
github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 // indirect
|
||||
github.com/becheran/wildmatch-go v1.0.0 // indirect
|
||||
github.com/cloudflare/circl v1.1.0 // indirect
|
||||
github.com/containerd/containerd v1.6.18 // indirect
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/docker/cli v20.10.20+incompatible // indirect
|
||||
github.com/docker/cli v23.0.1+incompatible // indirect
|
||||
github.com/docker/distribution v2.8.1+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.7.0 // indirect
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.0 // indirect
|
||||
github.com/go-git/gcfg v1.5.0 // indirect
|
||||
github.com/go-restruct/restruct v1.2.0-alpha // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/huandu/xstrings v1.3.2 // indirect
|
||||
github.com/huandu/xstrings v1.3.3 // indirect
|
||||
github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 // indirect
|
||||
github.com/imdario/mergo v0.3.12 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
||||
github.com/imdario/mergo v0.3.13 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/klauspost/compress v1.15.11 // indirect
|
||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||
github.com/klauspost/compress v1.16.0 // indirect
|
||||
github.com/klauspost/pgzip v1.2.5 // indirect
|
||||
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
@@ -105,11 +118,13 @@ require (
|
||||
github.com/opencontainers/image-spec v1.1.0-rc2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.15 // indirect
|
||||
github.com/pjbgf/sha1cd v0.3.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.8.0 // indirect
|
||||
github.com/shopspring/decimal v1.2.0 // indirect
|
||||
github.com/skeema/knownhosts v1.1.0 // indirect
|
||||
github.com/spf13/cast v1.5.0 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/stretchr/objx v0.5.0 // indirect
|
||||
@@ -119,19 +134,21 @@ require (
|
||||
github.com/therootcompany/xz v1.0.1 // indirect
|
||||
github.com/ulikunitz/xz v0.5.10 // indirect
|
||||
github.com/vbatts/tar-split v0.11.2 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
|
||||
golang.org/x/sync v0.1.0 // indirect
|
||||
golang.org/x/sys v0.4.0 // indirect
|
||||
golang.org/x/text v0.6.0 // indirect
|
||||
golang.org/x/tools v0.1.12 // indirect
|
||||
golang.org/x/sys v0.7.0 // indirect
|
||||
golang.org/x/text v0.9.0 // indirect
|
||||
golang.org/x/tools v0.7.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||
google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef // indirect
|
||||
google.golang.org/grpc v1.52.0 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
google.golang.org/protobuf v1.29.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
lukechampine.com/uint128 v1.1.1 // indirect
|
||||
modernc.org/cc/v3 v3.36.0 // indirect
|
||||
modernc.org/ccgo/v3 v3.16.6 // indirect
|
||||
@@ -149,7 +166,7 @@ require (
|
||||
// go: warning: github.com/andybalholm/brotli@v1.0.1: retracted by module author: occasional panics and data corruption
|
||||
github.com/andybalholm/brotli v1.0.4 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
golang.org/x/crypto v0.1.0 // indirect
|
||||
golang.org/x/crypto v0.6.0 // indirect
|
||||
)
|
||||
|
||||
retract (
|
||||
|
||||
@@ -3,6 +3,7 @@ package config
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"reflect"
|
||||
"sort"
|
||||
@@ -17,6 +18,8 @@ import (
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger"
|
||||
golangCataloger "github.com/anchore/syft/syft/pkg/cataloger/golang"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/kernel"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -38,24 +41,28 @@ type Application struct {
|
||||
ConfigPath string `yaml:"configPath,omitempty" json:"configPath" mapstructure:"config"`
|
||||
Verbosity uint `yaml:"verbosity,omitempty" json:"verbosity" mapstructure:"verbosity"`
|
||||
// -q, indicates to not show any status output to stderr (ETUI or logging UI)
|
||||
Quiet bool `yaml:"quiet" json:"quiet" mapstructure:"quiet"`
|
||||
Outputs []string `yaml:"output" json:"output" mapstructure:"output"` // -o, the format to use for output
|
||||
OutputTemplatePath string `yaml:"output-template-path" json:"output-template-path" mapstructure:"output-template-path"` // -t template file to use for output
|
||||
File string `yaml:"file" json:"file" mapstructure:"file"` // --file, the file to write report output to
|
||||
CheckForAppUpdate bool `yaml:"check-for-app-update" json:"check-for-app-update" mapstructure:"check-for-app-update"` // whether to check for an application update on start up or not
|
||||
Dev development `yaml:"dev" json:"dev" mapstructure:"dev"`
|
||||
Log logging `yaml:"log" json:"log" mapstructure:"log"` // all logging-related options
|
||||
Catalogers []string `yaml:"catalogers" json:"catalogers" mapstructure:"catalogers"`
|
||||
Package pkg `yaml:"package" json:"package" mapstructure:"package"`
|
||||
FileMetadata FileMetadata `yaml:"file-metadata" json:"file-metadata" mapstructure:"file-metadata"`
|
||||
FileClassification fileClassification `yaml:"file-classification" json:"file-classification" mapstructure:"file-classification"`
|
||||
FileContents fileContents `yaml:"file-contents" json:"file-contents" mapstructure:"file-contents"`
|
||||
Secrets secrets `yaml:"secrets" json:"secrets" mapstructure:"secrets"`
|
||||
Registry registry `yaml:"registry" json:"registry" mapstructure:"registry"`
|
||||
Exclusions []string `yaml:"exclude" json:"exclude" mapstructure:"exclude"`
|
||||
Platform string `yaml:"platform" json:"platform" mapstructure:"platform"`
|
||||
Name string `yaml:"name" json:"name" mapstructure:"name"`
|
||||
Parallelism int `yaml:"parallelism" json:"parallelism" mapstructure:"parallelism"` // the number of catalog workers to run in parallel
|
||||
Quiet bool `yaml:"quiet" json:"quiet" mapstructure:"quiet"`
|
||||
Outputs []string `yaml:"output" json:"output" mapstructure:"output"` // -o, the format to use for output
|
||||
OutputTemplatePath string `yaml:"output-template-path" json:"output-template-path" mapstructure:"output-template-path"` // -t template file to use for output
|
||||
File string `yaml:"file" json:"file" mapstructure:"file"` // --file, the file to write report output to
|
||||
CheckForAppUpdate bool `yaml:"check-for-app-update" json:"check-for-app-update" mapstructure:"check-for-app-update"` // whether to check for an application update on start up or not
|
||||
Dev development `yaml:"dev" json:"dev" mapstructure:"dev"`
|
||||
Log logging `yaml:"log" json:"log" mapstructure:"log"` // all logging-related options
|
||||
Catalogers []string `yaml:"catalogers" json:"catalogers" mapstructure:"catalogers"`
|
||||
Package pkg `yaml:"package" json:"package" mapstructure:"package"`
|
||||
Golang golang `yaml:"golang" json:"golang" mapstructure:"golang"`
|
||||
LinuxKernel linuxKernel `yaml:"linux-kernel" json:"linux-kernel" mapstructure:"linux-kernel"`
|
||||
Attest attest `yaml:"attest" json:"attest" mapstructure:"attest"`
|
||||
FileMetadata FileMetadata `yaml:"file-metadata" json:"file-metadata" mapstructure:"file-metadata"`
|
||||
FileClassification fileClassification `yaml:"file-classification" json:"file-classification" mapstructure:"file-classification"`
|
||||
FileContents fileContents `yaml:"file-contents" json:"file-contents" mapstructure:"file-contents"`
|
||||
Secrets secrets `yaml:"secrets" json:"secrets" mapstructure:"secrets"`
|
||||
Registry registry `yaml:"registry" json:"registry" mapstructure:"registry"`
|
||||
Exclusions []string `yaml:"exclude" json:"exclude" mapstructure:"exclude"`
|
||||
Platform string `yaml:"platform" json:"platform" mapstructure:"platform"`
|
||||
Name string `yaml:"name" json:"name" mapstructure:"name"`
|
||||
Parallelism int `yaml:"parallelism" json:"parallelism" mapstructure:"parallelism"` // the number of catalog workers to run in parallel
|
||||
DefaultImagePullSource string `yaml:"default-image-pull-source" json:"default-image-pull-source" mapstructure:"default-image-pull-source"` // specify default image pull source
|
||||
}
|
||||
|
||||
func (cfg Application) ToCatalogerConfig() cataloger.Config {
|
||||
@@ -67,6 +74,15 @@ func (cfg Application) ToCatalogerConfig() cataloger.Config {
|
||||
},
|
||||
Catalogers: cfg.Catalogers,
|
||||
Parallelism: cfg.Parallelism,
|
||||
Golang: golangCataloger.NewGoCatalogerOpts().
|
||||
WithSearchLocalModCacheLicenses(cfg.Golang.SearchLocalModCacheLicenses).
|
||||
WithLocalModCacheDir(cfg.Golang.LocalModCacheDir).
|
||||
WithSearchRemoteLicenses(cfg.Golang.SearchRemoteLicenses).
|
||||
WithProxy(cfg.Golang.Proxy).
|
||||
WithNoProxy(cfg.Golang.NoProxy),
|
||||
LinuxKernel: kernel.LinuxCatalogerConfig{
|
||||
CatalogModules: cfg.LinuxKernel.CatalogModules,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,6 +138,12 @@ func (cfg *Application) parseConfigValues() error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := checkDefaultSourceValues(cfg.DefaultImagePullSource); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// check for valid default source options
|
||||
// parse nested config options
|
||||
// for each field in the configuration struct, see if the field implements the parser interface
|
||||
// note: the app config is a pointer, so we need to grab the elements explicitly (to traverse the address)
|
||||
@@ -184,6 +206,7 @@ func loadDefaultValues(v *viper.Viper) {
|
||||
v.SetDefault("check-for-app-update", true)
|
||||
v.SetDefault("catalogers", nil)
|
||||
v.SetDefault("parallelism", 1)
|
||||
v.SetDefault("default-image-pull-source", "")
|
||||
|
||||
// for each field in the configuration struct, see if the field implements the defaultValueLoader interface and invoke it if it does
|
||||
value := reflect.ValueOf(Application{})
|
||||
@@ -207,6 +230,7 @@ func (cfg Application) String() string {
|
||||
return string(appaStr)
|
||||
}
|
||||
|
||||
// nolint:funlen
|
||||
func loadConfig(v *viper.Viper, configPath string) error {
|
||||
var err error
|
||||
// use explicitly the given user config
|
||||
@@ -222,13 +246,26 @@ func loadConfig(v *viper.Viper, configPath string) error {
|
||||
|
||||
// start searching for valid configs in order...
|
||||
// 1. look for .<appname>.yaml (in the current directory)
|
||||
confFilePath := "." + internal.ApplicationName
|
||||
|
||||
// TODO: Remove this before v1.0.0
|
||||
// See syft #1634
|
||||
v.AddConfigPath(".")
|
||||
v.SetConfigName("." + internal.ApplicationName)
|
||||
if err = v.ReadInConfig(); err == nil {
|
||||
v.Set("config", v.ConfigFileUsed())
|
||||
return nil
|
||||
} else if !errors.As(err, &viper.ConfigFileNotFoundError{}) {
|
||||
return fmt.Errorf("unable to parse config=%q: %w", v.ConfigFileUsed(), err)
|
||||
v.SetConfigName(confFilePath)
|
||||
|
||||
// check if config.yaml exists in the current directory
|
||||
// DEPRECATED: this will be removed in v1.0.0
|
||||
if _, err := os.Stat("config.yaml"); err == nil {
|
||||
log.Warn("DEPRECATED: ./config.yaml as a configuration file is deprecated and will be removed as an option in v1.0.0, please rename to .syft.yaml")
|
||||
}
|
||||
|
||||
if _, err := os.Stat(confFilePath + ".yaml"); err == nil {
|
||||
if err = v.ReadInConfig(); err == nil {
|
||||
v.Set("config", v.ConfigFileUsed())
|
||||
return nil
|
||||
} else if !errors.As(err, &viper.ConfigFileNotFoundError{}) {
|
||||
return fmt.Errorf("unable to parse config=%q: %w", v.ConfigFileUsed(), err)
|
||||
}
|
||||
}
|
||||
|
||||
// 2. look for .<appname>/config.yaml (in the current directory)
|
||||
@@ -255,11 +292,12 @@ func loadConfig(v *viper.Viper, configPath string) error {
|
||||
}
|
||||
|
||||
// 4. look for <appname>/config.yaml in xdg locations (starting with xdg home config dir, then moving upwards)
|
||||
v.AddConfigPath(path.Join(xdg.ConfigHome, internal.ApplicationName))
|
||||
v.SetConfigName("config")
|
||||
configPath = path.Join(xdg.ConfigHome, internal.ApplicationName)
|
||||
v.AddConfigPath(configPath)
|
||||
for _, dir := range xdg.ConfigDirs {
|
||||
v.AddConfigPath(path.Join(dir, internal.ApplicationName))
|
||||
}
|
||||
v.SetConfigName("config")
|
||||
if err = v.ReadInConfig(); err == nil {
|
||||
v.Set("config", v.ConfigFileUsed())
|
||||
return nil
|
||||
@@ -268,3 +306,15 @@ func loadConfig(v *viper.Viper, configPath string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var validDefaultSourceValues = []string{"registry", "docker", "podman", ""}
|
||||
|
||||
func checkDefaultSourceValues(source string) error {
|
||||
validValues := internal.NewStringSet(validDefaultSourceValues...)
|
||||
if !validValues.Contains(source) {
|
||||
validValuesString := strings.Join(validDefaultSourceValues, ", ")
|
||||
return fmt.Errorf("%s is not a valid default source; please use one of the following: %s''", source, validValuesString)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"github.com/adrg/xdg"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TODO: set negative case when config.yaml is no longer a valid option
|
||||
func TestApplicationConfig(t *testing.T) {
|
||||
// disable homedir package cache for testing
|
||||
originalCacheOpt := homedir.DisableCache
|
||||
homedir.DisableCache = true
|
||||
t.Cleanup(func() {
|
||||
homedir.DisableCache = originalCacheOpt
|
||||
})
|
||||
|
||||
// ensure we have no side effects for xdg package for future tests
|
||||
originalXDG := os.Getenv("XDG_CONFIG_HOME")
|
||||
t.Cleanup(func() {
|
||||
// note: we're not using t.Setenv since the effect we're trying to eliminate is within the xdg package
|
||||
require.NoError(t, os.Setenv("XDG_CONFIG_HOME", originalXDG))
|
||||
xdg.Reload()
|
||||
})
|
||||
|
||||
// config is picked up at desired configuration paths
|
||||
// VALID: .syft.yaml, .syft/config.yaml, ~/.syft.yaml, <XDG_CONFIG_HOME>/syft/config.yaml
|
||||
// DEPRECATED: config.yaml is currently supported by
|
||||
tests := []struct {
|
||||
name string
|
||||
setup func(t *testing.T) string
|
||||
assertions func(t *testing.T, app *Application)
|
||||
cleanup func()
|
||||
}{
|
||||
{
|
||||
name: "explicit config",
|
||||
setup: func(t *testing.T) string {
|
||||
return "./test-fixtures/.syft.yaml"
|
||||
}, // no-op for explicit config
|
||||
assertions: func(t *testing.T, app *Application) {
|
||||
assert.Equal(t, "test-explicit-config", app.File)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "current working directory named config",
|
||||
setup: func(t *testing.T) string {
|
||||
err := os.Chdir("./test-fixtures/config-wd-file") // change application cwd to test-fixtures
|
||||
require.NoError(t, err)
|
||||
return ""
|
||||
},
|
||||
assertions: func(t *testing.T, app *Application) {
|
||||
assert.Equal(t, "test-wd-named-config", app.File)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "current working directory syft dir config",
|
||||
setup: func(t *testing.T) string {
|
||||
err := os.Chdir("./test-fixtures/config-dir-test") // change application cwd to test-fixtures
|
||||
require.NoError(t, err)
|
||||
return ""
|
||||
},
|
||||
assertions: func(t *testing.T, app *Application) {
|
||||
assert.Equal(t, "test-dir-config", app.File)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "home directory file config",
|
||||
setup: func(t *testing.T) string {
|
||||
// Because Setenv affects the whole process, it cannot be used in parallel tests or
|
||||
// tests with parallel ancestors: see separate XDG test for consequence of this
|
||||
t.Setenv("HOME", "./test-fixtures/config-home-test/config-file")
|
||||
return ""
|
||||
},
|
||||
assertions: func(t *testing.T, app *Application) {
|
||||
assert.Equal(t, "test-home-config", app.File)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "XDG file config",
|
||||
setup: func(t *testing.T) string {
|
||||
wd, err := os.Getwd()
|
||||
require.NoError(t, err)
|
||||
configDir := path.Join(wd, "./test-fixtures/config-home-test") // set HOME to testdata
|
||||
// note: this explicitly has multiple XDG paths, make certain we use the first VALID one (not the first one)
|
||||
t.Setenv("XDG_CONFIG_DIRS", fmt.Sprintf("/another/foo/bar:%s", configDir))
|
||||
xdg.Reload()
|
||||
return ""
|
||||
},
|
||||
assertions: func(t *testing.T, app *Application) {
|
||||
assert.Equal(t, "test-home-XDG-config", app.File)
|
||||
},
|
||||
cleanup: func() {
|
||||
require.NoError(t, os.Unsetenv("XDG_CONFIG_DIRS"))
|
||||
xdg.Reload()
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
if test.cleanup != nil {
|
||||
t.Cleanup(test.cleanup)
|
||||
}
|
||||
wd, err := os.Getwd()
|
||||
require.NoError(t, err)
|
||||
|
||||
defer os.Chdir(wd) // reset working directory after test
|
||||
application := &Application{}
|
||||
viperInstance := viper.New()
|
||||
|
||||
// this will override home in case you are running this test locally and DO have a syft config
|
||||
// in your home directory... now it will be ignored. Same for XDG_CONFIG_DIRS.
|
||||
t.Setenv("HOME", "/foo/bar")
|
||||
t.Setenv("XDG_CONFIG_DIRS", "/foo/bar")
|
||||
xdg.Reload()
|
||||
|
||||
configPath := test.setup(t)
|
||||
err = application.LoadAllValues(viperInstance, configPath)
|
||||
require.NoError(t, err)
|
||||
test.assertions(t, application)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package config
|
||||
|
||||
import "github.com/spf13/viper"
|
||||
|
||||
type attest struct {
|
||||
// IMPORTANT: do not show the attestation key/password in any YAML/JSON output (sensitive information)
|
||||
Key string `yaml:"-" json:"-" mapstructure:"key"`
|
||||
Password string `yaml:"-" json:"-" mapstructure:"password"`
|
||||
}
|
||||
|
||||
func (cfg attest) loadDefaultValues(v *viper.Viper) {
|
||||
v.SetDefault("attest.key", "")
|
||||
v.SetDefault("attest.password", "")
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package config
|
||||
|
||||
import "github.com/spf13/viper"
|
||||
|
||||
type golang struct {
|
||||
SearchLocalModCacheLicenses bool `json:"search-local-mod-cache-licenses" yaml:"search-local-mod-cache-licenses" mapstructure:"search-local-mod-cache-licenses"`
|
||||
LocalModCacheDir string `json:"local-mod-cache-dir" yaml:"local-mod-cache-dir" mapstructure:"local-mod-cache-dir"`
|
||||
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"`
|
||||
}
|
||||
|
||||
func (cfg golang) loadDefaultValues(v *viper.Viper) {
|
||||
v.SetDefault("golang.search-local-mod-cache-licenses", false)
|
||||
v.SetDefault("golang.local-mod-cache-dir", "")
|
||||
v.SetDefault("golang.search-remote-licenses", false)
|
||||
v.SetDefault("golang.proxy", "")
|
||||
v.SetDefault("golang.no-proxy", "")
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package config
|
||||
|
||||
import "github.com/spf13/viper"
|
||||
|
||||
type linuxKernel struct {
|
||||
CatalogModules bool `json:"catalog-modules" yaml:"catalog-modules" mapstructure:"catalog-modules"`
|
||||
}
|
||||
|
||||
func (cfg linuxKernel) loadDefaultValues(v *viper.Viper) {
|
||||
v.SetDefault("linux-kernel.catalog-modules", true)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
# same as --file; write output report to a file (default is to write to stdout)
|
||||
file: "test-explicit-config"
|
||||
package:
|
||||
cataloger:
|
||||
scope: "squashed"
|
||||
|
||||
# same as --scope; limit the scope of the cataloger to only the specified types
|
||||
@@ -0,0 +1,5 @@
|
||||
# same as --file; write output report to a file (default is to write to stdout)
|
||||
file: "test-dir-config"
|
||||
package:
|
||||
cataloger:
|
||||
scope: "squashed"
|
||||
@@ -0,0 +1,5 @@
|
||||
# same as --file; write output report to a file (default is to write to stdout)
|
||||
file: "test-home-config"
|
||||
package:
|
||||
cataloger:
|
||||
scope: "squashed"
|
||||
@@ -0,0 +1,5 @@
|
||||
# same as --file; write output report to a file (default is to write to stdout)
|
||||
file: "test-home-XDG-config"
|
||||
package:
|
||||
cataloger:
|
||||
scope: "squashed"
|
||||
@@ -0,0 +1,5 @@
|
||||
# same as --file; write output report to a file (default is to write to stdout)
|
||||
file: "test-wd-named-config"
|
||||
package:
|
||||
cataloger:
|
||||
scope: "squashed"
|
||||
@@ -6,5 +6,5 @@ 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 = "6.2.0"
|
||||
JSONSchemaVersion = "7.1.4"
|
||||
)
|
||||
|
||||
@@ -161,10 +161,7 @@ func UnzipToDir(archivePath, targetDir string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = extractSingleFile(file, joinedPath, archivePath); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return extractSingleFile(file, joinedPath, archivePath)
|
||||
}
|
||||
|
||||
return TraverseFilesInZip(archivePath, visitor)
|
||||
|
||||
@@ -146,9 +146,7 @@ func findArchiveStartOffset(r io.ReaderAt, size int64) (startOfArchive uint64, e
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
startOfArchive = 0 // Prefixed data not supported
|
||||
}
|
||||
|
||||
startOfArchive = uint64(directoryEndOffset) - d.directorySize - d.directoryOffset
|
||||
|
||||
// Make sure directoryOffset points to somewhere in our file.
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package licenses
|
||||
|
||||
import "github.com/anchore/syft/internal"
|
||||
|
||||
// all of these taken from https://github.com/golang/pkgsite/blob/8996ff632abee854aef1b764ca0501f262f8f523/internal/licenses/licenses.go#L338
|
||||
// which unfortunately is not exported. But fortunately is under BSD-style license.
|
||||
|
||||
var (
|
||||
FileNames = []string{
|
||||
"COPYING",
|
||||
"COPYING.md",
|
||||
"COPYING.markdown",
|
||||
"COPYING.txt",
|
||||
"LICENCE",
|
||||
"LICENCE.md",
|
||||
"LICENCE.markdown",
|
||||
"LICENCE.txt",
|
||||
"LICENSE",
|
||||
"LICENSE.md",
|
||||
"LICENSE.markdown",
|
||||
"LICENSE.txt",
|
||||
"LICENSE-2.0.txt",
|
||||
"LICENCE-2.0.txt",
|
||||
"LICENSE-APACHE",
|
||||
"LICENCE-APACHE",
|
||||
"LICENSE-APACHE-2.0.txt",
|
||||
"LICENCE-APACHE-2.0.txt",
|
||||
"LICENSE-MIT",
|
||||
"LICENCE-MIT",
|
||||
"LICENSE.MIT",
|
||||
"LICENCE.MIT",
|
||||
"LICENSE.code",
|
||||
"LICENCE.code",
|
||||
"LICENSE.docs",
|
||||
"LICENCE.docs",
|
||||
"LICENSE.rst",
|
||||
"LICENCE.rst",
|
||||
"MIT-LICENSE",
|
||||
"MIT-LICENCE",
|
||||
"MIT-LICENSE.md",
|
||||
"MIT-LICENCE.md",
|
||||
"MIT-LICENSE.markdown",
|
||||
"MIT-LICENCE.markdown",
|
||||
"MIT-LICENSE.txt",
|
||||
"MIT-LICENCE.txt",
|
||||
"MIT_LICENSE",
|
||||
"MIT_LICENCE",
|
||||
"UNLICENSE",
|
||||
"UNLICENCE",
|
||||
}
|
||||
|
||||
FileNameSet = internal.NewStringSet(FileNames...)
|
||||
)
|
||||
@@ -0,0 +1,33 @@
|
||||
package licenses
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/google/licensecheck"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
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) (licenses []string, err error) {
|
||||
contents, err := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cov := licensecheck.Scan(contents)
|
||||
|
||||
if cov.Percent < float64(coverageThreshold) {
|
||||
licenses = append(licenses, unknownLicenseType)
|
||||
}
|
||||
for _, m := range cov.Match {
|
||||
if slices.Contains(licenses, m.ID) {
|
||||
continue
|
||||
}
|
||||
licenses = append(licenses, m.ID)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -11,8 +11,6 @@ import (
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/scylladb/go-set/strset"
|
||||
)
|
||||
|
||||
// This program generates license_list.go.
|
||||
@@ -35,7 +33,7 @@ var licenseIDs = map[string]string{
|
||||
}
|
||||
`))
|
||||
|
||||
var versionMatch = regexp.MustCompile(`-([0-9]+)\.?([0-9]+)?\.?([0-9]+)?\.?`)
|
||||
var versionMatch = regexp.MustCompile(`([0-9]+)\.?([0-9]+)?\.?([0-9]+)?\.?`)
|
||||
|
||||
func main() {
|
||||
if err := run(); err != nil {
|
||||
@@ -49,7 +47,6 @@ func run() error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to get licenses list: %w", err)
|
||||
}
|
||||
|
||||
var result LicenseList
|
||||
if err = json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
return fmt.Errorf("unable to decode license list: %w", err)
|
||||
@@ -103,58 +100,59 @@ func run() error {
|
||||
// The third pass is for overwriting deprecated licenses with replacements, for example GPL-2.0+ is deprecated
|
||||
// and now maps to GPL-2.0-or-later.
|
||||
func processSPDXLicense(result LicenseList) map[string]string {
|
||||
// first pass build map
|
||||
var licenseIDs = make(map[string]string)
|
||||
for _, l := range result.Licenses {
|
||||
cleanID := strings.ToLower(l.ID)
|
||||
if _, exists := licenseIDs[cleanID]; exists {
|
||||
log.Fatalf("duplicate license ID found: %q", cleanID)
|
||||
}
|
||||
licenseIDs[cleanID] = l.ID
|
||||
}
|
||||
|
||||
// The order of variations/permutations of a license ID matters because of we how shuffle its digits,
|
||||
// that is because the permutation code can generate the same value for two difference licenses,
|
||||
// for example: The licenses `ABC-1.0` and `ABC-1.1` can both map to `ABC-1`,
|
||||
// so we need to guarantee the order they are created to avoid mapping them wrongly. So we use a sorted list.
|
||||
// To overwrite deprecated licenses during the first pass we would later on rely on map order,
|
||||
// [which in go is not consistent by design](https://stackoverflow.com/a/55925880).
|
||||
// The order of variations/permutations of a license ID matter.
|
||||
// The permutation code can generate the same value for two difference licenses,
|
||||
// for example: The licenses `ABC-1.0` and `ABC-1.1` can both map to `ABC1`,
|
||||
// we need to guarantee the order they are created to avoid mapping them incorrectly.
|
||||
// To do this we use a sorted list.
|
||||
sort.Slice(result.Licenses, func(i, j int) bool {
|
||||
return result.Licenses[i].ID < result.Licenses[j].ID
|
||||
})
|
||||
|
||||
// second pass to build exceptions and replacements
|
||||
replaced := strset.New()
|
||||
// keys are simplified by removing dashes and lowercasing ID
|
||||
// this is so license declarations in the wild like: LGPL3 LGPL-3 lgpl3 and lgpl-3 can all match
|
||||
licenseIDs := make(map[string]string)
|
||||
for _, l := range result.Licenses {
|
||||
var multipleID []string
|
||||
cleanID := strings.ToLower(l.ID)
|
||||
// licensePerms includes the cleanID in return slice
|
||||
cleanID := cleanLicenseID(l.ID)
|
||||
licensePerms := buildLicenseIDPermutations(cleanID)
|
||||
|
||||
var replacement *License
|
||||
// if license is deprecated, find its replacement and add to licenseIDs
|
||||
if l.Deprecated {
|
||||
replacement = result.findReplacementLicense(l)
|
||||
idToMap := l.ID
|
||||
replacement := result.findReplacementLicense(l)
|
||||
if replacement != nil {
|
||||
licenseIDs[cleanID] = replacement.ID
|
||||
idToMap = replacement.ID
|
||||
}
|
||||
// it's important to use the original licensePerms here so that the deprecated license
|
||||
// can now point to the new correct license
|
||||
for _, id := range licensePerms {
|
||||
if _, exists := licenseIDs[id]; exists {
|
||||
// can be used to debug duplicate license permutations and confirm that examples like GPL1
|
||||
// do not point to GPL-1.1
|
||||
// log.Println("duplicate license list permutation found when mapping deprecated license to replacement")
|
||||
// log.Printf("already have key: %q for SPDX ID: %q; attempted to map replacement ID: %q for deprecated ID: %q\n", id, value, replacement.ID, l.ID)
|
||||
continue
|
||||
}
|
||||
licenseIDs[id] = idToMap
|
||||
}
|
||||
}
|
||||
|
||||
multipleID = append(multipleID, buildLicensePermutations(cleanID)...)
|
||||
for _, id := range multipleID {
|
||||
// don't make replacements for IDs that have already been replaced. Since we have a sorted license list
|
||||
// the earliest replacement is correct (any future replacements are not.
|
||||
// e.g. replace lgpl-2 with LGPL-2.1-only is wrong, but with LGPL-2.0-only is correct)
|
||||
if replacement == nil || replaced.Has(id) {
|
||||
if _, exists := licenseIDs[id]; !exists {
|
||||
licenseIDs[id] = l.ID
|
||||
}
|
||||
} else {
|
||||
// a useful debugging line during builds
|
||||
log.Printf("replacing %s with %s\n", id, replacement.ID)
|
||||
|
||||
licenseIDs[id] = replacement.ID
|
||||
replaced.Add(id)
|
||||
// if license is not deprecated, add all permutations to licenseIDs
|
||||
for _, id := range licensePerms {
|
||||
if _, exists := licenseIDs[id]; exists {
|
||||
// log.Println("found duplicate license permutation key for non deprecated license")
|
||||
// log.Printf("already have key: %q for SPDX ID: %q; tried to insert as SPDX ID:%q\n", id, value, l.ID)
|
||||
continue
|
||||
}
|
||||
licenseIDs[id] = l.ID
|
||||
}
|
||||
}
|
||||
|
||||
return licenseIDs
|
||||
}
|
||||
|
||||
func cleanLicenseID(id string) string {
|
||||
cleanID := strings.ToLower(id)
|
||||
return strings.ReplaceAll(cleanID, "-", "")
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -20,51 +20,7 @@ type License struct {
|
||||
SeeAlso []string `json:"seeAlso"`
|
||||
}
|
||||
|
||||
func (l License) canReplace(other License) bool {
|
||||
if l.Deprecated {
|
||||
return false
|
||||
}
|
||||
|
||||
// We want to replace deprecated licenses with non-deprecated counterparts
|
||||
// For more information, see: https://github.com/spdx/license-list-XML/issues/1676
|
||||
if other.Deprecated {
|
||||
switch {
|
||||
case strings.ReplaceAll(l.ID, "-only", "") == other.ID:
|
||||
return true
|
||||
case strings.ReplaceAll(l.ID, "-or-later", "+") == other.ID:
|
||||
return true
|
||||
case l.ID == "BSD-2-Clause" && other.ID == "BSD-2-Clause-NetBSD":
|
||||
return true
|
||||
case l.ID == "BSD-2-Clause-Views" && other.ID == "BSD-2-Clause-FreeBSD":
|
||||
return true
|
||||
case l.ID == "bzip2-1.0.6" && other.ID == "bzip2-1.0.5":
|
||||
return true
|
||||
case l.ID == "SMLNJ" && other.ID == "StandardML-NJ":
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if l.Name != other.Name {
|
||||
return false
|
||||
}
|
||||
|
||||
if l.OSIApproved != other.OSIApproved {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(l.SeeAlso) != len(other.SeeAlso) {
|
||||
return false
|
||||
}
|
||||
|
||||
for i, sa := range l.SeeAlso {
|
||||
if sa != other.SeeAlso[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return l.ID == other.ID
|
||||
}
|
||||
|
||||
// findReplacementLicense returns a replacement license for a deprecated license
|
||||
func (ll LicenseList) findReplacementLicense(deprecated License) *License {
|
||||
for _, l := range ll.Licenses {
|
||||
if l.canReplace(deprecated) {
|
||||
@@ -75,16 +31,62 @@ func (ll LicenseList) findReplacementLicense(deprecated License) *License {
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildLicensePermutations(license string) (perms []string) {
|
||||
lv := findLicenseVersion(license)
|
||||
vp := versionPermutations(lv)
|
||||
|
||||
version := strings.Join(lv, ".")
|
||||
for _, p := range vp {
|
||||
perms = append(perms, strings.Replace(license, version, p, 1))
|
||||
func (l License) canReplace(deprecated License) bool {
|
||||
// don't replace a license with a deprecated license
|
||||
if l.Deprecated {
|
||||
return false
|
||||
}
|
||||
|
||||
return perms
|
||||
// We want to replace deprecated licenses with non-deprecated counterparts
|
||||
// For more information, see: https://github.com/spdx/license-list-XML/issues/1676
|
||||
switch {
|
||||
case strings.ReplaceAll(l.ID, "-only", "") == deprecated.ID:
|
||||
return true
|
||||
case strings.ReplaceAll(l.ID, "-or-later", "+") == deprecated.ID:
|
||||
return true
|
||||
case l.ID == "BSD-2-Clause" && deprecated.ID == "BSD-2-Clause-NetBSD":
|
||||
return true
|
||||
case l.ID == "BSD-2-Clause-Views" && deprecated.ID == "BSD-2-Clause-FreeBSD":
|
||||
return true
|
||||
case l.ID == "bzip2-1.0.6" && deprecated.ID == "bzip2-1.0.5":
|
||||
return true
|
||||
case l.ID == "SMLNJ" && deprecated.ID == "StandardML-NJ":
|
||||
return true
|
||||
}
|
||||
|
||||
if l.Name != deprecated.Name {
|
||||
return false
|
||||
}
|
||||
|
||||
if l.OSIApproved != deprecated.OSIApproved {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(l.SeeAlso) != len(deprecated.SeeAlso) {
|
||||
return false
|
||||
}
|
||||
|
||||
for i, sa := range l.SeeAlso {
|
||||
if sa != deprecated.SeeAlso[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return l.ID == deprecated.ID
|
||||
}
|
||||
|
||||
func buildLicenseIDPermutations(cleanID string) (perms []string) {
|
||||
lv := findLicenseVersion(cleanID)
|
||||
vp := versionPermutations(lv)
|
||||
|
||||
permSet := strset.New()
|
||||
version := strings.Join(lv, ".")
|
||||
for _, p := range vp {
|
||||
permSet.Add(strings.Replace(cleanID, version, p, 1))
|
||||
}
|
||||
|
||||
permSet.Add(cleanID)
|
||||
return permSet.List()
|
||||
}
|
||||
|
||||
func findLicenseVersion(license string) (version []string) {
|
||||
|
||||
@@ -67,56 +67,57 @@ func TestLicensePermutations(t *testing.T) {
|
||||
{
|
||||
"GPL-1-only",
|
||||
[]string{
|
||||
"GPL-1-only",
|
||||
"GPL-1.0-only",
|
||||
"GPL-1.0.0-only",
|
||||
"gpl1only",
|
||||
"gpl1.0only",
|
||||
"gpl1.0.0only",
|
||||
},
|
||||
},
|
||||
{
|
||||
"GPL-2",
|
||||
[]string{
|
||||
"GPL-2",
|
||||
"GPL-2.0",
|
||||
"GPL-2.0.0",
|
||||
"gpl2",
|
||||
"gpl2.0",
|
||||
"gpl2.0.0",
|
||||
},
|
||||
},
|
||||
{
|
||||
"GPL-2.0+",
|
||||
[]string{
|
||||
"GPL-2+",
|
||||
"GPL-2.0+",
|
||||
"GPL-2.0.0+",
|
||||
"gpl2+",
|
||||
"gpl2.0+",
|
||||
"gpl2.0.0+",
|
||||
},
|
||||
},
|
||||
{
|
||||
"GPL-3.0.0-or-later",
|
||||
[]string{
|
||||
"GPL-3-or-later",
|
||||
"GPL-3.0-or-later",
|
||||
"GPL-3.0.0-or-later",
|
||||
"gpl3orlater",
|
||||
"gpl3.0orlater",
|
||||
"gpl3.0.0orlater",
|
||||
},
|
||||
},
|
||||
{
|
||||
"abc-1.1",
|
||||
[]string{
|
||||
"abc-1",
|
||||
"abc-1.1",
|
||||
"abc-1.1.0",
|
||||
"abc1",
|
||||
"abc1.1",
|
||||
"abc1.1.0",
|
||||
},
|
||||
},
|
||||
{
|
||||
"oldap-2.0",
|
||||
[]string{
|
||||
"oldap-2",
|
||||
"oldap-2.0",
|
||||
"oldap-2.0.0",
|
||||
"oldap2",
|
||||
"oldap2.0",
|
||||
"oldap2.0.0",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.shortName, func(t *testing.T) {
|
||||
perms := buildLicensePermutations(test.shortName)
|
||||
cleanID := cleanLicenseID(test.shortName)
|
||||
perms := buildLicenseIDPermutations(cleanID)
|
||||
assert.ElementsMatch(t, test.permutations, perms)
|
||||
})
|
||||
}
|
||||
@@ -183,10 +184,6 @@ func TestFindLicenseVersion(t *testing.T) {
|
||||
"GPL-2",
|
||||
[]string{"2"},
|
||||
},
|
||||
{
|
||||
"bzip2-1",
|
||||
[]string{"1"},
|
||||
},
|
||||
{
|
||||
"php-3.01",
|
||||
[]string{"3", "01"},
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -12,9 +12,26 @@ import (
|
||||
// EX: gpl-2.0.0-only ---> GPL-2.0-only
|
||||
// See the debian link for more details on the spdx license differences
|
||||
|
||||
const (
|
||||
LicenseRefPrefix = "LicenseRef-" // prefix for non-standard licenses
|
||||
)
|
||||
|
||||
//go:generate go run ./generate
|
||||
|
||||
func ID(id string) (string, bool) {
|
||||
value, exists := licenseIDs[strings.ToLower(id)]
|
||||
return value, exists
|
||||
// ID returns the canonical license ID for the given license ID
|
||||
// Note: this function is only concerned with returning a best match of an SPDX license ID
|
||||
// SPDX Expressions will be handled by a parent package which will call this function
|
||||
func ID(id string) (value string, exists bool) {
|
||||
// first look for a canonical license
|
||||
if value, exists := licenseIDs[cleanLicenseID(id)]; exists {
|
||||
return value, exists
|
||||
}
|
||||
// we did not find, so treat it as a separate license
|
||||
return "", false
|
||||
}
|
||||
|
||||
func cleanLicenseID(id string) string {
|
||||
id = strings.TrimSpace(id)
|
||||
id = strings.ToLower(id)
|
||||
return strings.ReplaceAll(id, "-", "")
|
||||
}
|
||||
|
||||
+1141
-1071
File diff suppressed because it is too large
Load Diff
@@ -9,8 +9,8 @@ import (
|
||||
func TestLicenceListIDs(t *testing.T) {
|
||||
// do a sanity check on the generated data
|
||||
assert.Equal(t, "0BSD", licenseIDs["0bsd"])
|
||||
assert.Equal(t, "ZPL-2.1", licenseIDs["zpl-2.1"])
|
||||
assert.Equal(t, "GPL-2.0-only", licenseIDs["gpl-2"])
|
||||
assert.Equal(t, "GPL-2.0-or-later", licenseIDs["gpl-2+"])
|
||||
assert.Equal(t, "ZPL-2.1", licenseIDs["zpl2.1"])
|
||||
assert.Equal(t, "GPL-2.0-only", licenseIDs["gpl2"])
|
||||
assert.Equal(t, "GPL-2.0-or-later", licenseIDs["gpl2+"])
|
||||
assert.NotEmpty(t, Version)
|
||||
}
|
||||
|
||||
@@ -6,55 +6,80 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestIDParse(t *testing.T) {
|
||||
func TestSPDXIDRecognition(t *testing.T) {
|
||||
var tests = []struct {
|
||||
shortName string
|
||||
spdx string
|
||||
id string
|
||||
found bool
|
||||
}{
|
||||
{
|
||||
"GPL-1-only",
|
||||
"GPL-1.0-only",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"gpl1",
|
||||
"GPL-1.0-only",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"gpl-1",
|
||||
"GPL-1.0-only",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"GPL-2",
|
||||
"GPL-2.0-only",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"GPL-2+",
|
||||
"GPL-2.0-or-later",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"GPL-3.0.0-or-later",
|
||||
"GPL-3.0-or-later",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"GPL-3-with-autoconf-exception",
|
||||
"GPL-3.0-with-autoconf-exception",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"CC-by-nc-3-de",
|
||||
"CC-BY-NC-3.0-DE",
|
||||
true,
|
||||
},
|
||||
// the below few cases are NOT expected, however, seem unavoidable given the current approach
|
||||
{
|
||||
"w3c-20150513.0.0",
|
||||
"W3C-20150513",
|
||||
},
|
||||
{
|
||||
"spencer-86.0.0",
|
||||
"Spencer-86",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"unicode-dfs-2015.0.0",
|
||||
"Unicode-DFS-2015",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"Unknown",
|
||||
"",
|
||||
false,
|
||||
},
|
||||
{
|
||||
" ",
|
||||
"",
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.shortName, func(t *testing.T) {
|
||||
got, exists := ID(test.shortName)
|
||||
assert.True(t, exists)
|
||||
assert.Equal(t, test.spdx, got)
|
||||
value, exists := ID(test.shortName)
|
||||
assert.Equal(t, test.found, exists)
|
||||
assert.Equal(t, test.id, value)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,3 +28,10 @@ func StringInSlice(a string, list []string) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func SplitAny(s string, seps string) []string {
|
||||
splitter := func(r rune) bool {
|
||||
return strings.ContainsRune(seps, r)
|
||||
}
|
||||
return strings.FieldsFunc(s, splitter)
|
||||
}
|
||||
|
||||
@@ -104,3 +104,37 @@ func TestTruncateMiddleEllipsis(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplitAny(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
fields string
|
||||
want []string
|
||||
}{
|
||||
{
|
||||
name: "simple",
|
||||
input: "a,b,c",
|
||||
fields: ",",
|
||||
want: []string{"a", "b", "c"},
|
||||
},
|
||||
{
|
||||
name: "empty",
|
||||
input: "",
|
||||
fields: ",",
|
||||
want: []string{},
|
||||
},
|
||||
{
|
||||
name: "multiple separators",
|
||||
input: "a,b\nc:d",
|
||||
fields: ",:\n",
|
||||
want: []string{"a", "b", "c", "d"},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equal(t, tt.want, SplitAny(tt.input, tt.fields))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,9 @@ type artifactMetadataContainer struct {
|
||||
Hackage pkg.HackageMetadata
|
||||
Java pkg.JavaMetadata
|
||||
KbPackage pkg.KbPackageMetadata
|
||||
LinuxKernel pkg.LinuxKernelMetadata
|
||||
LinuxKernelModule pkg.LinuxKernelModuleMetadata
|
||||
Nix pkg.NixStoreMetadata
|
||||
NpmPackage pkg.NpmPackageJSONMetadata
|
||||
NpmPackageLock pkg.NpmPackageLockJSONMetadata
|
||||
MixLock pkg.MixLockMetadata
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -7,6 +7,12 @@ const (
|
||||
// has been completed.
|
||||
OwnershipByFileOverlapRelationship RelationshipType = "ownership-by-file-overlap"
|
||||
|
||||
// EvidentByRelationship is a package-to-file relationship indicating the that existence of this package is evident
|
||||
// by the contents of a file. This does not necessarily mean that the package is contained within that file
|
||||
// or that it is described by it (either or both may be true). This does NOT map to an existing specific SPDX
|
||||
// relationship. Instead, this should be mapped to OTHER and the comment field be updated to show EVIDENT_BY.
|
||||
EvidentByRelationship RelationshipType = "evident-by"
|
||||
|
||||
// ContainsRelationship (supports any-to-any linkages) is a proxy for the SPDX 2.2 CONTAINS relationship.
|
||||
ContainsRelationship RelationshipType = "contains"
|
||||
|
||||
|
||||
+1
-2
@@ -106,9 +106,8 @@ func stripSlashes(s string) string {
|
||||
for i, c := range s {
|
||||
if c == '\\' && i+1 < len(s) && strings.ContainsRune(allowedCPEPunctuation, rune(s[i+1])) {
|
||||
continue
|
||||
} else {
|
||||
sb.WriteRune(c)
|
||||
}
|
||||
sb.WriteRune(c)
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package event
|
||||
|
||||
import (
|
||||
"github.com/wagoodman/go-partybus"
|
||||
"github.com/wagoodman/go-progress"
|
||||
|
||||
"github.com/anchore/syft/internal/bus"
|
||||
)
|
||||
|
||||
type CatalogerTask struct {
|
||||
prog *progress.Manual
|
||||
// Title
|
||||
Title string
|
||||
// TitleOnCompletion a string to use as title when completed
|
||||
TitleOnCompletion string
|
||||
// SubStatus indicates this progress should be rendered as a sub-item
|
||||
SubStatus bool
|
||||
// RemoveOnCompletion indicates this progress line will be removed when completed
|
||||
RemoveOnCompletion bool
|
||||
// value is the value to display -- not public as SetValue needs to be called to initialize this progress
|
||||
value string
|
||||
}
|
||||
|
||||
func (e *CatalogerTask) init() {
|
||||
e.prog = progress.NewManual(-1)
|
||||
|
||||
bus.Publish(partybus.Event{
|
||||
Type: CatalogerTaskStarted,
|
||||
Source: e,
|
||||
})
|
||||
}
|
||||
|
||||
func (e *CatalogerTask) SetCompleted() {
|
||||
if e.prog != nil {
|
||||
e.prog.SetCompleted()
|
||||
}
|
||||
}
|
||||
|
||||
func (e *CatalogerTask) SetValue(value string) {
|
||||
if e.prog == nil {
|
||||
e.init()
|
||||
}
|
||||
e.value = value
|
||||
}
|
||||
|
||||
func (e *CatalogerTask) GetValue() string {
|
||||
return e.value
|
||||
}
|
||||
|
||||
func (e *CatalogerTask) GetMonitor() *progress.Manual {
|
||||
return e.prog
|
||||
}
|
||||
@@ -34,4 +34,7 @@ const (
|
||||
|
||||
// AttestationStarted is a partybus event that occurs when starting an SBOM attestation process
|
||||
AttestationStarted partybus.EventType = "syft-attestation-started-event"
|
||||
|
||||
// CatalogerTaskStarted is a partybus event that occurs when starting a task within a cataloger
|
||||
CatalogerTaskStarted partybus.EventType = "syft-cataloger-task-started"
|
||||
)
|
||||
|
||||
@@ -111,6 +111,19 @@ func ParseFileIndexingStarted(e partybus.Event) (string, progress.StagedProgress
|
||||
return path, prog, nil
|
||||
}
|
||||
|
||||
func ParseCatalogerTaskStarted(e partybus.Event) (*event.CatalogerTask, error) {
|
||||
if err := checkEventType(e.Type, event.CatalogerTaskStarted); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
source, ok := e.Source.(*event.CatalogerTask)
|
||||
if !ok {
|
||||
return nil, newPayloadErr(e.Type, "Source", e.Source)
|
||||
}
|
||||
|
||||
return source, nil
|
||||
}
|
||||
|
||||
func ParseExit(e partybus.Event) (func() error, error) {
|
||||
if err := checkEventType(e.Type, event.Exit); err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package file
|
||||
|
||||
import (
|
||||
"github.com/anchore/stereoscope/pkg/file"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
@@ -20,7 +21,7 @@ func allRegularFiles(resolver source.FileResolver) (locations []source.Location)
|
||||
continue
|
||||
}
|
||||
|
||||
if metadata.Type != source.RegularFile {
|
||||
if metadata.Type != file.TypeRegular {
|
||||
continue
|
||||
}
|
||||
locations = append(locations, resolvedLocation)
|
||||
|
||||
@@ -3,6 +3,7 @@ package file
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/scylladb/go-set/strset"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -69,8 +70,8 @@ func Test_allRegularFiles(t *testing.T) {
|
||||
virtualLocations.Add(l.VirtualPath)
|
||||
}
|
||||
}
|
||||
assert.ElementsMatch(t, tt.wantRealPaths.List(), realLocations.List(), "mismatched real paths")
|
||||
assert.ElementsMatch(t, tt.wantVirtualPaths.List(), virtualLocations.List(), "mismatched virtual paths")
|
||||
assert.ElementsMatch(t, tt.wantRealPaths.List(), realLocations.List(), "real paths differ: "+cmp.Diff(tt.wantRealPaths.List(), realLocations.List()))
|
||||
assert.ElementsMatch(t, tt.wantVirtualPaths.List(), virtualLocations.List(), "virtual paths differ: "+cmp.Diff(tt.wantVirtualPaths.List(), virtualLocations.List()))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/wagoodman/go-partybus"
|
||||
"github.com/wagoodman/go-progress"
|
||||
|
||||
"github.com/anchore/stereoscope/pkg/file"
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/internal/bus"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
@@ -50,10 +51,10 @@ func (i *DigestsCataloger) Catalog(resolver source.FileResolver) (map[source.Coo
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
prog.N++
|
||||
prog.Increment()
|
||||
results[location.Coordinates] = result
|
||||
}
|
||||
log.Debugf("file digests cataloger processed %d files", prog.N)
|
||||
log.Debugf("file digests cataloger processed %d files", prog.Current())
|
||||
prog.SetCompleted()
|
||||
return results, nil
|
||||
}
|
||||
@@ -65,7 +66,7 @@ func (i *DigestsCataloger) catalogLocation(resolver source.FileResolver, locatio
|
||||
}
|
||||
|
||||
// we should only attempt to report digests for files that are regular files (don't attempt to resolve links)
|
||||
if meta.Type != source.RegularFile {
|
||||
if meta.Type != file.TypeRegular {
|
||||
return nil, errUndigestableFile
|
||||
}
|
||||
|
||||
@@ -122,9 +123,7 @@ func CleanDigestAlgorithmName(name string) string {
|
||||
|
||||
func digestsCatalogingProgress(locations int64) (*progress.Stage, *progress.Manual) {
|
||||
stage := &progress.Stage{}
|
||||
prog := &progress.Manual{
|
||||
Total: locations,
|
||||
}
|
||||
prog := progress.NewManual(locations)
|
||||
|
||||
bus.Publish(partybus.Event{
|
||||
Type: event.FileDigestsCatalogerStarted,
|
||||
|
||||
@@ -146,7 +146,7 @@ func TestDigestsCataloger_MixFileTypes(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get file=%q : %+v", test.path, err)
|
||||
}
|
||||
l := source.NewLocationFromImage(test.path, *ref, img)
|
||||
l := source.NewLocationFromImage(test.path, *ref.Reference, img)
|
||||
|
||||
if len(actual[l.Coordinates]) == 0 {
|
||||
if test.expected != "" {
|
||||
|
||||
@@ -32,18 +32,16 @@ func (i *MetadataCataloger) Catalog(resolver source.FileResolver) (map[source.Co
|
||||
}
|
||||
|
||||
results[location.Coordinates] = metadata
|
||||
prog.N++
|
||||
prog.Increment()
|
||||
}
|
||||
log.Debugf("file metadata cataloger processed %d files", prog.N)
|
||||
log.Debugf("file metadata cataloger processed %d files", prog.Current())
|
||||
prog.SetCompleted()
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func metadataCatalogingProgress(locations int64) (*progress.Stage, *progress.Manual) {
|
||||
stage := &progress.Stage{}
|
||||
prog := &progress.Manual{
|
||||
Total: locations,
|
||||
}
|
||||
prog := progress.NewManual(locations)
|
||||
|
||||
bus.Publish(partybus.Event{
|
||||
Type: event.FileMetadataCatalogerStarted,
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/stereoscope/pkg/file"
|
||||
"github.com/anchore/stereoscope/pkg/imagetest"
|
||||
@@ -50,8 +51,9 @@ func TestFileMetadataCataloger(t *testing.T) {
|
||||
path: "/file-1.txt",
|
||||
exists: true,
|
||||
expected: source.FileMetadata{
|
||||
Path: "/file-1.txt",
|
||||
Mode: 0644,
|
||||
Type: "RegularFile",
|
||||
Type: file.TypeRegular,
|
||||
UserID: 1,
|
||||
GroupID: 2,
|
||||
Size: 7,
|
||||
@@ -62,8 +64,9 @@ func TestFileMetadataCataloger(t *testing.T) {
|
||||
path: "/hardlink-1",
|
||||
exists: true,
|
||||
expected: source.FileMetadata{
|
||||
Path: "/hardlink-1",
|
||||
Mode: 0644,
|
||||
Type: "HardLink",
|
||||
Type: file.TypeHardLink,
|
||||
LinkDestination: "file-1.txt",
|
||||
UserID: 1,
|
||||
GroupID: 2,
|
||||
@@ -74,8 +77,9 @@ func TestFileMetadataCataloger(t *testing.T) {
|
||||
path: "/symlink-1",
|
||||
exists: true,
|
||||
expected: source.FileMetadata{
|
||||
Path: "/symlink-1",
|
||||
Mode: 0777 | os.ModeSymlink,
|
||||
Type: "SymbolicLink",
|
||||
Type: file.TypeSymLink,
|
||||
LinkDestination: "file-1.txt",
|
||||
UserID: 0,
|
||||
GroupID: 0,
|
||||
@@ -86,8 +90,9 @@ func TestFileMetadataCataloger(t *testing.T) {
|
||||
path: "/char-device-1",
|
||||
exists: true,
|
||||
expected: source.FileMetadata{
|
||||
Path: "/char-device-1",
|
||||
Mode: 0644 | os.ModeDevice | os.ModeCharDevice,
|
||||
Type: "CharacterDevice",
|
||||
Type: file.TypeCharacterDevice,
|
||||
UserID: 0,
|
||||
GroupID: 0,
|
||||
MIMEType: "",
|
||||
@@ -97,8 +102,9 @@ func TestFileMetadataCataloger(t *testing.T) {
|
||||
path: "/block-device-1",
|
||||
exists: true,
|
||||
expected: source.FileMetadata{
|
||||
Path: "/block-device-1",
|
||||
Mode: 0644 | os.ModeDevice,
|
||||
Type: "BlockDevice",
|
||||
Type: file.TypeBlockDevice,
|
||||
UserID: 0,
|
||||
GroupID: 0,
|
||||
MIMEType: "",
|
||||
@@ -108,8 +114,9 @@ func TestFileMetadataCataloger(t *testing.T) {
|
||||
path: "/fifo-1",
|
||||
exists: true,
|
||||
expected: source.FileMetadata{
|
||||
Path: "/fifo-1",
|
||||
Mode: 0644 | os.ModeNamedPipe,
|
||||
Type: "FIFONode",
|
||||
Type: file.TypeFIFO,
|
||||
UserID: 0,
|
||||
GroupID: 0,
|
||||
MIMEType: "",
|
||||
@@ -119,11 +126,13 @@ func TestFileMetadataCataloger(t *testing.T) {
|
||||
path: "/bin",
|
||||
exists: true,
|
||||
expected: source.FileMetadata{
|
||||
Path: "/bin",
|
||||
Mode: 0755 | os.ModeDir,
|
||||
Type: "Directory",
|
||||
Type: file.TypeDirectory,
|
||||
UserID: 0,
|
||||
GroupID: 0,
|
||||
MIMEType: "",
|
||||
IsDir: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -131,11 +140,9 @@ func TestFileMetadataCataloger(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
t.Run(test.path, func(t *testing.T) {
|
||||
_, ref, err := img.SquashedTree().File(file.Path(test.path))
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get file: %+v", err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
l := source.NewLocationFromImage(test.path, *ref, img)
|
||||
l := source.NewLocationFromImage(test.path, *ref.Reference, img)
|
||||
|
||||
assert.Equal(t, test.expected, actual[l.Coordinates], "mismatched metadata")
|
||||
|
||||
|
||||
@@ -55,12 +55,12 @@ func (i *SecretsCataloger) Catalog(resolver source.FileResolver) (map[source.Coo
|
||||
return nil, err
|
||||
}
|
||||
if len(result) > 0 {
|
||||
secretsDiscovered.N += int64(len(result))
|
||||
secretsDiscovered.Add(int64(len(result)))
|
||||
results[location.Coordinates] = result
|
||||
}
|
||||
prog.N++
|
||||
prog.Increment()
|
||||
}
|
||||
log.Debugf("secrets cataloger discovered %d secrets", secretsDiscovered.N)
|
||||
log.Debugf("secrets cataloger discovered %d secrets", secretsDiscovered.Current())
|
||||
prog.SetCompleted()
|
||||
return results, nil
|
||||
}
|
||||
@@ -139,9 +139,7 @@ type SecretsMonitor struct {
|
||||
func secretsCatalogingProgress(locations int64) (*progress.Stage, *progress.Manual, *progress.Manual) {
|
||||
stage := &progress.Stage{}
|
||||
secretsDiscovered := &progress.Manual{}
|
||||
prog := &progress.Manual{
|
||||
Total: locations,
|
||||
}
|
||||
prog := progress.NewManual(locations)
|
||||
|
||||
bus.Publish(partybus.Event{
|
||||
Type: event.SecretsCatalogerStarted,
|
||||
|
||||
+2
-2
@@ -30,12 +30,12 @@ const (
|
||||
|
||||
// TODO: deprecated, moved to syft/formats/formats.go. will be removed in v1.0.0
|
||||
func FormatIDs() (ids []sbom.FormatID) {
|
||||
return formats.IDs()
|
||||
return formats.AllIDs()
|
||||
}
|
||||
|
||||
// TODO: deprecated, moved to syft/formats/formats.go. will be removed in v1.0.0
|
||||
func FormatByID(id sbom.FormatID) sbom.Format {
|
||||
return formats.ByID(id)
|
||||
return formats.ByNameAndVersion(string(id), "")
|
||||
}
|
||||
|
||||
// TODO: deprecated, moved to syft/formats/formats.go. will be removed in v1.0.0
|
||||
|
||||
@@ -29,7 +29,7 @@ func Test_encodeComponentProperties(t *testing.T) {
|
||||
input: pkg.Package{
|
||||
FoundBy: "cataloger",
|
||||
Locations: source.NewLocationSet(
|
||||
source.Location{Coordinates: source.Coordinates{RealPath: "test"}},
|
||||
source.NewLocationFromCoordinates(source.Coordinates{RealPath: "test"}),
|
||||
),
|
||||
Metadata: pkg.ApkMetadata{
|
||||
Package: "libc-utils",
|
||||
|
||||
@@ -16,7 +16,15 @@ func encodeLicenses(p pkg.Package) *cyclonedx.Licenses {
|
||||
ID: value,
|
||||
},
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
// not found so append the licenseName as is
|
||||
lc = append(lc, cyclonedx.LicenseChoice{
|
||||
License: &cyclonedx.License{
|
||||
Name: licenseName,
|
||||
},
|
||||
})
|
||||
}
|
||||
if len(lc) > 0 {
|
||||
return &lc
|
||||
@@ -28,7 +36,16 @@ func decodeLicenses(c *cyclonedx.Component) (out []string) {
|
||||
if c.Licenses != nil {
|
||||
for _, l := range *c.Licenses {
|
||||
if l.License != nil {
|
||||
out = append(out, l.License.ID)
|
||||
var lic string
|
||||
switch {
|
||||
case l.License.ID != "":
|
||||
lic = l.License.ID
|
||||
case l.License.Name != "":
|
||||
lic = l.License.Name
|
||||
default:
|
||||
continue
|
||||
}
|
||||
out = append(out, lic)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,9 @@ func Test_encodeLicense(t *testing.T) {
|
||||
"made-up",
|
||||
},
|
||||
},
|
||||
expected: nil,
|
||||
expected: &cyclonedx.Licenses{
|
||||
{License: &cyclonedx.License{Name: "made-up"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with SPDX license",
|
||||
|
||||
@@ -22,10 +22,11 @@ func License(p pkg.Package) string {
|
||||
}
|
||||
|
||||
// take all licenses and assume an AND expression; for information about license expressions see https://spdx.github.io/spdx-spec/appendix-IV-SPDX-license-expressions/
|
||||
var parsedLicenses []string
|
||||
for _, l := range p.Licenses {
|
||||
if value, exists := spdxlicense.ID(l); exists {
|
||||
parsedLicenses = append(parsedLicenses, value)
|
||||
parsedLicenses := parseLicenses(p.Licenses)
|
||||
|
||||
for i, v := range parsedLicenses {
|
||||
if strings.HasPrefix(v, spdxlicense.LicenseRefPrefix) {
|
||||
parsedLicenses[i] = SanitizeElementID(v)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,3 +36,16 @@ func License(p pkg.Package) string {
|
||||
|
||||
return strings.Join(parsedLicenses, " AND ")
|
||||
}
|
||||
|
||||
func parseLicenses(raw []string) (parsedLicenses []string) {
|
||||
for _, l := range raw {
|
||||
if value, exists := spdxlicense.ID(l); exists {
|
||||
parsedLicenses = append(parsedLicenses, value)
|
||||
} else {
|
||||
// we did not find a valid SPDX license ID so treat as separate license
|
||||
otherLicense := spdxlicense.LicenseRefPrefix + l
|
||||
parsedLicenses = append(parsedLicenses, otherLicense)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ func Test_License(t *testing.T) {
|
||||
"made-up",
|
||||
},
|
||||
},
|
||||
expected: NOASSERTION,
|
||||
expected: "LicenseRef-made-up",
|
||||
},
|
||||
{
|
||||
name: "with SPDX license",
|
||||
@@ -65,6 +65,17 @@ func Test_License(t *testing.T) {
|
||||
},
|
||||
expected: "GPL-2.0-only",
|
||||
},
|
||||
{
|
||||
name: "includes valid LicenseRef-",
|
||||
input: pkg.Package{
|
||||
Licenses: []string{
|
||||
"one thing first",
|
||||
"two things/#$^second",
|
||||
"MIT",
|
||||
},
|
||||
},
|
||||
expected: "LicenseRef-one-thing-first AND LicenseRef-two-things----second AND MIT",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
//nolint:funlen
|
||||
func SourceInfo(p pkg.Package) string {
|
||||
answer := ""
|
||||
switch p.Type {
|
||||
@@ -45,6 +46,12 @@ func SourceInfo(p pkg.Package) string {
|
||||
answer = "acquired package info from cabal or stack manifest files"
|
||||
case pkg.HexPkg:
|
||||
answer = "acquired package info from rebar3 or mix manifest file"
|
||||
case pkg.LinuxKernelPkg:
|
||||
answer = "acquired package info from linux kernel archive"
|
||||
case pkg.LinuxKernelModulePkg:
|
||||
answer = "acquired package info from linux kernel module files"
|
||||
case pkg.NixPkg:
|
||||
answer = "acquired package info from nix store path"
|
||||
default:
|
||||
answer = "acquired package info from the following paths"
|
||||
}
|
||||
|
||||
@@ -199,6 +199,30 @@ func Test_SourceInfo(t *testing.T) {
|
||||
"from rebar3 or mix manifest file",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: pkg.Package{
|
||||
Type: pkg.LinuxKernelPkg,
|
||||
},
|
||||
expected: []string{
|
||||
"from linux kernel archive",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: pkg.Package{
|
||||
Type: pkg.LinuxKernelModulePkg,
|
||||
},
|
||||
expected: []string{
|
||||
"from linux kernel module files",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: pkg.Package{
|
||||
Type: pkg.NixPkg,
|
||||
},
|
||||
expected: []string{
|
||||
"from nix store path",
|
||||
},
|
||||
},
|
||||
}
|
||||
var pkgTypes []pkg.Type
|
||||
for _, test := range tests {
|
||||
|
||||
@@ -8,8 +8,9 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/spdx/tools-golang/spdx/common"
|
||||
spdx "github.com/spdx/tools-golang/spdx/v2_3"
|
||||
"github.com/spdx/tools-golang/spdx"
|
||||
"golang.org/x/exp/maps"
|
||||
"golang.org/x/exp/slices"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
@@ -23,7 +24,6 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
spdxVersion = "SPDX-2.3"
|
||||
noAssertion = "NOASSERTION"
|
||||
)
|
||||
|
||||
@@ -40,11 +40,11 @@ func ToFormatModel(s sbom.SBOM) *spdx.Document {
|
||||
// for the primary package purpose field:
|
||||
// https://spdx.github.io/spdx-spec/v2.3/package-information/#724-primary-package-purpose-field
|
||||
documentDescribesRelationship := &spdx.Relationship{
|
||||
RefA: common.DocElementID{
|
||||
RefA: spdx.DocElementID{
|
||||
ElementRefID: "DOCUMENT",
|
||||
},
|
||||
Relationship: string(DescribesRelationship),
|
||||
RefB: common.DocElementID{
|
||||
RefB: spdx.DocElementID{
|
||||
ElementRefID: "DOCUMENT",
|
||||
},
|
||||
RelationshipComment: "",
|
||||
@@ -55,11 +55,11 @@ func ToFormatModel(s sbom.SBOM) *spdx.Document {
|
||||
return &spdx.Document{
|
||||
// 6.1: SPDX Version; should be in the format "SPDX-x.x"
|
||||
// Cardinality: mandatory, one
|
||||
SPDXVersion: spdxVersion,
|
||||
SPDXVersion: spdx.Version,
|
||||
|
||||
// 6.2: Data License; should be "CC0-1.0"
|
||||
// Cardinality: mandatory, one
|
||||
DataLicense: "CC0-1.0",
|
||||
DataLicense: spdx.DataLicense,
|
||||
|
||||
// 6.3: SPDX Identifier; should be "DOCUMENT" to represent mandatory identifier of SPDXRef-DOCUMENT
|
||||
// Cardinality: mandatory, one
|
||||
@@ -104,7 +104,7 @@ func ToFormatModel(s sbom.SBOM) *spdx.Document {
|
||||
// 6.8: Creators: may have multiple keys for Person, Organization
|
||||
// and/or Tool
|
||||
// Cardinality: mandatory, one or many
|
||||
Creators: []common.Creator{
|
||||
Creators: []spdx.Creator{
|
||||
{
|
||||
Creator: "Anchore, Inc",
|
||||
CreatorType: "Organization",
|
||||
@@ -126,10 +126,11 @@ func ToFormatModel(s sbom.SBOM) *spdx.Document {
|
||||
Packages: toPackages(s.Artifacts.PackageCatalog, s),
|
||||
Files: toFiles(s),
|
||||
Relationships: relationships,
|
||||
OtherLicenses: toOtherLicenses(s.Artifacts.PackageCatalog),
|
||||
}
|
||||
}
|
||||
|
||||
func toSPDXID(identifiable artifact.Identifiable) common.ElementID {
|
||||
func toSPDXID(identifiable artifact.Identifiable) spdx.ElementID {
|
||||
id := ""
|
||||
if p, ok := identifiable.(pkg.Package); ok {
|
||||
id = SanitizeElementID(fmt.Sprintf("Package-%+v-%s-%s", p.Type, p.Name, p.ID()))
|
||||
@@ -137,7 +138,7 @@ func toSPDXID(identifiable artifact.Identifiable) common.ElementID {
|
||||
id = string(identifiable.ID())
|
||||
}
|
||||
// NOTE: the spdx libraries prepend SPDXRef-, so we don't do it here
|
||||
return common.ElementID(id)
|
||||
return spdx.ElementID(id)
|
||||
}
|
||||
|
||||
// packages populates all Package Information from the package Catalog (see https://spdx.github.io/spdx-spec/3-package-information/)
|
||||
@@ -313,9 +314,9 @@ func toPackages(catalog *pkg.Catalog, sbom sbom.SBOM) (results []*spdx.Package)
|
||||
return results
|
||||
}
|
||||
|
||||
func toPackageChecksums(p pkg.Package) ([]common.Checksum, bool) {
|
||||
func toPackageChecksums(p pkg.Package) ([]spdx.Checksum, bool) {
|
||||
filesAnalyzed := false
|
||||
var checksums []common.Checksum
|
||||
var checksums []spdx.Checksum
|
||||
switch meta := p.Metadata.(type) {
|
||||
// we generate digest for some Java packages
|
||||
// spdx.github.io/spdx-spec/package-information/#710-package-checksum-field
|
||||
@@ -325,8 +326,8 @@ func toPackageChecksums(p pkg.Package) ([]common.Checksum, bool) {
|
||||
filesAnalyzed = true
|
||||
for _, digest := range meta.ArchiveDigests {
|
||||
algo := strings.ToUpper(digest.Algorithm)
|
||||
checksums = append(checksums, common.Checksum{
|
||||
Algorithm: common.ChecksumAlgorithm(algo),
|
||||
checksums = append(checksums, spdx.Checksum{
|
||||
Algorithm: spdx.ChecksumAlgorithm(algo),
|
||||
Value: digest.Value,
|
||||
})
|
||||
}
|
||||
@@ -339,20 +340,20 @@ func toPackageChecksums(p pkg.Package) ([]common.Checksum, bool) {
|
||||
break
|
||||
}
|
||||
algo = strings.ToUpper(algo)
|
||||
checksums = append(checksums, common.Checksum{
|
||||
Algorithm: common.ChecksumAlgorithm(algo),
|
||||
checksums = append(checksums, spdx.Checksum{
|
||||
Algorithm: spdx.ChecksumAlgorithm(algo),
|
||||
Value: hexStr,
|
||||
})
|
||||
}
|
||||
return checksums, filesAnalyzed
|
||||
}
|
||||
|
||||
func toPackageOriginator(p pkg.Package) *common.Originator {
|
||||
func toPackageOriginator(p pkg.Package) *spdx.Originator {
|
||||
kind, originator := Originator(p)
|
||||
if kind == "" || originator == "" {
|
||||
return nil
|
||||
}
|
||||
return &common.Originator{
|
||||
return &spdx.Originator{
|
||||
Originator: originator,
|
||||
OriginatorType: kind,
|
||||
}
|
||||
@@ -386,11 +387,11 @@ func toRelationships(relationships []artifact.Relationship) (result []*spdx.Rela
|
||||
}
|
||||
|
||||
result = append(result, &spdx.Relationship{
|
||||
RefA: common.DocElementID{
|
||||
RefA: spdx.DocElementID{
|
||||
ElementRefID: toSPDXID(r.From),
|
||||
},
|
||||
Relationship: string(relationshipType),
|
||||
RefB: common.DocElementID{
|
||||
RefB: spdx.DocElementID{
|
||||
ElementRefID: toSPDXID(r.To),
|
||||
},
|
||||
RelationshipComment: comment,
|
||||
@@ -407,6 +408,8 @@ func lookupRelationship(ty artifact.RelationshipType) (bool, RelationshipType, s
|
||||
return true, DependencyOfRelationship, ""
|
||||
case artifact.OwnershipByFileOverlapRelationship:
|
||||
return true, OtherRelationship, fmt.Sprintf("%s: indicates that the parent package claims ownership of a child package since the parent metadata indicates overlap with a location that a cataloger found the child package by", ty)
|
||||
case artifact.EvidentByRelationship:
|
||||
return true, OtherRelationship, fmt.Sprintf("%s: indicates the package's existence is evident by the given file", ty)
|
||||
}
|
||||
return false, "", ""
|
||||
}
|
||||
@@ -462,10 +465,10 @@ func toFiles(s sbom.SBOM) (results []*spdx.File) {
|
||||
return results
|
||||
}
|
||||
|
||||
func toFileChecksums(digests []file.Digest) (checksums []common.Checksum) {
|
||||
checksums = make([]common.Checksum, 0, len(digests))
|
||||
func toFileChecksums(digests []file.Digest) (checksums []spdx.Checksum) {
|
||||
checksums = make([]spdx.Checksum, 0, len(digests))
|
||||
for _, digest := range digests {
|
||||
checksums = append(checksums, common.Checksum{
|
||||
checksums = append(checksums, spdx.Checksum{
|
||||
Algorithm: toChecksumAlgorithm(digest.Algorithm),
|
||||
Value: digest.Value,
|
||||
})
|
||||
@@ -473,9 +476,9 @@ func toFileChecksums(digests []file.Digest) (checksums []common.Checksum) {
|
||||
return checksums
|
||||
}
|
||||
|
||||
func toChecksumAlgorithm(algorithm string) common.ChecksumAlgorithm {
|
||||
func toChecksumAlgorithm(algorithm string) spdx.ChecksumAlgorithm {
|
||||
// this needs to be an uppercase version of our algorithm
|
||||
return common.ChecksumAlgorithm(strings.ToUpper(algorithm))
|
||||
return spdx.ChecksumAlgorithm(strings.ToUpper(algorithm))
|
||||
}
|
||||
|
||||
func toFileTypes(metadata *source.FileMetadata) (ty []string) {
|
||||
@@ -513,11 +516,37 @@ func toFileTypes(metadata *source.FileMetadata) (ty []string) {
|
||||
return ty
|
||||
}
|
||||
|
||||
func toOtherLicenses(catalog *pkg.Catalog) []*spdx.OtherLicense {
|
||||
licenses := map[string]bool{}
|
||||
for _, p := range catalog.Sorted() {
|
||||
for _, license := range parseLicenses(p.Licenses) {
|
||||
if strings.HasPrefix(license, spdxlicense.LicenseRefPrefix) {
|
||||
licenses[license] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var result []*spdx.OtherLicense
|
||||
|
||||
sorted := maps.Keys(licenses)
|
||||
slices.Sort(sorted)
|
||||
for _, license := range sorted {
|
||||
// separate the actual ID from the prefix
|
||||
name := strings.TrimPrefix(license, spdxlicense.LicenseRefPrefix)
|
||||
result = append(result, &spdx.OtherLicense{
|
||||
LicenseIdentifier: SanitizeElementID(license),
|
||||
LicenseName: name,
|
||||
ExtractedText: NONE, // we probably should have some extracted text here, but this is good enough for now
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// TODO: handle SPDX excludes file case
|
||||
// f file is an "excludes" file, skip it /* exclude SPDX analysis file(s) */
|
||||
// see: https://spdx.github.io/spdx-spec/v2.3/package-information/#79-package-verification-code-field
|
||||
// the above link contains the SPDX algorithm for a package verification code
|
||||
func newPackageVerificationCode(p pkg.Package, sbom sbom.SBOM) *common.PackageVerificationCode {
|
||||
func newPackageVerificationCode(p pkg.Package, sbom sbom.SBOM) *spdx.PackageVerificationCode {
|
||||
// key off of the contains relationship;
|
||||
// spdx validator will fail if a package claims to contain a file but no sha1 provided
|
||||
// if a sha1 for a file is provided then the validator will fail if the package does not have
|
||||
@@ -558,7 +587,7 @@ func newPackageVerificationCode(p pkg.Package, sbom sbom.SBOM) *common.PackageVe
|
||||
//nolint:gosec
|
||||
hasher := sha1.New()
|
||||
_, _ = hasher.Write([]byte(b.String()))
|
||||
return &common.PackageVerificationCode{
|
||||
return &spdx.PackageVerificationCode{
|
||||
// 7.9.1: Package Verification Code Value
|
||||
// Cardinality: mandatory, one
|
||||
Value: fmt.Sprintf("%+x", hasher.Sum(nil)),
|
||||
|
||||
@@ -4,8 +4,7 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/spdx/tools-golang/spdx/common"
|
||||
spdx "github.com/spdx/tools-golang/spdx/v2_3"
|
||||
"github.com/spdx/tools-golang/spdx"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
@@ -21,7 +20,7 @@ func Test_toPackageChecksums(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
pkg pkg.Package
|
||||
expected []common.Checksum
|
||||
expected []spdx.Checksum
|
||||
filesAnalyzed bool
|
||||
}{
|
||||
{
|
||||
@@ -39,7 +38,7 @@ func Test_toPackageChecksums(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []common.Checksum{
|
||||
expected: []spdx.Checksum{
|
||||
{
|
||||
Algorithm: "SHA1",
|
||||
Value: "1234",
|
||||
@@ -57,7 +56,7 @@ func Test_toPackageChecksums(t *testing.T) {
|
||||
ArchiveDigests: []file.Digest{},
|
||||
},
|
||||
},
|
||||
expected: []common.Checksum{},
|
||||
expected: []spdx.Checksum{},
|
||||
filesAnalyzed: false,
|
||||
},
|
||||
{
|
||||
@@ -67,7 +66,7 @@ func Test_toPackageChecksums(t *testing.T) {
|
||||
Version: "1.0.0",
|
||||
Language: pkg.Java,
|
||||
},
|
||||
expected: []common.Checksum{},
|
||||
expected: []spdx.Checksum{},
|
||||
filesAnalyzed: false,
|
||||
},
|
||||
{
|
||||
@@ -81,7 +80,7 @@ func Test_toPackageChecksums(t *testing.T) {
|
||||
H1Digest: "h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw=",
|
||||
},
|
||||
},
|
||||
expected: []common.Checksum{
|
||||
expected: []spdx.Checksum{
|
||||
{
|
||||
Algorithm: "SHA256",
|
||||
Value: "f5f1c0b4ad2e0dfa6f79eaaaa3586411925c16f61702208ddd4bad2fc17dc47c",
|
||||
@@ -97,7 +96,7 @@ func Test_toPackageChecksums(t *testing.T) {
|
||||
Language: pkg.Java,
|
||||
Metadata: struct{}{},
|
||||
},
|
||||
expected: []common.Checksum{},
|
||||
expected: []spdx.Checksum{},
|
||||
filesAnalyzed: false,
|
||||
},
|
||||
}
|
||||
@@ -210,6 +209,12 @@ func Test_lookupRelationship(t *testing.T) {
|
||||
ty: OtherRelationship,
|
||||
comment: "ownership-by-file-overlap: indicates that the parent package claims ownership of a child package since the parent metadata indicates overlap with a location that a cataloger found the child package by",
|
||||
},
|
||||
{
|
||||
input: artifact.EvidentByRelationship,
|
||||
exists: true,
|
||||
ty: OtherRelationship,
|
||||
comment: "evident-by: indicates the package's existence is evident by the given file",
|
||||
},
|
||||
{
|
||||
input: "made-up",
|
||||
exists: false,
|
||||
@@ -229,7 +234,7 @@ func Test_toFileChecksums(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
digests []file.Digest
|
||||
expected []common.Checksum
|
||||
expected []spdx.Checksum
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
@@ -246,7 +251,7 @@ func Test_toFileChecksums(t *testing.T) {
|
||||
Value: "meh",
|
||||
},
|
||||
},
|
||||
expected: []common.Checksum{
|
||||
expected: []spdx.Checksum{
|
||||
{
|
||||
Algorithm: "SHA256",
|
||||
Value: "deadbeefcafe",
|
||||
@@ -275,8 +280,8 @@ func Test_fileIDsForPackage(t *testing.T) {
|
||||
FileSystemID: "nowhere",
|
||||
}
|
||||
|
||||
docElementId := func(identifiable artifact.Identifiable) common.DocElementID {
|
||||
return common.DocElementID{
|
||||
docElementId := func(identifiable artifact.Identifiable) spdx.DocElementID {
|
||||
return spdx.DocElementID{
|
||||
ElementRefID: toSPDXID(identifiable),
|
||||
}
|
||||
}
|
||||
@@ -370,7 +375,7 @@ func Test_fileIDsForPackage(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_H1Digest(t *testing.T) {
|
||||
sbom := sbom.SBOM{}
|
||||
s := sbom.SBOM{}
|
||||
tests := []struct {
|
||||
name string
|
||||
pkg pkg.Package
|
||||
@@ -417,7 +422,7 @@ func Test_H1Digest(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
catalog := pkg.NewCatalog(test.pkg)
|
||||
pkgs := toPackages(catalog, sbom)
|
||||
pkgs := toPackages(catalog, s)
|
||||
require.Len(t, pkgs, 1)
|
||||
for _, p := range pkgs {
|
||||
if test.expectedDigest == "" {
|
||||
@@ -432,3 +437,67 @@ func Test_H1Digest(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_OtherLicenses(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
pkg pkg.Package
|
||||
expected []*spdx.OtherLicense
|
||||
}{
|
||||
{
|
||||
name: "no licenseRef",
|
||||
pkg: pkg.Package{
|
||||
Licenses: []string{
|
||||
"MIT",
|
||||
},
|
||||
},
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "single licenseRef",
|
||||
pkg: pkg.Package{
|
||||
Licenses: []string{
|
||||
"un known",
|
||||
},
|
||||
},
|
||||
expected: []*spdx.OtherLicense{
|
||||
{
|
||||
LicenseIdentifier: "LicenseRef-un-known",
|
||||
LicenseName: "un known",
|
||||
ExtractedText: NONE,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple licenseRef",
|
||||
pkg: pkg.Package{
|
||||
Licenses: []string{
|
||||
"un known",
|
||||
"not known %s",
|
||||
"MIT",
|
||||
},
|
||||
},
|
||||
expected: []*spdx.OtherLicense{
|
||||
{
|
||||
LicenseIdentifier: "LicenseRef-not-known--s",
|
||||
LicenseName: "not known %s",
|
||||
ExtractedText: NONE,
|
||||
},
|
||||
{
|
||||
LicenseIdentifier: "LicenseRef-un-known",
|
||||
LicenseName: "un known",
|
||||
ExtractedText: NONE,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
catalog := pkg.NewCatalog(test.pkg)
|
||||
otherLicenses := toOtherLicenses(catalog)
|
||||
require.Len(t, otherLicenses, len(test.expected))
|
||||
require.Equal(t, test.expected, otherLicenses)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
spdx "github.com/spdx/tools-golang/spdx/v2_3"
|
||||
"github.com/spdx/tools-golang/spdx"
|
||||
|
||||
"github.com/anchore/packageurl-go"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
@@ -176,9 +176,16 @@ func toSyftRelationships(spdxIDMap map[string]interface{}, doc *spdx.Document) [
|
||||
var to artifact.Identifiable
|
||||
var typ artifact.RelationshipType
|
||||
if toLocationOk {
|
||||
if r.Relationship == string(ContainsRelationship) {
|
||||
switch RelationshipType(r.Relationship) {
|
||||
case ContainsRelationship:
|
||||
typ = artifact.ContainsRelationship
|
||||
to = toLocation
|
||||
case OtherRelationship:
|
||||
// Encoding uses a specifically formatted comment...
|
||||
if strings.Index(r.RelationshipComment, string(artifact.EvidentByRelationship)) == 0 {
|
||||
typ = artifact.EvidentByRelationship
|
||||
to = toLocation
|
||||
}
|
||||
}
|
||||
} else {
|
||||
switch RelationshipType(r.Relationship) {
|
||||
@@ -188,7 +195,7 @@ func toSyftRelationships(spdxIDMap map[string]interface{}, doc *spdx.Document) [
|
||||
case OtherRelationship:
|
||||
// Encoding uses a specifically formatted comment...
|
||||
if strings.Index(r.RelationshipComment, string(artifact.OwnershipByFileOverlapRelationship)) == 0 {
|
||||
typ = artifact.DependencyOfRelationship
|
||||
typ = artifact.OwnershipByFileOverlapRelationship
|
||||
to = toPackage
|
||||
}
|
||||
}
|
||||
@@ -220,10 +227,8 @@ func toSyftCoordinates(f *spdx.File) source.Coordinates {
|
||||
}
|
||||
|
||||
func toSyftLocation(f *spdx.File) *source.Location {
|
||||
return &source.Location{
|
||||
Coordinates: toSyftCoordinates(f),
|
||||
VirtualPath: f.FileName,
|
||||
}
|
||||
l := source.NewVirtualLocationFromCoordinates(toSyftCoordinates(f), f.FileName)
|
||||
return &l
|
||||
}
|
||||
|
||||
func requireAndTrimPrefix(val interface{}, prefix string) string {
|
||||
|
||||
@@ -3,11 +3,12 @@ package spdxhelpers
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/spdx/tools-golang/spdx/common"
|
||||
spdx "github.com/spdx/tools-golang/spdx/v2_3"
|
||||
"github.com/spdx/tools-golang/spdx"
|
||||
"github.com/spdx/tools-golang/spdx/v2/common"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
@@ -246,9 +247,9 @@ func TestH1Digest(t *testing.T) {
|
||||
RefType: "purl",
|
||||
},
|
||||
},
|
||||
PackageChecksums: []common.Checksum{
|
||||
PackageChecksums: []spdx.Checksum{
|
||||
{
|
||||
Algorithm: common.SHA256,
|
||||
Algorithm: spdx.SHA256,
|
||||
Value: "f5f1c0b4ad2e0dfa6f79eaaaa3586411925c16f61702208ddd4bad2fc17dc47c",
|
||||
},
|
||||
},
|
||||
@@ -267,9 +268,9 @@ func TestH1Digest(t *testing.T) {
|
||||
RefType: "purl",
|
||||
},
|
||||
},
|
||||
PackageChecksums: []common.Checksum{
|
||||
PackageChecksums: []spdx.Checksum{
|
||||
{
|
||||
Algorithm: common.SHA1,
|
||||
Algorithm: spdx.SHA1,
|
||||
Value: "f5f1c0b4ad2e0dfa6f79eaaaa3586411925c16f61702208ddd4bad2fc17dc47c",
|
||||
},
|
||||
},
|
||||
@@ -288,9 +289,9 @@ func TestH1Digest(t *testing.T) {
|
||||
RefType: "purl",
|
||||
},
|
||||
},
|
||||
PackageChecksums: []common.Checksum{
|
||||
PackageChecksums: []spdx.Checksum{
|
||||
{
|
||||
Algorithm: common.SHA256,
|
||||
Algorithm: spdx.SHA256,
|
||||
Value: "",
|
||||
},
|
||||
},
|
||||
@@ -308,3 +309,113 @@ func TestH1Digest(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_toSyftRelationships(t *testing.T) {
|
||||
type args struct {
|
||||
spdxIDMap map[string]interface{}
|
||||
doc *spdx.Document
|
||||
}
|
||||
|
||||
pkg1 := pkg.Package{
|
||||
Name: "github.com/googleapis/gnostic",
|
||||
Version: "v0.5.5",
|
||||
}
|
||||
pkg1.SetID()
|
||||
|
||||
pkg2 := pkg.Package{
|
||||
Name: "rfc3339",
|
||||
Version: "1.2",
|
||||
Type: pkg.RpmPkg,
|
||||
}
|
||||
pkg2.SetID()
|
||||
|
||||
pkg3 := pkg.Package{
|
||||
Name: "rfc3339",
|
||||
Version: "1.2",
|
||||
Type: pkg.PythonPkg,
|
||||
}
|
||||
pkg3.SetID()
|
||||
|
||||
loc1 := source.NewLocationFromCoordinates(source.Coordinates{
|
||||
RealPath: "/somewhere/real",
|
||||
FileSystemID: "abc",
|
||||
})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []artifact.Relationship
|
||||
}{
|
||||
{
|
||||
name: "evident-by relationship",
|
||||
args: args{
|
||||
spdxIDMap: map[string]interface{}{
|
||||
string(toSPDXID(pkg1)): &pkg1,
|
||||
string(toSPDXID(loc1)): &loc1,
|
||||
},
|
||||
doc: &spdx.Document{
|
||||
Relationships: []*spdx.Relationship{
|
||||
{
|
||||
RefA: common.DocElementID{
|
||||
ElementRefID: toSPDXID(pkg1),
|
||||
},
|
||||
RefB: common.DocElementID{
|
||||
ElementRefID: toSPDXID(loc1),
|
||||
},
|
||||
Relationship: spdx.RelationshipOther,
|
||||
RelationshipComment: "evident-by: indicates the package's existence is evident by the given file",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []artifact.Relationship{
|
||||
{
|
||||
From: pkg1,
|
||||
To: loc1,
|
||||
Type: artifact.EvidentByRelationship,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ownership-by-file-overlap relationship",
|
||||
args: args{
|
||||
spdxIDMap: map[string]interface{}{
|
||||
string(toSPDXID(pkg2)): &pkg2,
|
||||
string(toSPDXID(pkg3)): &pkg3,
|
||||
},
|
||||
doc: &spdx.Document{
|
||||
Relationships: []*spdx.Relationship{
|
||||
{
|
||||
RefA: common.DocElementID{
|
||||
ElementRefID: toSPDXID(pkg2),
|
||||
},
|
||||
RefB: common.DocElementID{
|
||||
ElementRefID: toSPDXID(pkg3),
|
||||
},
|
||||
Relationship: spdx.RelationshipOther,
|
||||
RelationshipComment: "ownership-by-file-overlap: indicates that the parent package claims ownership of a child package since the parent metadata indicates overlap with a location that a cataloger found the child package by",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []artifact.Relationship{
|
||||
{
|
||||
From: pkg2,
|
||||
To: pkg3,
|
||||
Type: artifact.OwnershipByFileOverlapRelationship,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
actual := toSyftRelationships(tt.args.spdxIDMap, tt.args.doc)
|
||||
require.Len(t, actual, len(tt.want))
|
||||
for i := range actual {
|
||||
require.Equal(t, tt.want[i].From.ID(), actual[i].From.ID())
|
||||
require.Equal(t, tt.want[i].To.ID(), actual[i].To.ID())
|
||||
require.Equal(t, tt.want[i].Type, actual[i].Type)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/syft/formats/common/testutils"
|
||||
"github.com/anchore/syft/syft/formats/internal/testutils"
|
||||
)
|
||||
|
||||
var updateCycloneDx = flag.Bool("update-cyclonedx", false, "update the *.golden files for cyclone-dx encoders")
|
||||
|
||||
@@ -7,13 +7,14 @@ import (
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
)
|
||||
|
||||
const ID sbom.FormatID = "cyclonedx-1-json"
|
||||
const ID sbom.FormatID = "cyclonedx-json"
|
||||
|
||||
func Format() sbom.Format {
|
||||
return sbom.NewFormat(
|
||||
ID,
|
||||
sbom.AnyVersion,
|
||||
encoder,
|
||||
cyclonedxhelpers.GetDecoder(cyclonedx.BOMFileFormatJSON),
|
||||
cyclonedxhelpers.GetValidator(cyclonedx.BOMFileFormatJSON),
|
||||
ID,
|
||||
)
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user