mirror of
https://github.com/anchore/syft.git
synced 2026-10-12 06:07:20 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
03971ace43 | ||
|
|
07aee798b0 | ||
|
|
6cf668f749 | ||
|
|
2ec4371c95 | ||
|
|
fc4d28f365 | ||
|
|
5290dfb9c2 | ||
|
|
05611c283d | ||
|
|
934644232a | ||
|
|
641bccc79b | ||
|
|
ac94bf530c | ||
|
|
e87cfe7319 | ||
|
|
260cb4c72d | ||
|
|
85bddaa43d | ||
|
|
a864dc9505 | ||
|
|
44e8ae2577 | ||
|
|
ac8f72fdd1 | ||
|
|
e063471c66 | ||
|
|
645debe7a4 | ||
|
|
4bfb849310 | ||
|
|
d524bd5fc3 | ||
|
|
a8416d674b | ||
|
|
725529f43f | ||
|
|
e480443c8c | ||
|
|
db386baf81 | ||
|
|
795a63f1c9 | ||
|
|
d4f9993b8d | ||
|
|
bb6fc6525c | ||
|
|
bc1edb9c8a | ||
|
|
64be0a1072 | ||
|
|
e3d6ffd30e | ||
|
|
8d36b21237 | ||
|
|
6a7d6e6071 | ||
|
|
e1e489a284 | ||
|
|
b125ea83ba | ||
|
|
3690f979b3 | ||
|
|
5dd726fc86 | ||
|
|
c8b8b1ca11 | ||
|
|
7b08608adb | ||
|
|
82f32c7301 | ||
|
|
4ffbeeeea5 | ||
|
|
b1d6dae203 | ||
|
|
0f1e8fca14 | ||
|
|
8b38549b79 | ||
|
|
23a3173c9f | ||
|
|
17aa8287e6 | ||
|
|
730d3e3187 | ||
|
|
997fbdfcf3 | ||
|
|
13ceed9336 | ||
|
|
668f102340 | ||
|
|
f1a124209a | ||
|
|
5dbb3fc41d | ||
|
|
614ea00905 | ||
|
|
247b054ab5 | ||
|
|
9e43725951 | ||
|
|
4f39287216 | ||
|
|
7a69e2129b | ||
|
|
f6996f7b9a | ||
|
|
bd523bdb5d |
@@ -0,0 +1,81 @@
|
||||
name: "Bootstrap"
|
||||
description: "Bootstrap all tools and dependencies"
|
||||
inputs:
|
||||
go-version:
|
||||
description: "Go version to install"
|
||||
required: true
|
||||
default: "1.18.x"
|
||||
use-go-cache:
|
||||
description: "Restore go cache"
|
||||
required: true
|
||||
default: "true"
|
||||
cache-key-prefix:
|
||||
description: "Prefix all cache keys with this value"
|
||||
required: true
|
||||
default: "831180ac25"
|
||||
build-cache-key-prefix:
|
||||
description: "Prefix build cache key with this value"
|
||||
required: true
|
||||
default: "f8b6d31dea"
|
||||
bootstrap-apt-packages:
|
||||
description: "Space delimited list of tools to install via apt"
|
||||
default: "libxml2-utils"
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ inputs.go-version }}
|
||||
|
||||
- name: Restore tool cache
|
||||
id: tool-cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ github.workspace }}/.tmp
|
||||
key: ${{ inputs.cache-key-prefix }}-${{ runner.os }}-tool-${{ hashFiles('Makefile') }}
|
||||
|
||||
# note: we need to keep restoring the go mod cache before bootstrapping tools since `go install` is used in
|
||||
# some installations of project tools.
|
||||
- name: Restore go module cache
|
||||
id: go-mod-cache
|
||||
if: inputs.use-go-cache == 'true'
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/go/pkg/mod
|
||||
key: ${{ inputs.cache-key-prefix }}-${{ runner.os }}-go-${{ inputs.go-version }}-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ inputs.cache-key-prefix }}-${{ runner.os }}-go-${{ inputs.go-version }}-
|
||||
|
||||
- name: (cache-miss) Bootstrap project tools
|
||||
shell: bash
|
||||
if: steps.tool-cache.outputs.cache-hit != 'true'
|
||||
run: make bootstrap-tools
|
||||
|
||||
- name: Restore go build cache
|
||||
id: go-cache
|
||||
if: inputs.use-go-cache == 'true'
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cache/go-build
|
||||
key: ${{ inputs.cache-key-prefix }}-${{ inputs.build-cache-key-prefix }}-${{ runner.os }}-go-${{ inputs.go-version }}-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ inputs.cache-key-prefix }}-${{ inputs.build-cache-key-prefix }}-${{ runner.os }}-go-${{ inputs.go-version }}-
|
||||
|
||||
- name: (cache-miss) Bootstrap go dependencies
|
||||
shell: bash
|
||||
if: steps.go-mod-cache.outputs.cache-hit != 'true' && inputs.use-go-cache == 'true'
|
||||
run: make bootstrap-go
|
||||
|
||||
- name: Install apt packages
|
||||
if: inputs.bootstrap-apt-packages != ''
|
||||
shell: bash
|
||||
run: |
|
||||
DEBIAN_FRONTEND=noninteractive sudo apt update && sudo -E apt install -y ${{ inputs.bootstrap-apt-packages }}
|
||||
|
||||
- name: Create all cache fingerprints
|
||||
shell: bash
|
||||
run: make fingerprints
|
||||
|
||||
Executable
+11
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
red=$(tput setaf 1)
|
||||
bold=$(tput bold)
|
||||
normal=$(tput sgr0)
|
||||
|
||||
# assert we are running in CI (or die!)
|
||||
if [[ -z "$CI" ]]; then
|
||||
echo "${bold}${red}This script should ONLY be run in CI. Exiting...${normal}"
|
||||
exit 1
|
||||
fi
|
||||
Executable
+36
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env python3
|
||||
import subprocess
|
||||
import sys
|
||||
import shlex
|
||||
|
||||
|
||||
class bcolors:
|
||||
HEADER = '\033[95m'
|
||||
OKBLUE = '\033[94m'
|
||||
OKCYAN = '\033[96m'
|
||||
OKGREEN = '\033[92m'
|
||||
WARNING = '\033[93m'
|
||||
FAIL = '\033[91m'
|
||||
ENDC = '\033[0m'
|
||||
BOLD = '\033[1m'
|
||||
UNDERLINE = '\033[4m'
|
||||
|
||||
|
||||
if len(sys.argv) < 3:
|
||||
print("Usage: coverage.py [threshold] [go-coverage-report]")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
threshold = float(sys.argv[1])
|
||||
report = sys.argv[2]
|
||||
|
||||
|
||||
args = shlex.split(f"go tool cover -func {report}")
|
||||
p = subprocess.run(args, capture_output=True, text=True)
|
||||
|
||||
percent_coverage = float(p.stdout.splitlines()[-1].split()[-1].replace("%", ""))
|
||||
print(f"{bcolors.BOLD}Coverage: {percent_coverage}%{bcolors.ENDC}")
|
||||
|
||||
if percent_coverage < threshold:
|
||||
print(f"{bcolors.BOLD}{bcolors.FAIL}Coverage below threshold of {threshold}%{bcolors.ENDC}")
|
||||
sys.exit(1)
|
||||
@@ -4,19 +4,18 @@ set -eu
|
||||
ORIGINAL_STATE_DIR=$(mktemp -d "TEMP-original-state-XXXXXXXXX")
|
||||
TIDY_STATE_DIR=$(mktemp -d "TEMP-tidy-state-XXXXXXXXX")
|
||||
|
||||
trap "cp -v ${ORIGINAL_STATE_DIR}/* ./ && rm -fR ${ORIGINAL_STATE_DIR} ${TIDY_STATE_DIR}" EXIT
|
||||
trap "cp ${ORIGINAL_STATE_DIR}/* ./ && rm -fR ${ORIGINAL_STATE_DIR} ${TIDY_STATE_DIR}" EXIT
|
||||
|
||||
echo "Capturing original state of files..."
|
||||
cp -v go.mod go.sum "${ORIGINAL_STATE_DIR}"
|
||||
# capturing original state of files...
|
||||
cp go.mod go.sum "${ORIGINAL_STATE_DIR}"
|
||||
|
||||
echo "Capturing state of go.mod and go.sum after running go mod tidy..."
|
||||
# capturing state of go.mod and go.sum after running go mod tidy...
|
||||
go mod tidy
|
||||
cp -v go.mod go.sum "${TIDY_STATE_DIR}"
|
||||
echo ""
|
||||
cp go.mod go.sum "${TIDY_STATE_DIR}"
|
||||
|
||||
set +e
|
||||
|
||||
# Detect difference between the git HEAD state and the go mod tidy state
|
||||
# detect difference between the git HEAD state and the go mod tidy state
|
||||
DIFF_MOD=$(diff -u "${ORIGINAL_STATE_DIR}/go.mod" "${TIDY_STATE_DIR}/go.mod")
|
||||
DIFF_SUM=$(diff -u "${ORIGINAL_STATE_DIR}/go.sum" "${TIDY_STATE_DIR}/go.sum")
|
||||
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
name: "Benchmark testing"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
|
||||
Benchmark-Test:
|
||||
name: "Benchmark tests"
|
||||
runs-on: ubuntu-20.04
|
||||
# note: we want benchmarks to run on pull_request events in order to publish results to a sticky comment, and
|
||||
# we also want to run on push such that merges to main are recorded to the cache. For this reason we don't filter
|
||||
# the job by event.
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
|
||||
- name: Restore base benchmark result
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: test/results/benchmark-main.txt
|
||||
# use base sha for PR or new commit hash for main push in benchmark result key
|
||||
key: ${{ runner.os }}-bench-${{ (github.event.pull_request.base.sha != github.event.after) && github.event.pull_request.base.sha || github.event.after }}
|
||||
|
||||
- name: Run benchmark tests
|
||||
id: benchmark
|
||||
run: |
|
||||
REF_NAME=${GITHUB_REF##*/} make benchmark
|
||||
OUTPUT=$(make show-benchstat)
|
||||
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"
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: benchmark-test-results
|
||||
path: test/results/**/*
|
||||
|
||||
- name: Update PR benchmark results comment
|
||||
uses: marocchino/sticky-pull-request-comment@v2
|
||||
continue-on-error: true
|
||||
with:
|
||||
header: benchmark
|
||||
message: |
|
||||
### Benchmark Test Results
|
||||
|
||||
<details>
|
||||
<summary>Benchmark results from the latest changes vs base branch</summary>
|
||||
|
||||
```
|
||||
${{ steps.benchmark.outputs.result }}
|
||||
```
|
||||
|
||||
</details>
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
environment: release
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- 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
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
git merge-base --is-ancestor ${GITHUB_REF##*/} origin/main && echo "${GITHUB_REF##*/} is a commit on main!"
|
||||
|
||||
- name: Check static analysis results
|
||||
uses: fountainhead/action-wait-for-check@v1.0.0
|
||||
uses: fountainhead/action-wait-for-check@v1.1.0
|
||||
id: static-analysis
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
|
||||
- name: Check unit test results
|
||||
uses: fountainhead/action-wait-for-check@v1.0.0
|
||||
uses: fountainhead/action-wait-for-check@v1.1.0
|
||||
id: unit
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -44,7 +44,7 @@ jobs:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
|
||||
- name: Check integration test results
|
||||
uses: fountainhead/action-wait-for-check@v1.0.0
|
||||
uses: fountainhead/action-wait-for-check@v1.1.0
|
||||
id: integration
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -53,7 +53,7 @@ jobs:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
|
||||
- name: Check acceptance test results (linux)
|
||||
uses: fountainhead/action-wait-for-check@v1.0.0
|
||||
uses: fountainhead/action-wait-for-check@v1.1.0
|
||||
id: acceptance-linux
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -62,7 +62,7 @@ jobs:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
|
||||
- name: Check acceptance test results (mac)
|
||||
uses: fountainhead/action-wait-for-check@v1.0.0
|
||||
uses: fountainhead/action-wait-for-check@v1.1.0
|
||||
id: acceptance-mac
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -71,7 +71,7 @@ jobs:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
|
||||
- name: Check cli test results (linux)
|
||||
uses: fountainhead/action-wait-for-check@v1.0.0
|
||||
uses: fountainhead/action-wait-for-check@v1.1.0
|
||||
id: cli-linux
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -97,33 +97,15 @@ jobs:
|
||||
contents: write
|
||||
packages: write
|
||||
steps:
|
||||
- uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Restore tool cache
|
||||
id: tool-cache
|
||||
uses: actions/cache@v2.1.3
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
with:
|
||||
path: ${{ github.workspace }}/.tmp
|
||||
key: ${{ runner.os }}-tool-${{ hashFiles('Makefile') }}
|
||||
|
||||
- name: Restore go cache
|
||||
id: go-cache
|
||||
uses: actions/cache@v2.1.3
|
||||
with:
|
||||
path: ~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ env.GO_VERSION }}-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-${{ env.GO_VERSION }}-
|
||||
|
||||
- name: (cache-miss) Bootstrap all project dependencies
|
||||
if: steps.tool-cache.outputs.cache-hit != 'true' || steps.go-cache.outputs.cache-hit != 'true'
|
||||
run: make bootstrap
|
||||
# use the same cache we used for building snapshots
|
||||
build-cache-key-prefix: "snapshot"
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
|
||||
@@ -39,10 +39,6 @@ jobs:
|
||||
sed -r -i -e 's/^(YAJSV_VERSION = ).*/\1'${YAJSV_LATEST_VERSION}'/' Makefile
|
||||
sed -r -i -e 's/^(COSIGN_VERSION = ).*/\1'${COSIGN_LATEST_VERSION}'/' Makefile
|
||||
|
||||
# update cosign in go.mod as well
|
||||
go get github.com/sigstore/cosign@$COSIGN_LATEST_VERSION
|
||||
go mod tidy
|
||||
|
||||
# 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"
|
||||
@@ -76,4 +72,4 @@ jobs:
|
||||
- [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 }})
|
||||
This is an auto-generated pull request to update all of the bootstrap tools to the latest versions.
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
name: "Validations"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.18.x"
|
||||
GO_STABLE_VERSION: true
|
||||
|
||||
jobs:
|
||||
|
||||
@@ -17,100 +14,42 @@ jobs:
|
||||
name: "Static analysis"
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
stable: ${{ env.GO_STABLE_VERSION }}
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Restore tool cache
|
||||
id: tool-cache
|
||||
uses: actions/cache@v2.1.3
|
||||
with:
|
||||
path: ${{ github.workspace }}/.tmp
|
||||
key: ${{ runner.os }}-tool-${{ hashFiles('Makefile') }}
|
||||
|
||||
- name: Restore go cache
|
||||
id: go-cache
|
||||
uses: actions/cache@v2.1.3
|
||||
with:
|
||||
path: ~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ env.GO_VERSION }}-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-${{ env.GO_VERSION }}-
|
||||
|
||||
- name: (cache-miss) Bootstrap all project dependencies
|
||||
if: steps.tool-cache.outputs.cache-hit != 'true' || steps.go-cache.outputs.cache-hit != 'true'
|
||||
run: make bootstrap
|
||||
|
||||
- name: Bootstrap CI environment dependencies
|
||||
run: make ci-bootstrap
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
|
||||
- name: Run static analysis
|
||||
run: make static-analysis
|
||||
|
||||
|
||||
Unit-Test:
|
||||
# Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline
|
||||
name: "Unit tests"
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
stable: ${{ env.GO_STABLE_VERSION }}
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Restore tool cache
|
||||
id: tool-cache
|
||||
uses: actions/cache@v2.1.3
|
||||
with:
|
||||
path: ${{ github.workspace }}/.tmp
|
||||
key: ${{ runner.os }}-tool-${{ hashFiles('Makefile') }}
|
||||
|
||||
- name: Restore go cache
|
||||
id: go-cache
|
||||
uses: actions/cache@v2.1.3
|
||||
with:
|
||||
path: ~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ env.GO_VERSION }}-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-${{ env.GO_VERSION }}-
|
||||
|
||||
- name: (cache-miss) Bootstrap all project dependencies
|
||||
if: steps.tool-cache.outputs.cache-hit != 'true' || steps.go-cache.outputs.cache-hit != 'true'
|
||||
run: make bootstrap
|
||||
|
||||
- name: Bootstrap CI environment dependencies
|
||||
run: make ci-bootstrap
|
||||
|
||||
- name: Build cache key for java test-fixture blobs (for unit tests)
|
||||
run: make java-packages-fingerprint
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
|
||||
- name: Restore Java test-fixture cache
|
||||
id: unit-java-cache
|
||||
uses: actions/cache@v2.1.3
|
||||
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: Build cache key for rpm test-fixture blobs (for unit tests)
|
||||
run: make rpm-binaries-fingerprint
|
||||
|
||||
- name: Restore RPM test-fixture cache
|
||||
id: unit-rpm-cache
|
||||
uses: actions/cache@v2.1.3
|
||||
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: Build cache key for go binary test-fixture blobs (for unit tests)
|
||||
run: make go-binaries-fingerprint
|
||||
|
||||
- name: Restore Go binary test-fixture cache
|
||||
- name: Restore go binary test-fixture cache
|
||||
id: unit-go-binary-cache
|
||||
uses: actions/cache@v2.1.3
|
||||
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' ) }}
|
||||
@@ -118,54 +57,22 @@ jobs:
|
||||
- name: Run unit tests
|
||||
run: make unit
|
||||
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: unit-test-results
|
||||
path: test/results/**/*
|
||||
|
||||
Integration-Test:
|
||||
# Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline
|
||||
name: "Integration tests"
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
stable: ${{ env.GO_STABLE_VERSION }}
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Restore tool cache
|
||||
id: tool-cache
|
||||
uses: actions/cache@v2.1.3
|
||||
with:
|
||||
path: ${{ github.workspace }}/.tmp
|
||||
key: ${{ runner.os }}-tool-${{ hashFiles('Makefile') }}
|
||||
|
||||
- name: Restore go cache
|
||||
id: go-cache
|
||||
uses: actions/cache@v2.1.3
|
||||
with:
|
||||
path: ~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ env.GO_VERSION }}-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-${{ env.GO_VERSION }}-
|
||||
|
||||
- name: (cache-miss) Bootstrap all project dependencies
|
||||
if: steps.tool-cache.outputs.cache-hit != 'true' || steps.go-cache.outputs.cache-hit != 'true'
|
||||
run: make bootstrap
|
||||
|
||||
- name: Bootstrap CI environment dependencies
|
||||
run: make ci-bootstrap
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
|
||||
- name: Validate syft output against the CycloneDX schema
|
||||
run: make validate-cyclonedx-schema
|
||||
|
||||
- name: Build key for tar cache
|
||||
run: make integration-fingerprint
|
||||
|
||||
- name: Restore integration test cache
|
||||
uses: actions/cache@v2.1.3
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ github.workspace }}/test/integration/test-fixtures/cache
|
||||
key: ${{ runner.os }}-integration-test-cache-${{ hashFiles('test/integration/test-fixtures/cache.fingerprint') }}
|
||||
@@ -173,123 +80,35 @@ jobs:
|
||||
- name: Run integration tests
|
||||
run: make integration
|
||||
|
||||
Benchmark-Test:
|
||||
name: "Benchmark tests"
|
||||
runs-on: ubuntu-20.04
|
||||
# note: we want benchmarks to run on pull_request events in order to publish results to a sticky comment, and
|
||||
# we also want to run on push such that merges to main are recorded to the cache. For this reason we don't filter
|
||||
# the job by event.
|
||||
steps:
|
||||
- uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
stable: ${{ env.GO_STABLE_VERSION }}
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Restore tool cache
|
||||
id: tool-cache
|
||||
uses: actions/cache@v2.1.3
|
||||
with:
|
||||
path: ${{ github.workspace }}/.tmp
|
||||
key: ${{ runner.os }}-tool-${{ hashFiles('Makefile') }}
|
||||
|
||||
- name: Restore go cache
|
||||
id: go-cache
|
||||
uses: actions/cache@v2.1.3
|
||||
with:
|
||||
path: ~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ env.GO_VERSION }}-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-${{ env.GO_VERSION }}-
|
||||
|
||||
- name: (cache-miss) Bootstrap all project dependencies
|
||||
if: steps.tool-cache.outputs.cache-hit != 'true' || steps.go-cache.outputs.cache-hit != 'true'
|
||||
run: make bootstrap
|
||||
|
||||
- name: Bootstrap CI environment dependencies
|
||||
run: make ci-bootstrap
|
||||
|
||||
- name: Restore base benchmark result
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: test/results/benchmark-main.txt
|
||||
# use base sha for PR or new commit hash for main push in benchmark result key
|
||||
key: ${{ runner.os }}-bench-${{ (github.event.pull_request.base.sha != github.event.after) && github.event.pull_request.base.sha || github.event.after }}
|
||||
|
||||
- name: Run benchmark tests
|
||||
id: benchmark
|
||||
run: |
|
||||
REF_NAME=${GITHUB_REF##*/} make benchmark
|
||||
OUTPUT=$(make show-benchstat)
|
||||
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"
|
||||
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: benchmark-test-results
|
||||
path: test/results/**/*
|
||||
|
||||
- name: Update PR benchmark results comment
|
||||
uses: marocchino/sticky-pull-request-comment@v2
|
||||
continue-on-error: true
|
||||
with:
|
||||
header: benchmark
|
||||
message: |
|
||||
### Benchmark Test Results
|
||||
|
||||
<details>
|
||||
<summary>Benchmark results from the latest changes vs base branch</summary>
|
||||
|
||||
```
|
||||
${{ steps.benchmark.outputs.result }}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
Build-Snapshot-Artifacts:
|
||||
name: "Build snapshot artifacts"
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/setup-go@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
stable: ${{ env.GO_STABLE_VERSION }}
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
|
||||
- name: Restore tool cache
|
||||
id: tool-cache
|
||||
uses: actions/cache@v2.1.3
|
||||
with:
|
||||
path: ${{ github.workspace }}/.tmp
|
||||
key: ${{ runner.os }}-tool-${{ hashFiles('Makefile') }}
|
||||
|
||||
- name: Restore go cache
|
||||
id: go-cache
|
||||
uses: actions/cache@v2.1.3
|
||||
with:
|
||||
path: ~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ env.GO_VERSION }}-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-${{ env.GO_VERSION }}-
|
||||
|
||||
- name: (cache-miss) Bootstrap all project dependencies
|
||||
if: steps.tool-cache.outputs.cache-hit != 'true' || steps.go-cache.outputs.cache-hit != 'true'
|
||||
run: make bootstrap
|
||||
# why have another build cache key? We don't want unit/integration/etc test build caches to replace
|
||||
# the snapshot build cache, which includes builds for all OSs and architectures. As long as this key is
|
||||
# unique from the build-cache-key-prefix in other CI jobs, we should be fine.
|
||||
#
|
||||
# note: ideally this value should match what is used in release (just to help with build times).
|
||||
build-cache-key-prefix: "snapshot"
|
||||
bootstrap-apt-packages: ""
|
||||
|
||||
- name: Build snapshot artifacts
|
||||
run: make snapshot
|
||||
|
||||
- uses: actions/upload-artifact@v2
|
||||
# why not use actions/upload-artifact? It is very slow (3 minutes to upload ~600MB of data, vs 10 seconds with this approach).
|
||||
# see https://github.com/actions/upload-artifact/issues/199 for more info
|
||||
- name: Upload snapshot artifacts
|
||||
uses: actions/cache/save@v3
|
||||
with:
|
||||
name: artifacts
|
||||
path: snapshot/**/*
|
||||
path: snapshot
|
||||
key: snapshot-build-${{ github.run_id }}
|
||||
|
||||
|
||||
Acceptance-Linux:
|
||||
# Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline
|
||||
@@ -297,22 +116,20 @@ jobs:
|
||||
needs: [Build-Snapshot-Artifacts]
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/download-artifact@v2
|
||||
- name: Download snapshot build
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
name: artifacts
|
||||
path: snapshot
|
||||
key: snapshot-build-${{ github.run_id }}
|
||||
|
||||
- name: Run comparison tests (Linux)
|
||||
run: make compare-linux
|
||||
|
||||
- name: Build key for image cache
|
||||
run: make install-fingerprint
|
||||
|
||||
- name: Restore install.sh test image cache
|
||||
id: install-test-image-cache
|
||||
uses: actions/cache@v2.1.3
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ github.workspace }}/test/install/cache
|
||||
key: ${{ runner.os }}-install-test-image-cache-${{ hashFiles('test/install/cache.fingerprint') }}
|
||||
@@ -335,16 +152,17 @@ jobs:
|
||||
needs: [Build-Snapshot-Artifacts]
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/download-artifact@v2
|
||||
- name: Download snapshot build
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
name: artifacts
|
||||
path: snapshot
|
||||
key: snapshot-build-${{ github.run_id }}
|
||||
|
||||
- name: Restore docker image cache for compare testing
|
||||
id: mac-compare-testing-cache
|
||||
uses: actions/cache@v2.1.3
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: image.tar
|
||||
key: ${{ runner.os }}-${{ hashFiles('test/compare/mac.sh') }}
|
||||
@@ -355,52 +173,29 @@ jobs:
|
||||
- name: Run install.sh tests (Mac)
|
||||
run: make install-test-ci-mac
|
||||
|
||||
|
||||
Cli-Linux:
|
||||
# Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline
|
||||
name: "CLI tests (Linux)"
|
||||
needs: [Build-Snapshot-Artifacts]
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
stable: ${{ env.GO_STABLE_VERSION }}
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
|
||||
- name: Restore go cache
|
||||
id: go-cache
|
||||
uses: actions/cache@v2.1.3
|
||||
with:
|
||||
path: ~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ env.GO_VERSION }}-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-${{ env.GO_VERSION }}-
|
||||
|
||||
- name: Restore tool cache
|
||||
id: tool-cache
|
||||
uses: actions/cache@v2.1.3
|
||||
with:
|
||||
path: ${{ github.workspace }}/.tmp
|
||||
key: ${{ runner.os }}-tool-${{ hashFiles('Makefile') }}
|
||||
|
||||
- name: (cache-miss) Bootstrap all project dependencies
|
||||
if: steps.tool-cache.outputs.cache-hit != 'true' || steps.go-cache.outputs.cache-hit != 'true'
|
||||
run: make bootstrap
|
||||
|
||||
- name: Build key for tar cache
|
||||
run: make cli-fingerprint
|
||||
|
||||
- name: Restore CLI test cache
|
||||
uses: actions/cache@v2.1.3
|
||||
- name: Restore CLI test-fixture cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ github.workspace }}/test/cli/test-fixtures/cache
|
||||
key: ${{ runner.os }}-cli-test-cache-${{ hashFiles('test/cli/test-fixtures/cache.fingerprint') }}
|
||||
|
||||
- uses: actions/download-artifact@v2
|
||||
- name: Download snapshot build
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
name: artifacts
|
||||
path: snapshot
|
||||
key: snapshot-build-${{ github.run_id }}
|
||||
|
||||
- name: Run CLI Tests (Linux)
|
||||
run: make cli
|
||||
|
||||
+6
-2
@@ -1,9 +1,12 @@
|
||||
# TODO: enable this when we have coverage on docstring comments
|
||||
#issues:
|
||||
issues:
|
||||
max-same-issues: 25
|
||||
|
||||
# TODO: enable this when we have coverage on docstring comments
|
||||
# # The list of ids of default excludes to include or disable.
|
||||
# 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
|
||||
@@ -15,6 +18,7 @@ linters:
|
||||
- errcheck
|
||||
- errorlint
|
||||
- exportloopref
|
||||
- forcetypeassert
|
||||
- funlen
|
||||
- gocognit
|
||||
- goconst
|
||||
|
||||
+1
-4
@@ -5,6 +5,7 @@ release:
|
||||
env:
|
||||
# required to support multi architecture docker builds
|
||||
- DOCKER_CLI_EXPERIMENTAL=enabled
|
||||
- CGO_ENABLED=0
|
||||
|
||||
builds:
|
||||
- id: linux-build
|
||||
@@ -19,8 +20,6 @@ builds:
|
||||
- s390x
|
||||
# set the modified timestamp on the output binary to the git timestamp to ensure a reproducible build
|
||||
mod_timestamp: &build-timestamp '{{ .CommitTimestamp }}'
|
||||
env: &build-env
|
||||
- CGO_ENABLED=0
|
||||
ldflags: &build-ldflags |
|
||||
-w
|
||||
-s
|
||||
@@ -39,7 +38,6 @@ builds:
|
||||
- amd64
|
||||
- arm64
|
||||
mod_timestamp: *build-timestamp
|
||||
env: *build-env
|
||||
ldflags: *build-ldflags
|
||||
hooks:
|
||||
post:
|
||||
@@ -55,7 +53,6 @@ builds:
|
||||
goarch:
|
||||
- amd64
|
||||
mod_timestamp: *build-timestamp
|
||||
env: *build-env
|
||||
ldflags: *build-ldflags
|
||||
|
||||
archives:
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
# Syft Contributors
|
||||
|
||||
The following Syft components were contributed by external authors/organizations.
|
||||
|
||||
## GraalVM Native Image
|
||||
|
||||
A cataloger contributed by Oracle Corporation that extracts packages given within GraalVM Native Image SBOMs.
|
||||
+64
-40
@@ -15,37 +15,16 @@ 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
|
||||
|
||||
#### Make output
|
||||
The main make tasks for common static analysis and testing are `lint`, `lint-fix`, `unit`, `integration`, and `cli`.
|
||||
```
|
||||
all Run all linux-based checks (linting, license check, unit, integration, and linux compare tests)
|
||||
benchmark Run benchmark tests and compare against the baseline (if available)
|
||||
bootstrap Download and install all tooling dependencies (+ prep tooling in the ./tmp dir)
|
||||
build Build release snapshot binaries and packages
|
||||
check-licenses Ensure transitive dependencies are compliant with the current license policy
|
||||
clean-test-image-cache Clean test image cache
|
||||
clean Remove previous builds, result reports, and test cache
|
||||
cli Run CLI tests
|
||||
compare-linux Run compare tests on build snapshot binaries and packages (Linux)
|
||||
compare-mac Run compare tests on build snapshot binaries and packages (Mac)
|
||||
generate-json-schema Generate a new json schema
|
||||
generate-license-list Generate an updated spdx license list
|
||||
help Display this help
|
||||
integration Run integration tests
|
||||
lint-fix Auto-format all source code + run golangci lint fixers
|
||||
lint Run gofmt + golangci lint checks
|
||||
show-test-image-cache Show all docker and image tar cache
|
||||
show-test-snapshots Show all test snapshots
|
||||
snapshot-with-signing Build snapshot release binaries and packages (with dummy signing)
|
||||
test Run all tests (currently unit, integration, linux compare, and cli tests)
|
||||
unit Run unit tests (with coverage)
|
||||
```
|
||||
|
||||
See `make help` for all the current make tasks.
|
||||
|
||||
## Architecture
|
||||
|
||||
Syft is used to generate a Software Bill of Materials (SBOM) from different kinds of input.
|
||||
|
||||
### Code organization for the cmd package
|
||||
|
||||
Syft's entrypoint can be found in the `cmd` package at `cmd/syft/main.go`. `main.go` builds a new syft `cli` via `cli.New()`
|
||||
and then executes the `cli` via `cli.Execute()`. The `cli` package is responsible for parsing command line arguments,
|
||||
setting up the application context and configuration, and executing the application. Each of syft's commands
|
||||
@@ -55,23 +34,24 @@ They are registered in `syft/cli/commands/go`.
|
||||
.
|
||||
└── syft/
|
||||
├── cli/
|
||||
│ ├── attest/
|
||||
│ ├── attest.go
|
||||
│ ├── commands.go
|
||||
│ ├── completion.go
|
||||
│ ├── convert/
|
||||
│ ├── convert.go
|
||||
│ ├── eventloop/
|
||||
│ ├── options/
|
||||
│ ├── packages/
|
||||
│ ├── packages.go
|
||||
│ ├── poweruser/
|
||||
│ ├── poweruser.go
|
||||
│ └── version.go
|
||||
│ ├── attest/
|
||||
│ ├── attest.go
|
||||
│ ├── commands.go
|
||||
│ ├── completion.go
|
||||
│ ├── convert/
|
||||
│ ├── convert.go
|
||||
│ ├── eventloop/
|
||||
│ ├── options/
|
||||
│ ├── packages/
|
||||
│ ├── packages.go
|
||||
│ ├── poweruser/
|
||||
│ ├── poweruser.go
|
||||
│ └── version.go
|
||||
└── main.go
|
||||
```
|
||||
|
||||
#### Execution flow
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant main as cmd/syft/main
|
||||
@@ -100,19 +80,19 @@ sequenceDiagram
|
||||
Syft's core library (see, exported) functionality is implemented in the `syft` package. The `syft` package is responsible for organizing the core
|
||||
SBOM data model, it's translated output formats, and the core SBOM generation logic.
|
||||
|
||||
#### Organization and design notes for the syft library
|
||||
- analysis creates a static SBOM which can be encoded and decoded
|
||||
- format objects, should strive to not add or enrich data in encoding that could otherwise be done during analysis
|
||||
- package catalogers and their organization can be viewed/added to the `syft/pkg/cataloger` package
|
||||
- file catalogers and their organization can be viewed/added to the `syft/file` package
|
||||
- The source package provides an abstraction to allow a user to loosely define a data source that can be cataloged
|
||||
- Logging Abstraction ...
|
||||
|
||||
#### Code example of syft as a library
|
||||
Here is a gist of using syft as a library to generate a SBOM from a docker image: [link](https://gist.github.com/wagoodman/57ed59a6d57600c23913071b8470175b).
|
||||
|
||||
Here is a gist of using syft as a library to generate a SBOM for a docker image: [link](https://gist.github.com/wagoodman/57ed59a6d57600c23913071b8470175b).
|
||||
The execution flow for the example is detailed below.
|
||||
|
||||
#### Execution flow examples for the syft library
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant source as source.New(ubuntu:latest)
|
||||
@@ -132,6 +112,50 @@ sequenceDiagram
|
||||
```
|
||||
|
||||
|
||||
### Syft Catalogers
|
||||
|
||||
##### Summary
|
||||
|
||||
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.
|
||||
|
||||
#### Building a new Cataloger
|
||||
|
||||
Catalogers must fulfill the interface [found here](https://github.com/anchore/syft/blob/main/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).
|
||||
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:
|
||||
|
||||
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).
|
||||
|
||||
`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).
|
||||
- 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).
|
||||
|
||||
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).
|
||||
|
||||
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.
|
||||
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)
|
||||
|
||||
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).
|
||||
|
||||
## Testing
|
||||
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
BIN = syft
|
||||
VERSION=$(shell git describe --dirty --always --tags)
|
||||
TEMPDIR = ./.tmp
|
||||
BIN := syft
|
||||
TEMP_DIR := ./.tmp
|
||||
|
||||
# commands and versions
|
||||
LINTCMD = $(TEMPDIR)/golangci-lint run --tests=false
|
||||
GOIMPORTS_CMD = $(TEMPDIR)/gosimports -local github.com/anchore
|
||||
RELEASE_CMD=$(TEMPDIR)/goreleaser release --rm-dist
|
||||
SNAPSHOT_CMD=$(RELEASE_CMD) --skip-publish --snapshot
|
||||
# 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
|
||||
SNAPSHOT_CMD := $(RELEASE_CMD) --skip-publish --skip-sign --snapshot
|
||||
|
||||
# tool versions
|
||||
GOLANGCILINT_VERSION = v1.50.1
|
||||
GOSIMPORTS_VERSION = v0.3.4
|
||||
BOUNCER_VERSION = v0.4.0
|
||||
CHRONICLE_VERSION = v0.4.2
|
||||
GORELEASER_VERSION = v1.13.0
|
||||
YAJSV_VERSION = v1.4.1
|
||||
COSIGN_VERSION = v1.13.1
|
||||
QUILL_VERSION = v0.2.0
|
||||
# Tool versions #################################
|
||||
GOLANGCILINT_VERSION := v1.50.1
|
||||
GOSIMPORTS_VERSION := v0.3.5
|
||||
BOUNCER_VERSION := v0.4.0
|
||||
CHRONICLE_VERSION := v0.4.2
|
||||
GORELEASER_VERSION := v1.14.1
|
||||
YAJSV_VERSION := v1.4.1
|
||||
COSIGN_VERSION := v1.13.1
|
||||
QUILL_VERSION := v0.2.0
|
||||
|
||||
# formatting variables
|
||||
# Formatting variables #################################
|
||||
BOLD := $(shell tput -T linux bold)
|
||||
PURPLE := $(shell tput -T linux setaf 5)
|
||||
GREEN := $(shell tput -T linux setaf 2)
|
||||
@@ -28,55 +27,23 @@ RESET := $(shell tput -T linux sgr0)
|
||||
TITLE := $(BOLD)$(PURPLE)
|
||||
SUCCESS := $(BOLD)$(GREEN)
|
||||
|
||||
# test variables
|
||||
RESULTSDIR = test/results
|
||||
COMPARE_DIR = ./test/compare
|
||||
COMPARE_TEST_IMAGE = centos:8.2.2004
|
||||
COVER_REPORT = $(RESULTSDIR)/unit-coverage-details.txt
|
||||
COVER_TOTAL = $(RESULTSDIR)/unit-coverage-summary.txt
|
||||
# the quality gate lower threshold for unit test total % coverage (by function statements)
|
||||
COVERAGE_THRESHOLD := 62
|
||||
# Test variables #################################
|
||||
COMPARE_DIR := ./test/compare
|
||||
COMPARE_TEST_IMAGE := centos:8.2.2004
|
||||
COVERAGE_THRESHOLD := 62 # the quality gate lower threshold for unit test total % coverage (by function statements)
|
||||
|
||||
# CI cache busting values; change these if you want CI to not use previous stored cache
|
||||
INTEGRATION_CACHE_BUSTER="894d8ca"
|
||||
CLI_CACHE_BUSTER="e5cdfd8"
|
||||
BOOTSTRAP_CACHE="c7afb99ad"
|
||||
|
||||
## Build variables
|
||||
DISTDIR=./dist
|
||||
SNAPSHOTDIR=./snapshot
|
||||
OS=$(shell uname | tr '[:upper:]' '[:lower:]')
|
||||
SNAPSHOT_BIN=$(realpath $(shell pwd)/$(SNAPSHOTDIR)/$(OS)-build_$(OS)_amd64_v1/$(BIN))
|
||||
|
||||
## Variable assertions
|
||||
ifndef TEMPDIR
|
||||
$(error TEMPDIR is not set)
|
||||
endif
|
||||
|
||||
ifndef RESULTSDIR
|
||||
$(error RESULTSDIR is not set)
|
||||
endif
|
||||
|
||||
ifndef COMPARE_DIR
|
||||
$(error COMPARE_DIR is not set)
|
||||
endif
|
||||
|
||||
ifndef DISTDIR
|
||||
$(error DISTDIR is not set)
|
||||
endif
|
||||
|
||||
ifndef SNAPSHOTDIR
|
||||
$(error SNAPSHOTDIR is not set)
|
||||
endif
|
||||
## Build variables #################################
|
||||
VERSION := $(shell git describe --dirty --always --tags)
|
||||
DIST_DIR := ./dist
|
||||
SNAPSHOT_DIR := ./snapshot
|
||||
CHANGELOG := CHANGELOG.md
|
||||
OS := $(shell uname | tr '[:upper:]' '[:lower:]')
|
||||
SNAPSHOT_BIN := $(realpath $(shell pwd)/$(SNAPSHOT_DIR)/$(OS)-build_$(OS)_amd64_v1/$(BIN))
|
||||
|
||||
ifndef VERSION
|
||||
$(error VERSION is not set)
|
||||
endif
|
||||
|
||||
ifndef REF_NAME
|
||||
REF_NAME = $(VERSION)
|
||||
endif
|
||||
|
||||
define title
|
||||
@printf '$(TITLE)$(1)$(RESET)\n'
|
||||
endef
|
||||
@@ -89,65 +56,58 @@ define safe_rm_rf_children
|
||||
bash -c 'test -z "$(1)" && false || rm -rf $(1)/*'
|
||||
endef
|
||||
|
||||
## Default Task
|
||||
.DEFAULT_GOAL:=help
|
||||
|
||||
## Tasks
|
||||
|
||||
.PHONY: all
|
||||
all: clean static-analysis test ## Run all linux-based checks (linting, license check, unit, integration, and linux compare tests)
|
||||
all: static-analysis test ## Run all linux-based checks (linting, license check, unit, integration, and linux compare tests)
|
||||
@printf '$(SUCCESS)All checks pass!$(RESET)\n'
|
||||
|
||||
.PHONY: static-analysis
|
||||
static-analysis: check-go-mod-tidy lint check-licenses check-json-schema-drift ## Run all static analysis checks
|
||||
|
||||
.PHONY: test
|
||||
test: unit validate-cyclonedx-schema integration benchmark compare-linux cli ## Run all tests (currently unit, integration, linux compare, and cli tests)
|
||||
test: unit integration validate-cyclonedx-schema benchmark cli ## Run all tests (currently unit, integration, linux compare, and cli tests)
|
||||
|
||||
.PHONY: ci-bootstrap
|
||||
ci-bootstrap:
|
||||
DEBIAN_FRONTEND=noninteractive sudo apt update && sudo -E apt install -y bc jq libxml2-utils
|
||||
|
||||
.PHONY:
|
||||
ci-bootstrap-mac:
|
||||
github_changelog_generator --version || sudo gem install github_changelog_generator
|
||||
## Bootstrapping targets #################################
|
||||
|
||||
$(RESULTSDIR):
|
||||
mkdir -p $(RESULTSDIR)
|
||||
|
||||
$(TEMPDIR):
|
||||
mkdir -p $(TEMPDIR)
|
||||
.PHONY: bootstrap
|
||||
bootstrap: $(TEMP_DIR) bootstrap-go bootstrap-tools ## Download and install all tooling dependencies (+ prep tooling in the ./tmp dir)
|
||||
$(call title,Bootstrapping dependencies)
|
||||
|
||||
.PHONY: bootstrap-tools
|
||||
bootstrap-tools: $(TEMPDIR)
|
||||
curl -sSfL https://raw.githubusercontent.com/anchore/quill/main/install.sh | sh -s -- -b $(TEMPDIR)/ $(QUILL_VERSION)
|
||||
GO111MODULE=off GOBIN=$(realpath $(TEMPDIR)) go get -u golang.org/x/perf/cmd/benchstat
|
||||
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(TEMPDIR)/ $(GOLANGCILINT_VERSION)
|
||||
curl -sSfL https://raw.githubusercontent.com/wagoodman/go-bouncer/master/bouncer.sh | sh -s -- -b $(TEMPDIR)/ $(BOUNCER_VERSION)
|
||||
curl -sSfL https://raw.githubusercontent.com/anchore/chronicle/main/install.sh | sh -s -- -b $(TEMPDIR)/ $(CHRONICLE_VERSION)
|
||||
.github/scripts/goreleaser-install.sh -d -b $(TEMPDIR)/ $(GORELEASER_VERSION)
|
||||
bootstrap-tools: $(TEMP_DIR)
|
||||
curl -sSfL https://raw.githubusercontent.com/anchore/quill/main/install.sh | sh -s -- -b $(TEMP_DIR)/ $(QUILL_VERSION)
|
||||
GO111MODULE=off GOBIN=$(realpath $(TEMP_DIR)) go get -u golang.org/x/perf/cmd/benchstat
|
||||
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(TEMP_DIR)/ $(GOLANGCILINT_VERSION)
|
||||
curl -sSfL https://raw.githubusercontent.com/wagoodman/go-bouncer/master/bouncer.sh | sh -s -- -b $(TEMP_DIR)/ $(BOUNCER_VERSION)
|
||||
curl -sSfL https://raw.githubusercontent.com/anchore/chronicle/main/install.sh | sh -s -- -b $(TEMP_DIR)/ $(CHRONICLE_VERSION)
|
||||
.github/scripts/goreleaser-install.sh -d -b $(TEMP_DIR)/ $(GORELEASER_VERSION)
|
||||
# the only difference between goimports and gosimports is that gosimports removes extra whitespace between import blocks (see https://github.com/golang/go/issues/20818)
|
||||
GOBIN="$(realpath $(TEMPDIR))" go install github.com/rinchsan/gosimports/cmd/gosimports@$(GOSIMPORTS_VERSION)
|
||||
GOBIN="$(realpath $(TEMPDIR))" go install github.com/neilpa/yajsv@$(YAJSV_VERSION)
|
||||
GOBIN="$(realpath $(TEMPDIR))" go install github.com/sigstore/cosign/cmd/cosign@$(COSIGN_VERSION)
|
||||
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)
|
||||
|
||||
.PHONY: bootstrap-go
|
||||
bootstrap-go:
|
||||
go mod download
|
||||
|
||||
.PHONY: bootstrap
|
||||
bootstrap: $(RESULTSDIR) bootstrap-go bootstrap-tools ## Download and install all tooling dependencies (+ prep tooling in the ./tmp dir)
|
||||
$(call title,Bootstrapping dependencies)
|
||||
$(TEMP_DIR):
|
||||
mkdir -p $(TEMP_DIR)
|
||||
|
||||
.PHONY: static-analysis
|
||||
static-analysis: check-go-mod-tidy check-licenses lint
|
||||
|
||||
## Static analysis targets #################################
|
||||
|
||||
.PHONY: lint
|
||||
lint: ## Run gofmt + golangci lint checks
|
||||
lint: ## Run gofmt + golangci lint checks
|
||||
$(call title,Running linters)
|
||||
# ensure there are no go fmt differences
|
||||
@printf "files with gofmt issues: [$(shell gofmt -l -s .)]\n"
|
||||
@test -z "$(shell gofmt -l -s .)"
|
||||
|
||||
# run all golangci-lint rules
|
||||
$(LINTCMD)
|
||||
$(LINT_CMD)
|
||||
@[ -z "$(shell $(GOIMPORTS_CMD) -d .)" ] || (echo "goimports needs to be fixed" && false)
|
||||
|
||||
# go tooling does not play well with certain filename characters, ensure the common cases don't result in future "go get" failures
|
||||
@@ -155,243 +115,107 @@ lint: ## Run gofmt + golangci lint checks
|
||||
@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
|
||||
lint-fix: ## Auto-format all source code + run golangci lint fixers
|
||||
$(call title,Running lint fixers)
|
||||
gofmt -w -s .
|
||||
$(GOIMPORTS_CMD) -w .
|
||||
$(LINTCMD) --fix
|
||||
$(LINT_CMD) --fix
|
||||
go mod tidy
|
||||
|
||||
.PHONY: check-licenses
|
||||
check-licenses: ## Ensure transitive dependencies are compliant with the current license policy
|
||||
$(TEMPDIR)/bouncer check ./...
|
||||
check-licenses: ## Ensure transitive dependencies are compliant with the current license policy
|
||||
$(call title,Checking for license compliance)
|
||||
$(TEMP_DIR)/bouncer check ./...
|
||||
|
||||
check-go-mod-tidy:
|
||||
@ .github/scripts/go-mod-tidy-check.sh && echo "go.mod and go.sum are 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)
|
||||
|
||||
|
||||
## Testing targets #################################
|
||||
|
||||
.PHONY: unit
|
||||
unit: $(TEMP_DIR) fixtures ## Run unit tests (with coverage)
|
||||
$(call title,Running unit tests)
|
||||
go test -coverprofile $(TEMP_DIR)/unit-coverage-details.txt $(shell go list ./... | grep -v anchore/syft/test)
|
||||
@.github/scripts/coverage.py $(COVERAGE_THRESHOLD) $(TEMP_DIR)/unit-coverage-details.txt
|
||||
|
||||
.PHONY: integration
|
||||
integration: ## Run integration tests
|
||||
$(call title,Running integration tests)
|
||||
go test -v ./test/integration
|
||||
|
||||
.PHONY: validate-cyclonedx-schema
|
||||
validate-cyclonedx-schema:
|
||||
cd schema/cyclonedx && make
|
||||
|
||||
.PHONY: unit
|
||||
unit: $(RESULTSDIR) fixtures ## Run unit tests (with coverage)
|
||||
$(call title,Running unit tests)
|
||||
go test -coverprofile $(COVER_REPORT) $(shell go list ./... | grep -v anchore/syft/test)
|
||||
@go tool cover -func $(COVER_REPORT) | grep total | awk '{print substr($$3, 1, length($$3)-1)}' > $(COVER_TOTAL)
|
||||
@echo "Coverage: $$(cat $(COVER_TOTAL))"
|
||||
@if [ $$(echo "$$(cat $(COVER_TOTAL)) >= $(COVERAGE_THRESHOLD)" | bc -l) -ne 1 ]; then echo "$(RED)$(BOLD)Failed coverage quality gate (> $(COVERAGE_THRESHOLD)%)$(RESET)" && false; fi
|
||||
.PHONY: cli
|
||||
cli: $(SNAPSHOT_DIR) ## Run CLI tests
|
||||
chmod 755 "$(SNAPSHOT_BIN)"
|
||||
$(SNAPSHOT_BIN) version
|
||||
SYFT_BINARY_LOCATION='$(SNAPSHOT_BIN)' \
|
||||
go test -count=1 -timeout=15m -v ./test/cli
|
||||
|
||||
|
||||
## Benchmark test targets #################################
|
||||
|
||||
.PHONY: benchmark
|
||||
benchmark: $(RESULTSDIR) ## Run benchmark tests and compare against the baseline (if available)
|
||||
benchmark: $(TEMP_DIR) ## Run benchmark tests and compare against the baseline (if available)
|
||||
$(call title,Running benchmark tests)
|
||||
go test -p 1 -run=^Benchmark -bench=. -count=5 -benchmem ./... | tee $(RESULTSDIR)/benchmark-$(REF_NAME).txt
|
||||
(test -s $(RESULTSDIR)/benchmark-main.txt && \
|
||||
$(TEMPDIR)/benchstat $(RESULTSDIR)/benchmark-main.txt $(RESULTSDIR)/benchmark-$(REF_NAME).txt || \
|
||||
$(TEMPDIR)/benchstat $(RESULTSDIR)/benchmark-$(REF_NAME).txt) \
|
||||
| tee $(RESULTSDIR)/benchstat.txt
|
||||
go test -p 1 -run=^Benchmark -bench=. -count=7 -benchmem ./... | tee $(TEMP_DIR)/benchmark-$(VERSION).txt
|
||||
(test -s $(TEMP_DIR)/benchmark-main.txt && \
|
||||
$(TEMP_DIR)/benchstat $(TEMP_DIR)/benchmark-main.txt $(TEMP_DIR)/benchmark-$(VERSION).txt || \
|
||||
$(TEMP_DIR)/benchstat $(TEMP_DIR)/benchmark-$(VERSION).txt) \
|
||||
| tee $(TEMP_DIR)/benchstat.txt
|
||||
|
||||
.PHONY: show-benchstat
|
||||
show-benchstat:
|
||||
@cat $(RESULTSDIR)/benchstat.txt
|
||||
@cat $(TEMP_DIR)/benchstat.txt
|
||||
|
||||
# note: this is used by CI to determine if the install test fixture cache (docker image tars) should be busted
|
||||
install-fingerprint:
|
||||
cd test/install && \
|
||||
|
||||
## Test-fixture-related targets #################################
|
||||
|
||||
# note: this is used by CI to determine if various test fixture cache should be restored or recreated
|
||||
fingerprints:
|
||||
$(call title,Creating all test cache input fingerprints)
|
||||
|
||||
# for IMAGE integration test fixtures
|
||||
cd test/integration/test-fixtures && \
|
||||
make cache.fingerprint
|
||||
|
||||
install-test: $(SNAPSHOTDIR)
|
||||
cd test/install && \
|
||||
make
|
||||
|
||||
install-test-cache-save: $(SNAPSHOTDIR)
|
||||
cd test/install && \
|
||||
make save
|
||||
|
||||
install-test-cache-load: $(SNAPSHOTDIR)
|
||||
cd test/install && \
|
||||
make load
|
||||
|
||||
install-test-ci-mac: $(SNAPSHOTDIR)
|
||||
cd test/install && \
|
||||
make ci-test-mac
|
||||
|
||||
.PHONY: integration
|
||||
integration: ## Run integration tests
|
||||
$(call title,Running integration tests)
|
||||
go test -v ./test/integration
|
||||
|
||||
# note: this is used by CI to determine if the integration test fixture cache (docker image tars) should be busted
|
||||
integration-fingerprint:
|
||||
$(call title,Integration test fixture fingerprint)
|
||||
find test/integration/test-fixtures/image-* -type f -exec md5sum {} + | awk '{print $1}' | sort | tee /dev/stderr | md5sum | tee test/integration/test-fixtures/cache.fingerprint && echo "$(INTEGRATION_CACHE_BUSTER)" >> test/integration/test-fixtures/cache.fingerprint
|
||||
|
||||
.PHONY: java-packages-fingerprint
|
||||
java-packages-fingerprint:
|
||||
$(call title,Java test fixture fingerprint)
|
||||
# for JAVA BUILD test fixtures
|
||||
cd syft/pkg/cataloger/java/test-fixtures/java-builds && \
|
||||
make packages.fingerprint
|
||||
|
||||
.PHONY: go-binaries-fingerprint
|
||||
go-binaries-fingerprint:
|
||||
$(call title,Go binaries test fixture fingerprint)
|
||||
# for GO BINARY test fixtures
|
||||
cd syft/pkg/cataloger/golang/test-fixtures/archs && \
|
||||
make binaries.fingerprint
|
||||
|
||||
.PHONY: rpm-binaries-fingerprint
|
||||
rpm-binaries-fingerprint:
|
||||
$(call title,RPM binary test fixture fingerprint)
|
||||
# for RPM test fixtures
|
||||
cd syft/pkg/cataloger/rpm/test-fixtures && \
|
||||
make rpms.fingerprint
|
||||
|
||||
# for INSTALL integration test fixtures
|
||||
cd test/install && \
|
||||
make cache.fingerprint
|
||||
|
||||
# for CLI test fixtures
|
||||
cd test/cli/test-fixtures && \
|
||||
make cache.fingerprint
|
||||
|
||||
.PHONY: fixtures
|
||||
fixtures:
|
||||
$(call title,Generating test fixtures)
|
||||
cd syft/pkg/cataloger/java/test-fixtures/java-builds && make
|
||||
cd syft/pkg/cataloger/rpm/test-fixtures && make
|
||||
|
||||
.PHONY: generate-json-schema
|
||||
generate-json-schema: ## Generate a new json schema
|
||||
cd schema/json && go run generate.go
|
||||
|
||||
.PHONY: generate-license-list
|
||||
generate-license-list: ## Generate an updated spdx license list
|
||||
go generate ./internal/spdxlicense/...
|
||||
gofmt -s -w ./internal/spdxlicense
|
||||
|
||||
.PHONY: build
|
||||
build: $(SNAPSHOTDIR) ## Build release snapshot binaries and packages
|
||||
|
||||
$(SNAPSHOTDIR): ## Build snapshot release binaries and packages
|
||||
$(call title,Building snapshot artifacts)
|
||||
|
||||
# create a config with the dist dir overridden
|
||||
echo "dist: $(SNAPSHOTDIR)" > $(TEMPDIR)/goreleaser.yaml
|
||||
cat .goreleaser.yaml >> $(TEMPDIR)/goreleaser.yaml
|
||||
|
||||
# build release snapshots
|
||||
bash -c "SKIP_SIGNING=true $(SNAPSHOT_CMD) --skip-sign --config $(TEMPDIR)/goreleaser.yaml"
|
||||
|
||||
.PHONY: snapshot-with-signing
|
||||
snapshot-with-signing: ## Build snapshot release binaries and packages (with dummy signing)
|
||||
$(call title,Building snapshot artifacts (+ signing))
|
||||
|
||||
# create a config with the dist dir overridden
|
||||
echo "dist: $(SNAPSHOTDIR)" > $(TEMPDIR)/goreleaser.yaml
|
||||
cat .goreleaser.yaml >> $(TEMPDIR)/goreleaser.yaml
|
||||
|
||||
# build release snapshots
|
||||
bash -c "\
|
||||
$(SNAPSHOT_CMD) \
|
||||
--config $(TEMPDIR)/goreleaser.yaml \
|
||||
|| (cat /tmp/quill-*.log && false)"
|
||||
|
||||
# note: we cannot clean the snapshot directory since the pipeline builds the snapshot separately
|
||||
.PHONY: compare-mac
|
||||
compare-mac: $(RESULTSDIR) $(SNAPSHOTDIR) ## Run compare tests on build snapshot binaries and packages (Mac)
|
||||
$(call title,Running compare test: Run on Mac)
|
||||
$(COMPARE_DIR)/mac.sh \
|
||||
$(SNAPSHOTDIR) \
|
||||
$(COMPARE_DIR) \
|
||||
$(COMPARE_TEST_IMAGE) \
|
||||
$(RESULTSDIR)
|
||||
|
||||
# note: we cannot clean the snapshot directory since the pipeline builds the snapshot separately
|
||||
.PHONY: compare-linux
|
||||
compare-linux: compare-test-deb-package-install compare-test-rpm-package-install ## Run compare tests on build snapshot binaries and packages (Linux)
|
||||
|
||||
.PHONY: compare-test-deb-package-install
|
||||
compare-test-deb-package-install: $(RESULTSDIR) $(SNAPSHOTDIR)
|
||||
$(call title,Running compare test: DEB install)
|
||||
$(COMPARE_DIR)/deb.sh \
|
||||
$(SNAPSHOTDIR) \
|
||||
$(COMPARE_DIR) \
|
||||
$(COMPARE_TEST_IMAGE) \
|
||||
$(RESULTSDIR)
|
||||
|
||||
.PHONY: compare-test-rpm-package-install
|
||||
compare-test-rpm-package-install: $(RESULTSDIR) $(SNAPSHOTDIR)
|
||||
$(call title,Running compare test: RPM install)
|
||||
$(COMPARE_DIR)/rpm.sh \
|
||||
$(SNAPSHOTDIR) \
|
||||
$(COMPARE_DIR) \
|
||||
$(COMPARE_TEST_IMAGE) \
|
||||
$(RESULTSDIR)
|
||||
|
||||
# note: this is used by CI to determine if the integration test fixture cache (docker image tars) should be busted
|
||||
cli-fingerprint:
|
||||
$(call title,CLI test fixture fingerprint)
|
||||
find test/cli/test-fixtures/image-* -type f -exec md5sum {} + | awk '{print $1}' | sort | md5sum | tee test/cli/test-fixtures/cache.fingerprint && echo "$(CLI_CACHE_BUSTER)" >> test/cli/test-fixtures/cache.fingerprint
|
||||
|
||||
.PHONY: cli
|
||||
cli: $(SNAPSHOTDIR) ## Run CLI tests
|
||||
chmod 755 "$(SNAPSHOT_BIN)"
|
||||
$(SNAPSHOT_BIN) version
|
||||
SYFT_BINARY_LOCATION='$(SNAPSHOT_BIN)' \
|
||||
go test -count=1 -timeout=15m -v ./test/cli
|
||||
|
||||
.PHONY: changelog
|
||||
changelog: clean-changelog CHANGELOG.md
|
||||
@docker run -it --rm \
|
||||
-v $(shell pwd)/CHANGELOG.md:/CHANGELOG.md \
|
||||
rawkode/mdv \
|
||||
-t 748.5989 \
|
||||
/CHANGELOG.md
|
||||
|
||||
CHANGELOG.md:
|
||||
$(TEMPDIR)/chronicle -vv > CHANGELOG.md
|
||||
|
||||
.PHONY: release
|
||||
release: clean-dist CHANGELOG.md
|
||||
$(call title,Publishing release artifacts)
|
||||
|
||||
# create a config with the dist dir overridden
|
||||
echo "dist: $(DISTDIR)" > $(TEMPDIR)/goreleaser.yaml
|
||||
cat .goreleaser.yaml >> $(TEMPDIR)/goreleaser.yaml
|
||||
|
||||
# note: notarization cannot be done in parallel, thus --parallelism 1
|
||||
bash -c "\
|
||||
$(RELEASE_CMD) \
|
||||
--config $(TEMPDIR)/goreleaser.yaml \
|
||||
--parallelism 1 \
|
||||
--release-notes <(cat CHANGELOG.md) \
|
||||
|| (cat /tmp/quill-*.log && false)"
|
||||
|
||||
# TODO: turn this into a post-release hook
|
||||
# upload the version file that supports the application version update check (excluding pre-releases)
|
||||
.github/scripts/update-version-file.sh "$(DISTDIR)" "$(VERSION)"
|
||||
|
||||
.PHONY: clean
|
||||
clean: clean-dist clean-snapshot clean-test-image-cache ## Remove previous builds, result reports, and test cache
|
||||
$(call safe_rm_rf_children,$(RESULTSDIR))
|
||||
|
||||
.PHONY: clean-snapshot
|
||||
clean-snapshot:
|
||||
$(call safe_rm_rf,$(SNAPSHOTDIR))
|
||||
rm -f $(TEMPDIR)/goreleaser.yaml
|
||||
|
||||
.PHONY: clean-dist
|
||||
clean-dist: clean-changelog
|
||||
$(call safe_rm_rf,$(DISTDIR))
|
||||
rm -f $(TEMPDIR)/goreleaser.yaml
|
||||
|
||||
.PHONY: clean-changelog
|
||||
clean-changelog:
|
||||
rm -f CHANGELOG.md
|
||||
|
||||
clean-test-image-cache: clean-test-image-tar-cache clean-test-image-docker-cache ## Clean test image cache
|
||||
|
||||
.PHONY: clear-test-image-tar-cache
|
||||
clean-test-image-tar-cache:
|
||||
## Delete all test cache (built docker image tars)
|
||||
find . -type f -wholename "**/test-fixtures/cache/stereoscope-fixture-*.tar" -delete
|
||||
|
||||
.PHONY: clear-test-image-docker-cache
|
||||
clean-test-image-docker-cache:
|
||||
## Purge all test docker images
|
||||
docker images --format '{{.ID}} {{.Repository}}' | grep stereoscope-fixture- | awk '{print $$1}' | uniq | xargs -r docker rmi --force
|
||||
|
||||
.PHONY: show-test-image-cache
|
||||
show-test-image-cache: ## Show all docker and image tar cache
|
||||
show-test-image-cache: ## Show all docker and image tar cache
|
||||
$(call title,Docker daemon cache)
|
||||
@docker images --format '{{.ID}} {{.Repository}}:{{.Tag}}' | grep stereoscope-fixture- | sort
|
||||
|
||||
@@ -399,10 +223,150 @@ show-test-image-cache: ## Show all docker and image tar cache
|
||||
@find . -type f -wholename "**/test-fixtures/cache/stereoscope-fixture-*.tar" | sort
|
||||
|
||||
.PHONY: show-test-snapshots
|
||||
show-test-snapshots: ## Show all test snapshots
|
||||
show-test-snapshots: ## Show all test snapshots
|
||||
$(call title,Test snapshots)
|
||||
@find . -type f -wholename "**/test-fixtures/snapshot/*" | sort
|
||||
|
||||
|
||||
## install.sh testing targets #################################
|
||||
|
||||
install-test: $(SNAPSHOT_DIR)
|
||||
cd test/install && \
|
||||
make
|
||||
|
||||
install-test-cache-save: $(SNAPSHOT_DIR)
|
||||
cd test/install && \
|
||||
make save
|
||||
|
||||
install-test-cache-load: $(SNAPSHOT_DIR)
|
||||
cd test/install && \
|
||||
make load
|
||||
|
||||
install-test-ci-mac: $(SNAPSHOT_DIR)
|
||||
cd test/install && \
|
||||
make ci-test-mac
|
||||
|
||||
# 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)
|
||||
$(call title,Running compare test: Run on Mac)
|
||||
$(COMPARE_DIR)/mac.sh \
|
||||
$(SNAPSHOT_DIR) \
|
||||
$(COMPARE_DIR) \
|
||||
$(COMPARE_TEST_IMAGE) \
|
||||
$(TEMP_DIR)
|
||||
|
||||
# note: we cannot clean the snapshot directory since the pipeline builds the snapshot separately
|
||||
.PHONY: compare-linux
|
||||
compare-linux: compare-test-deb-package-install compare-test-rpm-package-install ## Run compare tests on build snapshot binaries and packages (Linux)
|
||||
|
||||
.PHONY: compare-test-deb-package-install
|
||||
compare-test-deb-package-install: $(TEMP_DIR) $(SNAPSHOT_DIR)
|
||||
$(call title,Running compare test: DEB install)
|
||||
$(COMPARE_DIR)/deb.sh \
|
||||
$(SNAPSHOT_DIR) \
|
||||
$(COMPARE_DIR) \
|
||||
$(COMPARE_TEST_IMAGE) \
|
||||
$(TEMP_DIR)
|
||||
|
||||
.PHONY: compare-test-rpm-package-install
|
||||
compare-test-rpm-package-install: $(TEMP_DIR) $(SNAPSHOT_DIR)
|
||||
$(call title,Running compare test: RPM install)
|
||||
$(COMPARE_DIR)/rpm.sh \
|
||||
$(SNAPSHOT_DIR) \
|
||||
$(COMPARE_DIR) \
|
||||
$(COMPARE_TEST_IMAGE) \
|
||||
$(TEMP_DIR)
|
||||
|
||||
|
||||
## Code generation targets #################################
|
||||
|
||||
.PHONY: generate-json-schema
|
||||
generate-json-schema: ## Generate a new json schema
|
||||
cd schema/json && go run generate.go
|
||||
|
||||
.PHONY: generate-license-list
|
||||
generate-license-list: ## Generate an updated spdx license list
|
||||
go generate ./internal/spdxlicense/...
|
||||
gofmt -s -w ./internal/spdxlicense
|
||||
|
||||
|
||||
## Build-related targets #################################
|
||||
|
||||
.PHONY: build
|
||||
build: $(SNAPSHOT_DIR) ## Build release snapshot binaries and packages
|
||||
|
||||
$(SNAPSHOT_DIR): ## Build snapshot release binaries and packages
|
||||
$(call title,Building snapshot artifacts)
|
||||
|
||||
# create a config with the dist dir overridden
|
||||
echo "dist: $(SNAPSHOT_DIR)" > $(TEMP_DIR)/goreleaser.yaml
|
||||
cat .goreleaser.yaml >> $(TEMP_DIR)/goreleaser.yaml
|
||||
|
||||
# build release snapshots
|
||||
$(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):
|
||||
$(TEMP_DIR)/chronicle -vv > $(CHANGELOG)
|
||||
|
||||
.PHONY: release
|
||||
release: 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
|
||||
cat .goreleaser.yaml >> $(TEMP_DIR)/goreleaser.yaml
|
||||
|
||||
bash -c "\
|
||||
$(RELEASE_CMD) \
|
||||
--config $(TEMP_DIR)/goreleaser.yaml \
|
||||
--release-notes <(cat $(CHANGELOG)) \
|
||||
|| (cat /tmp/quill-*.log && false)"
|
||||
|
||||
# upload the version file that supports the application version update check (excluding pre-releases)
|
||||
.github/scripts/update-version-file.sh "$(DIST_DIR)" "$(VERSION)"
|
||||
|
||||
## Cleanup targets #################################
|
||||
|
||||
.PHONY: clean
|
||||
clean: clean-dist clean-snapshot clean-test-image-cache ## Remove previous builds, result reports, and test cache
|
||||
$(call safe_rm_rf_children,$(TEMP_DIR))
|
||||
|
||||
.PHONY: clean-snapshot
|
||||
clean-snapshot:
|
||||
$(call safe_rm_rf,$(SNAPSHOT_DIR))
|
||||
rm -f $(TEMP_DIR)/goreleaser.yaml
|
||||
|
||||
.PHONY: clean-dist
|
||||
clean-dist: clean-changelog
|
||||
$(call safe_rm_rf,$(DIST_DIR))
|
||||
rm -f $(TEMP_DIR)/goreleaser.yaml
|
||||
|
||||
.PHONY: clean-changelog
|
||||
clean-changelog:
|
||||
rm -f $(CHANGELOG)
|
||||
|
||||
clean-test-image-cache: clean-test-image-tar-cache clean-test-image-docker-cache ## Clean test image cache
|
||||
|
||||
.PHONY: clear-test-image-tar-cache
|
||||
clean-test-image-tar-cache: ## Delete all test cache (built docker image tars)
|
||||
find . -type f -wholename "**/test-fixtures/cache/stereoscope-fixture-*.tar" -delete
|
||||
|
||||
.PHONY: clear-test-image-docker-cache
|
||||
clean-test-image-docker-cache: ## Purge all test docker images
|
||||
docker images --format '{{.ID}} {{.Repository}}' | grep stereoscope-fixture- | awk '{print $$1}' | uniq | xargs -r docker rmi --force
|
||||
|
||||
## Halp! #################################
|
||||
|
||||
.PHONY: help
|
||||
help: ## Display this help
|
||||
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "$(BOLD)$(CYAN)%-25s$(RESET)%s\n", $$1, $$2}'
|
||||
|
||||
@@ -38,9 +38,11 @@ For commercial support options with Syft or Grype, please [contact Anchore](http
|
||||
- Debian (dpkg)
|
||||
- Dotnet (deps.json)
|
||||
- Objective-C (cocoapods)
|
||||
- Elixir (mix)
|
||||
- Erlang (rebar3)
|
||||
- Go (go.mod, Go binaries)
|
||||
- Haskell (cabal, stack)
|
||||
- Java (jar, ear, war, par, sar)
|
||||
- Java (jar, ear, war, par, sar, native-image)
|
||||
- JavaScript (npm, yarn)
|
||||
- Jenkins Plugins (jpi, hpi)
|
||||
- PHP (composer)
|
||||
@@ -75,13 +77,12 @@ choco install syft -y
|
||||
|
||||
### Homebrew
|
||||
```bash
|
||||
brew tap anchore/syft
|
||||
brew install syft
|
||||
```
|
||||
|
||||
### Nix
|
||||
|
||||
**Note**: Nix packaging of Syft is [community maintained](https://github.com/NixOS/nixpkgs/blob/master/pkgs/tools/admin/syft/default.nix). Syft is currently only in the [unstable channel](https://nixos.wiki/wiki/Nix_channels#The_official_channels) awaiting the `22.05` release
|
||||
**Note**: Nix packaging of Syft is [community maintained](https://github.com/NixOS/nixpkgs/blob/master/pkgs/tools/admin/syft/default.nix). Syft is available in the [stable channel](https://nixos.wiki/wiki/Nix_channels#The_official_channels) since NixOS `22.05`.
|
||||
|
||||
```bash
|
||||
nix-env -i syft
|
||||
@@ -366,6 +367,8 @@ syft convert sbom.syft.json -o cyclonedx-json=sbom.cdx.json # convert it to Cyc
|
||||
### Keyless support
|
||||
Syft supports generating attestations using cosign's [keyless](https://github.com/sigstore/cosign/blob/main/KEYLESS.md) signatures.
|
||||
|
||||
Note: users need to have >= v1.12.0 of cosign installed for this command to function
|
||||
|
||||
To use this feature with a format like CycloneDX json simply run:
|
||||
```
|
||||
syft attest --output cyclonedx-json <IMAGE WITH OCI WRITE ACCESS>
|
||||
@@ -489,17 +492,6 @@ package:
|
||||
# same as -s ; SYFT_PACKAGE_CATALOGER_SCOPE env var
|
||||
scope: "squashed"
|
||||
|
||||
# cataloging file classifications is exposed through the power-user subcommand
|
||||
file-classification:
|
||||
cataloger:
|
||||
# enable/disable cataloging of file classifications
|
||||
# SYFT_FILE_CLASSIFICATION_CATALOGER_ENABLED env var
|
||||
enabled: true
|
||||
|
||||
# the search space to look for file classifications (options: all-layers, squashed)
|
||||
# SYFT_FILE_CLASSIFICATION_CATALOGER_SCOPE env var
|
||||
scope: "squashed"
|
||||
|
||||
# cataloging file contents is exposed through the power-user subcommand
|
||||
file-contents:
|
||||
cataloger:
|
||||
@@ -534,6 +526,9 @@ file-metadata:
|
||||
# SYFT_FILE_METADATA_DIGESTS env var
|
||||
digests: ["sha256"]
|
||||
|
||||
# maximum number of workers used to process the list of package catalogers in parallel
|
||||
parallelism: 1
|
||||
|
||||
# cataloging secrets is exposed through the power-user subcommand
|
||||
secrets:
|
||||
cataloger:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# See documentation here: https://github.com/artifacthub/hub/blob/v1.6.0/docs/metadata/artifacthub-repo.yml
|
||||
|
||||
repositoryID: eced152f-b15d-4879-8b3b-1175397192ba
|
||||
owners:
|
||||
- name: wagoodman
|
||||
email: wagoodman@gmail.com
|
||||
|
||||
+12
-32
@@ -4,7 +4,6 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
sigopts "github.com/sigstore/cosign/cmd/cosign/cli/options"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
@@ -15,33 +14,27 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
attestExample = ` {{.appName}} {{.command}} --output [FORMAT] --key [KEY] alpine:latest
|
||||
Supports the following image sources:
|
||||
{{.appName}} {{.command}} --key [KEY] yourrepo/yourimage:tag defaults to using images from a Docker daemon. If Docker is not present, the image is pulled directly from the registry.
|
||||
{{.appName}} {{.command}} --key [KEY] path/to/a/file/or/dir only for OCI tar or OCI directory
|
||||
attestExample = ` {{.appName}} {{.command}} --output [FORMAT] alpine:latest defaults to using images from a Docker daemon. If Docker is not present, the image is pulled directly from the registry
|
||||
`
|
||||
attestSchemeHelp = "\n" + indent + schemeHelpHeader + "\n" + imageSchemeHelp
|
||||
|
||||
attestHelp = attestExample + attestSchemeHelp
|
||||
attestHelp = attestExample + attestSchemeHelp
|
||||
)
|
||||
|
||||
func Attest(v *viper.Viper, app *config.Application, ro *options.RootOptions) *cobra.Command {
|
||||
ao := options.AttestOptions{}
|
||||
//nolint:dupl
|
||||
func Attest(v *viper.Viper, app *config.Application, ro *options.RootOptions, po *options.PackagesOptions) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "attest --output [FORMAT] --key [KEY] [SOURCE]",
|
||||
Short: "Generate a package SBOM as an attestation for the given [SOURCE] container image",
|
||||
Long: "Generate a packaged-based Software Bill Of Materials (SBOM) from a container image as the predicate of an in-toto attestation",
|
||||
Use: "attest --output [FORMAT] <IMAGE>",
|
||||
Short: "Generate an SBOM as an attestation for the given [SOURCE] container image",
|
||||
Long: "Generate a packaged-based Software Bill Of Materials (SBOM) from a container image as the predicate of an in-toto attestation that will be uploaded to the image registry",
|
||||
Example: internal.Tprintf(attestHelp, map[string]interface{}{
|
||||
"appName": internal.ApplicationName,
|
||||
"command": "attest",
|
||||
}),
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
// run to unmarshal viper object onto app config
|
||||
// the viper object correctly
|
||||
if err := app.LoadAllValues(v, ro.Config); err != nil {
|
||||
return fmt.Errorf("invalid application config: %w", err)
|
||||
return fmt.Errorf("unable to load configuration: %w", err)
|
||||
}
|
||||
// configure logging for command
|
||||
|
||||
newLogWrapper(app)
|
||||
logApplicationConfig(app)
|
||||
return validateArgs(cmd, args)
|
||||
@@ -49,29 +42,16 @@ func Attest(v *viper.Viper, app *config.Application, ro *options.RootOptions) *c
|
||||
SilenceUsage: true,
|
||||
SilenceErrors: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
// this MUST be called first to make sure app config decodes
|
||||
// the viper object correctly
|
||||
if app.CheckForAppUpdate {
|
||||
checkForApplicationUpdate()
|
||||
}
|
||||
|
||||
// build cosign key options for attestation
|
||||
ko := sigopts.KeyOpts{
|
||||
KeyRef: app.Attest.KeyRef,
|
||||
FulcioURL: app.Attest.FulcioURL,
|
||||
IDToken: app.Attest.FulcioIdentityToken,
|
||||
InsecureSkipFulcioVerify: app.Attest.InsecureSkipFulcioVerify,
|
||||
RekorURL: app.Attest.RekorURL,
|
||||
OIDCIssuer: app.Attest.OIDCIssuer,
|
||||
OIDCClientID: app.Attest.OIDCClientID,
|
||||
OIDCRedirectURL: app.Attest.OIDCRedirectURL,
|
||||
}
|
||||
|
||||
return attest.Run(cmd.Context(), app, ko, args)
|
||||
return attest.Run(cmd.Context(), app, args)
|
||||
},
|
||||
}
|
||||
|
||||
err := ao.AddFlags(cmd, v)
|
||||
// syft attest is an enhancment of the packages command, so it should have the same flags
|
||||
err := po.AddFlags(cmd, v)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
+134
-305
@@ -1,105 +1,60 @@
|
||||
package attest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/in-toto/in-toto-golang/in_toto"
|
||||
sigopts "github.com/sigstore/cosign/cmd/cosign/cli/options"
|
||||
"github.com/sigstore/cosign/cmd/cosign/cli/rekor"
|
||||
"github.com/sigstore/cosign/cmd/cosign/cli/sign"
|
||||
"github.com/sigstore/cosign/pkg/cosign"
|
||||
"github.com/sigstore/cosign/pkg/cosign/attestation"
|
||||
cbundle "github.com/sigstore/cosign/pkg/cosign/bundle"
|
||||
"github.com/sigstore/cosign/pkg/oci/mutate"
|
||||
ociremote "github.com/sigstore/cosign/pkg/oci/remote"
|
||||
"github.com/sigstore/cosign/pkg/oci/static"
|
||||
sigs "github.com/sigstore/cosign/pkg/signature"
|
||||
"github.com/sigstore/cosign/pkg/types"
|
||||
"github.com/sigstore/rekor/pkg/generated/client"
|
||||
"github.com/sigstore/rekor/pkg/generated/models"
|
||||
"github.com/sigstore/sigstore/pkg/signature/dsse"
|
||||
signatureoptions "github.com/sigstore/sigstore/pkg/signature/options"
|
||||
"github.com/wagoodman/go-partybus"
|
||||
"github.com/wagoodman/go-progress"
|
||||
"golang.org/x/exp/slices"
|
||||
|
||||
"github.com/anchore/stereoscope"
|
||||
"github.com/anchore/stereoscope/pkg/image"
|
||||
"github.com/anchore/syft/cmd/syft/cli/eventloop"
|
||||
"github.com/anchore/syft/cmd/syft/cli/options"
|
||||
"github.com/anchore/syft/cmd/syft/cli/packages"
|
||||
"github.com/anchore/syft/internal/bus"
|
||||
"github.com/anchore/syft/internal/config"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/ui"
|
||||
"github.com/anchore/syft/syft"
|
||||
"github.com/anchore/syft/syft/event"
|
||||
"github.com/anchore/syft/syft/formats/cyclonedxjson"
|
||||
"github.com/anchore/syft/syft/formats/spdxjson"
|
||||
"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"
|
||||
)
|
||||
|
||||
var (
|
||||
allowedAttestFormats = []sbom.FormatID{
|
||||
syftjson.ID,
|
||||
spdxjson.ID,
|
||||
cyclonedxjson.ID,
|
||||
}
|
||||
|
||||
intotoJSONDsseType = `application/vnd.in-toto+json`
|
||||
)
|
||||
|
||||
func Run(ctx context.Context, app *config.Application, ko sigopts.KeyOpts, args []string) error {
|
||||
// We cannot generate an attestation for more than one output
|
||||
if len(app.Outputs) > 1 {
|
||||
return fmt.Errorf("unable to generate attestation for more than one output")
|
||||
}
|
||||
|
||||
// can only be an image for attestation or OCI DIR
|
||||
userInput := args[0]
|
||||
si, err := parseImageSource(userInput, app)
|
||||
func Run(ctx context.Context, app *config.Application, args []string) error {
|
||||
err := ValidateOutputOptions(app)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
output := parseAttestationOutput(app.Outputs)
|
||||
|
||||
format := syft.FormatByName(output)
|
||||
|
||||
// user typo or unknown outputs provided
|
||||
if format == nil {
|
||||
format = syft.FormatByID(syftjson.ID) // default attestation format
|
||||
}
|
||||
predicateType := formatPredicateType(format)
|
||||
if predicateType == "" {
|
||||
return fmt.Errorf(
|
||||
"could not produce attestation predicate for given format: %q. Available formats: %+v",
|
||||
options.FormatAliases(format.ID()),
|
||||
options.FormatAliases(allowedAttestFormats...),
|
||||
)
|
||||
writer, err := options.MakeWriter(app.Outputs, app.File, app.OutputTemplatePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to write to report destination: %w", err)
|
||||
}
|
||||
|
||||
if app.Attest.KeyRef != "" {
|
||||
passFunc, err := selectPassFunc(app.Attest.KeyRef, app.Attest.Password)
|
||||
if err != nil {
|
||||
return err
|
||||
defer func() {
|
||||
if err := writer.Close(); err != nil {
|
||||
fmt.Printf("unable to close report destination: %+v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
ko.PassFunc = passFunc
|
||||
}
|
||||
|
||||
sv, err := sign.SignerFromKeyOpts(ctx, "", "", ko)
|
||||
// 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)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("could not generate source input for packages command: %w", err)
|
||||
}
|
||||
|
||||
if si.Scheme != source.ImageScheme {
|
||||
return fmt.Errorf("attestations are only supported for oci images at this time")
|
||||
}
|
||||
defer sv.Close()
|
||||
|
||||
eventBus := partybus.NewBus()
|
||||
stereoscope.SetBus(eventBus)
|
||||
@@ -107,7 +62,7 @@ func Run(ctx context.Context, app *config.Application, ko sigopts.KeyOpts, args
|
||||
subscription := eventBus.Subscribe()
|
||||
|
||||
return eventloop.EventLoop(
|
||||
execWorker(app, *si, format, predicateType, sv),
|
||||
execWorker(app, *si, writer),
|
||||
eventloop.SetupSignals(),
|
||||
subscription,
|
||||
stereoscope.Cleanup,
|
||||
@@ -115,284 +70,158 @@ func Run(ctx context.Context, app *config.Application, ko sigopts.KeyOpts, args
|
||||
)
|
||||
}
|
||||
|
||||
func parseAttestationOutput(outputs []string) (format string) {
|
||||
if len(outputs) == 0 {
|
||||
outputs = append(outputs, string(syftjson.ID))
|
||||
func buildSBOM(app *config.Application, si source.Input, writer sbom.Writer, errs chan error) ([]byte, error) {
|
||||
src, cleanup, err := source.New(si, app.Registry.ToOptions(), app.Exclusions)
|
||||
if cleanup != nil {
|
||||
defer cleanup()
|
||||
}
|
||||
|
||||
return outputs[0]
|
||||
}
|
||||
|
||||
func parseImageSource(userInput string, app *config.Application) (s *source.Input, err error) {
|
||||
si, err := source.ParseInputWithName(userInput, app.Platform, false, app.Name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not generate source input for attest command: %w", err)
|
||||
return nil, fmt.Errorf("failed to construct source from user input %q: %w", si.UserInput, err)
|
||||
}
|
||||
|
||||
switch si.Scheme {
|
||||
case source.ImageScheme, source.UnknownScheme:
|
||||
// at this point we know that it cannot be dir: or file: schemes;
|
||||
// we will assume that the unknown scheme could represent an image;
|
||||
si.Scheme = source.ImageScheme
|
||||
default:
|
||||
return nil, fmt.Errorf("attest command can only be used with image sources but discovered %q when given %q", si.Scheme, userInput)
|
||||
s, err := packages.GenerateSBOM(src, errs, app)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// if the original detection was from the local daemon we want to short circuit
|
||||
// that and attempt to generate the image source from its current registry source instead
|
||||
switch si.ImageSource {
|
||||
case image.UnknownSource, image.OciRegistrySource:
|
||||
si.ImageSource = image.OciRegistrySource
|
||||
case image.SingularitySource:
|
||||
default:
|
||||
return nil, fmt.Errorf("attest command can only be used with image sources fetch directly from the registry, but discovered an image source of %q when given %q", si.ImageSource, userInput)
|
||||
if s == nil {
|
||||
return nil, fmt.Errorf("no SBOM produced for %q", si.UserInput)
|
||||
}
|
||||
|
||||
return si, nil
|
||||
// note: only works for single format no multi writer support
|
||||
sBytes, err := writer.Bytes(*s)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to build SBOM bytes: %w", err)
|
||||
}
|
||||
|
||||
return sBytes, nil
|
||||
}
|
||||
|
||||
func execWorker(app *config.Application, sourceInput source.Input, format sbom.Format, predicateType string, sv *sign.SignerVerifier) <-chan error {
|
||||
func execWorker(app *config.Application, si source.Input, writer sbom.Writer) <-chan error {
|
||||
errs := make(chan error)
|
||||
go func() {
|
||||
defer close(errs)
|
||||
|
||||
src, cleanup, err := source.NewFromRegistry(sourceInput, app.Registry.ToOptions(), app.Exclusions)
|
||||
if cleanup != nil {
|
||||
defer cleanup()
|
||||
}
|
||||
sBytes, err := buildSBOM(app, si, writer, errs)
|
||||
if err != nil {
|
||||
errs <- fmt.Errorf("failed to construct source from user input %q: %w", sourceInput.UserInput, err)
|
||||
errs <- fmt.Errorf("unable to build SBOM: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
s, err := packages.GenerateSBOM(src, errs, app)
|
||||
if err != nil {
|
||||
errs <- err
|
||||
return
|
||||
}
|
||||
// TODO: add multi writer support
|
||||
for _, o := range app.Outputs {
|
||||
f, err := os.CreateTemp("", o)
|
||||
if err != nil {
|
||||
errs <- fmt.Errorf("unable to create temp file: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
sbomBytes, err := syft.Encode(*s, format)
|
||||
if err != nil {
|
||||
errs <- err
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
defer os.Remove(f.Name())
|
||||
|
||||
signedPayload, err := generateAttestation(sourceInput, sbomBytes, src, sv, predicateType)
|
||||
if err != nil {
|
||||
errs <- err
|
||||
return
|
||||
}
|
||||
if _, err := f.Write(sBytes); err != nil {
|
||||
errs <- fmt.Errorf("unable to write SBOM to temp file: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = publishAttestation(app, signedPayload, src, sv)
|
||||
if err != nil {
|
||||
errs <- err
|
||||
return
|
||||
// TODO: what other validation here besides binary name?
|
||||
cmd := "cosign"
|
||||
if !commandExists(cmd) {
|
||||
errs <- fmt.Errorf("unable to find cosign in PATH; make sure you have it installed")
|
||||
return
|
||||
}
|
||||
|
||||
args := []string{"attest", si.UserInput, "--type", "custom", "--predicate", f.Name()}
|
||||
execCmd := exec.Command(cmd, args...)
|
||||
execCmd.Env = os.Environ()
|
||||
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()
|
||||
if err != nil {
|
||||
errs <- fmt.Errorf("unable to create os pipe: %w", err)
|
||||
return
|
||||
}
|
||||
defer w.Close()
|
||||
|
||||
b := &busWriter{r: r, w: w, mon: &progress.Manual{N: -1}}
|
||||
execCmd.Stdout = b
|
||||
execCmd.Stderr = b
|
||||
defer b.mon.SetCompleted()
|
||||
|
||||
// attest the SBOM
|
||||
err = execCmd.Run()
|
||||
if err != nil {
|
||||
b.mon.Err = err
|
||||
errs <- fmt.Errorf("unable to attest SBOM: %w", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
bus.Publish(partybus.Event{
|
||||
Type: event.Exit,
|
||||
Value: func() error {
|
||||
return nil
|
||||
},
|
||||
Type: event.Exit,
|
||||
Value: func() error { return nil },
|
||||
})
|
||||
}()
|
||||
return errs
|
||||
}
|
||||
|
||||
func generateAttestation(si source.Input, predicate []byte, src *source.Source, sv *sign.SignerVerifier, predicateType string) ([]byte, error) {
|
||||
var h v1.Hash
|
||||
|
||||
switch si.ImageSource {
|
||||
case image.OciRegistrySource:
|
||||
switch len(src.Image.Metadata.RepoDigests) {
|
||||
case 0:
|
||||
return nil, fmt.Errorf("cannot generate attestation since no repo digests were found; make sure you're passing an OCI registry source for the attest command")
|
||||
case 1:
|
||||
d, err := name.NewDigest(src.Image.Metadata.RepoDigests[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
h, err = v1.NewHash(d.Identifier())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("cannot generate attestation since multiple repo digests were found for the image: %+v", src.Image.Metadata.RepoDigests)
|
||||
}
|
||||
|
||||
case image.SingularitySource:
|
||||
var err error
|
||||
h, err = v1.NewHash(src.Image.Metadata.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func ValidateOutputOptions(app *config.Application) error {
|
||||
var usesTemplateOutput bool
|
||||
for _, o := range app.Outputs {
|
||||
if o == template.ID.String() {
|
||||
usesTemplateOutput = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
sh, err := attestation.GenerateStatement(attestation.GenerateOpts{
|
||||
Predicate: bytes.NewBuffer(predicate),
|
||||
Type: predicateType,
|
||||
Digest: h.Hex,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if usesTemplateOutput && app.OutputTemplatePath == "" {
|
||||
return fmt.Errorf(`must specify path to template file when using "template" output format`)
|
||||
}
|
||||
|
||||
payload, err := json.Marshal(sh)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if len(app.Outputs) > 1 {
|
||||
return fmt.Errorf("multiple SBOM format is not supported for attest at this time")
|
||||
}
|
||||
|
||||
wrapped := dsse.WrapSigner(sv, intotoJSONDsseType)
|
||||
return wrapped.SignMessage(bytes.NewReader(payload), signatureoptions.WithContext(context.Background()))
|
||||
}
|
||||
|
||||
// publishAttestation publishes signedPayload to the location specified by the user.
|
||||
func publishAttestation(app *config.Application, signedPayload []byte, src *source.Source, sv *sign.SignerVerifier) error {
|
||||
switch {
|
||||
// We want to give the option to not upload the generated attestation
|
||||
// if passed or if the user is using local PKI
|
||||
case app.Attest.NoUpload || app.Attest.KeyRef != "":
|
||||
if app.File != "" {
|
||||
return os.WriteFile(app.File, signedPayload, 0600)
|
||||
}
|
||||
|
||||
_, err := os.Stdout.Write(signedPayload)
|
||||
return err
|
||||
|
||||
default:
|
||||
ref, err := name.ParseReference(src.Metadata.ImageMetadata.UserInput)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
digest, err := ociremote.ResolveDigest(ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return uploadAttestation(app, signedPayload, digest, sv)
|
||||
// cannot use table as default output format when using template output
|
||||
if slices.Contains(app.Outputs, table.ID.String()) {
|
||||
app.Outputs = []string{syftjson.ID.String()}
|
||||
}
|
||||
}
|
||||
|
||||
func trackUploadAttestation() (*progress.Stage, *progress.Manual) {
|
||||
stage := &progress.Stage{}
|
||||
prog := &progress.Manual{}
|
||||
|
||||
bus.Publish(partybus.Event{
|
||||
Type: event.UploadAttestation,
|
||||
Value: progress.StagedProgressable(&struct {
|
||||
progress.Stager
|
||||
progress.Progressable
|
||||
}{
|
||||
Stager: stage,
|
||||
Progressable: prog,
|
||||
}),
|
||||
})
|
||||
|
||||
return stage, prog
|
||||
}
|
||||
|
||||
// uploads signed SBOM payload to Rekor transparency log along with key information;
|
||||
// returns a bundle for attestation annotations
|
||||
// rekor bundle includes a signed payload and rekor timestamp;
|
||||
// the bundle is then wrapped onto an OCI signed entity and uploaded to
|
||||
// the user's image's OCI registry repository as *.att
|
||||
func uploadAttestation(app *config.Application, signedPayload []byte, digest name.Digest, sv *sign.SignerVerifier) error {
|
||||
// add application/vnd.dsse.envelope.v1+json as media type for other applications to decode attestation
|
||||
opts := []static.Option{static.WithLayerMediaType(types.DssePayloadType)}
|
||||
if sv.Cert != nil {
|
||||
opts = append(opts, static.WithCertChain(sv.Cert, sv.Chain))
|
||||
}
|
||||
|
||||
stage, prog := trackUploadAttestation()
|
||||
defer prog.SetCompleted() // just in case we return early
|
||||
|
||||
prog.Total = 2
|
||||
stage.Current = "uploading signing information to transparency log"
|
||||
|
||||
// uploads payload to Rekor transparency log along with key information;
|
||||
// returns bundle for attesation annotations
|
||||
// rekor bundle includes a signed payload and rekor timestamp;
|
||||
// the bundle is then wrapped onto an OCI signed entity and uploaded to
|
||||
// the user's image's OCI registry repository as *.att
|
||||
bundle, err := uploadToTlog(context.TODO(), sv, app.Attest.RekorURL, func(r *client.Rekor, b []byte) (*models.LogEntryAnon, error) {
|
||||
return cosign.TLogUploadInTotoAttestation(context.TODO(), r, signedPayload, b)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
prog.N = 1
|
||||
stage.Current = "uploading attestation to OCI registry"
|
||||
|
||||
// add bundle OCI attestation that is uploaded to
|
||||
opts = append(opts, static.WithBundle(bundle))
|
||||
sig, err := static.NewAttestation(signedPayload, opts...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
se, err := ociremote.SignedEntity(digest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newSE, err := mutate.AttachAttestationToEntity(se, sig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Publish the attestations associated with this entity
|
||||
err = ociremote.WriteAttestations(digest.Repository, newSE)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
prog.SetCompleted()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func formatPredicateType(format sbom.Format) string {
|
||||
switch format.ID() {
|
||||
case spdxjson.ID:
|
||||
return in_toto.PredicateSPDX
|
||||
case cyclonedxjson.ID:
|
||||
return in_toto.PredicateCycloneDX
|
||||
case syftjson.ID:
|
||||
return "https://syft.dev/bom"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
type busWriter struct {
|
||||
w *os.File
|
||||
r *os.File
|
||||
hasWritten bool
|
||||
mon *progress.Manual
|
||||
}
|
||||
|
||||
type tlogUploadFn func(*client.Rekor, []byte) (*models.LogEntryAnon, error)
|
||||
|
||||
func uploadToTlog(ctx context.Context, sv *sign.SignerVerifier, rekorURL string, upload tlogUploadFn) (*cbundle.RekorBundle, error) {
|
||||
var rekorBytes []byte
|
||||
// Upload the cert or the public key, depending on what we have
|
||||
if sv.Cert != nil {
|
||||
rekorBytes = sv.Cert
|
||||
} else {
|
||||
pemBytes, err := sigs.PublicKeyPem(sv, signatureoptions.WithContext(ctx))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rekorBytes = pemBytes
|
||||
func (b *busWriter) Write(p []byte) (n int, err error) {
|
||||
if !b.hasWritten {
|
||||
b.hasWritten = true
|
||||
bus.Publish(
|
||||
partybus.Event{
|
||||
Type: event.AttestationStarted,
|
||||
Source: monitor.GenericTask{
|
||||
Title: monitor.Title{
|
||||
Default: "Create attestation",
|
||||
WhileRunning: "Creating attestation",
|
||||
OnSuccess: "Created attestation",
|
||||
},
|
||||
Context: "cosign",
|
||||
},
|
||||
Value: &monitor.ShellProgress{
|
||||
Reader: b.r,
|
||||
Manual: b.mon,
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
return b.w.Write(p)
|
||||
}
|
||||
|
||||
rekorClient, err := rekor.NewClient(rekorURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
entry, err := upload(rekorClient, rekorBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if entry.LogIndex != nil {
|
||||
log.Debugf("transparency log entry created with index: %v", *entry.LogIndex)
|
||||
}
|
||||
return cbundle.EntryToBundle(entry), nil
|
||||
func commandExists(cmd string) bool {
|
||||
_, err := exec.LookPath(cmd)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
package attest
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/sigstore/cosign/pkg/cosign"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
)
|
||||
|
||||
func selectPassFunc(keypath, password string) (cosign.PassFunc, error) {
|
||||
keyContents, err := os.ReadFile(keypath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var fn cosign.PassFunc = func(bool) (b []byte, err error) { return nil, nil }
|
||||
|
||||
_, err = cosign.LoadPrivateKey(keyContents, nil)
|
||||
if err != nil {
|
||||
fn = func(bool) (b []byte, err error) {
|
||||
return fetchPassword(password)
|
||||
}
|
||||
}
|
||||
|
||||
return fn, nil
|
||||
}
|
||||
|
||||
func fetchPassword(password string) (b []byte, err error) {
|
||||
potentiallyPipedInput, err := internal.IsPipedInput()
|
||||
if err != nil {
|
||||
log.Warnf("unable to determine if there is piped input: %+v", err)
|
||||
}
|
||||
|
||||
switch {
|
||||
case password != "":
|
||||
return []byte(password), nil
|
||||
case potentiallyPipedInput:
|
||||
// handle piped in passwords
|
||||
pwBytes, err := io.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get password from stdin: %w", err)
|
||||
}
|
||||
// be resilient to input that may have newline characters (in case someone is using echo without -n)
|
||||
cleanPw := strings.TrimRight(string(pwBytes), "\n")
|
||||
return []byte(cleanPw), nil
|
||||
case internal.IsTerminal():
|
||||
return cosign.GetPassFromTerm(false)
|
||||
}
|
||||
|
||||
return nil, errors.New("no method available to fetch password")
|
||||
}
|
||||
@@ -26,8 +26,10 @@ import (
|
||||
const indent = " "
|
||||
|
||||
// New constructs the `syft packages` command, aliases the root command to `syft packages`,
|
||||
// and constructs the `syft power-user` and `syft attest` commands. It is also responsible for
|
||||
// and constructs the `syft power-user` command. It is also responsible for
|
||||
// organizing flag usage and injecting the application config for each command.
|
||||
// It also constructs the syft attest command and the syft version command.
|
||||
|
||||
// Because of how the `cobra` library behaves, the application's configuration is initialized
|
||||
// at this level. Values from the config should only be used after `app.LoadAllValues` has been called.
|
||||
// Cobra does not have knowledge of the user provided flags until the `RunE` block of each command.
|
||||
@@ -46,9 +48,9 @@ func New() (*cobra.Command, error) {
|
||||
packagesCmd := Packages(v, app, ro, po)
|
||||
|
||||
// root options are also passed to the attestCmd so that a user provided config location can be discovered
|
||||
attestCmd := Attest(v, app, ro)
|
||||
poweruserCmd := PowerUser(v, app, ro)
|
||||
convertCmd := Convert(v, app, ro, po)
|
||||
attestCmd := Attest(v, app, ro, po)
|
||||
|
||||
// rootCmd is currently an alias for the packages command
|
||||
rootCmd := &cobra.Command{
|
||||
@@ -73,11 +75,7 @@ func New() (*cobra.Command, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// attest also uses flags from the packagesCmd since it generates an sbom
|
||||
err = po.AddFlags(attestCmd, v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// poweruser also uses the packagesCmd flags since it is a specialized version of the command
|
||||
err = po.AddFlags(poweruserCmd, v)
|
||||
if err != nil {
|
||||
@@ -87,13 +85,11 @@ func New() (*cobra.Command, error) {
|
||||
// commands to add to root
|
||||
cmds := []*cobra.Command{
|
||||
packagesCmd,
|
||||
attestCmd,
|
||||
poweruserCmd,
|
||||
convertCmd,
|
||||
poweruserCmd,
|
||||
poweruserCmd,
|
||||
Completion(),
|
||||
attestCmd,
|
||||
Version(v, app),
|
||||
cranecmd.NewCmdAuthLogin("syft"),
|
||||
cranecmd.NewCmdAuthLogin("syft"), // syft login uses the same command as crane
|
||||
}
|
||||
|
||||
// Add sub-commands.
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func Completion() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "completion [bash|zsh|fish]",
|
||||
Short: "Generate a shell completion for Syft (listing local docker images)",
|
||||
Long: `To load completions (docker image list):
|
||||
Bash:
|
||||
$ source <(syft completion bash)
|
||||
# To load completions for each session, execute once:
|
||||
Linux:
|
||||
$ syft completion bash > /etc/bash_completion.d/syft
|
||||
MacOS:
|
||||
$ syft completion bash > /usr/local/etc/bash_completion.d/syft
|
||||
Zsh:
|
||||
# If shell completion is not already enabled in your environment you will need
|
||||
# to enable it. You can execute the following once:
|
||||
$ echo "autoload -U compinit; compinit" >> ~/.zshrc
|
||||
# To load completions for each session, execute once:
|
||||
$ syft completion zsh > "${fpath[1]}/_syft"
|
||||
# You will need to start a new shell for this setup to take effect.
|
||||
Fish:
|
||||
$ syft completion fish | source
|
||||
# To load completions for each session, execute once:
|
||||
$ syft completion fish > ~/.config/fish/completions/syft.fish
|
||||
`,
|
||||
DisableFlagsInUseLine: true,
|
||||
ValidArgs: []string{"bash", "zsh", "fish"},
|
||||
Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
var err error
|
||||
switch args[0] {
|
||||
case "bash":
|
||||
err = cmd.Root().GenBashCompletion(os.Stdout)
|
||||
case "zsh":
|
||||
err = cmd.Root().GenZshCompletion(os.Stdout)
|
||||
case "fish":
|
||||
err = cmd.Root().GenFishCompletion(os.Stdout, true)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -22,7 +22,6 @@ func Tasks(app *config.Application) ([]Task, error) {
|
||||
generateCatalogFileMetadataTask,
|
||||
generateCatalogFileDigestsTask,
|
||||
generateCatalogSecretsTask,
|
||||
generateCatalogFileClassificationsTask,
|
||||
generateCatalogContentsTask,
|
||||
}
|
||||
|
||||
@@ -162,34 +161,6 @@ func generateCatalogSecretsTask(app *config.Application) (Task, error) {
|
||||
return task, nil
|
||||
}
|
||||
|
||||
func generateCatalogFileClassificationsTask(app *config.Application) (Task, error) {
|
||||
if !app.FileClassification.Cataloger.Enabled {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// TODO: in the future we could expose out the classifiers via configuration
|
||||
classifierCataloger, err := file.NewClassificationCataloger(file.DefaultClassifiers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
task := func(results *sbom.Artifacts, src *source.Source) ([]artifact.Relationship, error) {
|
||||
resolver, err := src.FileResolver(app.FileClassification.Cataloger.ScopeOpt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result, err := classifierCataloger.Catalog(resolver)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results.FileClassifications = result
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return task, nil
|
||||
}
|
||||
|
||||
func generateCatalogContentsTask(app *config.Application) (Task, error) {
|
||||
if !app.FileContents.Cataloger.Enabled {
|
||||
return nil, nil
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
package options
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const defaultKeyFileName = "cosign.key"
|
||||
|
||||
type AttestOptions struct {
|
||||
Key string
|
||||
Cert string
|
||||
CertChain string
|
||||
NoUpload bool
|
||||
Force bool
|
||||
Recursive bool
|
||||
|
||||
Rekor RekorOptions
|
||||
Fulcio FulcioOptions
|
||||
OIDC OIDCOptions
|
||||
}
|
||||
|
||||
var _ Interface = (*AttestOptions)(nil)
|
||||
|
||||
func (o *AttestOptions) AddFlags(cmd *cobra.Command, v *viper.Viper) error {
|
||||
if err := o.Rekor.AddFlags(cmd, v); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := o.Fulcio.AddFlags(cmd, v); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := o.OIDC.AddFlags(cmd, v); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&o.Key, "key", "", defaultKeyFileName,
|
||||
"path to the private key file to use for attestation")
|
||||
|
||||
cmd.Flags().StringVarP(&o.Cert, "cert", "", "",
|
||||
"path to the x.509 certificate in PEM format to include in the OCI Signature")
|
||||
|
||||
cmd.Flags().BoolVarP(&o.NoUpload, "no-upload", "", false,
|
||||
"do not upload the generated attestation")
|
||||
|
||||
cmd.Flags().BoolVarP(&o.Force, "force", "", false,
|
||||
"skip warnings and confirmations")
|
||||
|
||||
cmd.Flags().BoolVarP(&o.Recursive, "recursive", "", false,
|
||||
"if a multi-arch image is specified, additionally sign each discrete image")
|
||||
|
||||
return bindAttestationConfigOptions(cmd.Flags(), v)
|
||||
}
|
||||
|
||||
func bindAttestationConfigOptions(flags *pflag.FlagSet, v *viper.Viper) error {
|
||||
if err := v.BindPFlag("attest.key", flags.Lookup("key")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := v.BindPFlag("attest.cert", flags.Lookup("cert")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := v.BindPFlag("attest.no-upload", flags.Lookup("no-upload")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := v.BindPFlag("attest.force", flags.Lookup("force")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := v.BindPFlag("attest.recursive", flags.Lookup("recursive")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -9,7 +9,7 @@ type VersionOptions struct {
|
||||
Output string
|
||||
}
|
||||
|
||||
var _ Interface = (*PackagesOptions)(nil)
|
||||
var _ Interface = (*VersionOptions)(nil)
|
||||
|
||||
func (o *VersionOptions) AddFlags(cmd *cobra.Command, v *viper.Viper) error {
|
||||
cmd.Flags().StringVarP(&o.Output, "output", "o", "text", "format to show version information (available=[text, json])")
|
||||
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
)
|
||||
|
||||
func Run(ctx context.Context, app *config.Application, args []string) error {
|
||||
err := validateOutputOptions(app)
|
||||
err := ValidateOutputOptions(app)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -134,7 +134,7 @@ func MergeRelationships(cs ...<-chan artifact.Relationship) (relationships []art
|
||||
return relationships
|
||||
}
|
||||
|
||||
func validateOutputOptions(app *config.Application) error {
|
||||
func ValidateOutputOptions(app *config.Application) error {
|
||||
var usesTemplateOutput bool
|
||||
for _, o := range app.Outputs {
|
||||
if o == template.ID.String() {
|
||||
|
||||
@@ -3,14 +3,13 @@ module github.com/anchore/syft
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/CycloneDX/cyclonedx-go v0.5.2
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
|
||||
github.com/acobaugh/osrelease v0.1.0
|
||||
github.com/adrg/xdg v0.3.3
|
||||
github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb
|
||||
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04
|
||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20220428202044-a072fa3cb6d7
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501
|
||||
github.com/bmatcuk/doublestar/v4 v4.0.2
|
||||
github.com/dustin/go-humanize v1.0.0
|
||||
github.com/facebookincubator/nvdtools v0.1.4
|
||||
@@ -37,281 +36,103 @@ require (
|
||||
github.com/spf13/cobra v1.6.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/spf13/viper v1.13.0
|
||||
github.com/stretchr/testify v1.8.0
|
||||
github.com/stretchr/testify v1.8.1
|
||||
github.com/vifraa/gopom v0.1.0
|
||||
github.com/wagoodman/go-partybus v0.0.0-20210627031916-db1f5573bbc5
|
||||
github.com/wagoodman/go-progress v0.0.0-20200731105512-1020f39e6240
|
||||
github.com/wagoodman/jotframe v0.0.0-20211129225309-56b0d0a4aebb
|
||||
github.com/xeipuuv/gojsonschema v1.2.0
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4
|
||||
golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458
|
||||
golang.org/x/term v0.0.0-20220919170432-7a66f970e087
|
||||
golang.org/x/net v0.4.0
|
||||
golang.org/x/term v0.3.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/anchore/go-logger v0.0.0-20220728155337-03b66a5207d8
|
||||
github.com/anchore/stereoscope v0.0.0-20221006201143-d24c9d626b33
|
||||
github.com/anchore/stereoscope v0.0.0-20221208011002-c5ff155d72f1
|
||||
github.com/docker/docker v20.10.17+incompatible
|
||||
github.com/google/go-containerregistry v0.11.0
|
||||
github.com/in-toto/in-toto-golang v0.4.1-0.20221018183522-731d0640b65f
|
||||
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/sigstore/cosign v1.13.1
|
||||
github.com/sigstore/rekor v0.12.1-0.20220915152154-4bb6f441c1b2
|
||||
github.com/sigstore/sigstore v1.4.4
|
||||
github.com/vbatts/go-mtree v0.5.0
|
||||
golang.org/x/exp v0.0.0-20220823124025-807a23277127
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
bitbucket.org/creachadair/shell v0.0.7 // indirect
|
||||
cloud.google.com/go/compute v1.10.0 // indirect
|
||||
github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/alibabacloudsdkgo/helper v0.2.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go v66.0.0+incompatible // indirect
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
||||
github.com/Azure/go-autorest/autorest v0.11.28 // indirect
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.21 // indirect
|
||||
github.com/Azure/go-autorest/autorest/azure/auth v0.5.11 // indirect
|
||||
github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 // indirect
|
||||
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
|
||||
github.com/Azure/go-autorest/logger v0.2.1 // indirect
|
||||
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
||||
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/Microsoft/go-winio v0.5.2 // indirect
|
||||
github.com/ThalesIgnite/crypto11 v1.2.5 // indirect
|
||||
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 // indirect
|
||||
github.com/alibabacloud-go/cr-20160607 v1.0.1 // indirect
|
||||
github.com/alibabacloud-go/cr-20181201 v1.0.10 // indirect
|
||||
github.com/alibabacloud-go/darabonba-openapi v0.1.18 // indirect
|
||||
github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68 // indirect
|
||||
github.com/alibabacloud-go/endpoint-util v1.1.1 // indirect
|
||||
github.com/alibabacloud-go/openapi-util v0.0.11 // indirect
|
||||
github.com/alibabacloud-go/tea v1.1.18 // indirect
|
||||
github.com/alibabacloud-go/tea-utils v1.4.4 // indirect
|
||||
github.com/alibabacloud-go/tea-xml v1.1.2 // indirect
|
||||
github.com/aliyun/credentials-go v1.2.3 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.16.16 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.17.8 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.12.21 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.17 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.23 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.17 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.24 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ecr v1.15.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.12.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.17 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.11.23 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.16.19 // indirect
|
||||
github.com/aws/smithy-go v1.13.3 // indirect
|
||||
github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20220517224237-e6f29200ae04 // indirect
|
||||
github.com/benbjohnson/clock v1.1.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bgentry/speakeasy v0.1.0 // indirect
|
||||
github.com/blang/semver v3.5.1+incompatible // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
|
||||
github.com/census-instrumentation/opencensus-proto v0.3.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/chrismellard/docker-credential-acr-env v0.0.0-20220119192733-fe33c00cee21 // indirect
|
||||
github.com/clbanning/mxj/v2 v2.5.6 // indirect
|
||||
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4 // indirect
|
||||
github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490 // indirect
|
||||
github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be // indirect
|
||||
github.com/containerd/containerd v1.6.8 // indirect
|
||||
github.com/containerd/containerd v1.6.12 // indirect
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.12.0 // indirect
|
||||
github.com/coreos/go-oidc/v3 v3.4.0 // indirect
|
||||
github.com/coreos/go-semver v0.3.0 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||
github.com/cyberphone/json-canonicalization v0.0.0-20210823021906-dc406ceaf94b // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dimchansky/utfbom v1.1.1 // indirect
|
||||
github.com/docker/cli v20.10.17+incompatible // indirect
|
||||
github.com/docker/distribution v2.8.1+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.6.4 // indirect
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/go-units v0.4.0 // indirect
|
||||
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
|
||||
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1 // indirect
|
||||
github.com/envoyproxy/protoc-gen-validate v0.6.2 // indirect
|
||||
github.com/fsnotify/fsnotify v1.5.4 // indirect
|
||||
github.com/fullstorydev/grpcurl v1.8.7 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.0 // indirect
|
||||
github.com/go-chi/chi v4.1.2+incompatible // indirect
|
||||
github.com/go-logr/logr v1.2.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-openapi/analysis v0.21.4 // indirect
|
||||
github.com/go-openapi/errors v0.20.3 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.0 // indirect
|
||||
github.com/go-openapi/loads v0.21.2 // indirect
|
||||
github.com/go-openapi/runtime v0.24.2 // indirect
|
||||
github.com/go-openapi/spec v0.20.7 // indirect
|
||||
github.com/go-openapi/strfmt v0.21.3 // indirect
|
||||
github.com/go-openapi/swag v0.22.3 // indirect
|
||||
github.com/go-openapi/validate v0.22.0 // indirect
|
||||
github.com/go-piv/piv-go v1.10.0 // indirect
|
||||
github.com/go-playground/locales v0.14.0 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.0 // indirect
|
||||
github.com/go-playground/validator/v10 v10.11.0 // indirect
|
||||
github.com/go-restruct/restruct v1.2.0-alpha // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.4.2 // indirect
|
||||
github.com/golang/glog v1.0.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/mock v1.6.0 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/btree v1.1.2 // indirect
|
||||
github.com/google/certificate-transparency-go v1.1.3 // indirect
|
||||
github.com/google/go-github/v45 v45.2.0 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/trillian v1.5.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect
|
||||
github.com/googleapis/gnostic v0.5.5 // indirect
|
||||
github.com/gorilla/websocket v1.4.2 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.1 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/huandu/xstrings v1.3.2 // 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/jedisct1/go-minisign v0.0.0-20211028175153-1c139d1cc84b // indirect
|
||||
github.com/jhump/protoreflect v1.13.0 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/jonboulle/clockwork v0.3.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/klauspost/compress v1.15.9 // indirect
|
||||
github.com/klauspost/pgzip v1.2.5 // indirect
|
||||
github.com/leodido/go-urn v1.2.1 // indirect
|
||||
github.com/letsencrypt/boulder v0.0.0-20220929215747-76583552c2be // indirect
|
||||
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381 // indirect
|
||||
github.com/magiconair/properties v1.8.6 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
||||
github.com/miekg/pkcs11 v1.1.1 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/mozillazg/docker-credential-acr-helper v0.3.0 // indirect
|
||||
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
|
||||
github.com/nwaples/rardecode v1.1.0 // indirect
|
||||
github.com/oklog/ulid v1.3.1 // indirect
|
||||
github.com/opencontainers/image-spec v1.0.3-0.20220114050600-8b9d41f48198 // indirect
|
||||
github.com/opentracing/opentracing-go v1.2.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.5 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.15 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_golang v1.13.0 // indirect
|
||||
github.com/prometheus/client_model v0.2.0 // indirect
|
||||
github.com/prometheus/common v0.37.0 // indirect
|
||||
github.com/prometheus/procfs v0.8.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/sassoftware/relic v0.0.0-20210427151427-dfb082b79b74 // indirect
|
||||
github.com/secure-systems-lab/go-securesystemslib v0.4.0 // indirect
|
||||
github.com/segmentio/ksuid v1.0.4 // indirect
|
||||
github.com/shibumi/go-pathspec v1.3.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.8.0 // indirect
|
||||
github.com/shopspring/decimal v1.2.0 // indirect
|
||||
github.com/sigstore/fulcio v0.6.0 // indirect
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect
|
||||
github.com/soheilhy/cmux v0.1.5 // indirect
|
||||
github.com/spf13/cast v1.5.0 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spiffe/go-spiffe/v2 v2.1.1 // indirect
|
||||
github.com/stretchr/objx v0.4.0 // indirect
|
||||
github.com/stretchr/objx v0.5.0 // indirect
|
||||
github.com/subosito/gotenv v1.4.1 // indirect
|
||||
github.com/sylabs/sif/v2 v2.8.1 // indirect
|
||||
github.com/sylabs/squashfs v0.6.1 // indirect
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect
|
||||
github.com/tent/canonical-json-go v0.0.0-20130607151641-96e4ba3a7613 // indirect
|
||||
github.com/thales-e-security/pool v0.0.2 // indirect
|
||||
github.com/therootcompany/xz v1.0.1 // indirect
|
||||
github.com/theupdateframework/go-tuf v0.5.2-0.20220930112810-3890c1e7ace4 // indirect
|
||||
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect
|
||||
github.com/tjfoc/gmsm v1.3.2 // indirect
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 // indirect
|
||||
github.com/transparency-dev/merkle v0.0.1 // indirect
|
||||
github.com/ulikunitz/xz v0.5.10 // indirect
|
||||
github.com/urfave/cli v1.22.7 // indirect
|
||||
github.com/vbatts/tar-split v0.11.2 // indirect
|
||||
github.com/xanzy/go-gitlab v0.73.1 // 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/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
|
||||
github.com/zeebo/errs v1.2.2 // indirect
|
||||
go.etcd.io/bbolt v1.3.6 // indirect
|
||||
go.etcd.io/etcd/api/v3 v3.6.0-alpha.0 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.0-alpha.0 // indirect
|
||||
go.etcd.io/etcd/client/v2 v2.306.0-alpha.0 // indirect
|
||||
go.etcd.io/etcd/client/v3 v3.6.0-alpha.0 // indirect
|
||||
go.etcd.io/etcd/etcdctl/v3 v3.6.0-alpha.0 // indirect
|
||||
go.etcd.io/etcd/etcdutl/v3 v3.6.0-alpha.0 // indirect
|
||||
go.etcd.io/etcd/pkg/v3 v3.6.0-alpha.0 // indirect
|
||||
go.etcd.io/etcd/raft/v3 v3.6.0-alpha.0 // indirect
|
||||
go.etcd.io/etcd/server/v3 v3.6.0-alpha.0 // indirect
|
||||
go.etcd.io/etcd/tests/v3 v3.6.0-alpha.0 // indirect
|
||||
go.etcd.io/etcd/v3 v3.6.0-alpha.0 // indirect
|
||||
go.mongodb.org/mongo-driver v1.10.0 // indirect
|
||||
go.opencensus.io v0.23.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.28.0 // indirect
|
||||
go.opentelemetry.io/otel v1.7.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.7.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.7.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.7.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.7.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.7.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v0.16.0 // indirect
|
||||
go.uber.org/atomic v1.10.0 // indirect
|
||||
go.uber.org/multierr v1.8.0 // indirect
|
||||
go.uber.org/zap v1.23.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20220823124025-807a23277127 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20221006150949-b44042a4b9c1 // indirect
|
||||
golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0 // indirect
|
||||
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec // indirect
|
||||
golang.org/x/text v0.3.8-0.20211004125949-5bd84dd9b33b // indirect
|
||||
golang.org/x/sys v0.3.0 // indirect
|
||||
golang.org/x/text v0.5.0 // indirect
|
||||
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect
|
||||
golang.org/x/tools v0.1.12 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||
google.golang.org/api v0.99.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e // indirect
|
||||
google.golang.org/grpc v1.50.1 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
gopkg.in/cheggaaa/pb.v1 v1.0.28 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
||||
k8s.io/api v0.23.5 // indirect
|
||||
k8s.io/apimachinery v0.23.5 // indirect
|
||||
k8s.io/client-go v0.23.5 // indirect
|
||||
k8s.io/klog/v2 v2.60.1-0.20220317184644-43cc75f9ae89 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20220124234850-424119656bbf // indirect
|
||||
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect
|
||||
lukechampine.com/uint128 v1.1.1 // indirect
|
||||
modernc.org/cc/v3 v3.36.0 // indirect
|
||||
modernc.org/ccgo/v3 v3.16.6 // indirect
|
||||
@@ -322,10 +143,6 @@ require (
|
||||
modernc.org/sqlite v1.17.3 // indirect
|
||||
modernc.org/strutil v1.1.1 // indirect
|
||||
modernc.org/token v1.0.0 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect
|
||||
sigs.k8s.io/release-utils v0.7.3 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect
|
||||
sigs.k8s.io/yaml v1.3.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
|
||||
@@ -53,9 +53,9 @@ type Application struct {
|
||||
Secrets secrets `yaml:"secrets" json:"secrets" mapstructure:"secrets"`
|
||||
Registry registry `yaml:"registry" json:"registry" mapstructure:"registry"`
|
||||
Exclusions []string `yaml:"exclude" json:"exclude" mapstructure:"exclude"`
|
||||
Attest attest `yaml:"attest" json:"attest" mapstructure:"attest"`
|
||||
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
|
||||
}
|
||||
|
||||
func (cfg Application) ToCatalogerConfig() cataloger.Config {
|
||||
@@ -65,7 +65,8 @@ func (cfg Application) ToCatalogerConfig() cataloger.Config {
|
||||
IncludeUnindexedArchives: cfg.Package.SearchUnindexedArchives,
|
||||
Scope: cfg.Package.Cataloger.ScopeOpt,
|
||||
},
|
||||
Catalogers: cfg.Catalogers,
|
||||
Catalogers: cfg.Catalogers,
|
||||
Parallelism: cfg.Parallelism,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,6 +183,7 @@ func loadDefaultValues(v *viper.Viper) {
|
||||
v.SetDefault("quiet", false)
|
||||
v.SetDefault("check-for-app-update", true)
|
||||
v.SetDefault("catalogers", nil)
|
||||
v.SetDefault("parallelism", 1)
|
||||
|
||||
// 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{})
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/sigstore/cosign/cmd/cosign/cli/options"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// IMPORTANT: do not show the password in any YAML/JSON output (sensitive information)
|
||||
type attest struct {
|
||||
KeyRef string `yaml:"key" json:"key" mapstructure:"key"` // same as --key, file path to the private key
|
||||
Cert string `yaml:"cert" json:"cert" mapstructure:"cert"`
|
||||
NoUpload bool `yaml:"no_upload" json:"noUpload" mapstructure:"no_upload"`
|
||||
Force bool `yaml:"force" json:"force" mapstructure:"force"`
|
||||
Recursive bool `yaml:"recursive" json:"recursive" mapstructure:"recursive"`
|
||||
Replace bool `yaml:"replace" json:"replace" mapstructure:"replace"`
|
||||
Password string `yaml:"-" json:"-" mapstructure:"password"` // password for the private key
|
||||
FulcioURL string `yaml:"fulcio_url" json:"fulcioUrl" mapstructure:"fulcio_url"`
|
||||
FulcioIdentityToken string `yaml:"fulcio_identity_token" json:"fulcio_identity_token" mapstructure:"fulcio_identity_token"`
|
||||
InsecureSkipFulcioVerify bool `yaml:"insecure_skip_verify" json:"insecure_skip_verify" mapstructure:"insecure_skip_verify"`
|
||||
RekorURL string `yaml:"rekor_url" json:"rekorUrl" mapstructure:"rekor_url"`
|
||||
OIDCIssuer string `yaml:"oidc_issuer" json:"oidcIssuer" mapstructure:"oidc_issuer"`
|
||||
OIDCClientID string `yaml:"oidc_client_id" json:"oidcClientId" mapstructure:"oidc_client_id"`
|
||||
OIDCRedirectURL string `yaml:"oidc_redirect_url" json:"OIDCRedirectURL" mapstructure:"oidc_redirect_url"`
|
||||
}
|
||||
|
||||
func (cfg *attest) parseConfigValues() error {
|
||||
if cfg.KeyRef != "" {
|
||||
expandedPath, err := homedir.Expand(cfg.KeyRef)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to expand key path=%q: %w", cfg.KeyRef, err)
|
||||
}
|
||||
cfg.KeyRef = expandedPath
|
||||
}
|
||||
|
||||
if cfg.Password == "" {
|
||||
// we allow for configuration via syft config/env vars and additionally interop with known cosign config env vars
|
||||
if pw, ok := os.LookupEnv("COSIGN_PASSWORD"); ok {
|
||||
cfg.Password = pw
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cfg attest) loadDefaultValues(v *viper.Viper) {
|
||||
v.SetDefault("attest.key", "")
|
||||
v.SetDefault("attest.password", "")
|
||||
v.SetDefault("attest.fulcio_url", options.DefaultFulcioURL)
|
||||
v.SetDefault("attest.rekor_url", options.DefaultRekorURL)
|
||||
v.SetDefault("attest.oidc_issuer", options.DefaultOIDCIssuerURL)
|
||||
v.SetDefault("attest.oidc_client_id", "sigstore")
|
||||
}
|
||||
@@ -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.0.0"
|
||||
JSONSchemaVersion = "6.2.0"
|
||||
)
|
||||
|
||||
@@ -423,6 +423,7 @@ func Test_processSPDXLicense(t *testing.T) {
|
||||
"cern-ohl-w-2": "CERN-OHL-W-2.0",
|
||||
"cern-ohl-w-2.0": "CERN-OHL-W-2.0",
|
||||
"cern-ohl-w-2.0.0": "CERN-OHL-W-2.0",
|
||||
"checkmk": "checkmk",
|
||||
"clartistic": "ClArtistic",
|
||||
"cnri-jython": "CNRI-Jython",
|
||||
"cnri-python": "CNRI-Python",
|
||||
@@ -521,6 +522,7 @@ func Test_processSPDXLicense(t *testing.T) {
|
||||
"fsfap": "FSFAP",
|
||||
"fsful": "FSFUL",
|
||||
"fsfullr": "FSFULLR",
|
||||
"fsfullrwd": "FSFULLRWD",
|
||||
"ftl": "FTL",
|
||||
"gd": "GD",
|
||||
"gfdl-1": "GFDL-1.1-only",
|
||||
@@ -668,6 +670,7 @@ func Test_processSPDXLicense(t *testing.T) {
|
||||
"jasper-2.0.0": "JasPer-2.0",
|
||||
"jpnic": "JPNIC",
|
||||
"json": "JSON",
|
||||
"knuth-ctan": "Knuth-CTAN",
|
||||
"lal-1": "LAL-1.2",
|
||||
"lal-1.2": "LAL-1.2",
|
||||
"lal-1.2.0": "LAL-1.2",
|
||||
@@ -716,6 +719,7 @@ func Test_processSPDXLicense(t *testing.T) {
|
||||
"libselinux-1.0": "libselinux-1.0",
|
||||
"libselinux-1.0.0": "libselinux-1.0",
|
||||
"libtiff": "libtiff",
|
||||
"libutil-david-nugent": "libutil-David-Nugent",
|
||||
"liliq-p-1": "LiLiQ-P-1.1",
|
||||
"liliq-p-1.1": "LiLiQ-P-1.1",
|
||||
"liliq-p-1.1.0": "LiLiQ-P-1.1",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,9 @@
|
||||
// Code generated by go generate; DO NOT EDIT.
|
||||
// This file was generated by robots at 2022-10-14 11:46:16.097525 -0400 EDT m=+0.276930979
|
||||
// This file was generated by robots at 2022-12-02 12:51:33.814385 -0500 EST m=+1.688731869
|
||||
// using data from https://spdx.org/licenses/licenses.json
|
||||
package spdxlicense
|
||||
|
||||
const Version = "3.18"
|
||||
const Version = "3.19"
|
||||
|
||||
var licenseIDs = map[string]string{
|
||||
"0bsd": "0BSD",
|
||||
@@ -372,6 +372,7 @@ var licenseIDs = map[string]string{
|
||||
"cern-ohl-w-2": "CERN-OHL-W-2.0",
|
||||
"cern-ohl-w-2.0": "CERN-OHL-W-2.0",
|
||||
"cern-ohl-w-2.0.0": "CERN-OHL-W-2.0",
|
||||
"checkmk": "checkmk",
|
||||
"clartistic": "ClArtistic",
|
||||
"cnri-jython": "CNRI-Jython",
|
||||
"cnri-python": "CNRI-Python",
|
||||
@@ -470,6 +471,7 @@ var licenseIDs = map[string]string{
|
||||
"fsfap": "FSFAP",
|
||||
"fsful": "FSFUL",
|
||||
"fsfullr": "FSFULLR",
|
||||
"fsfullrwd": "FSFULLRWD",
|
||||
"ftl": "FTL",
|
||||
"gd": "GD",
|
||||
"gfdl-1": "GFDL-1.1-only",
|
||||
@@ -617,6 +619,7 @@ var licenseIDs = map[string]string{
|
||||
"jasper-2.0.0": "JasPer-2.0",
|
||||
"jpnic": "JPNIC",
|
||||
"json": "JSON",
|
||||
"knuth-ctan": "Knuth-CTAN",
|
||||
"lal-1": "LAL-1.2",
|
||||
"lal-1.2": "LAL-1.2",
|
||||
"lal-1.2.0": "LAL-1.2",
|
||||
@@ -665,6 +668,7 @@ var licenseIDs = map[string]string{
|
||||
"libselinux-1.0": "libselinux-1.0",
|
||||
"libselinux-1.0.0": "libselinux-1.0",
|
||||
"libtiff": "libtiff",
|
||||
"libutil-david-nugent": "libutil-David-Nugent",
|
||||
"liliq-p-1": "LiLiQ-P-1.1",
|
||||
"liliq-p-1.1": "LiLiQ-P-1.1",
|
||||
"liliq-p-1.1.0": "LiLiQ-P-1.1",
|
||||
@@ -984,7 +988,7 @@ var licenseIDs = map[string]string{
|
||||
"sspl-1": "SSPL-1.0",
|
||||
"sspl-1.0": "SSPL-1.0",
|
||||
"sspl-1.0.0": "SSPL-1.0",
|
||||
"standardml-nj": "StandardML-NJ",
|
||||
"standardml-nj": "SMLNJ",
|
||||
"sugarcrm-1": "SugarCRM-1.1.3",
|
||||
"sugarcrm-1.1": "SugarCRM-1.1.3",
|
||||
"sugarcrm-1.1.3": "SugarCRM-1.1.3",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//go:build linux || darwin
|
||||
// +build linux darwin
|
||||
//go:build linux || darwin || netbsd
|
||||
// +build linux darwin netbsd
|
||||
|
||||
package ui
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//go:build linux || darwin
|
||||
// +build linux darwin
|
||||
//go:build linux || darwin || netbsd
|
||||
// +build linux darwin netbsd
|
||||
|
||||
package ui
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//go:build linux || darwin
|
||||
// +build linux darwin
|
||||
//go:build linux || darwin || netbsd
|
||||
// +build linux darwin netbsd
|
||||
|
||||
package ui
|
||||
|
||||
|
||||
+28
-20
@@ -27,26 +27,34 @@ can be extended to include specific package metadata struct shapes in the future
|
||||
// This should represent all possible metadatas represented in the pkg.Package.Metadata field (an interface{}).
|
||||
// When a new package metadata definition is created it will need to be manually added here. The variable name does
|
||||
// not matter as long as it is exported.
|
||||
|
||||
// TODO: this should be generated from reflection of whats in the pkg package
|
||||
type artifactMetadataContainer struct {
|
||||
Apk pkg.ApkMetadata
|
||||
Alpm pkg.AlpmMetadata
|
||||
Dpkg pkg.DpkgMetadata
|
||||
Gem pkg.GemMetadata
|
||||
Java pkg.JavaMetadata
|
||||
Npm pkg.NpmPackageJSONMetadata
|
||||
Python pkg.PythonPackageMetadata
|
||||
Rpm pkg.RpmMetadata
|
||||
Cargo pkg.CargoPackageMetadata
|
||||
Go pkg.GolangBinMetadata
|
||||
Php pkg.PhpComposerJSONMetadata
|
||||
Dart pkg.DartPubMetadata
|
||||
Dotnet pkg.DotnetDepsMetadata
|
||||
Portage pkg.PortageMetadata
|
||||
Conan pkg.ConanMetadata
|
||||
ConanLock pkg.ConanLockMetadata
|
||||
KbPackage pkg.KbPackageMetadata
|
||||
Hackage pkg.HackageMetadata
|
||||
SwiftCocopods pkg.CocoapodsMetadata
|
||||
Alpm pkg.AlpmMetadata
|
||||
Apk pkg.ApkMetadata
|
||||
Binary pkg.BinaryMetadata
|
||||
Cocopods pkg.CocoapodsMetadata
|
||||
Conan pkg.ConanMetadata
|
||||
ConanLock pkg.ConanLockMetadata
|
||||
Dart pkg.DartPubMetadata
|
||||
Dotnet pkg.DotnetDepsMetadata
|
||||
Dpkg pkg.DpkgMetadata
|
||||
Gem pkg.GemMetadata
|
||||
GoBin pkg.GolangBinMetadata
|
||||
GoMod pkg.GolangModMetadata
|
||||
Hackage pkg.HackageMetadata
|
||||
Java pkg.JavaMetadata
|
||||
KbPackage pkg.KbPackageMetadata
|
||||
NpmPackage pkg.NpmPackageJSONMetadata
|
||||
NpmPackageLock pkg.NpmPackageLockJSONMetadata
|
||||
MixLock pkg.MixLockMetadata
|
||||
Php pkg.PhpComposerJSONMetadata
|
||||
Portage pkg.PortageMetadata
|
||||
PythonPackage pkg.PythonPackageMetadata
|
||||
PythonPipfilelock pkg.PythonPipfileLockMetadata
|
||||
Rebar pkg.RebarLockMetadata
|
||||
Rpm pkg.RpmMetadata
|
||||
RustCargo pkg.CargoPackageMetadata
|
||||
}
|
||||
|
||||
func main() {
|
||||
@@ -136,7 +144,7 @@ func write(schema []byte) {
|
||||
}
|
||||
|
||||
// the generated schema is different, bail with error :(
|
||||
fmt.Printf("Cowardly refusing to overwrite existing schema (%s)!\nSee the scheam/json/README.md for how to increment\n", filename)
|
||||
fmt.Printf("Cowardly refusing to overwrite existing schema (%s)!\nSee the schema/json/README.md for how to increment\n", filename)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,610 +0,0 @@
|
||||
{
|
||||
"$schema" : "http://json-schema.org/draft-07/schema#",
|
||||
"$id" : "http://spdx.org/rdf/terms",
|
||||
"title" : "SPDX 2.2",
|
||||
"type" : "object",
|
||||
"properties" : {
|
||||
"Document" : {
|
||||
"type" : "object",
|
||||
"properties" : {
|
||||
"revieweds" : {
|
||||
"description" : "Reviewed",
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"type" : "object",
|
||||
"properties" : {
|
||||
"reviewer" : {
|
||||
"description" : "The name and, optionally, contact information of the person who performed the review. Values of this property must conform to the agent and tool syntax.",
|
||||
"type" : "string"
|
||||
},
|
||||
"comment" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"reviewDate" : {
|
||||
"description" : "The date and time at which the SpdxDocument was reviewed. This value must be in UTC and have 'Z' as its timezone indicator.",
|
||||
"type" : "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"hasExtractedLicensingInfos" : {
|
||||
"description" : "Indicates that a particular ExtractedLicensingInfo was defined in the subject SpdxDocument.",
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"type" : "object",
|
||||
"properties" : {
|
||||
"seeAlsos" : {
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"type" : "string"
|
||||
}
|
||||
},
|
||||
"name" : {
|
||||
"description" : "Identify name of this SpdxElement.",
|
||||
"type" : "string"
|
||||
},
|
||||
"comment" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"licenseId" : {
|
||||
"description" : "A human readable short form license identifier for a license. The license ID is iether on the standard license oist or the form \"LicenseRef-\"[idString] where [idString] is a unique string containing letters, numbers, \".\", \"-\" or \"+\".",
|
||||
"type" : "string"
|
||||
},
|
||||
"extractedText" : {
|
||||
"description" : "Verbatim license or licensing notice text that was discovered.",
|
||||
"type" : "string"
|
||||
}
|
||||
},
|
||||
"description" : "An ExtractedLicensingInfo represents a license or licensing notice that was found in the package. Any license text that is recognized as a license may be represented as a License rather than an ExtractedLicensingInfo."
|
||||
}
|
||||
},
|
||||
"name" : {
|
||||
"description" : "Identify name of this SpdxElement.",
|
||||
"type" : "string"
|
||||
},
|
||||
"comment" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"spdxVersion" : {
|
||||
"description" : "Provide a reference number that can be used to understand how to parse and interpret the rest of the file. It will enable both future changes to the specification and to support backward compatibility. The version number consists of a major and minor version indicator. The major field will be incremented when incompatible changes between versions are made (one or more sections are created, modified or deleted). The minor field will be incremented when backwards compatible changes are made.",
|
||||
"type" : "string"
|
||||
},
|
||||
"annotations" : {
|
||||
"description" : "Provide additional information about an SpdxElement.",
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"type" : "object",
|
||||
"properties" : {
|
||||
"annotationDate" : {
|
||||
"description" : "Identify when the comment was made. This is to be specified according to the combined date and time in the UTC format, as specified in the ISO 8601 standard.",
|
||||
"type" : "string"
|
||||
},
|
||||
"comment" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"annotator" : {
|
||||
"description" : "This field identifies the person, organization or tool that has commented on a file, package, or the entire document.",
|
||||
"type" : "string"
|
||||
},
|
||||
"annotationType" : {
|
||||
"description" : "Type of the annotation.",
|
||||
"type" : "string",
|
||||
"enum" : [ "OTHER", "REVIEW" ]
|
||||
}
|
||||
},
|
||||
"description" : "An Annotation is a comment on an SpdxItem by an agent."
|
||||
}
|
||||
},
|
||||
"describesPackages" : {
|
||||
"description" : "The describesPackage property relates an SpdxDocument to the package which it describes.",
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"description" : "SPDX ID for Package. The describesPackage property relates an SpdxDocument to the package which it describes.",
|
||||
"type" : "string"
|
||||
}
|
||||
},
|
||||
"dataLicense" : {
|
||||
"description" : "License expression for dataLicense. Compliance with the SPDX specification includes populating the SPDX fields therein with data related to such fields (\"SPDX-Metadata\"). The SPDX specification contains numerous fields where an SPDX document creator may provide relevant explanatory text in SPDX-Metadata. Without opining on the lawfulness of \"database rights\" (in jurisdictions where applicable), such explanatory text is copyrightable subject matter in most Berne Convention countries. By using the SPDX specification, or any portion hereof, you hereby agree that any copyright rights (as determined by your jurisdiction) in any SPDX-Metadata, including without limitation explanatory text, shall be subject to the terms of the Creative Commons CC0 1.0 Universal license. For SPDX-Metadata not containing any copyright rights, you hereby agree and acknowledge that the SPDX-Metadata is provided to you \"as-is\" and without any representations or warranties of any kind concerning the SPDX-Metadata, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non-infringement, or the absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law.",
|
||||
"type" : "string"
|
||||
},
|
||||
"externalDocumentRefs" : {
|
||||
"description" : "Identify any external SPDX documents referenced within this SPDX document.",
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"type" : "object",
|
||||
"properties" : {
|
||||
"externalDocumentId" : {
|
||||
"description" : "externalDocumentId is a string containing letters, numbers, ., - and/or + which uniquely identifies an external document within this document.",
|
||||
"type" : "string"
|
||||
},
|
||||
"checksum" : {
|
||||
"type" : "object",
|
||||
"properties" : {
|
||||
"algorithm" : {
|
||||
"description" : "Identifies the algorithm used to produce the subject Checksum. Currently, SHA-1 is the only supported algorithm. It is anticipated that other algorithms will be supported at a later time.",
|
||||
"type" : "string",
|
||||
"enum" : [ "SHA256", "SHA1", "SHA384", "MD2", "MD4", "SHA512", "MD6", "MD5", "SHA224" ]
|
||||
},
|
||||
"checksumValue" : {
|
||||
"description" : "The checksumValue property provides a lower case hexidecimal encoded digest value produced using a specific algorithm.",
|
||||
"type" : "string"
|
||||
}
|
||||
},
|
||||
"description" : "A Checksum is value that allows the contents of a file to be authenticated. Even small changes to the content of the file will change its checksum. This class allows the results of a variety of checksum and cryptographic message digest algorithms to be represented."
|
||||
},
|
||||
"spdxDocument" : {
|
||||
"description" : "SPDX ID for SpdxDocument. A propoerty containing an SPDX document.",
|
||||
"type" : "string"
|
||||
}
|
||||
},
|
||||
"description" : "Information about an external SPDX document reference including the checksum. This allows for verification of the external references."
|
||||
}
|
||||
},
|
||||
"creationInfo" : {
|
||||
"type" : "object",
|
||||
"properties" : {
|
||||
"comment" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"created" : {
|
||||
"description" : "Identify when the SPDX file was originally created. The date is to be specified according to combined date and time in UTC format as specified in ISO 8601 standard. This field is distinct from the fields in section 8, which involves the addition of information during a subsequent review.",
|
||||
"type" : "string"
|
||||
},
|
||||
"creators" : {
|
||||
"description" : "Identify who (or what, in the case of a tool) created the SPDX file. If the SPDX file was created by an individual, indicate the person's name. If the SPDX file was created on behalf of a company or organization, indicate the entity name. If the SPDX file was created using a software tool, indicate the name and version for that tool. If multiple participants or tools were involved, use multiple instances of this field. Person name or organization name may be designated as “anonymous” if appropriate.",
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"description" : "Identify who (or what, in the case of a tool) created the SPDX file. If the SPDX file was created by an individual, indicate the person's name. If the SPDX file was created on behalf of a company or organization, indicate the entity name. If the SPDX file was created using a software tool, indicate the name and version for that tool. If multiple participants or tools were involved, use multiple instances of this field. Person name or organization name may be designated as “anonymous” if appropriate.",
|
||||
"type" : "string"
|
||||
},
|
||||
"minItems" : 1
|
||||
},
|
||||
"licenseListVersion" : {
|
||||
"description" : "An optional field for creators of the SPDX file to provide the version of the SPDX License List used when the SPDX file was created.",
|
||||
"type" : "string"
|
||||
}
|
||||
},
|
||||
"description" : "One instance is required for each SPDX file produced. It provides the necessary information for forward and backward compatibility for processing tools."
|
||||
},
|
||||
"packages" : {
|
||||
"description" : "Packages referenced in the SPDX document",
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"type" : "object",
|
||||
"properties" : {
|
||||
"attributionTexts" : {
|
||||
"description" : "This field provides a place for the SPDX data creator to record acknowledgements that may be required to be communicated in some contexts. This is not meant to include theactual complete license text (see licenseConculded and licenseDeclared), and may or may not include copyright notices (see also copyrightText). The SPDX data creator may use this field to record other acknowledgements, such as particular clauses from license texts, which may be necessary or desirable to reproduce.",
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"description" : "This field provides a place for the SPDX data creator to record acknowledgements that may be required to be communicated in some contexts. This is not meant to include theactual complete license text (see licenseConculded and licenseDeclared), and may or may not include copyright notices (see also copyrightText). The SPDX data creator may use this field to record other acknowledgements, such as particular clauses from license texts, which may be necessary or desirable to reproduce.",
|
||||
"type" : "string"
|
||||
}
|
||||
},
|
||||
"annotations" : {
|
||||
"description" : "Provide additional information about an SpdxElement.",
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"type" : "object",
|
||||
"properties" : {
|
||||
"annotationDate" : {
|
||||
"description" : "Identify when the comment was made. This is to be specified according to the combined date and time in the UTC format, as specified in the ISO 8601 standard.",
|
||||
"type" : "string"
|
||||
},
|
||||
"comment" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"annotator" : {
|
||||
"description" : "This field identifies the person, organization or tool that has commented on a file, package, or the entire document.",
|
||||
"type" : "string"
|
||||
},
|
||||
"annotationType" : {
|
||||
"description" : "Type of the annotation.",
|
||||
"type" : "string",
|
||||
"enum" : [ "OTHER", "REVIEW" ]
|
||||
}
|
||||
},
|
||||
"description" : "An Annotation is a comment on an SpdxItem by an agent."
|
||||
}
|
||||
},
|
||||
"supplier" : {
|
||||
"description" : "The name and, optionally, contact information of the person or organization who was the immediate supplier of this package to the recipient. The supplier may be different than originator when the software has been repackaged. Values of this property must conform to the agent and tool syntax.",
|
||||
"type" : "string"
|
||||
},
|
||||
"homepage" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"packageVerificationCode" : {
|
||||
"type" : "object",
|
||||
"properties" : {
|
||||
"packageVerificationCodeValue" : {
|
||||
"description" : "The actual package verification code as a hex encoded value.",
|
||||
"type" : "string"
|
||||
},
|
||||
"packageVerificationCodeExcludedFiles" : {
|
||||
"description" : "A file that was excluded when calculating the package verification code. This is usually a file containing SPDX data regarding the package. If a package contains more than one SPDX file all SPDX files must be excluded from the package verification code. If this is not done it would be impossible to correctly calculate the verification codes in both files.",
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"description" : "A file that was excluded when calculating the package verification code. This is usually a file containing SPDX data regarding the package. If a package contains more than one SPDX file all SPDX files must be excluded from the package verification code. If this is not done it would be impossible to correctly calculate the verification codes in both files.",
|
||||
"type" : "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description" : "A manifest based verification code (the algorithm is defined in section 4.7 of the full specification) of the SPDX Item. This allows consumers of this data and/or database to determine if an SPDX item they have in hand is identical to the SPDX item from which the data was produced. This algorithm works even if the SPDX document is included in the SPDX item."
|
||||
},
|
||||
"checksums" : {
|
||||
"description" : "The checksum property provides a mechanism that can be used to verify that the contents of a File or Package have not changed.",
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"type" : "object",
|
||||
"properties" : {
|
||||
"algorithm" : {
|
||||
"description" : "Identifies the algorithm used to produce the subject Checksum. Currently, SHA-1 is the only supported algorithm. It is anticipated that other algorithms will be supported at a later time.",
|
||||
"type" : "string",
|
||||
"enum" : [ "SHA256", "SHA1", "SHA384", "MD2", "MD4", "SHA512", "MD6", "MD5", "SHA224" ]
|
||||
},
|
||||
"checksumValue" : {
|
||||
"description" : "The checksumValue property provides a lower case hexidecimal encoded digest value produced using a specific algorithm.",
|
||||
"type" : "string"
|
||||
}
|
||||
},
|
||||
"description" : "A Checksum is value that allows the contents of a file to be authenticated. Even small changes to the content of the file will change its checksum. This class allows the results of a variety of checksum and cryptographic message digest algorithms to be represented."
|
||||
}
|
||||
},
|
||||
"downloadLocation" : {
|
||||
"description" : "The URI at which this package is available for download. Private (i.e., not publicly reachable) URIs are acceptable as values of this property. The values http://spdx.org/rdf/terms#none and http://spdx.org/rdf/terms#noassertion may be used to specify that the package is not downloadable or that no attempt was made to determine its download location, respectively.",
|
||||
"type" : "string"
|
||||
},
|
||||
"filesAnalyzed" : {
|
||||
"description" : "Indicates whether the file content of this package has been available for or subjected to analysis when creating the SPDX document. If false indicates packages that represent metadata or URI references to a project, product, artifact, distribution or a component. If set to false, the package must not contain any files.",
|
||||
"type" : "boolean"
|
||||
},
|
||||
"externalRefs" : {
|
||||
"description" : "An External Reference allows a Package to reference an external source of additional information, metadata, enumerations, asset identifiers, or downloadable content believed to be relevant to the Package.",
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"type" : "object",
|
||||
"properties" : {
|
||||
"comment" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"referenceCategory" : {
|
||||
"description" : "Category for the external reference",
|
||||
"type" : "string",
|
||||
"enum" : [ "OTHER", "SECURITY", "PACKAGE_MANAGER" ]
|
||||
},
|
||||
"referenceLocator" : {
|
||||
"description" : "The unique string with no spaces necessary to access the package-specific information, metadata, or content within the target location. The format of the locator is subject to constraints defined by the <type>.",
|
||||
"type" : "string"
|
||||
},
|
||||
"referenceType" : {
|
||||
"description" : "Type of the external reference. These are definined in an appendix in the SPDX specification.",
|
||||
"type" : "string"
|
||||
}
|
||||
},
|
||||
"description" : "An External Reference allows a Package to reference an external source of additional information, metadata, enumerations, asset identifiers, or downloadable content believed to be relevant to the Package."
|
||||
}
|
||||
},
|
||||
"licenseComments" : {
|
||||
"description" : "The licenseComments property allows the preparer of the SPDX document to describe why the licensing in spdx:licenseConcluded was chosen.",
|
||||
"type" : "string"
|
||||
},
|
||||
"name" : {
|
||||
"description" : "Identify name of this SpdxElement.",
|
||||
"type" : "string"
|
||||
},
|
||||
"hasFiles" : {
|
||||
"description" : "Indicates that a particular file belongs to a package.",
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"description" : "SPDX ID for File. Indicates that a particular file belongs to a package.",
|
||||
"type" : "string"
|
||||
}
|
||||
},
|
||||
"comment" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"summary" : {
|
||||
"description" : "Provides a short description of the package.",
|
||||
"type" : "string"
|
||||
},
|
||||
"copyrightText" : {
|
||||
"description" : "The text of copyright declarations recited in the Package or File.",
|
||||
"type" : "string"
|
||||
},
|
||||
"originator" : {
|
||||
"description" : "The name and, optionally, contact information of the person or organization that originally created the package. Values of this property must conform to the agent and tool syntax.",
|
||||
"type" : "string"
|
||||
},
|
||||
"packageFileName" : {
|
||||
"description" : "The base name of the package file name. For example, zlib-1.2.5.tar.gz.",
|
||||
"type" : "string"
|
||||
},
|
||||
"licenseInfoFromFiles" : {
|
||||
"description" : "The licensing information that was discovered directly within the package. There will be an instance of this property for each distinct value of alllicenseInfoInFile properties of all files contained in the package.",
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"description" : "License expression for licenseInfoFromFiles. The licensing information that was discovered directly within the package. There will be an instance of this property for each distinct value of alllicenseInfoInFile properties of all files contained in the package.",
|
||||
"type" : "string"
|
||||
}
|
||||
},
|
||||
"versionInfo" : {
|
||||
"description" : "Provides an indication of the version of the package that is described by this SpdxDocument.",
|
||||
"type" : "string"
|
||||
},
|
||||
"sourceInfo" : {
|
||||
"description" : "Allows the producer(s) of the SPDX document to describe how the package was acquired and/or changed from the original source.",
|
||||
"type" : "string"
|
||||
},
|
||||
"description" : {
|
||||
"description" : "Provides a detailed description of the package.",
|
||||
"type" : "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"files" : {
|
||||
"description" : "Files referenced in the SPDX document",
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"type" : "object",
|
||||
"properties" : {
|
||||
"fileTypes" : {
|
||||
"description" : "The type of the file.",
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"description" : "The type of the file.",
|
||||
"type" : "string",
|
||||
"enum" : [ "OTHER", "DOCUMENTATION", "IMAGE", "VIDEO", "ARCHIVE", "SPDX", "APPLICATION", "SOURCE", "BINARY", "TEXT", "AUDIO" ]
|
||||
}
|
||||
},
|
||||
"attributionTexts" : {
|
||||
"description" : "This field provides a place for the SPDX data creator to record acknowledgements that may be required to be communicated in some contexts. This is not meant to include theactual complete license text (see licenseConculded and licenseDeclared), and may or may not include copyright notices (see also copyrightText). The SPDX data creator may use this field to record other acknowledgements, such as particular clauses from license texts, which may be necessary or desirable to reproduce.",
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"description" : "This field provides a place for the SPDX data creator to record acknowledgements that may be required to be communicated in some contexts. This is not meant to include theactual complete license text (see licenseConculded and licenseDeclared), and may or may not include copyright notices (see also copyrightText). The SPDX data creator may use this field to record other acknowledgements, such as particular clauses from license texts, which may be necessary or desirable to reproduce.",
|
||||
"type" : "string"
|
||||
}
|
||||
},
|
||||
"annotations" : {
|
||||
"description" : "Provide additional information about an SpdxElement.",
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"type" : "object",
|
||||
"properties" : {
|
||||
"annotationDate" : {
|
||||
"description" : "Identify when the comment was made. This is to be specified according to the combined date and time in the UTC format, as specified in the ISO 8601 standard.",
|
||||
"type" : "string"
|
||||
},
|
||||
"comment" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"annotator" : {
|
||||
"description" : "This field identifies the person, organization or tool that has commented on a file, package, or the entire document.",
|
||||
"type" : "string"
|
||||
},
|
||||
"annotationType" : {
|
||||
"description" : "Type of the annotation.",
|
||||
"type" : "string",
|
||||
"enum" : [ "OTHER", "REVIEW" ]
|
||||
}
|
||||
},
|
||||
"description" : "An Annotation is a comment on an SpdxItem by an agent."
|
||||
}
|
||||
},
|
||||
"checksums" : {
|
||||
"description" : "The checksum property provides a mechanism that can be used to verify that the contents of a File or Package have not changed.",
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"type" : "object",
|
||||
"properties" : {
|
||||
"algorithm" : {
|
||||
"description" : "Identifies the algorithm used to produce the subject Checksum. Currently, SHA-1 is the only supported algorithm. It is anticipated that other algorithms will be supported at a later time.",
|
||||
"type" : "string",
|
||||
"enum" : [ "SHA256", "SHA1", "SHA384", "MD2", "MD4", "SHA512", "MD6", "MD5", "SHA224" ]
|
||||
},
|
||||
"checksumValue" : {
|
||||
"description" : "The checksumValue property provides a lower case hexidecimal encoded digest value produced using a specific algorithm.",
|
||||
"type" : "string"
|
||||
}
|
||||
},
|
||||
"description" : "A Checksum is value that allows the contents of a file to be authenticated. Even small changes to the content of the file will change its checksum. This class allows the results of a variety of checksum and cryptographic message digest algorithms to be represented."
|
||||
},
|
||||
"minItems" : 1
|
||||
},
|
||||
"noticeText" : {
|
||||
"description" : "This field provides a place for the SPDX file creator to record potential legal notices found in the file. This may or may not include copyright statements.",
|
||||
"type" : "string"
|
||||
},
|
||||
"artifactOfs" : {
|
||||
"description" : "Indicates the project in which the SpdxElement originated. Tools must preserve doap:homepage and doap:name properties and the URI (if one is known) of doap:Project resources that are values of this property. All other properties of doap:Projects are not directly supported by SPDX and may be dropped when translating to or from some SPDX formats.",
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"type" : "object",
|
||||
"properties" : { }
|
||||
}
|
||||
},
|
||||
"licenseComments" : {
|
||||
"description" : "The licenseComments property allows the preparer of the SPDX document to describe why the licensing in spdx:licenseConcluded was chosen.",
|
||||
"type" : "string"
|
||||
},
|
||||
"fileName" : {
|
||||
"description" : "The name of the file relative to the root of the package.",
|
||||
"type" : "string"
|
||||
},
|
||||
"name" : {
|
||||
"description" : "Identify name of this SpdxElement.",
|
||||
"type" : "string"
|
||||
},
|
||||
"comment" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"copyrightText" : {
|
||||
"description" : "The text of copyright declarations recited in the Package or File.",
|
||||
"type" : "string"
|
||||
},
|
||||
"fileContributors" : {
|
||||
"description" : "This field provides a place for the SPDX file creator to record file contributors. Contributors could include names of copyright holders and/or authors who may not be copyright holders yet contributed to the file content.",
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"description" : "This field provides a place for the SPDX file creator to record file contributors. Contributors could include names of copyright holders and/or authors who may not be copyright holders yet contributed to the file content.",
|
||||
"type" : "string"
|
||||
}
|
||||
},
|
||||
"licenseInfoInFiles" : {
|
||||
"description" : "Licensing information that was discovered directly in the subject file. This is also considered a declared license for the file.",
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"description" : "License expression for licenseInfoInFile. Licensing information that was discovered directly in the subject file. This is also considered a declared license for the file.",
|
||||
"type" : "string"
|
||||
},
|
||||
"minItems" : 1
|
||||
},
|
||||
"licenseInfoFromFiles" : {
|
||||
"description" : "The licensing information that was discovered directly within the package. There will be an instance of this property for each distinct value of alllicenseInfoInFile properties of all files contained in the package.",
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"description" : "License expression for licenseInfoFromFiles. The licensing information that was discovered directly within the package. There will be an instance of this property for each distinct value of alllicenseInfoInFile properties of all files contained in the package.",
|
||||
"type" : "string"
|
||||
}
|
||||
},
|
||||
"fileDependencies" : {
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"description" : "SPDX ID for File",
|
||||
"type" : "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"snippets" : {
|
||||
"description" : "Snippets referenced in the SPDX document",
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"type" : "object",
|
||||
"properties" : {
|
||||
"ranges" : {
|
||||
"description" : "This field defines the byte range in the original host file (in X.2) that the snippet information applies to",
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"type" : "object",
|
||||
"properties" : {
|
||||
"startPointer" : {
|
||||
"type" : "object",
|
||||
"properties" : {
|
||||
"reference" : {
|
||||
"description" : "SPDX ID for File",
|
||||
"type" : "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"endPointer" : {
|
||||
"type" : "object",
|
||||
"properties" : {
|
||||
"reference" : {
|
||||
"description" : "SPDX ID for File",
|
||||
"type" : "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"minItems" : 1
|
||||
},
|
||||
"licenseComments" : {
|
||||
"description" : "The licenseComments property allows the preparer of the SPDX document to describe why the licensing in spdx:licenseConcluded was chosen.",
|
||||
"type" : "string"
|
||||
},
|
||||
"attributionTexts" : {
|
||||
"description" : "This field provides a place for the SPDX data creator to record acknowledgements that may be required to be communicated in some contexts. This is not meant to include theactual complete license text (see licenseConculded and licenseDeclared), and may or may not include copyright notices (see also copyrightText). The SPDX data creator may use this field to record other acknowledgements, such as particular clauses from license texts, which may be necessary or desirable to reproduce.",
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"description" : "This field provides a place for the SPDX data creator to record acknowledgements that may be required to be communicated in some contexts. This is not meant to include theactual complete license text (see licenseConculded and licenseDeclared), and may or may not include copyright notices (see also copyrightText). The SPDX data creator may use this field to record other acknowledgements, such as particular clauses from license texts, which may be necessary or desirable to reproduce.",
|
||||
"type" : "string"
|
||||
}
|
||||
},
|
||||
"name" : {
|
||||
"description" : "Identify name of this SpdxElement.",
|
||||
"type" : "string"
|
||||
},
|
||||
"snippetFromFile" : {
|
||||
"description" : "SPDX ID for File. File containing the SPDX element (e.g. the file contaning a snippet).",
|
||||
"type" : "string"
|
||||
},
|
||||
"comment" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"copyrightText" : {
|
||||
"description" : "The text of copyright declarations recited in the Package or File.",
|
||||
"type" : "string"
|
||||
},
|
||||
"licenseInfoInSnippets" : {
|
||||
"description" : "Licensing information that was discovered directly in the subject snippet. This is also considered a declared license for the snippet.",
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"description" : "License expression for licenseInfoInSnippet. Licensing information that was discovered directly in the subject snippet. This is also considered a declared license for the snippet.",
|
||||
"type" : "string"
|
||||
}
|
||||
},
|
||||
"annotations" : {
|
||||
"description" : "Provide additional information about an SpdxElement.",
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"type" : "object",
|
||||
"properties" : {
|
||||
"annotationDate" : {
|
||||
"description" : "Identify when the comment was made. This is to be specified according to the combined date and time in the UTC format, as specified in the ISO 8601 standard.",
|
||||
"type" : "string"
|
||||
},
|
||||
"comment" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"annotator" : {
|
||||
"description" : "This field identifies the person, organization or tool that has commented on a file, package, or the entire document.",
|
||||
"type" : "string"
|
||||
},
|
||||
"annotationType" : {
|
||||
"description" : "Type of the annotation.",
|
||||
"type" : "string",
|
||||
"enum" : [ "OTHER", "REVIEW" ]
|
||||
}
|
||||
},
|
||||
"description" : "An Annotation is a comment on an SpdxItem by an agent."
|
||||
}
|
||||
},
|
||||
"licenseInfoFromFiles" : {
|
||||
"description" : "The licensing information that was discovered directly within the package. There will be an instance of this property for each distinct value of alllicenseInfoInFile properties of all files contained in the package.",
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"description" : "License expression for licenseInfoFromFiles. The licensing information that was discovered directly within the package. There will be an instance of this property for each distinct value of alllicenseInfoInFile properties of all files contained in the package.",
|
||||
"type" : "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"relationships" : {
|
||||
"description" : "Relationships referenced in the SPDX document",
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"type" : "object",
|
||||
"properties" : {
|
||||
"comment" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"relationshipType" : {
|
||||
"description" : "Describes the type of relationship between two SPDX elements.",
|
||||
"type" : "string",
|
||||
"enum" : [ "VARIANT_OF", "COPY_OF", "PATCH_FOR", "TEST_DEPENDENCY_OF", "CONTAINED_BY", "DATA_FILE_OF", "OPTIONAL_COMPONENT_OF", "ANCESTOR_OF", "GENERATES", "CONTAINS", "OPTIONAL_DEPENDENCY_OF", "FILE_ADDED", "DEV_DEPENDENCY_OF", "DEPENDENCY_OF", "BUILD_DEPENDENCY_OF", "DESCRIBES", "PREREQUISITE_FOR", "HAS_PREREQUISITE", "PROVIDED_DEPENDENCY_OF", "DYNAMIC_LINK", "DESCRIBED_BY", "METAFILE_OF", "DEPENDENCY_MANIFEST_OF", "PATCH_APPLIED", "RUNTIME_DEPENDENCY_OF", "TEST_OF", "TEST_TOOL_OF", "DEPENDS_ON", "FILE_MODIFIED", "DISTRIBUTION_ARTIFACT", "DOCUMENTATION_OF", "GENERATED_FROM", "STATIC_LINK", "OTHER", "BUILD_TOOL_OF", "TEST_CASE_OF", "PACKAGE_OF", "DESCENDANT_OF", "FILE_DELETED", "EXPANDED_FROM_ARCHIVE", "DEV_TOOL_OF", "EXAMPLE_OF" ]
|
||||
},
|
||||
"relatedSpdxElement" : {
|
||||
"description" : "SPDX ID for SpdxElement. A related SpdxElement.",
|
||||
"type" : "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,740 @@
|
||||
{
|
||||
"$schema" : "http://json-schema.org/draft-07/schema#",
|
||||
"$id" : "http://spdx.org/rdf/terms/2.3",
|
||||
"title" : "SPDX 2.3",
|
||||
"type" : "object",
|
||||
"properties" : {
|
||||
"SPDXID" : {
|
||||
"type" : "string",
|
||||
"description" : "Uniquely identify any element in an SPDX document which may be referenced by other elements."
|
||||
},
|
||||
"annotations" : {
|
||||
"description" : "Provide additional information about an SpdxElement.",
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"type" : "object",
|
||||
"properties" : {
|
||||
"annotationDate" : {
|
||||
"description" : "Identify when the comment was made. This is to be specified according to the combined date and time in the UTC format, as specified in the ISO 8601 standard.",
|
||||
"type" : "string"
|
||||
},
|
||||
"annotationType" : {
|
||||
"description" : "Type of the annotation.",
|
||||
"type" : "string",
|
||||
"enum" : [ "OTHER", "REVIEW" ]
|
||||
},
|
||||
"annotator" : {
|
||||
"description" : "This field identifies the person, organization, or tool that has commented on a file, package, snippet, or the entire document.",
|
||||
"type" : "string"
|
||||
},
|
||||
"comment" : {
|
||||
"type" : "string"
|
||||
}
|
||||
},
|
||||
"required" : [ "annotationDate", "annotationType", "annotator", "comment" ],
|
||||
"additionalProperties" : false,
|
||||
"description" : "An Annotation is a comment on an SpdxItem by an agent."
|
||||
}
|
||||
},
|
||||
"comment" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"creationInfo" : {
|
||||
"type" : "object",
|
||||
"properties" : {
|
||||
"comment" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"created" : {
|
||||
"description" : "Identify when the SPDX document was originally created. The date is to be specified according to combined date and time in UTC format as specified in ISO 8601 standard.",
|
||||
"type" : "string"
|
||||
},
|
||||
"creators" : {
|
||||
"description" : "Identify who (or what, in the case of a tool) created the SPDX document. If the SPDX document was created by an individual, indicate the person's name. If the SPDX document was created on behalf of a company or organization, indicate the entity name. If the SPDX document was created using a software tool, indicate the name and version for that tool. If multiple participants or tools were involved, use multiple instances of this field. Person name or organization name may be designated as “anonymous” if appropriate.",
|
||||
"minItems" : 1,
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"description" : "Identify who (or what, in the case of a tool) created the SPDX document. If the SPDX document was created by an individual, indicate the person's name. If the SPDX document was created on behalf of a company or organization, indicate the entity name. If the SPDX document was created using a software tool, indicate the name and version for that tool. If multiple participants or tools were involved, use multiple instances of this field. Person name or organization name may be designated as “anonymous” if appropriate.",
|
||||
"type" : "string"
|
||||
}
|
||||
},
|
||||
"licenseListVersion" : {
|
||||
"description" : "An optional field for creators of the SPDX file to provide the version of the SPDX License List used when the SPDX file was created.",
|
||||
"type" : "string"
|
||||
}
|
||||
},
|
||||
"required" : [ "created", "creators" ],
|
||||
"additionalProperties" : false,
|
||||
"description" : "One instance is required for each SPDX file produced. It provides the necessary information for forward and backward compatibility for processing tools."
|
||||
},
|
||||
"dataLicense" : {
|
||||
"description" : "License expression for dataLicense. See SPDX Annex D for the license expression syntax. Compliance with the SPDX specification includes populating the SPDX fields therein with data related to such fields (\"SPDX-Metadata\"). The SPDX specification contains numerous fields where an SPDX document creator may provide relevant explanatory text in SPDX-Metadata. Without opining on the lawfulness of \"database rights\" (in jurisdictions where applicable), such explanatory text is copyrightable subject matter in most Berne Convention countries. By using the SPDX specification, or any portion hereof, you hereby agree that any copyright rights (as determined by your jurisdiction) in any SPDX-Metadata, including without limitation explanatory text, shall be subject to the terms of the Creative Commons CC0 1.0 Universal license. For SPDX-Metadata not containing any copyright rights, you hereby agree and acknowledge that the SPDX-Metadata is provided to you \"as-is\" and without any representations or warranties of any kind concerning the SPDX-Metadata, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non-infringement, or the absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law.",
|
||||
"type" : "string"
|
||||
},
|
||||
"externalDocumentRefs" : {
|
||||
"description" : "Identify any external SPDX documents referenced within this SPDX document.",
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"type" : "object",
|
||||
"properties" : {
|
||||
"checksum" : {
|
||||
"type" : "object",
|
||||
"properties" : {
|
||||
"algorithm" : {
|
||||
"description" : "Identifies the algorithm used to produce the subject Checksum. Currently, SHA-1 is the only supported algorithm. It is anticipated that other algorithms will be supported at a later time.",
|
||||
"type" : "string",
|
||||
"enum" : [ "SHA1", "BLAKE3", "SHA3-384", "SHA256", "SHA384", "BLAKE2b-512", "BLAKE2b-256", "SHA3-512", "MD2", "ADLER32", "MD4", "SHA3-256", "BLAKE2b-384", "SHA512", "MD6", "MD5", "SHA224" ]
|
||||
},
|
||||
"checksumValue" : {
|
||||
"description" : "The checksumValue property provides a lower case hexidecimal encoded digest value produced using a specific algorithm.",
|
||||
"type" : "string"
|
||||
}
|
||||
},
|
||||
"required" : [ "algorithm", "checksumValue" ],
|
||||
"additionalProperties" : false,
|
||||
"description" : "A Checksum is value that allows the contents of a file to be authenticated. Even small changes to the content of the file will change its checksum. This class allows the results of a variety of checksum and cryptographic message digest algorithms to be represented."
|
||||
},
|
||||
"externalDocumentId" : {
|
||||
"description" : "externalDocumentId is a string containing letters, numbers, ., - and/or + which uniquely identifies an external document within this document.",
|
||||
"type" : "string"
|
||||
},
|
||||
"spdxDocument" : {
|
||||
"description" : "SPDX ID for SpdxDocument. A property containing an SPDX document.",
|
||||
"type" : "string"
|
||||
}
|
||||
},
|
||||
"required" : [ "checksum", "externalDocumentId", "spdxDocument" ],
|
||||
"additionalProperties" : false,
|
||||
"description" : "Information about an external SPDX document reference including the checksum. This allows for verification of the external references."
|
||||
}
|
||||
},
|
||||
"hasExtractedLicensingInfos" : {
|
||||
"description" : "Indicates that a particular ExtractedLicensingInfo was defined in the subject SpdxDocument.",
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"type" : "object",
|
||||
"properties" : {
|
||||
"comment" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"crossRefs" : {
|
||||
"description" : "Cross Reference Detail for a license SeeAlso URL",
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"type" : "object",
|
||||
"properties" : {
|
||||
"isLive" : {
|
||||
"description" : "Indicate a URL is still a live accessible location on the public internet",
|
||||
"type" : "boolean"
|
||||
},
|
||||
"isValid" : {
|
||||
"description" : "True if the URL is a valid well formed URL",
|
||||
"type" : "boolean"
|
||||
},
|
||||
"isWayBackLink" : {
|
||||
"description" : "True if the License SeeAlso URL points to a Wayback archive",
|
||||
"type" : "boolean"
|
||||
},
|
||||
"match" : {
|
||||
"description" : "Status of a License List SeeAlso URL reference if it refers to a website that matches the license text.",
|
||||
"type" : "string"
|
||||
},
|
||||
"order" : {
|
||||
"description" : "The ordinal order of this element within a list",
|
||||
"type" : "integer"
|
||||
},
|
||||
"timestamp" : {
|
||||
"description" : "Timestamp",
|
||||
"type" : "string"
|
||||
},
|
||||
"url" : {
|
||||
"description" : "URL Reference",
|
||||
"type" : "string"
|
||||
}
|
||||
},
|
||||
"required" : [ "url" ],
|
||||
"additionalProperties" : false,
|
||||
"description" : "Cross reference details for the a URL reference"
|
||||
}
|
||||
},
|
||||
"extractedText" : {
|
||||
"description" : "Provide a copy of the actual text of the license reference extracted from the package, file or snippet that is associated with the License Identifier to aid in future analysis.",
|
||||
"type" : "string"
|
||||
},
|
||||
"licenseId" : {
|
||||
"description" : "A human readable short form license identifier for a license. The license ID is either on the standard license list or the form \"LicenseRef-[idString]\" where [idString] is a unique string containing letters, numbers, \".\" or \"-\". When used within a license expression, the license ID can optionally include a reference to an external document in the form \"DocumentRef-[docrefIdString]:LicenseRef-[idString]\" where docRefIdString is an ID for an external document reference.",
|
||||
"type" : "string"
|
||||
},
|
||||
"name" : {
|
||||
"description" : "Identify name of this SpdxElement.",
|
||||
"type" : "string"
|
||||
},
|
||||
"seeAlsos" : {
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"type" : "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required" : [ "extractedText", "licenseId" ],
|
||||
"additionalProperties" : false,
|
||||
"description" : "An ExtractedLicensingInfo represents a license or licensing notice that was found in a package, file or snippet. Any license text that is recognized as a license may be represented as a License rather than an ExtractedLicensingInfo."
|
||||
}
|
||||
},
|
||||
"name" : {
|
||||
"description" : "Identify name of this SpdxElement.",
|
||||
"type" : "string"
|
||||
},
|
||||
"revieweds" : {
|
||||
"description" : "Reviewed",
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"type" : "object",
|
||||
"properties" : {
|
||||
"comment" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"reviewDate" : {
|
||||
"description" : "The date and time at which the SpdxDocument was reviewed. This value must be in UTC and have 'Z' as its timezone indicator.",
|
||||
"type" : "string"
|
||||
},
|
||||
"reviewer" : {
|
||||
"description" : "The name and, optionally, contact information of the person who performed the review. Values of this property must conform to the agent and tool syntax. The reviewer property is deprecated in favor of Annotation with an annotationType review.",
|
||||
"type" : "string"
|
||||
}
|
||||
},
|
||||
"required" : [ "reviewDate" ],
|
||||
"additionalProperties" : false,
|
||||
"description" : "This class has been deprecated in favor of an Annotation with an Annotation type of review."
|
||||
}
|
||||
},
|
||||
"spdxVersion" : {
|
||||
"description" : "Provide a reference number that can be used to understand how to parse and interpret the rest of the file. It will enable both future changes to the specification and to support backward compatibility. The version number consists of a major and minor version indicator. The major field will be incremented when incompatible changes between versions are made (one or more sections are created, modified or deleted). The minor field will be incremented when backwards compatible changes are made.",
|
||||
"type" : "string"
|
||||
},
|
||||
"documentNamespace" : {
|
||||
"type" : "string",
|
||||
"description" : "The URI provides an unambiguous mechanism for other SPDX documents to reference SPDX elements within this SPDX document."
|
||||
},
|
||||
"documentDescribes" : {
|
||||
"description" : "Packages, files and/or Snippets described by this SPDX document",
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"type" : "string",
|
||||
"description" : "SPDX ID for each Package, File, or Snippet."
|
||||
}
|
||||
},
|
||||
"packages" : {
|
||||
"description" : "Packages referenced in the SPDX document",
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"type" : "object",
|
||||
"properties" : {
|
||||
"SPDXID" : {
|
||||
"type" : "string",
|
||||
"description" : "Uniquely identify any element in an SPDX document which may be referenced by other elements."
|
||||
},
|
||||
"annotations" : {
|
||||
"description" : "Provide additional information about an SpdxElement.",
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"type" : "object",
|
||||
"properties" : {
|
||||
"annotationDate" : {
|
||||
"description" : "Identify when the comment was made. This is to be specified according to the combined date and time in the UTC format, as specified in the ISO 8601 standard.",
|
||||
"type" : "string"
|
||||
},
|
||||
"annotationType" : {
|
||||
"description" : "Type of the annotation.",
|
||||
"type" : "string",
|
||||
"enum" : [ "OTHER", "REVIEW" ]
|
||||
},
|
||||
"annotator" : {
|
||||
"description" : "This field identifies the person, organization, or tool that has commented on a file, package, snippet, or the entire document.",
|
||||
"type" : "string"
|
||||
},
|
||||
"comment" : {
|
||||
"type" : "string"
|
||||
}
|
||||
},
|
||||
"required" : [ "annotationDate", "annotationType", "annotator", "comment" ],
|
||||
"additionalProperties" : false,
|
||||
"description" : "An Annotation is a comment on an SpdxItem by an agent."
|
||||
}
|
||||
},
|
||||
"attributionTexts" : {
|
||||
"description" : "This field provides a place for the SPDX data creator to record acknowledgements that may be required to be communicated in some contexts. This is not meant to include the actual complete license text (see licenseConculded and licenseDeclared), and may or may not include copyright notices (see also copyrightText). The SPDX data creator may use this field to record other acknowledgements, such as particular clauses from license texts, which may be necessary or desirable to reproduce.",
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"description" : "This field provides a place for the SPDX data creator to record acknowledgements that may be required to be communicated in some contexts. This is not meant to include the actual complete license text (see licenseConculded and licenseDeclared), and may or may not include copyright notices (see also copyrightText). The SPDX data creator may use this field to record other acknowledgements, such as particular clauses from license texts, which may be necessary or desirable to reproduce.",
|
||||
"type" : "string"
|
||||
}
|
||||
},
|
||||
"builtDate" : {
|
||||
"description" : "This field provides a place for recording the actual date the package was built.",
|
||||
"type" : "string"
|
||||
},
|
||||
"checksums" : {
|
||||
"description" : "The checksum property provides a mechanism that can be used to verify that the contents of a File or Package have not changed.",
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"type" : "object",
|
||||
"properties" : {
|
||||
"algorithm" : {
|
||||
"description" : "Identifies the algorithm used to produce the subject Checksum. Currently, SHA-1 is the only supported algorithm. It is anticipated that other algorithms will be supported at a later time.",
|
||||
"type" : "string",
|
||||
"enum" : [ "SHA1", "BLAKE3", "SHA3-384", "SHA256", "SHA384", "BLAKE2b-512", "BLAKE2b-256", "SHA3-512", "MD2", "ADLER32", "MD4", "SHA3-256", "BLAKE2b-384", "SHA512", "MD6", "MD5", "SHA224" ]
|
||||
},
|
||||
"checksumValue" : {
|
||||
"description" : "The checksumValue property provides a lower case hexidecimal encoded digest value produced using a specific algorithm.",
|
||||
"type" : "string"
|
||||
}
|
||||
},
|
||||
"required" : [ "algorithm", "checksumValue" ],
|
||||
"additionalProperties" : false,
|
||||
"description" : "A Checksum is value that allows the contents of a file to be authenticated. Even small changes to the content of the file will change its checksum. This class allows the results of a variety of checksum and cryptographic message digest algorithms to be represented."
|
||||
}
|
||||
},
|
||||
"comment" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"copyrightText" : {
|
||||
"description" : "The text of copyright declarations recited in the package, file or snippet.\n\nIf the copyrightText field is not present, it implies an equivalent meaning to NOASSERTION.",
|
||||
"type" : "string"
|
||||
},
|
||||
"description" : {
|
||||
"description" : "Provides a detailed description of the package.",
|
||||
"type" : "string"
|
||||
},
|
||||
"downloadLocation" : {
|
||||
"description" : "The URI at which this package is available for download. Private (i.e., not publicly reachable) URIs are acceptable as values of this property. The values http://spdx.org/rdf/terms#none and http://spdx.org/rdf/terms#noassertion may be used to specify that the package is not downloadable or that no attempt was made to determine its download location, respectively.",
|
||||
"type" : "string"
|
||||
},
|
||||
"externalRefs" : {
|
||||
"description" : "An External Reference allows a Package to reference an external source of additional information, metadata, enumerations, asset identifiers, or downloadable content believed to be relevant to the Package.",
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"type" : "object",
|
||||
"properties" : {
|
||||
"comment" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"referenceCategory" : {
|
||||
"description" : "Category for the external reference",
|
||||
"type" : "string",
|
||||
"enum" : [ "OTHER", "PERSISTENT-ID", "PERSISTENT_ID", "SECURITY", "PACKAGE-MANAGER", "PACKAGE_MANAGER" ]
|
||||
},
|
||||
"referenceLocator" : {
|
||||
"description" : "The unique string with no spaces necessary to access the package-specific information, metadata, or content within the target location. The format of the locator is subject to constraints defined by the <type>.",
|
||||
"type" : "string"
|
||||
},
|
||||
"referenceType" : {
|
||||
"description" : "Type of the external reference. These are defined in an appendix in the SPDX specification.",
|
||||
"type" : "string"
|
||||
}
|
||||
},
|
||||
"required" : [ "referenceCategory", "referenceLocator", "referenceType" ],
|
||||
"additionalProperties" : false,
|
||||
"description" : "An External Reference allows a Package to reference an external source of additional information, metadata, enumerations, asset identifiers, or downloadable content believed to be relevant to the Package."
|
||||
}
|
||||
},
|
||||
"filesAnalyzed" : {
|
||||
"description" : "Indicates whether the file content of this package has been available for or subjected to analysis when creating the SPDX document. If false indicates packages that represent metadata or URI references to a project, product, artifact, distribution or a component. If set to false, the package must not contain any files.",
|
||||
"type" : "boolean"
|
||||
},
|
||||
"hasFiles" : {
|
||||
"description" : "Indicates that a particular file belongs to a package.",
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"description" : "SPDX ID for File. Indicates that a particular file belongs to a package.",
|
||||
"type" : "string"
|
||||
}
|
||||
},
|
||||
"homepage" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"licenseComments" : {
|
||||
"description" : "The licenseComments property allows the preparer of the SPDX document to describe why the licensing in spdx:licenseConcluded was chosen.",
|
||||
"type" : "string"
|
||||
},
|
||||
"licenseConcluded" : {
|
||||
"description" : "License expression for licenseConcluded. See SPDX Annex D for the license expression syntax. The licensing that the preparer of this SPDX document has concluded, based on the evidence, actually applies to the SPDX Item.\n\nIf the licenseConcluded field is not present for an SPDX Item, it implies an equivalent meaning to NOASSERTION.",
|
||||
"type" : "string"
|
||||
},
|
||||
"licenseDeclared" : {
|
||||
"description" : "License expression for licenseDeclared. See SPDX Annex D for the license expression syntax. The licensing that the creators of the software in the package, or the packager, have declared. Declarations by the original software creator should be preferred, if they exist.",
|
||||
"type" : "string"
|
||||
},
|
||||
"licenseInfoFromFiles" : {
|
||||
"description" : "The licensing information that was discovered directly within the package. There will be an instance of this property for each distinct value of alllicenseInfoInFile properties of all files contained in the package.\n\nIf the licenseInfoFromFiles field is not present for a package and filesAnalyzed property for that same package is true or omitted, it implies an equivalent meaning to NOASSERTION.",
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"description" : "License expression for licenseInfoFromFiles. See SPDX Annex D for the license expression syntax. The licensing information that was discovered directly within the package. There will be an instance of this property for each distinct value of alllicenseInfoInFile properties of all files contained in the package.\n\nIf the licenseInfoFromFiles field is not present for a package and filesAnalyzed property for that same package is true or omitted, it implies an equivalent meaning to NOASSERTION.",
|
||||
"type" : "string"
|
||||
}
|
||||
},
|
||||
"name" : {
|
||||
"description" : "Identify name of this SpdxElement.",
|
||||
"type" : "string"
|
||||
},
|
||||
"originator" : {
|
||||
"description" : "The name and, optionally, contact information of the person or organization that originally created the package. Values of this property must conform to the agent and tool syntax.",
|
||||
"type" : "string"
|
||||
},
|
||||
"packageFileName" : {
|
||||
"description" : "The base name of the package file name. For example, zlib-1.2.5.tar.gz.",
|
||||
"type" : "string"
|
||||
},
|
||||
"packageVerificationCode" : {
|
||||
"type" : "object",
|
||||
"properties" : {
|
||||
"packageVerificationCodeExcludedFiles" : {
|
||||
"description" : "A file that was excluded when calculating the package verification code. This is usually a file containing SPDX data regarding the package. If a package contains more than one SPDX file all SPDX files must be excluded from the package verification code. If this is not done it would be impossible to correctly calculate the verification codes in both files.",
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"description" : "A file that was excluded when calculating the package verification code. This is usually a file containing SPDX data regarding the package. If a package contains more than one SPDX file all SPDX files must be excluded from the package verification code. If this is not done it would be impossible to correctly calculate the verification codes in both files.",
|
||||
"type" : "string"
|
||||
}
|
||||
},
|
||||
"packageVerificationCodeValue" : {
|
||||
"description" : "The actual package verification code as a hex encoded value.",
|
||||
"type" : "string"
|
||||
}
|
||||
},
|
||||
"required" : [ "packageVerificationCodeValue" ],
|
||||
"additionalProperties" : false,
|
||||
"description" : "A manifest based verification code (the algorithm is defined in section 4.7 of the full specification) of the SPDX Item. This allows consumers of this data and/or database to determine if an SPDX item they have in hand is identical to the SPDX item from which the data was produced. This algorithm works even if the SPDX document is included in the SPDX item."
|
||||
},
|
||||
"primaryPackagePurpose" : {
|
||||
"description" : "This field provides information about the primary purpose of the identified package. Package Purpose is intrinsic to how the package is being used rather than the content of the package.",
|
||||
"type" : "string",
|
||||
"enum" : [ "OTHER", "INSTALL", "ARCHIVE", "FIRMWARE", "APPLICATION", "FRAMEWORK", "LIBRARY", "CONTAINER", "SOURCE", "DEVICE", "OPERATING_SYSTEM", "FILE" ]
|
||||
},
|
||||
"releaseDate" : {
|
||||
"description" : "This field provides a place for recording the date the package was released.",
|
||||
"type" : "string"
|
||||
},
|
||||
"sourceInfo" : {
|
||||
"description" : "Allows the producer(s) of the SPDX document to describe how the package was acquired and/or changed from the original source.",
|
||||
"type" : "string"
|
||||
},
|
||||
"summary" : {
|
||||
"description" : "Provides a short description of the package.",
|
||||
"type" : "string"
|
||||
},
|
||||
"supplier" : {
|
||||
"description" : "The name and, optionally, contact information of the person or organization who was the immediate supplier of this package to the recipient. The supplier may be different than originator when the software has been repackaged. Values of this property must conform to the agent and tool syntax.",
|
||||
"type" : "string"
|
||||
},
|
||||
"validUntilDate" : {
|
||||
"description" : "This field provides a place for recording the end of the support period for a package from the supplier.",
|
||||
"type" : "string"
|
||||
},
|
||||
"versionInfo" : {
|
||||
"description" : "Provides an indication of the version of the package that is described by this SpdxDocument.",
|
||||
"type" : "string"
|
||||
}
|
||||
},
|
||||
"required" : [ "SPDXID", "downloadLocation", "name" ],
|
||||
"additionalProperties" : false
|
||||
}
|
||||
},
|
||||
"files" : {
|
||||
"description" : "Files referenced in the SPDX document",
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"type" : "object",
|
||||
"properties" : {
|
||||
"SPDXID" : {
|
||||
"type" : "string",
|
||||
"description" : "Uniquely identify any element in an SPDX document which may be referenced by other elements."
|
||||
},
|
||||
"annotations" : {
|
||||
"description" : "Provide additional information about an SpdxElement.",
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"type" : "object",
|
||||
"properties" : {
|
||||
"annotationDate" : {
|
||||
"description" : "Identify when the comment was made. This is to be specified according to the combined date and time in the UTC format, as specified in the ISO 8601 standard.",
|
||||
"type" : "string"
|
||||
},
|
||||
"annotationType" : {
|
||||
"description" : "Type of the annotation.",
|
||||
"type" : "string",
|
||||
"enum" : [ "OTHER", "REVIEW" ]
|
||||
},
|
||||
"annotator" : {
|
||||
"description" : "This field identifies the person, organization, or tool that has commented on a file, package, snippet, or the entire document.",
|
||||
"type" : "string"
|
||||
},
|
||||
"comment" : {
|
||||
"type" : "string"
|
||||
}
|
||||
},
|
||||
"required" : [ "annotationDate", "annotationType", "annotator", "comment" ],
|
||||
"additionalProperties" : false,
|
||||
"description" : "An Annotation is a comment on an SpdxItem by an agent."
|
||||
}
|
||||
},
|
||||
"artifactOfs" : {
|
||||
"description" : "Indicates the project in which the SpdxElement originated. Tools must preserve doap:homepage and doap:name properties and the URI (if one is known) of doap:Project resources that are values of this property. All other properties of doap:Projects are not directly supported by SPDX and may be dropped when translating to or from some SPDX formats.",
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"type" : "object"
|
||||
}
|
||||
},
|
||||
"attributionTexts" : {
|
||||
"description" : "This field provides a place for the SPDX data creator to record acknowledgements that may be required to be communicated in some contexts. This is not meant to include the actual complete license text (see licenseConculded and licenseDeclared), and may or may not include copyright notices (see also copyrightText). The SPDX data creator may use this field to record other acknowledgements, such as particular clauses from license texts, which may be necessary or desirable to reproduce.",
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"description" : "This field provides a place for the SPDX data creator to record acknowledgements that may be required to be communicated in some contexts. This is not meant to include the actual complete license text (see licenseConculded and licenseDeclared), and may or may not include copyright notices (see also copyrightText). The SPDX data creator may use this field to record other acknowledgements, such as particular clauses from license texts, which may be necessary or desirable to reproduce.",
|
||||
"type" : "string"
|
||||
}
|
||||
},
|
||||
"checksums" : {
|
||||
"description" : "The checksum property provides a mechanism that can be used to verify that the contents of a File or Package have not changed.",
|
||||
"minItems" : 1,
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"type" : "object",
|
||||
"properties" : {
|
||||
"algorithm" : {
|
||||
"description" : "Identifies the algorithm used to produce the subject Checksum. Currently, SHA-1 is the only supported algorithm. It is anticipated that other algorithms will be supported at a later time.",
|
||||
"type" : "string",
|
||||
"enum" : [ "SHA1", "BLAKE3", "SHA3-384", "SHA256", "SHA384", "BLAKE2b-512", "BLAKE2b-256", "SHA3-512", "MD2", "ADLER32", "MD4", "SHA3-256", "BLAKE2b-384", "SHA512", "MD6", "MD5", "SHA224" ]
|
||||
},
|
||||
"checksumValue" : {
|
||||
"description" : "The checksumValue property provides a lower case hexidecimal encoded digest value produced using a specific algorithm.",
|
||||
"type" : "string"
|
||||
}
|
||||
},
|
||||
"required" : [ "algorithm", "checksumValue" ],
|
||||
"additionalProperties" : false,
|
||||
"description" : "A Checksum is value that allows the contents of a file to be authenticated. Even small changes to the content of the file will change its checksum. This class allows the results of a variety of checksum and cryptographic message digest algorithms to be represented."
|
||||
}
|
||||
},
|
||||
"comment" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"copyrightText" : {
|
||||
"description" : "The text of copyright declarations recited in the package, file or snippet.\n\nIf the copyrightText field is not present, it implies an equivalent meaning to NOASSERTION.",
|
||||
"type" : "string"
|
||||
},
|
||||
"fileContributors" : {
|
||||
"description" : "This field provides a place for the SPDX file creator to record file contributors. Contributors could include names of copyright holders and/or authors who may not be copyright holders yet contributed to the file content.",
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"description" : "This field provides a place for the SPDX file creator to record file contributors. Contributors could include names of copyright holders and/or authors who may not be copyright holders yet contributed to the file content.",
|
||||
"type" : "string"
|
||||
}
|
||||
},
|
||||
"fileDependencies" : {
|
||||
"description" : "This field is deprecated since SPDX 2.0 in favor of using Section 7 which provides more granularity about relationships.",
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"description" : "SPDX ID for File. This field is deprecated since SPDX 2.0 in favor of using Section 7 which provides more granularity about relationships.",
|
||||
"type" : "string"
|
||||
}
|
||||
},
|
||||
"fileName" : {
|
||||
"description" : "The name of the file relative to the root of the package.",
|
||||
"type" : "string"
|
||||
},
|
||||
"fileTypes" : {
|
||||
"description" : "The type of the file.",
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"description" : "The type of the file.",
|
||||
"type" : "string",
|
||||
"enum" : [ "OTHER", "DOCUMENTATION", "IMAGE", "VIDEO", "ARCHIVE", "SPDX", "APPLICATION", "SOURCE", "BINARY", "TEXT", "AUDIO" ]
|
||||
}
|
||||
},
|
||||
"licenseComments" : {
|
||||
"description" : "The licenseComments property allows the preparer of the SPDX document to describe why the licensing in spdx:licenseConcluded was chosen.",
|
||||
"type" : "string"
|
||||
},
|
||||
"licenseConcluded" : {
|
||||
"description" : "License expression for licenseConcluded. See SPDX Annex D for the license expression syntax. The licensing that the preparer of this SPDX document has concluded, based on the evidence, actually applies to the SPDX Item.\n\nIf the licenseConcluded field is not present for an SPDX Item, it implies an equivalent meaning to NOASSERTION.",
|
||||
"type" : "string"
|
||||
},
|
||||
"licenseInfoInFiles" : {
|
||||
"description" : "Licensing information that was discovered directly in the subject file. This is also considered a declared license for the file.\n\nIf the licenseInfoInFile field is not present for a file, it implies an equivalent meaning to NOASSERTION.",
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"description" : "License expression for licenseInfoInFile. See SPDX Annex D for the license expression syntax. Licensing information that was discovered directly in the subject file. This is also considered a declared license for the file.\n\nIf the licenseInfoInFile field is not present for a file, it implies an equivalent meaning to NOASSERTION.",
|
||||
"type" : "string"
|
||||
}
|
||||
},
|
||||
"noticeText" : {
|
||||
"description" : "This field provides a place for the SPDX file creator to record potential legal notices found in the file. This may or may not include copyright statements.",
|
||||
"type" : "string"
|
||||
}
|
||||
},
|
||||
"required" : [ "SPDXID", "checksums", "fileName" ],
|
||||
"additionalProperties" : false
|
||||
}
|
||||
},
|
||||
"snippets" : {
|
||||
"description" : "Snippets referenced in the SPDX document",
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"type" : "object",
|
||||
"properties" : {
|
||||
"SPDXID" : {
|
||||
"type" : "string",
|
||||
"description" : "Uniquely identify any element in an SPDX document which may be referenced by other elements."
|
||||
},
|
||||
"annotations" : {
|
||||
"description" : "Provide additional information about an SpdxElement.",
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"type" : "object",
|
||||
"properties" : {
|
||||
"annotationDate" : {
|
||||
"description" : "Identify when the comment was made. This is to be specified according to the combined date and time in the UTC format, as specified in the ISO 8601 standard.",
|
||||
"type" : "string"
|
||||
},
|
||||
"annotationType" : {
|
||||
"description" : "Type of the annotation.",
|
||||
"type" : "string",
|
||||
"enum" : [ "OTHER", "REVIEW" ]
|
||||
},
|
||||
"annotator" : {
|
||||
"description" : "This field identifies the person, organization, or tool that has commented on a file, package, snippet, or the entire document.",
|
||||
"type" : "string"
|
||||
},
|
||||
"comment" : {
|
||||
"type" : "string"
|
||||
}
|
||||
},
|
||||
"required" : [ "annotationDate", "annotationType", "annotator", "comment" ],
|
||||
"additionalProperties" : false,
|
||||
"description" : "An Annotation is a comment on an SpdxItem by an agent."
|
||||
}
|
||||
},
|
||||
"attributionTexts" : {
|
||||
"description" : "This field provides a place for the SPDX data creator to record acknowledgements that may be required to be communicated in some contexts. This is not meant to include the actual complete license text (see licenseConculded and licenseDeclared), and may or may not include copyright notices (see also copyrightText). The SPDX data creator may use this field to record other acknowledgements, such as particular clauses from license texts, which may be necessary or desirable to reproduce.",
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"description" : "This field provides a place for the SPDX data creator to record acknowledgements that may be required to be communicated in some contexts. This is not meant to include the actual complete license text (see licenseConculded and licenseDeclared), and may or may not include copyright notices (see also copyrightText). The SPDX data creator may use this field to record other acknowledgements, such as particular clauses from license texts, which may be necessary or desirable to reproduce.",
|
||||
"type" : "string"
|
||||
}
|
||||
},
|
||||
"comment" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"copyrightText" : {
|
||||
"description" : "The text of copyright declarations recited in the package, file or snippet.\n\nIf the copyrightText field is not present, it implies an equivalent meaning to NOASSERTION.",
|
||||
"type" : "string"
|
||||
},
|
||||
"licenseComments" : {
|
||||
"description" : "The licenseComments property allows the preparer of the SPDX document to describe why the licensing in spdx:licenseConcluded was chosen.",
|
||||
"type" : "string"
|
||||
},
|
||||
"licenseConcluded" : {
|
||||
"description" : "License expression for licenseConcluded. See SPDX Annex D for the license expression syntax. The licensing that the preparer of this SPDX document has concluded, based on the evidence, actually applies to the SPDX Item.\n\nIf the licenseConcluded field is not present for an SPDX Item, it implies an equivalent meaning to NOASSERTION.",
|
||||
"type" : "string"
|
||||
},
|
||||
"licenseInfoInSnippets" : {
|
||||
"description" : "Licensing information that was discovered directly in the subject snippet. This is also considered a declared license for the snippet.\n\nIf the licenseInfoInSnippet field is not present for a snippet, it implies an equivalent meaning to NOASSERTION.",
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"description" : "License expression for licenseInfoInSnippet. See SPDX Annex D for the license expression syntax. Licensing information that was discovered directly in the subject snippet. This is also considered a declared license for the snippet.\n\nIf the licenseInfoInSnippet field is not present for a snippet, it implies an equivalent meaning to NOASSERTION.",
|
||||
"type" : "string"
|
||||
}
|
||||
},
|
||||
"name" : {
|
||||
"description" : "Identify name of this SpdxElement.",
|
||||
"type" : "string"
|
||||
},
|
||||
"ranges" : {
|
||||
"description" : "This field defines the byte range in the original host file (in X.2) that the snippet information applies to",
|
||||
"minItems" : 1,
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"type" : "object",
|
||||
"properties" : {
|
||||
"endPointer" : {
|
||||
"type" : "object",
|
||||
"properties" : {
|
||||
"reference" : {
|
||||
"description" : "SPDX ID for File",
|
||||
"type" : "string"
|
||||
},
|
||||
"offset" : {
|
||||
"type" : "integer",
|
||||
"description" : "Byte offset in the file"
|
||||
},
|
||||
"lineNumber" : {
|
||||
"type" : "integer",
|
||||
"description" : "line number offset in the file"
|
||||
}
|
||||
},
|
||||
"required" : [ "reference" ],
|
||||
"additionalProperties" : false
|
||||
},
|
||||
"startPointer" : {
|
||||
"type" : "object",
|
||||
"properties" : {
|
||||
"reference" : {
|
||||
"description" : "SPDX ID for File",
|
||||
"type" : "string"
|
||||
},
|
||||
"offset" : {
|
||||
"type" : "integer",
|
||||
"description" : "Byte offset in the file"
|
||||
},
|
||||
"lineNumber" : {
|
||||
"type" : "integer",
|
||||
"description" : "line number offset in the file"
|
||||
}
|
||||
},
|
||||
"required" : [ "reference" ],
|
||||
"additionalProperties" : false
|
||||
}
|
||||
},
|
||||
"required" : [ "endPointer", "startPointer" ],
|
||||
"additionalProperties" : false
|
||||
}
|
||||
},
|
||||
"snippetFromFile" : {
|
||||
"description" : "SPDX ID for File. File containing the SPDX element (e.g. the file contaning a snippet).",
|
||||
"type" : "string"
|
||||
}
|
||||
},
|
||||
"required" : [ "SPDXID", "name", "ranges", "snippetFromFile" ],
|
||||
"additionalProperties" : false
|
||||
}
|
||||
},
|
||||
"relationships" : {
|
||||
"description" : "Relationships referenced in the SPDX document",
|
||||
"type" : "array",
|
||||
"items" : {
|
||||
"type" : "object",
|
||||
"properties" : {
|
||||
"spdxElementId" : {
|
||||
"type" : "string",
|
||||
"description" : "Id to which the SPDX element is related"
|
||||
},
|
||||
"comment" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"relatedSpdxElement" : {
|
||||
"description" : "SPDX ID for SpdxElement. A related SpdxElement.",
|
||||
"type" : "string"
|
||||
},
|
||||
"relationshipType" : {
|
||||
"description" : "Describes the type of relationship between two SPDX elements.",
|
||||
"type" : "string",
|
||||
"enum" : [ "VARIANT_OF", "COPY_OF", "PATCH_FOR", "TEST_DEPENDENCY_OF", "CONTAINED_BY", "DATA_FILE_OF", "OPTIONAL_COMPONENT_OF", "ANCESTOR_OF", "GENERATES", "CONTAINS", "OPTIONAL_DEPENDENCY_OF", "FILE_ADDED", "REQUIREMENT_DESCRIPTION_FOR", "DEV_DEPENDENCY_OF", "DEPENDENCY_OF", "BUILD_DEPENDENCY_OF", "DESCRIBES", "PREREQUISITE_FOR", "HAS_PREREQUISITE", "PROVIDED_DEPENDENCY_OF", "DYNAMIC_LINK", "DESCRIBED_BY", "METAFILE_OF", "DEPENDENCY_MANIFEST_OF", "PATCH_APPLIED", "RUNTIME_DEPENDENCY_OF", "TEST_OF", "TEST_TOOL_OF", "DEPENDS_ON", "SPECIFICATION_FOR", "FILE_MODIFIED", "DISTRIBUTION_ARTIFACT", "AMENDS", "DOCUMENTATION_OF", "GENERATED_FROM", "STATIC_LINK", "OTHER", "BUILD_TOOL_OF", "TEST_CASE_OF", "PACKAGE_OF", "DESCENDANT_OF", "FILE_DELETED", "EXPANDED_FROM_ARCHIVE", "DEV_TOOL_OF", "EXAMPLE_OF" ]
|
||||
}
|
||||
},
|
||||
"required" : [ "spdxElementId", "relatedSpdxElement", "relationshipType" ],
|
||||
"additionalProperties" : false
|
||||
}
|
||||
}
|
||||
},
|
||||
"required" : [ "SPDXID", "creationInfo", "dataLicense", "name", "spdxVersion", "documentNamespace" ],
|
||||
"additionalProperties" : false
|
||||
}
|
||||
@@ -17,6 +17,15 @@ const (
|
||||
DescribedByRelationship RelationshipType = "described-by"
|
||||
)
|
||||
|
||||
func AllRelationshipTypes() []RelationshipType {
|
||||
return []RelationshipType{
|
||||
OwnershipByFileOverlapRelationship,
|
||||
ContainsRelationship,
|
||||
DependencyOfRelationship,
|
||||
DescribedByRelationship,
|
||||
}
|
||||
}
|
||||
|
||||
type RelationshipType string
|
||||
|
||||
type Relationship struct {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package pkg
|
||||
package cpe
|
||||
|
||||
import (
|
||||
"sort"
|
||||
@@ -6,15 +6,15 @@ import (
|
||||
"github.com/facebookincubator/nvdtools/wfn"
|
||||
)
|
||||
|
||||
var _ sort.Interface = (*CPEBySpecificity)(nil)
|
||||
var _ sort.Interface = (*BySpecificity)(nil)
|
||||
|
||||
type CPEBySpecificity []wfn.Attributes
|
||||
type BySpecificity []wfn.Attributes
|
||||
|
||||
func (c CPEBySpecificity) Len() int { return len(c) }
|
||||
func (c BySpecificity) Len() int { return len(c) }
|
||||
|
||||
func (c CPEBySpecificity) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
|
||||
func (c BySpecificity) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
|
||||
|
||||
func (c CPEBySpecificity) Less(i, j int) bool {
|
||||
func (c BySpecificity) Less(i, j int) bool {
|
||||
iScore := weightedCountForSpecifiedFields(c[i])
|
||||
jScore := weightedCountForSpecifiedFields(c[j])
|
||||
|
||||
@@ -29,7 +29,7 @@ func (c CPEBySpecificity) Less(i, j int) bool {
|
||||
}
|
||||
|
||||
// if score and length are equal then text sort
|
||||
// note that we are not using CPEString from the syft pkg
|
||||
// note that we are not using String from the syft pkg
|
||||
// as we are not encoding/decoding this CPE string so we don't
|
||||
// need the proper quoted version of the CPE.
|
||||
return c[i].BindToFmtString() < c[j].BindToFmtString()
|
||||
@@ -0,0 +1,98 @@
|
||||
package cpe
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_BySpecificity(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input []CPE
|
||||
expected []CPE
|
||||
}{
|
||||
{
|
||||
name: "sort strictly by wfn *",
|
||||
input: []CPE{
|
||||
Must("cpe:2.3:a:*:package:1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:some:package:1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:*:package:1:*:*:*:*:some:*:*"),
|
||||
Must("cpe:2.3:a:some:package:1:*:*:*:*:some:*:*"),
|
||||
Must("cpe:2.3:a:some:package:*:*:*:*:*:*:*:*"),
|
||||
},
|
||||
expected: []CPE{
|
||||
Must("cpe:2.3:a:some:package:1:*:*:*:*:some:*:*"),
|
||||
Must("cpe:2.3:a:some:package:1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:some:package:*:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:*:package:1:*:*:*:*:some:*:*"),
|
||||
Must("cpe:2.3:a:*:package:1:*:*:*:*:*:*:*"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sort strictly by field length",
|
||||
input: []CPE{
|
||||
Must("cpe:2.3:a:1:22:1:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:55555:1:1:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:1:1:333:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:1:666666:1:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:1:1:1:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:1:1:1:*:*:*:*:4444:*:*"),
|
||||
},
|
||||
expected: []CPE{
|
||||
Must("cpe:2.3:a:1:666666:1:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:55555:1:1:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:1:1:1:*:*:*:*:4444:*:*"),
|
||||
Must("cpe:2.3:a:1:1:333:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:1:22:1:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:1:1:1:*:*:*:*:1:*:*"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sort by mix of field length and specificity",
|
||||
input: []CPE{
|
||||
Must("cpe:2.3:a:1:666666:*:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:*:1:1:*:*:*:*:4444:*:*"),
|
||||
Must("cpe:2.3:a:1:*:333:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:1:1:1:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:1:22:1:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:55555:1:1:*:*:*:*:1:*:*"),
|
||||
},
|
||||
expected: []CPE{
|
||||
Must("cpe:2.3:a:55555:1:1:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:1:22:1:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:1:1:1:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:1:666666:*:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:*:1:1:*:*:*:*:4444:*:*"),
|
||||
Must("cpe:2.3:a:1:*:333:*:*:*:*:*:*:*"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sort by mix of field length, specificity, dash",
|
||||
input: []CPE{
|
||||
Must("cpe:2.3:a:alpine:alpine_keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:alpine_keys:alpine_keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:alpine-keys:alpine_keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:alpine:alpine-keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:alpine-keys:alpine-keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:alpine_keys:alpine-keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
},
|
||||
expected: []CPE{
|
||||
Must("cpe:2.3:a:alpine-keys:alpine-keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:alpine-keys:alpine_keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:alpine_keys:alpine-keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:alpine_keys:alpine_keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:alpine:alpine-keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:alpine:alpine_keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
sort.Sort(BySpecificity(test.input))
|
||||
assert.Equal(t, test.expected, test.input)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package pkg
|
||||
package cpe
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -24,17 +24,17 @@ const cpeRegexString = ((`^([c][pP][eE]:/[AHOaho]?(:[A-Za-z0-9\._\-~%]*){0,6})`)
|
||||
|
||||
var cpeRegex = regexp.MustCompile(cpeRegexString)
|
||||
|
||||
// NewCPE will parse a formatted CPE string and return a CPE object. Some input, such as the existence of whitespace
|
||||
// New will parse a formatted CPE string and return a CPE object. Some input, such as the existence of whitespace
|
||||
// characters is allowed, however, a more strict validation is done after this sanitization process.
|
||||
func NewCPE(cpeStr string) (CPE, error) {
|
||||
func New(cpeStr string) (CPE, error) {
|
||||
// get a CPE object based on the given string --don't validate yet since it may be possible to escape select cases on the callers behalf
|
||||
c, err := newCPEWithoutValidation(cpeStr)
|
||||
c, err := newWithoutValidation(cpeStr)
|
||||
if err != nil {
|
||||
return CPE{}, fmt.Errorf("unable to parse CPE string: %w", err)
|
||||
}
|
||||
|
||||
// ensure that this CPE can be validated after being fully sanitized
|
||||
if ValidateCPEString(CPEString(c)) != nil {
|
||||
if ValidateString(String(c)) != nil {
|
||||
return CPE{}, err
|
||||
}
|
||||
|
||||
@@ -43,7 +43,16 @@ func NewCPE(cpeStr string) (CPE, error) {
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func ValidateCPEString(cpeStr string) error {
|
||||
// Must returns a CPE or panics if the provided string is not valid
|
||||
func Must(cpeStr string) CPE {
|
||||
c, err := New(cpeStr)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func ValidateString(cpeStr string) error {
|
||||
// We should filter out all CPEs that do not match the official CPE regex
|
||||
// The facebook nvdtools parser can sometimes incorrectly parse invalid CPE strings
|
||||
if !cpeRegex.MatchString(cpeStr) {
|
||||
@@ -52,7 +61,7 @@ func ValidateCPEString(cpeStr string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func newCPEWithoutValidation(cpeStr string) (CPE, error) {
|
||||
func newWithoutValidation(cpeStr string) (CPE, error) {
|
||||
value, err := wfn.Parse(cpeStr)
|
||||
if err != nil {
|
||||
return CPE{}, fmt.Errorf("failed to parse CPE=%q: %w", cpeStr, err)
|
||||
@@ -63,30 +72,22 @@ func newCPEWithoutValidation(cpeStr string) (CPE, error) {
|
||||
}
|
||||
|
||||
// we need to compare the raw data since we are constructing CPEs in other locations
|
||||
value.Vendor = normalizeCpeField(value.Vendor)
|
||||
value.Product = normalizeCpeField(value.Product)
|
||||
value.Language = normalizeCpeField(value.Language)
|
||||
value.Version = normalizeCpeField(value.Version)
|
||||
value.TargetSW = normalizeCpeField(value.TargetSW)
|
||||
value.Part = normalizeCpeField(value.Part)
|
||||
value.Edition = normalizeCpeField(value.Edition)
|
||||
value.Other = normalizeCpeField(value.Other)
|
||||
value.SWEdition = normalizeCpeField(value.SWEdition)
|
||||
value.TargetHW = normalizeCpeField(value.TargetHW)
|
||||
value.Update = normalizeCpeField(value.Update)
|
||||
value.Vendor = normalizeField(value.Vendor)
|
||||
value.Product = normalizeField(value.Product)
|
||||
value.Language = normalizeField(value.Language)
|
||||
value.Version = normalizeField(value.Version)
|
||||
value.TargetSW = normalizeField(value.TargetSW)
|
||||
value.Part = normalizeField(value.Part)
|
||||
value.Edition = normalizeField(value.Edition)
|
||||
value.Other = normalizeField(value.Other)
|
||||
value.SWEdition = normalizeField(value.SWEdition)
|
||||
value.TargetHW = normalizeField(value.TargetHW)
|
||||
value.Update = normalizeField(value.Update)
|
||||
|
||||
return *value, nil
|
||||
}
|
||||
|
||||
func MustCPE(cpeStr string) CPE {
|
||||
c, err := NewCPE(cpeStr)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func normalizeCpeField(field string) string {
|
||||
func normalizeField(field string) string {
|
||||
// replace spaces with underscores (per section 5.3.2 of the CPE spec v 2.3)
|
||||
field = strings.ReplaceAll(field, " ", "_")
|
||||
|
||||
@@ -112,7 +113,7 @@ func stripSlashes(s string) string {
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func CPEString(c CPE) string {
|
||||
func String(c CPE) string {
|
||||
output := CPE{}
|
||||
output.Vendor = sanitize(c.Vendor)
|
||||
output.Product = sanitize(c.Product)
|
||||
@@ -1,4 +1,4 @@
|
||||
package pkg
|
||||
package cpe
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
@@ -11,14 +11,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func must(c CPE, e error) CPE {
|
||||
if e != nil {
|
||||
panic(e)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func TestNewCPE(t *testing.T) {
|
||||
func Test_New(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
@@ -27,29 +20,29 @@ func TestNewCPE(t *testing.T) {
|
||||
{
|
||||
name: "gocase",
|
||||
input: `cpe:/a:10web:form_maker:1.0.0::~~~wordpress~~`,
|
||||
expected: must(NewCPE(`cpe:2.3:a:10web:form_maker:1.0.0:*:*:*:*:wordpress:*:*`)),
|
||||
expected: Must(`cpe:2.3:a:10web:form_maker:1.0.0:*:*:*:*:wordpress:*:*`),
|
||||
},
|
||||
{
|
||||
name: "dashes",
|
||||
input: `cpe:/a:7-zip:7-zip:4.56:beta:~~~windows~~`,
|
||||
expected: must(NewCPE(`cpe:2.3:a:7-zip:7-zip:4.56:beta:*:*:*:windows:*:*`)),
|
||||
expected: Must(`cpe:2.3:a:7-zip:7-zip:4.56:beta:*:*:*:windows:*:*`),
|
||||
},
|
||||
{
|
||||
name: "URL escape characters",
|
||||
input: `cpe:/a:%240.99_kindle_books_project:%240.99_kindle_books:6::~~~android~~`,
|
||||
expected: must(NewCPE(`cpe:2.3:a:\$0.99_kindle_books_project:\$0.99_kindle_books:6:*:*:*:*:android:*:*`)),
|
||||
expected: Must(`cpe:2.3:a:\$0.99_kindle_books_project:\$0.99_kindle_books:6:*:*:*:*:android:*:*`),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
actual, err := NewCPE(test.input)
|
||||
actual, err := New(test.input)
|
||||
if err != nil {
|
||||
t.Fatalf("got an error while creating CPE: %+v", err)
|
||||
}
|
||||
|
||||
if CPEString(actual) != CPEString(test.expected) {
|
||||
t.Errorf("mismatched entries:\n\texpected:%+v\n\t actual:%+v\n", CPEString(test.expected), CPEString(actual))
|
||||
if String(actual) != String(test.expected) {
|
||||
t.Errorf("mismatched entries:\n\texpected:%+v\n\t actual:%+v\n", String(test.expected), String(actual))
|
||||
}
|
||||
|
||||
})
|
||||
@@ -81,7 +74,7 @@ func Test_normalizeCpeField(t *testing.T) {
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.field, func(t *testing.T) {
|
||||
assert.Equal(t, test.expected, normalizeCpeField(test.field))
|
||||
assert.Equal(t, test.expected, normalizeField(test.field))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -98,14 +91,14 @@ func Test_CPEParser(t *testing.T) {
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.CPEString, func(t *testing.T) {
|
||||
c1, err := NewCPE(test.CPEString)
|
||||
c1, err := New(test.CPEString)
|
||||
assert.NoError(t, err)
|
||||
c2, err := NewCPE(test.CPEUrl)
|
||||
c2, err := New(test.CPEUrl)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, c1, c2)
|
||||
assert.Equal(t, c1, test.WFN)
|
||||
assert.Equal(t, c2, test.WFN)
|
||||
assert.Equal(t, CPEString(test.WFN), test.CPEString)
|
||||
assert.Equal(t, String(test.WFN), test.CPEString)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -167,16 +160,16 @@ func Test_InvalidCPE(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
c, err := NewCPE(test.in)
|
||||
c, err := New(test.in)
|
||||
if test.expectedErr {
|
||||
assert.Error(t, err)
|
||||
if t.Failed() {
|
||||
t.Logf("got CPE: %q details: %+v", CPEString(c), c)
|
||||
t.Logf("got CPE: %q details: %+v", String(c), c)
|
||||
}
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, test.expected, CPEString(c))
|
||||
assert.Equal(t, test.expected, String(c))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -222,13 +215,13 @@ func Test_RoundTrip(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
// CPE string must be preserved through a round trip
|
||||
assert.Equal(t, test.cpe, CPEString(MustCPE(test.cpe)))
|
||||
assert.Equal(t, test.cpe, String(Must(test.cpe)))
|
||||
// The parsed CPE must be the same after a round trip
|
||||
assert.Equal(t, MustCPE(test.cpe), MustCPE(CPEString(MustCPE(test.cpe))))
|
||||
assert.Equal(t, Must(test.cpe), Must(String(Must(test.cpe))))
|
||||
// The test case parsed CPE must be the same after parsing the input string
|
||||
assert.Equal(t, test.parsedCPE, MustCPE(test.cpe))
|
||||
assert.Equal(t, test.parsedCPE, Must(test.cpe))
|
||||
// The test case parsed CPE must produce the same string as the input cpe
|
||||
assert.Equal(t, CPEString(test.parsedCPE), test.cpe)
|
||||
assert.Equal(t, String(test.parsedCPE), test.cpe)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
package pkg
|
||||
package cpe
|
||||
|
||||
import (
|
||||
"sort"
|
||||
)
|
||||
|
||||
func mergeCPEs(a, b []CPE) (result []CPE) {
|
||||
func Merge(a, b []CPE) (result []CPE) {
|
||||
aCPEs := make(map[string]CPE)
|
||||
|
||||
// keep all CPEs from a and create a quick string-based lookup
|
||||
@@ -20,6 +20,6 @@ func mergeCPEs(a, b []CPE) (result []CPE) {
|
||||
}
|
||||
}
|
||||
|
||||
sort.Sort(CPEBySpecificity(result))
|
||||
sort.Sort(BySpecificity(result))
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package cpe
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_Merge(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input [][]CPE
|
||||
expected []CPE
|
||||
}{
|
||||
{
|
||||
name: "merge, removing duplicates and ordered",
|
||||
input: [][]CPE{
|
||||
{
|
||||
Must("cpe:2.3:a:*:package:1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:some:package:*:*:*:*:*:*:*:*"),
|
||||
},
|
||||
{
|
||||
Must("cpe:2.3:a:some:package:1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:some:package:*:*:*:*:*:*:*:*"),
|
||||
},
|
||||
},
|
||||
expected: []CPE{
|
||||
Must("cpe:2.3:a:some:package:1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:some:package:*:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:*:package:1:*:*:*:*:*:*:*"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
out := Merge(test.input[0], test.input[1])
|
||||
assert.Equal(t, test.expected, out)
|
||||
})
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -32,6 +32,6 @@ const (
|
||||
// ImportStarted is a partybus event that occurs when an SBOM upload process has begun
|
||||
ImportStarted partybus.EventType = "syft-import-started-event"
|
||||
|
||||
// UploadAttestation is a partybus event that occurs when syft uploads an attestation to an OCI registry (+ any transparency log)
|
||||
UploadAttestation partybus.EventType = "syft-upload-attestation"
|
||||
// AttestationStarted is a partybus event that occurs when starting an SBOM attestation process
|
||||
AttestationStarted partybus.EventType = "syft-attestation-started-event"
|
||||
)
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package monitor
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/wagoodman/go-progress"
|
||||
)
|
||||
|
||||
type ShellProgress struct {
|
||||
io.Reader
|
||||
*progress.Manual
|
||||
}
|
||||
|
||||
type Title struct {
|
||||
Default string
|
||||
WhileRunning string
|
||||
OnSuccess string
|
||||
}
|
||||
|
||||
type GenericTask struct {
|
||||
Title Title
|
||||
Context string
|
||||
}
|
||||
@@ -5,11 +5,13 @@ package parsers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/wagoodman/go-partybus"
|
||||
"github.com/wagoodman/go-progress"
|
||||
|
||||
"github.com/anchore/syft/syft/event"
|
||||
"github.com/anchore/syft/syft/event/monitor"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger"
|
||||
)
|
||||
@@ -153,15 +155,20 @@ func ParseImportStarted(e partybus.Event) (string, progress.StagedProgressable,
|
||||
return host, prog, nil
|
||||
}
|
||||
|
||||
func ParseUploadAttestation(e partybus.Event) (progress.StagedProgressable, error) {
|
||||
if err := checkEventType(e.Type, event.UploadAttestation); err != nil {
|
||||
return nil, err
|
||||
func ParseAttestationStartedEvent(e partybus.Event) (io.Reader, progress.Progressable, *monitor.GenericTask, error) {
|
||||
if err := checkEventType(e.Type, event.AttestationStarted); err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
prog, ok := e.Value.(progress.StagedProgressable)
|
||||
source, ok := e.Source.(monitor.GenericTask)
|
||||
if !ok {
|
||||
return nil, newPayloadErr(e.Type, "Value", e.Value)
|
||||
return nil, nil, nil, newPayloadErr(e.Type, "Source", e.Source)
|
||||
}
|
||||
|
||||
return prog, nil
|
||||
sp, ok := e.Value.(*monitor.ShellProgress)
|
||||
if !ok {
|
||||
return nil, nil, nil, newPayloadErr(e.Type, "Value", e.Value)
|
||||
}
|
||||
|
||||
return sp.Reader, sp.Manual, &source, nil
|
||||
}
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
package file
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
type ClassificationCataloger struct {
|
||||
classifiers []Classifier
|
||||
}
|
||||
|
||||
func NewClassificationCataloger(classifiers []Classifier) (*ClassificationCataloger, error) {
|
||||
return &ClassificationCataloger{
|
||||
classifiers: classifiers,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (i *ClassificationCataloger) Catalog(resolver source.FileResolver) (map[source.Coordinates][]Classification, error) {
|
||||
results := make(map[source.Coordinates][]Classification)
|
||||
|
||||
numResults := 0
|
||||
for _, location := range allRegularFiles(resolver) {
|
||||
for _, classifier := range i.classifiers {
|
||||
result, err := classifier.Classify(resolver, location)
|
||||
if err != nil {
|
||||
log.Warnf("file classification cataloger failed with class=%q at location=%+v: %+v", classifier.Class, location, err)
|
||||
continue
|
||||
}
|
||||
if result != nil {
|
||||
results[location.Coordinates] = append(results[location.Coordinates], *result)
|
||||
numResults++
|
||||
}
|
||||
}
|
||||
}
|
||||
log.Debugf("file classifier discovered %d results", numResults)
|
||||
|
||||
return results, nil
|
||||
}
|
||||
@@ -1,210 +0,0 @@
|
||||
package file
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/anchore/stereoscope/pkg/imagetest"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func TestClassifierCataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
fixtureDir string
|
||||
location string
|
||||
expected []Classification
|
||||
expectedErr func(assert.TestingT, error, ...interface{}) bool
|
||||
}{
|
||||
{
|
||||
name: "positive-libpython3.7.so",
|
||||
fixtureDir: "test-fixtures/classifiers/positive",
|
||||
location: "libpython3.7.so",
|
||||
expected: []Classification{
|
||||
{
|
||||
Class: "python-binary",
|
||||
Metadata: map[string]string{
|
||||
"version": "3.7.4a-vZ9",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedErr: assert.NoError,
|
||||
},
|
||||
{
|
||||
name: "positive-python3.6",
|
||||
fixtureDir: "test-fixtures/classifiers/positive",
|
||||
location: "python3.6",
|
||||
expected: []Classification{
|
||||
{
|
||||
Class: "python-binary",
|
||||
Metadata: map[string]string{
|
||||
"version": "3.6.3a-vZ9",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedErr: assert.NoError,
|
||||
},
|
||||
{
|
||||
name: "positive-patchlevel.h",
|
||||
fixtureDir: "test-fixtures/classifiers/positive",
|
||||
location: "patchlevel.h",
|
||||
expected: []Classification{
|
||||
{
|
||||
Class: "cpython-source",
|
||||
Metadata: map[string]string{
|
||||
"version": "3.9-aZ5",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedErr: assert.NoError,
|
||||
},
|
||||
{
|
||||
name: "positive-go",
|
||||
fixtureDir: "test-fixtures/classifiers/positive",
|
||||
location: "go",
|
||||
expected: []Classification{
|
||||
{
|
||||
Class: "go-binary",
|
||||
Metadata: map[string]string{
|
||||
"version": "1.14",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedErr: assert.NoError,
|
||||
},
|
||||
{
|
||||
name: "positive-go-hint",
|
||||
fixtureDir: "test-fixtures/classifiers/positive",
|
||||
location: "VERSION",
|
||||
expected: []Classification{
|
||||
{
|
||||
Class: "go-binary-hint",
|
||||
Metadata: map[string]string{
|
||||
"version": "1.15",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedErr: assert.NoError,
|
||||
},
|
||||
{
|
||||
name: "positive-busybox",
|
||||
fixtureDir: "test-fixtures/classifiers/positive",
|
||||
location: "[", // note: busybox is a link to [
|
||||
expected: []Classification{
|
||||
{
|
||||
Class: "busybox-binary",
|
||||
VirtualPath: "busybox",
|
||||
Metadata: map[string]string{
|
||||
"version": "3.33.3",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedErr: assert.NoError,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
|
||||
c, err := NewClassificationCataloger(DefaultClassifiers)
|
||||
test.expectedErr(t, err)
|
||||
|
||||
src, err := source.NewFromDirectory(test.fixtureDir)
|
||||
test.expectedErr(t, err)
|
||||
|
||||
resolver, err := src.FileResolver(source.SquashedScope)
|
||||
test.expectedErr(t, err)
|
||||
|
||||
actualResults, err := c.Catalog(resolver)
|
||||
test.expectedErr(t, err)
|
||||
|
||||
ok := false
|
||||
for actualLoc, actualClassification := range actualResults {
|
||||
if test.location == actualLoc.RealPath {
|
||||
ok = true
|
||||
assert.Equal(t, test.expected, actualClassification)
|
||||
}
|
||||
}
|
||||
|
||||
if !ok {
|
||||
t.Fatalf("could not find test location=%q", test.location)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestClassifierCataloger_DefaultClassifiers_PositiveCases_Image(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
fixtureImage string
|
||||
location string
|
||||
expected []Classification
|
||||
expectedErr func(assert.TestingT, error, ...interface{}) bool
|
||||
}{
|
||||
{
|
||||
name: "busybox-regression",
|
||||
fixtureImage: "image-busybox",
|
||||
location: "/bin/[",
|
||||
expected: []Classification{
|
||||
{
|
||||
Class: "busybox-binary",
|
||||
VirtualPath: "/bin/busybox",
|
||||
Metadata: map[string]string{
|
||||
"version": "1.35.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedErr: assert.NoError,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
|
||||
c, err := NewClassificationCataloger(DefaultClassifiers)
|
||||
test.expectedErr(t, err)
|
||||
|
||||
img := imagetest.GetFixtureImage(t, "docker-archive", test.fixtureImage)
|
||||
src, err := source.NewFromImage(img, "test-img")
|
||||
test.expectedErr(t, err)
|
||||
|
||||
resolver, err := src.FileResolver(source.SquashedScope)
|
||||
test.expectedErr(t, err)
|
||||
|
||||
actualResults, err := c.Catalog(resolver)
|
||||
test.expectedErr(t, err)
|
||||
|
||||
ok := false
|
||||
for actuaLoc, actualClassification := range actualResults {
|
||||
if actuaLoc.RealPath == test.location {
|
||||
ok = true
|
||||
assert.Equal(t, test.expected, actualClassification)
|
||||
}
|
||||
}
|
||||
|
||||
if !ok {
|
||||
t.Fatalf("could not find test location=%q", test.location)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestClassifierCataloger_DefaultClassifiers_NegativeCases(t *testing.T) {
|
||||
|
||||
c, err := NewClassificationCataloger(DefaultClassifiers)
|
||||
assert.NoError(t, err)
|
||||
|
||||
src, err := source.NewFromDirectory("test-fixtures/classifiers/negative")
|
||||
assert.NoError(t, err)
|
||||
|
||||
resolver, err := src.FileResolver(source.SquashedScope)
|
||||
assert.NoError(t, err)
|
||||
|
||||
actualResults, err := c.Catalog(resolver)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 0, len(actualResults))
|
||||
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
package file
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"text/template"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
var DefaultClassifiers = []Classifier{
|
||||
{
|
||||
Class: "python-binary",
|
||||
FilepathPatterns: []*regexp.Regexp{
|
||||
regexp.MustCompile(`(.*/|^)python(?P<version>[0-9]+\.[0-9]+)$`),
|
||||
regexp.MustCompile(`(.*/|^)libpython(?P<version>[0-9]+\.[0-9]+).so.*$`),
|
||||
},
|
||||
EvidencePatternTemplates: []string{
|
||||
`(?m)(?P<version>{{ .version }}\.[0-9]+[-_a-zA-Z0-9]*)`,
|
||||
},
|
||||
},
|
||||
{
|
||||
Class: "cpython-source",
|
||||
FilepathPatterns: []*regexp.Regexp{
|
||||
regexp.MustCompile(`(.*/|^)patchlevel.h$`),
|
||||
},
|
||||
EvidencePatternTemplates: []string{
|
||||
`(?m)#define\s+PY_VERSION\s+"?(?P<version>[0-9\.\-_a-zA-Z]+)"?`,
|
||||
},
|
||||
},
|
||||
{
|
||||
Class: "go-binary",
|
||||
FilepathPatterns: []*regexp.Regexp{
|
||||
regexp.MustCompile(`(.*/|^)go$`),
|
||||
},
|
||||
EvidencePatternTemplates: []string{
|
||||
`(?m)go(?P<version>[0-9]+\.[0-9]+(\.[0-9]+|beta[0-9]+|alpha[0-9]+|rc[0-9]+)?)`,
|
||||
},
|
||||
},
|
||||
{
|
||||
Class: "nodejs-binary",
|
||||
FilepathPatterns: []*regexp.Regexp{
|
||||
regexp.MustCompile(`(.*/|^)node$`),
|
||||
},
|
||||
EvidencePatternTemplates: []string{
|
||||
// regex that matches node.js/vx.y.z
|
||||
`(?m)node\.js\/v(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`,
|
||||
},
|
||||
},
|
||||
{
|
||||
Class: "go-binary-hint",
|
||||
FilepathPatterns: []*regexp.Regexp{
|
||||
regexp.MustCompile(`(.*/|^)VERSION$`),
|
||||
},
|
||||
EvidencePatternTemplates: []string{
|
||||
`(?m)go(?P<version>[0-9]+\.[0-9]+(\.[0-9]+|beta[0-9]+|alpha[0-9]+|rc[0-9]+)?)`,
|
||||
},
|
||||
},
|
||||
{
|
||||
Class: "busybox-binary",
|
||||
FilepathPatterns: []*regexp.Regexp{
|
||||
regexp.MustCompile(`(.*/|^)busybox$`),
|
||||
},
|
||||
EvidencePatternTemplates: []string{
|
||||
`(?m)BusyBox\s+v(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
type Classifier struct {
|
||||
Class string
|
||||
FilepathPatterns []*regexp.Regexp
|
||||
EvidencePatternTemplates []string
|
||||
}
|
||||
|
||||
type Classification struct {
|
||||
Class string `json:"class"`
|
||||
VirtualPath string `json:"virtual_path"`
|
||||
Metadata map[string]string `json:"metadata"`
|
||||
}
|
||||
|
||||
func (c Classifier) Classify(resolver source.FileResolver, location source.Location) (*Classification, error) {
|
||||
doesFilepathMatch, filepathNamedGroupValues := FilepathMatches(c.FilepathPatterns, location)
|
||||
if !doesFilepathMatch {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
contentReader, err := resolver.FileContentsByLocation(location)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer internal.CloseAndLogError(contentReader, location.VirtualPath)
|
||||
|
||||
// TODO: there is room for improvement here, as this may use an excessive amount of memory. Alternate approach is to leverage a RuneReader.
|
||||
contents, err := io.ReadAll(contentReader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result *Classification
|
||||
for _, patternTemplate := range c.EvidencePatternTemplates {
|
||||
tmpl, err := template.New("").Parse(patternTemplate)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to parse classifier template=%q : %w", patternTemplate, err)
|
||||
}
|
||||
|
||||
patternBuf := &bytes.Buffer{}
|
||||
err = tmpl.Execute(patternBuf, filepathNamedGroupValues)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to render template: %w", err)
|
||||
}
|
||||
|
||||
pattern, err := regexp.Compile(patternBuf.String())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to compile rendered regex=%q: %w", patternBuf.String(), err)
|
||||
}
|
||||
|
||||
if !pattern.Match(contents) {
|
||||
continue
|
||||
}
|
||||
|
||||
matchMetadata := internal.MatchNamedCaptureGroups(pattern, string(contents))
|
||||
if result == nil {
|
||||
result = &Classification{
|
||||
Class: c.Class,
|
||||
VirtualPath: location.VirtualPath,
|
||||
Metadata: matchMetadata,
|
||||
}
|
||||
} else {
|
||||
for key, value := range matchMetadata {
|
||||
result.Metadata[key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func FilepathMatches(patterns []*regexp.Regexp, location source.Location) (bool, map[string]string) {
|
||||
for _, path := range []string{location.RealPath, location.VirtualPath} {
|
||||
if path == "" {
|
||||
continue
|
||||
}
|
||||
for _, pattern := range patterns {
|
||||
if pattern.MatchString(path) {
|
||||
return true, internal.MatchNamedCaptureGroups(pattern, path)
|
||||
}
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
package file
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func TestFilepathMatches(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
location source.Location
|
||||
patterns []string
|
||||
expectedMatches bool
|
||||
expectedNamedGroups map[string]string
|
||||
}{
|
||||
{
|
||||
name: "simple-filename-match",
|
||||
location: source.Location{
|
||||
Coordinates: source.Coordinates{
|
||||
RealPath: "python2.7",
|
||||
},
|
||||
},
|
||||
patterns: []string{
|
||||
`python([0-9]+\.[0-9]+)$`,
|
||||
},
|
||||
expectedMatches: true,
|
||||
},
|
||||
{
|
||||
name: "filepath-match",
|
||||
location: source.Location{
|
||||
Coordinates: source.Coordinates{
|
||||
RealPath: "/usr/bin/python2.7",
|
||||
},
|
||||
},
|
||||
patterns: []string{
|
||||
`python([0-9]+\.[0-9]+)$`,
|
||||
},
|
||||
expectedMatches: true,
|
||||
},
|
||||
{
|
||||
name: "virtual-filepath-match",
|
||||
location: source.Location{
|
||||
VirtualPath: "/usr/bin/python2.7",
|
||||
},
|
||||
patterns: []string{
|
||||
`python([0-9]+\.[0-9]+)$`,
|
||||
},
|
||||
expectedMatches: true,
|
||||
},
|
||||
{
|
||||
name: "full-filepath-match",
|
||||
location: source.Location{
|
||||
VirtualPath: "/usr/bin/python2.7",
|
||||
},
|
||||
patterns: []string{
|
||||
`.*/bin/python([0-9]+\.[0-9]+)$`,
|
||||
},
|
||||
expectedMatches: true,
|
||||
},
|
||||
{
|
||||
name: "anchored-filename-match-FAILS",
|
||||
location: source.Location{
|
||||
Coordinates: source.Coordinates{
|
||||
RealPath: "/usr/bin/python2.7",
|
||||
},
|
||||
},
|
||||
patterns: []string{
|
||||
`^python([0-9]+\.[0-9]+)$`,
|
||||
},
|
||||
expectedMatches: false,
|
||||
},
|
||||
{
|
||||
name: "empty-filename-match-FAILS",
|
||||
location: source.Location{},
|
||||
patterns: []string{
|
||||
`^python([0-9]+\.[0-9]+)$`,
|
||||
},
|
||||
expectedMatches: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
var patterns []*regexp.Regexp
|
||||
for _, p := range test.patterns {
|
||||
patterns = append(patterns, regexp.MustCompile(p))
|
||||
}
|
||||
actualMatches, actualNamedGroups := FilepathMatches(patterns, test.location)
|
||||
assert.Equal(t, test.expectedMatches, actualMatches)
|
||||
assert.Equal(t, test.expectedNamedGroups, actualNamedGroups)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -92,15 +92,11 @@ func DigestsFromFile(closer io.ReadCloser, hashes []crypto.Hash) ([]Digest, erro
|
||||
writers[idx] = hashers[idx]
|
||||
}
|
||||
|
||||
size, err := io.Copy(io.MultiWriter(writers...), closer)
|
||||
_, err := io.Copy(io.MultiWriter(writers...), closer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if size == 0 {
|
||||
return make([]Digest, 0), nil
|
||||
}
|
||||
|
||||
result := make([]Digest, len(hashes))
|
||||
// only capture digests when there is content. It is important to do this based on SIZE and not
|
||||
// FILE TYPE. The reasoning is that it is possible for a tar to be crafted with a header-only
|
||||
|
||||
@@ -29,12 +29,6 @@ func testDigests(t testing.TB, root string, files []string, hashes ...crypto.Has
|
||||
t.Fatalf("could not read %q : %+v", f, err)
|
||||
}
|
||||
|
||||
if len(b) == 0 {
|
||||
// we don't keep digests for empty files
|
||||
digests[source.NewLocation(f).Coordinates] = []Digest{}
|
||||
continue
|
||||
}
|
||||
|
||||
for _, hash := range hashes {
|
||||
h := hash.New()
|
||||
h.Write(b)
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
!libpython3.7.so
|
||||
@@ -1 +0,0 @@
|
||||
go1.14
|
||||
@@ -27,8 +27,13 @@ func encodeComponent(p pkg.Package) cyclonedx.Component {
|
||||
properties = &props
|
||||
}
|
||||
|
||||
componentType := cyclonedx.ComponentTypeLibrary
|
||||
if p.Type == pkg.BinaryPkg {
|
||||
componentType = cyclonedx.ComponentTypeApplication
|
||||
}
|
||||
|
||||
return cyclonedx.Component{
|
||||
Type: cyclonedx.ComponentTypeLibrary,
|
||||
Type: componentType,
|
||||
Name: p.Name,
|
||||
Group: encodeGroup(p),
|
||||
Version: p.Version,
|
||||
|
||||
@@ -106,6 +106,25 @@ func Test_encodeComponentProperties(t *testing.T) {
|
||||
{Name: "syft:metadata:h1Digest", Value: "h1:KlOXYy8wQWTUJYFgkUI40Lzr06ofg5IRXUK5C7qZt1k="},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "from go mod",
|
||||
input: pkg.Package{
|
||||
Name: "golang.org/x/net",
|
||||
Version: "v0.0.0-20211006190231-62292e806868",
|
||||
Language: pkg.Go,
|
||||
Type: pkg.GoModulePkg,
|
||||
MetadataType: pkg.GolangModMetadataType,
|
||||
Metadata: pkg.GolangModMetadata{
|
||||
H1Digest: "h1:KlOXYy8wQWTUJYFgkUI40Lzr06ofg5IRXUK5C7qZt1k=",
|
||||
},
|
||||
},
|
||||
expected: &[]cyclonedx.Property{
|
||||
{Name: "syft:package:language", Value: pkg.Go.String()},
|
||||
{Name: "syft:package:metadataType", Value: "GolangModMetadata"},
|
||||
{Name: "syft:package:type", Value: "go-module"},
|
||||
{Name: "syft:metadata:h1Digest", Value: "h1:KlOXYy8wQWTUJYFgkUI40Lzr06ofg5IRXUK5C7qZt1k="},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "from rpm",
|
||||
input: pkg.Package{
|
||||
@@ -144,6 +163,60 @@ func Test_encodeComponentProperties(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_encodeCompomentType(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
pkg pkg.Package
|
||||
want cyclonedx.Component
|
||||
}{
|
||||
{
|
||||
name: "non-binary package",
|
||||
pkg: pkg.Package{
|
||||
Name: "pkg1",
|
||||
Version: "1.9.2",
|
||||
Type: pkg.GoModulePkg,
|
||||
},
|
||||
want: cyclonedx.Component{
|
||||
Name: "pkg1",
|
||||
Version: "1.9.2",
|
||||
Type: cyclonedx.ComponentTypeLibrary,
|
||||
Properties: &[]cyclonedx.Property{
|
||||
{
|
||||
Name: "syft:package:type",
|
||||
Value: "go-module",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "non-binary package",
|
||||
pkg: pkg.Package{
|
||||
Name: "pkg1",
|
||||
Version: "3.1.2",
|
||||
Type: pkg.BinaryPkg,
|
||||
},
|
||||
want: cyclonedx.Component{
|
||||
Name: "pkg1",
|
||||
Version: "3.1.2",
|
||||
Type: cyclonedx.ComponentTypeApplication,
|
||||
Properties: &[]cyclonedx.Property{
|
||||
{
|
||||
Name: "syft:package:type",
|
||||
Value: "binary",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tt.pkg.ID()
|
||||
p := encodeComponent(tt.pkg)
|
||||
assert.Equal(t, tt.want, p)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_deriveBomRef(t *testing.T) {
|
||||
pkgWithPurl := pkg.Package{
|
||||
Name: "django",
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"github.com/CycloneDX/cyclonedx-go"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/cpe"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
@@ -11,7 +12,7 @@ func encodeSingleCPE(p pkg.Package) string {
|
||||
// Since the CPEs in a package are sorted by specificity
|
||||
// we can extract the first CPE as the one to output in cyclonedx
|
||||
if len(p.CPEs) > 0 {
|
||||
return pkg.CPEString(p.CPEs[0])
|
||||
return cpe.String(p.CPEs[0])
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -24,15 +25,15 @@ func encodeCPEs(p pkg.Package) (out []cyclonedx.Property) {
|
||||
}
|
||||
out = append(out, cyclonedx.Property{
|
||||
Name: "syft:cpe23",
|
||||
Value: pkg.CPEString(c),
|
||||
Value: cpe.String(c),
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func decodeCPEs(c *cyclonedx.Component) (out []pkg.CPE) {
|
||||
func decodeCPEs(c *cyclonedx.Component) (out []cpe.CPE) {
|
||||
if c.CPE != "" {
|
||||
cp, err := pkg.NewCPE(c.CPE)
|
||||
cp, err := cpe.New(c.CPE)
|
||||
if err != nil {
|
||||
log.Warnf("invalid CPE: %s", c.CPE)
|
||||
} else {
|
||||
@@ -43,7 +44,7 @@ func decodeCPEs(c *cyclonedx.Component) (out []pkg.CPE) {
|
||||
if c.Properties != nil {
|
||||
for _, p := range *c.Properties {
|
||||
if p.Name == "syft:cpe23" {
|
||||
cp, err := pkg.NewCPE(p.Value)
|
||||
cp, err := cpe.New(p.Value)
|
||||
if err != nil {
|
||||
log.Warnf("invalid CPE: %s", p.Value)
|
||||
} else {
|
||||
|
||||
@@ -5,12 +5,13 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/anchore/syft/syft/cpe"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
func Test_encodeCPE(t *testing.T) {
|
||||
testCPE := pkg.MustCPE("cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*")
|
||||
testCPE2 := pkg.MustCPE("cpe:2.3:a:name:name2:3.2:*:*:*:*:*:*:*")
|
||||
testCPE := cpe.Must("cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*")
|
||||
testCPE2 := cpe.Must("cpe:2.3:a:name:name2:3.2:*:*:*:*:*:*:*")
|
||||
tests := []struct {
|
||||
name string
|
||||
input pkg.Package
|
||||
@@ -20,14 +21,14 @@ func Test_encodeCPE(t *testing.T) {
|
||||
// note: since this is an optional field, no value is preferred over NONE or NOASSERTION
|
||||
name: "no metadata",
|
||||
input: pkg.Package{
|
||||
CPEs: []pkg.CPE{},
|
||||
CPEs: []cpe.CPE{},
|
||||
},
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "single CPE",
|
||||
input: pkg.Package{
|
||||
CPEs: []pkg.CPE{
|
||||
CPEs: []cpe.CPE{
|
||||
testCPE,
|
||||
},
|
||||
},
|
||||
@@ -36,7 +37,7 @@ func Test_encodeCPE(t *testing.T) {
|
||||
{
|
||||
name: "multiple CPEs",
|
||||
input: pkg.Package{
|
||||
CPEs: []pkg.CPE{
|
||||
CPEs: []cpe.CPE{
|
||||
testCPE2,
|
||||
testCPE,
|
||||
},
|
||||
|
||||
@@ -216,7 +216,7 @@ func collectRelationships(bom *cyclonedx.BOM, s *sbom.SBOM, idMap map[string]int
|
||||
}
|
||||
|
||||
for _, t := range *d.Dependencies {
|
||||
to, toExists := idMap[t.Ref].(artifact.Identifiable)
|
||||
to, toExists := idMap[t].(artifact.Identifiable)
|
||||
if !toExists {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ func Test_decode(t *testing.T) {
|
||||
},
|
||||
},
|
||||
CPE: "cpe:2.3:*:another:package:2:*:*:*:*:*:*:*",
|
||||
PackageURL: "pkg:alpine/alpine-baselayout@3.2.0-r16?arch=x86_64&upstream=alpine-baselayout&distro=alpine-3.14.2",
|
||||
PackageURL: "pkg:apk/alpine/alpine-baselayout@3.2.0-r16?arch=x86_64&upstream=alpine-baselayout&distro=alpine-3.14.2",
|
||||
Properties: &[]cyclonedx.Property{
|
||||
|
||||
{
|
||||
@@ -173,12 +173,8 @@ func Test_decode(t *testing.T) {
|
||||
},
|
||||
Dependencies: &[]cyclonedx.Dependency{
|
||||
{
|
||||
Ref: "p1",
|
||||
Dependencies: &[]cyclonedx.Dependency{
|
||||
{
|
||||
Ref: "p2",
|
||||
},
|
||||
},
|
||||
Ref: "p1",
|
||||
Dependencies: &[]string{"p2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -197,7 +193,7 @@ func Test_decode(t *testing.T) {
|
||||
{
|
||||
pkg: "package-2",
|
||||
ver: "2.0.2",
|
||||
purl: "pkg:alpine/alpine-baselayout@3.2.0-r16?arch=x86_64&upstream=alpine-baselayout&distro=alpine-3.14.2",
|
||||
purl: "pkg:apk/alpine/alpine-baselayout@3.2.0-r16?arch=x86_64&upstream=alpine-baselayout&distro=alpine-3.14.2",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -263,8 +259,9 @@ func Test_decode(t *testing.T) {
|
||||
|
||||
func Test_missingDataDecode(t *testing.T) {
|
||||
bom := &cyclonedx.BOM{
|
||||
Metadata: nil,
|
||||
Components: &[]cyclonedx.Component{},
|
||||
Metadata: nil,
|
||||
Components: &[]cyclonedx.Component{},
|
||||
SpecVersion: cyclonedx.SpecVersion1_4,
|
||||
}
|
||||
|
||||
_, err := ToSyftModel(bom)
|
||||
@@ -287,7 +284,9 @@ func Test_missingDataDecode(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_missingComponentsDecode(t *testing.T) {
|
||||
bom := &cyclonedx.BOM{}
|
||||
bom := &cyclonedx.BOM{
|
||||
SpecVersion: cyclonedx.SpecVersion1_4,
|
||||
}
|
||||
bomBytes, _ := json.Marshal(&bom)
|
||||
decode := GetDecoder(cyclonedx.BOMFileFormatJSON)
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/cpe"
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
@@ -100,12 +101,12 @@ func toOSComponent(distro *linux.Release) []cyclonedx.Component {
|
||||
}
|
||||
|
||||
func formatCPE(cpeString string) string {
|
||||
cpe, err := pkg.NewCPE(cpeString)
|
||||
c, err := cpe.New(cpeString)
|
||||
if err != nil {
|
||||
log.Debugf("skipping invalid CPE: %s", cpeString)
|
||||
return ""
|
||||
}
|
||||
return pkg.CPEString(cpe)
|
||||
return cpe.String(c)
|
||||
}
|
||||
|
||||
// NewBomDescriptor returns a new BomDescriptor tailored for the current time and "syft" tool details.
|
||||
@@ -157,8 +158,10 @@ func toDependencies(relationships []artifact.Relationship) []cyclonedx.Dependenc
|
||||
continue
|
||||
}
|
||||
|
||||
innerDeps := []cyclonedx.Dependency{}
|
||||
innerDeps = append(innerDeps, cyclonedx.Dependency{Ref: deriveBomRef(*fromPkg)})
|
||||
// ind dep
|
||||
|
||||
innerDeps := []string{}
|
||||
innerDeps = append(innerDeps, deriveBomRef(*fromPkg))
|
||||
result = append(result, cyclonedx.Dependency{
|
||||
Ref: deriveBomRef(*toPkg),
|
||||
Dependencies: &innerDeps,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package spdxhelpers
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/cpe"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
@@ -10,7 +11,7 @@ func ExternalRefs(p pkg.Package) (externalRefs []ExternalRef) {
|
||||
for _, c := range p.CPEs {
|
||||
externalRefs = append(externalRefs, ExternalRef{
|
||||
ReferenceCategory: SecurityReferenceCategory,
|
||||
ReferenceLocator: pkg.CPEString(c),
|
||||
ReferenceLocator: cpe.String(c),
|
||||
ReferenceType: Cpe23ExternalRefType,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -5,11 +5,12 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/anchore/syft/syft/cpe"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
func Test_ExternalRefs(t *testing.T) {
|
||||
testCPE := pkg.MustCPE("cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*")
|
||||
testCPE := cpe.Must("cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*")
|
||||
tests := []struct {
|
||||
name string
|
||||
input pkg.Package
|
||||
@@ -18,7 +19,7 @@ func Test_ExternalRefs(t *testing.T) {
|
||||
{
|
||||
name: "cpe + purl",
|
||||
input: pkg.Package{
|
||||
CPEs: []pkg.CPE{
|
||||
CPEs: []cpe.CPE{
|
||||
testCPE,
|
||||
},
|
||||
PURL: "a-purl",
|
||||
@@ -26,7 +27,7 @@ func Test_ExternalRefs(t *testing.T) {
|
||||
expected: []ExternalRef{
|
||||
{
|
||||
ReferenceCategory: SecurityReferenceCategory,
|
||||
ReferenceLocator: pkg.CPEString(testCPE),
|
||||
ReferenceLocator: cpe.String(testCPE),
|
||||
ReferenceType: Cpe23ExternalRefType,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -8,6 +8,11 @@ const (
|
||||
// Example: The package 'WildFly' is described by SPDX document WildFly.spdx.
|
||||
DescribedByRelationship RelationshipType = "DESCRIBED_BY"
|
||||
|
||||
// Describes is to be used when SPDXRef-DOCUMENT describes SPDXRef-A.
|
||||
// Example: An SPDX document WildFly.spdx describes package ‘WildFly’.
|
||||
// Note this is a logical relationship to help organize related items within an SPDX document that is mandatory if more than one package or set of files (not in a package) is present.
|
||||
DescribesRelationship RelationshipType = "DESCRIBES"
|
||||
|
||||
// ContainsRelationship is to be used when SPDXRef-A contains SPDXRef-B.
|
||||
// Example: An ARCHIVE file bar.tgz contains a SOURCE file foo.c.
|
||||
ContainsRelationship RelationshipType = "CONTAINS"
|
||||
|
||||
@@ -43,6 +43,8 @@ func SourceInfo(p pkg.Package) string {
|
||||
answer = "acquired package info from portage DB"
|
||||
case pkg.HackagePkg:
|
||||
answer = "acquired package info from cabal or stack manifest files"
|
||||
case pkg.HexPkg:
|
||||
answer = "acquired package info from rebar3 or mix manifest file"
|
||||
default:
|
||||
answer = "acquired package info from the following paths"
|
||||
}
|
||||
|
||||
@@ -191,6 +191,14 @@ func Test_SourceInfo(t *testing.T) {
|
||||
"acquired package info from the following paths",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: pkg.Package{
|
||||
Type: pkg.HexPkg,
|
||||
},
|
||||
expected: []string{
|
||||
"from rebar3 or mix manifest file",
|
||||
},
|
||||
},
|
||||
}
|
||||
var pkgTypes []pkg.Type
|
||||
for _, test := range tests {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
//nolint:gosec // sha1 is used as a required hash function for SPDX, not a crypto function
|
||||
package spdxhelpers
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
@@ -31,6 +33,24 @@ const (
|
||||
//nolint:funlen
|
||||
func ToFormatModel(s sbom.SBOM) *spdx.Document {
|
||||
name, namespace := DocumentNameAndNamespace(s.Source)
|
||||
relationships := toRelationships(s.RelationshipsSorted())
|
||||
|
||||
// for valid SPDX we need a document describes relationship
|
||||
// TODO: remove this placeholder after deciding on correct behavior
|
||||
// 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{
|
||||
ElementRefID: "DOCUMENT",
|
||||
},
|
||||
Relationship: string(DescribesRelationship),
|
||||
RefB: common.DocElementID{
|
||||
ElementRefID: "DOCUMENT",
|
||||
},
|
||||
RelationshipComment: "",
|
||||
}
|
||||
|
||||
relationships = append(relationships, documentDescribesRelationship)
|
||||
|
||||
return &spdx.Document{
|
||||
// 6.1: SPDX Version; should be in the format "SPDX-x.x"
|
||||
@@ -103,9 +123,9 @@ func ToFormatModel(s sbom.SBOM) *spdx.Document {
|
||||
// Cardinality: optional, one
|
||||
CreatorComment: "",
|
||||
},
|
||||
Packages: toPackages(s.Artifacts.PackageCatalog),
|
||||
Packages: toPackages(s.Artifacts.PackageCatalog, s),
|
||||
Files: toFiles(s),
|
||||
Relationships: toRelationships(s.RelationshipsSorted()),
|
||||
Relationships: relationships,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,7 +143,7 @@ func toSPDXID(identifiable artifact.Identifiable) common.ElementID {
|
||||
// packages populates all Package Information from the package Catalog (see https://spdx.github.io/spdx-spec/3-package-information/)
|
||||
//
|
||||
//nolint:funlen
|
||||
func toPackages(catalog *pkg.Catalog) (results []*spdx.Package) {
|
||||
func toPackages(catalog *pkg.Catalog, sbom sbom.SBOM) (results []*spdx.Package) {
|
||||
for _, p := range catalog.Sorted() {
|
||||
// name should be guaranteed to be unique, but semantically useful and stable
|
||||
id := toSPDXID(p)
|
||||
@@ -132,7 +152,25 @@ func toPackages(catalog *pkg.Catalog) (results []*spdx.Package) {
|
||||
// in the Comments on License field (section 7.16). With respect to NOASSERTION, a written explanation in
|
||||
// the Comments on License field (section 7.16) is preferred.
|
||||
license := License(p)
|
||||
checksums, filesAnalyzed := toPackageChecksums(p)
|
||||
|
||||
// two ways to get filesAnalyzed == true:
|
||||
// 1. syft has generated a sha1 digest for the package itself - usually in the java cataloger
|
||||
// 2. syft has generated a sha1 digest for the package's contents
|
||||
packageChecksums, filesAnalyzed := toPackageChecksums(p)
|
||||
|
||||
packageVerificationCode := newPackageVerificationCode(p, sbom)
|
||||
if packageVerificationCode != nil {
|
||||
filesAnalyzed = true
|
||||
}
|
||||
|
||||
// invalid SPDX document state
|
||||
if filesAnalyzed && packageVerificationCode == nil {
|
||||
// this is an invalid document state
|
||||
// we reset the filesAnalyzed flag to false to avoid
|
||||
// cases where a package digest was generated but there was
|
||||
// not enough metadata to generate a verification code regarding the files
|
||||
filesAnalyzed = false
|
||||
}
|
||||
|
||||
results = append(results, &spdx.Package{
|
||||
// NOT PART OF SPEC
|
||||
@@ -193,7 +231,7 @@ func toPackages(catalog *pkg.Catalog) (results []*spdx.Package) {
|
||||
// 7.9: Package Verification Code
|
||||
// Cardinality: optional, one if filesAnalyzed is true / omitted;
|
||||
// zero (must be omitted) if filesAnalyzed is false
|
||||
PackageVerificationCode: nil,
|
||||
PackageVerificationCode: packageVerificationCode,
|
||||
|
||||
// 7.10: Package Checksum: may have keys for SHA1, SHA256 and/or MD5
|
||||
// Cardinality: optional, one or many
|
||||
@@ -203,7 +241,7 @@ func toPackages(catalog *pkg.Catalog) (results []*spdx.Package) {
|
||||
// to determine if any file in the original package has been changed. If the SPDX file is to be included
|
||||
// in a package, this value should not be calculated. The SHA-1 algorithm will be used to provide the
|
||||
// checksum by default.
|
||||
PackageChecksums: checksums,
|
||||
PackageChecksums: packageChecksums,
|
||||
|
||||
// 7.11: Package Home Page
|
||||
// Cardinality: optional, one
|
||||
@@ -275,17 +313,6 @@ func toPackages(catalog *pkg.Catalog) (results []*spdx.Package) {
|
||||
return results
|
||||
}
|
||||
|
||||
func toPackageOriginator(p pkg.Package) *common.Originator {
|
||||
kind, originator := Originator(p)
|
||||
if kind == "" || originator == "" {
|
||||
return nil
|
||||
}
|
||||
return &common.Originator{
|
||||
Originator: originator,
|
||||
OriginatorType: kind,
|
||||
}
|
||||
}
|
||||
|
||||
func toPackageChecksums(p pkg.Package) ([]common.Checksum, bool) {
|
||||
filesAnalyzed := false
|
||||
var checksums []common.Checksum
|
||||
@@ -293,6 +320,7 @@ func toPackageChecksums(p pkg.Package) ([]common.Checksum, bool) {
|
||||
// we generate digest for some Java packages
|
||||
// spdx.github.io/spdx-spec/package-information/#710-package-checksum-field
|
||||
case pkg.JavaMetadata:
|
||||
// if syft has generated the digest here then filesAnalyzed is true
|
||||
if len(meta.ArchiveDigests) > 0 {
|
||||
filesAnalyzed = true
|
||||
for _, digest := range meta.ArchiveDigests {
|
||||
@@ -304,6 +332,7 @@ func toPackageChecksums(p pkg.Package) ([]common.Checksum, bool) {
|
||||
}
|
||||
}
|
||||
case pkg.GolangBinMetadata:
|
||||
// because the H1 digest is found in the Golang metadata we cannot claim that the files were analyzed
|
||||
algo, hexStr, err := util.HDigestToSHA(meta.H1Digest)
|
||||
if err != nil {
|
||||
log.Debugf("invalid h1digest: %s: %v", meta.H1Digest, err)
|
||||
@@ -318,6 +347,17 @@ func toPackageChecksums(p pkg.Package) ([]common.Checksum, bool) {
|
||||
return checksums, filesAnalyzed
|
||||
}
|
||||
|
||||
func toPackageOriginator(p pkg.Package) *common.Originator {
|
||||
kind, originator := Originator(p)
|
||||
if kind == "" || originator == "" {
|
||||
return nil
|
||||
}
|
||||
return &common.Originator{
|
||||
Originator: originator,
|
||||
OriginatorType: kind,
|
||||
}
|
||||
}
|
||||
|
||||
func formatSPDXExternalRefs(p pkg.Package) (refs []*spdx.PackageExternalReference) {
|
||||
for _, ref := range ExternalRefs(p) {
|
||||
refs = append(refs, &spdx.PackageExternalReference{
|
||||
@@ -385,6 +425,15 @@ func toFiles(s sbom.SBOM) (results []*spdx.File) {
|
||||
digests = digestsForLocation
|
||||
}
|
||||
|
||||
// if we don't have any metadata or digests for this location
|
||||
// then the file is most likely a symlink or non-regular file
|
||||
// for now we include a 0 sha1 digest as requested by the spdx spec
|
||||
// TODO: update location code in core SBOM so that we can map complex links
|
||||
// back to their real file digest location.
|
||||
if len(digests) == 0 {
|
||||
digests = append(digests, file.Digest{Algorithm: "sha1", Value: "0000000000000000000000000000000000000000"})
|
||||
}
|
||||
|
||||
// TODO: add file classifications (?) and content as a snippet
|
||||
|
||||
var comment string
|
||||
@@ -414,6 +463,7 @@ func toFiles(s sbom.SBOM) (results []*spdx.File) {
|
||||
}
|
||||
|
||||
func toFileChecksums(digests []file.Digest) (checksums []common.Checksum) {
|
||||
checksums = make([]common.Checksum, 0, len(digests))
|
||||
for _, digest := range digests {
|
||||
checksums = append(checksums, common.Checksum{
|
||||
Algorithm: toChecksumAlgorithm(digest.Algorithm),
|
||||
@@ -462,3 +512,55 @@ func toFileTypes(metadata *source.FileMetadata) (ty []string) {
|
||||
|
||||
return ty
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// key off of the contains relationship;
|
||||
// spdx validator will fail if a package claims to contain a file but no sha1 provided
|
||||
// if a sha1 for a file is provided then the validator will fail if the package does not have
|
||||
// a package verification code
|
||||
coordinates := sbom.CoordinatesForPackage(p, artifact.ContainsRelationship)
|
||||
var digests []file.Digest
|
||||
for _, c := range coordinates {
|
||||
digest := sbom.Artifacts.FileDigests[c]
|
||||
if len(digest) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
var d file.Digest
|
||||
for _, digest := range digest {
|
||||
if digest.Algorithm == "sha1" {
|
||||
d = digest
|
||||
break
|
||||
}
|
||||
}
|
||||
digests = append(digests, d)
|
||||
}
|
||||
|
||||
if len(digests) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// sort templist in ascending order by SHA1 value
|
||||
sort.SliceStable(digests, func(i, j int) bool {
|
||||
return digests[i].Value < digests[j].Value
|
||||
})
|
||||
|
||||
// filelist = templist with "/n"s removed. /* ordered sequence of SHA1 values with no separators
|
||||
var b strings.Builder
|
||||
for _, digest := range digests {
|
||||
b.WriteString(digest.Value)
|
||||
}
|
||||
|
||||
//nolint:gosec
|
||||
hasher := sha1.New()
|
||||
_, _ = hasher.Write([]byte(b.String()))
|
||||
return &common.PackageVerificationCode{
|
||||
// 7.9.1: Package Verification Code Value
|
||||
// Cardinality: mandatory, one
|
||||
Value: fmt.Sprintf("%+x", hasher.Sum(nil)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
@@ -369,6 +370,7 @@ func Test_fileIDsForPackage(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_H1Digest(t *testing.T) {
|
||||
sbom := sbom.SBOM{}
|
||||
tests := []struct {
|
||||
name string
|
||||
pkg pkg.Package
|
||||
@@ -415,7 +417,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)
|
||||
pkgs := toPackages(catalog, sbom)
|
||||
require.Len(t, pkgs, 1)
|
||||
for _, p := range pkgs {
|
||||
if test.expectedDigest == "" {
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/anchore/packageurl-go"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/cpe"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/formats/common/util"
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
@@ -381,15 +382,15 @@ func findPURLValue(p *spdx.Package) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func extractCPEs(p *spdx.Package) (cpes []pkg.CPE) {
|
||||
func extractCPEs(p *spdx.Package) (cpes []cpe.CPE) {
|
||||
for _, r := range p.PackageExternalReferences {
|
||||
if r.RefType == string(Cpe23ExternalRefType) {
|
||||
cpe, err := pkg.NewCPE(r.Locator)
|
||||
c, err := cpe.New(r.Locator)
|
||||
if err != nil {
|
||||
log.Warnf("unable to extract SPDX CPE=%q: %+v", r.Locator, err)
|
||||
continue
|
||||
}
|
||||
cpes = append(cpes, cpe)
|
||||
cpes = append(cpes, c)
|
||||
}
|
||||
}
|
||||
return cpes
|
||||
|
||||
@@ -46,7 +46,7 @@ func TestToSyftModel(t *testing.T) {
|
||||
},
|
||||
{
|
||||
Category: "PACKAGE-MANAGER",
|
||||
Locator: "pkg:alpine/pkg-1@5.4.3?arch=x86_64&upstream=p1-origin&distro=alpine-3.10.9",
|
||||
Locator: "pkg:apk/alpine/pkg-1@5.4.3?arch=x86_64&upstream=p1-origin&distro=alpine-3.10.9",
|
||||
RefType: "purl",
|
||||
},
|
||||
},
|
||||
@@ -145,7 +145,7 @@ func Test_extractMetadata(t *testing.T) {
|
||||
PackageExternalReferences: []*spdx.PackageExternalReference{
|
||||
{
|
||||
Category: "PACKAGE-MANAGER",
|
||||
Locator: "pkg:alpine/pkg-2@7.3.1?arch=x86_64&upstream=apk-origin@9.1.3&distro=alpine-3.10.9",
|
||||
Locator: "pkg:apk/alpine/pkg-2@7.3.1?arch=x86_64&upstream=apk-origin@9.1.3&distro=alpine-3.10.9",
|
||||
RefType: "purl",
|
||||
},
|
||||
},
|
||||
|
||||
@@ -9,12 +9,14 @@ import (
|
||||
|
||||
"github.com/sergi/go-diff/diffmatchpatch"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/go-testutils"
|
||||
"github.com/anchore/stereoscope/pkg/filetree"
|
||||
"github.com/anchore/stereoscope/pkg/image"
|
||||
"github.com/anchore/stereoscope/pkg/imagetest"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/cpe"
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
@@ -35,7 +37,7 @@ func FromSnapshot() ImageOption {
|
||||
}
|
||||
}
|
||||
|
||||
func AssertEncoderAgainstGoldenImageSnapshot(t *testing.T, format sbom.Format, sbom sbom.SBOM, testImage string, updateSnapshot bool, redactors ...redactor) {
|
||||
func AssertEncoderAgainstGoldenImageSnapshot(t *testing.T, format sbom.Format, sbom sbom.SBOM, testImage string, updateSnapshot bool, json bool, redactors ...redactor) {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
// grab the latest image contents and persist
|
||||
@@ -61,15 +63,17 @@ func AssertEncoderAgainstGoldenImageSnapshot(t *testing.T, format sbom.Format, s
|
||||
expected = r(expected)
|
||||
}
|
||||
|
||||
// assert that the golden file snapshot matches the actual contents
|
||||
if !bytes.Equal(expected, actual) {
|
||||
if json {
|
||||
require.JSONEq(t, string(expected), string(actual))
|
||||
} else if !bytes.Equal(expected, actual) {
|
||||
// assert that the golden file snapshot matches the actual contents
|
||||
dmp := diffmatchpatch.New()
|
||||
diffs := dmp.DiffMain(string(expected), string(actual), true)
|
||||
t.Errorf("mismatched output:\n%s", dmp.DiffPrettyText(diffs))
|
||||
}
|
||||
}
|
||||
|
||||
func AssertEncoderAgainstGoldenSnapshot(t *testing.T, format sbom.Format, sbom sbom.SBOM, updateSnapshot bool, redactors ...redactor) {
|
||||
func AssertEncoderAgainstGoldenSnapshot(t *testing.T, format sbom.Format, sbom sbom.SBOM, updateSnapshot bool, json bool, redactors ...redactor) {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
err := format.Encode(&buffer, sbom)
|
||||
@@ -90,7 +94,9 @@ func AssertEncoderAgainstGoldenSnapshot(t *testing.T, format sbom.Format, sbom s
|
||||
expected = r(expected)
|
||||
}
|
||||
|
||||
if !bytes.Equal(expected, actual) {
|
||||
if json {
|
||||
require.JSONEq(t, string(expected), string(actual))
|
||||
} else if !bytes.Equal(expected, actual) {
|
||||
dmp := diffmatchpatch.New()
|
||||
diffs := dmp.DiffMain(string(expected), string(actual), true)
|
||||
t.Logf("len: %d\nexpected: %s", len(expected), expected)
|
||||
@@ -174,8 +180,8 @@ func populateImageCatalog(catalog *pkg.Catalog, img *image.Image) {
|
||||
Version: "1.0.1",
|
||||
},
|
||||
PURL: "a-purl-1", // intentionally a bad pURL for test fixtures
|
||||
CPEs: []pkg.CPE{
|
||||
pkg.MustCPE("cpe:2.3:*:some:package:1:*:*:*:*:*:*:*"),
|
||||
CPEs: []cpe.CPE{
|
||||
cpe.Must("cpe:2.3:*:some:package:1:*:*:*:*:*:*:*"),
|
||||
},
|
||||
})
|
||||
catalog.Add(pkg.Package{
|
||||
@@ -192,8 +198,8 @@ func populateImageCatalog(catalog *pkg.Catalog, img *image.Image) {
|
||||
Version: "2.0.1",
|
||||
},
|
||||
PURL: "pkg:deb/debian/package-2@2.0.1",
|
||||
CPEs: []pkg.CPE{
|
||||
pkg.MustCPE("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
|
||||
CPEs: []cpe.CPE{
|
||||
cpe.Must("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -254,8 +260,8 @@ func newDirectoryCatalog() *pkg.Catalog {
|
||||
},
|
||||
},
|
||||
PURL: "a-purl-2", // intentionally a bad pURL for test fixtures
|
||||
CPEs: []pkg.CPE{
|
||||
pkg.MustCPE("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
|
||||
CPEs: []cpe.CPE{
|
||||
cpe.Must("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
|
||||
},
|
||||
})
|
||||
catalog.Add(pkg.Package{
|
||||
@@ -272,8 +278,8 @@ func newDirectoryCatalog() *pkg.Catalog {
|
||||
Version: "2.0.1",
|
||||
},
|
||||
PURL: "pkg:deb/debian/package-2@2.0.1",
|
||||
CPEs: []pkg.CPE{
|
||||
pkg.MustCPE("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
|
||||
CPEs: []cpe.CPE{
|
||||
cpe.Must("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ func encoder(output io.Writer, s sbom.SBOM) error {
|
||||
bom := cyclonedxhelpers.ToFormatModel(s)
|
||||
enc := cyclonedx.NewBOMEncoder(output, cyclonedx.BOMFileFormatJSON)
|
||||
enc.SetPretty(true)
|
||||
|
||||
enc.SetEscapeHTML(false)
|
||||
err := enc.Encode(bom)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ func TestCycloneDxDirectoryEncoder(t *testing.T) {
|
||||
Format(),
|
||||
testutils.DirectoryInput(t),
|
||||
*updateCycloneDx,
|
||||
true,
|
||||
cycloneDxRedactor,
|
||||
)
|
||||
}
|
||||
@@ -26,19 +27,20 @@ func TestCycloneDxImageEncoder(t *testing.T) {
|
||||
testutils.ImageInput(t, testImage),
|
||||
testImage,
|
||||
*updateCycloneDx,
|
||||
true,
|
||||
cycloneDxRedactor,
|
||||
)
|
||||
}
|
||||
|
||||
func cycloneDxRedactor(s []byte) []byte {
|
||||
serialPattern := regexp.MustCompile(`urn:uuid:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}`)
|
||||
rfc3339Pattern := regexp.MustCompile(`([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])[Tt]([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.[0-9]+)?(([Zz])|([\+|\-]([01][0-9]|2[0-3]):[0-5][0-9]))`)
|
||||
rfc3339Pattern := regexp.MustCompile(`([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])[Tt]([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.[0-9]+)?(([Zz])|([+|\-]([01][0-9]|2[0-3]):[0-5][0-9]))`)
|
||||
sha256Pattern := regexp.MustCompile(`sha256:[A-Fa-f0-9]{64}`)
|
||||
for _, pattern := range []*regexp.Regexp{serialPattern, rfc3339Pattern, sha256Pattern} {
|
||||
s = pattern.ReplaceAll(s, []byte("redacted"))
|
||||
s = pattern.ReplaceAll(s, []byte(""))
|
||||
}
|
||||
// the bom-ref will be autogenerated every time, the value here should not be directly tested in snapshot tests
|
||||
s = regexp.MustCompile(` "bom-ref": .*\n`).ReplaceAll(s, []byte(""))
|
||||
s = regexp.MustCompile(`\s+"bom-ref":\s*"[^"]+",?\n`).ReplaceAll(s, []byte(""))
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"bomFormat": "CycloneDX",
|
||||
"specVersion": "1.4",
|
||||
"serialNumber": "urn:uuid:cfd602eb-022b-4a61-84d4-50d34612bd84",
|
||||
"serialNumber": "urn:uuid:0b628da1-274e-4c24-821c-f9452f37db54",
|
||||
"version": 1,
|
||||
"metadata": {
|
||||
"timestamp": "2022-11-07T09:11:21-05:00",
|
||||
"timestamp": "2022-12-22T18:33:51-05:00",
|
||||
"tools": [
|
||||
{
|
||||
"vendor": "anchore",
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"bomFormat": "CycloneDX",
|
||||
"specVersion": "1.4",
|
||||
"serialNumber": "urn:uuid:ced49687-6384-48fa-a930-ef83a258bf77",
|
||||
"serialNumber": "urn:uuid:542fc1a1-81ac-4b76-b9e2-8e6b9d8c840a",
|
||||
"version": 1,
|
||||
"metadata": {
|
||||
"timestamp": "2022-11-07T09:11:21-05:00",
|
||||
"timestamp": "2022-12-22T18:33:51-05:00",
|
||||
"tools": [
|
||||
{
|
||||
"vendor": "anchore",
|
||||
@@ -13,7 +13,7 @@
|
||||
}
|
||||
],
|
||||
"component": {
|
||||
"bom-ref": "522dc6b135a55bb4",
|
||||
"bom-ref": "ffd645a093c0fe70",
|
||||
"type": "container",
|
||||
"name": "user-image-input",
|
||||
"version": "sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368"
|
||||
@@ -53,7 +53,7 @@
|
||||
},
|
||||
{
|
||||
"name": "syft:location:0:layerID",
|
||||
"value": "sha256:fb6beecb75b39f4bb813dbf177e501edd5ddb3e69bb45cedeb78c676ee1b7a59"
|
||||
"value": "sha256:62058900d4ce269c900160b8dd255fe310c3a459dda236d041102fa070f84406"
|
||||
},
|
||||
{
|
||||
"name": "syft:location:0:path",
|
||||
@@ -83,7 +83,7 @@
|
||||
},
|
||||
{
|
||||
"name": "syft:location:0:layerID",
|
||||
"value": "sha256:319b588ce64253a87b533c8ed01cf0025e0eac98e7b516e12532957e1244fdec"
|
||||
"value": "sha256:623ad97366f39ae279f1925673cdacb4851ddf2e3266f04e63010ec080a098c1"
|
||||
},
|
||||
{
|
||||
"name": "syft:location:0:path",
|
||||
|
||||
@@ -15,6 +15,7 @@ func TestCycloneDxDirectoryEncoder(t *testing.T) {
|
||||
Format(),
|
||||
testutils.DirectoryInput(t),
|
||||
*updateCycloneDx,
|
||||
false,
|
||||
cycloneDxRedactor,
|
||||
)
|
||||
}
|
||||
@@ -26,6 +27,7 @@ func TestCycloneDxImageEncoder(t *testing.T) {
|
||||
testutils.ImageInput(t, testImage),
|
||||
testImage,
|
||||
*updateCycloneDx,
|
||||
false,
|
||||
cycloneDxRedactor,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ func TestSPDXJSONDirectoryEncoder(t *testing.T) {
|
||||
Format(),
|
||||
testutils.DirectoryInput(t),
|
||||
*updateSpdxJson,
|
||||
true,
|
||||
spdxJsonRedactor,
|
||||
)
|
||||
}
|
||||
@@ -26,6 +27,7 @@ func TestSPDXJSONImageEncoder(t *testing.T) {
|
||||
testutils.ImageInput(t, testImage, testutils.FromSnapshot()),
|
||||
testImage,
|
||||
*updateSpdxJson,
|
||||
true,
|
||||
spdxJsonRedactor,
|
||||
)
|
||||
}
|
||||
@@ -39,17 +41,18 @@ func TestSPDXRelationshipOrder(t *testing.T) {
|
||||
s,
|
||||
testImage,
|
||||
*updateSpdxJson,
|
||||
true,
|
||||
spdxJsonRedactor,
|
||||
)
|
||||
}
|
||||
|
||||
func spdxJsonRedactor(s []byte) []byte {
|
||||
// each SBOM reports the time it was generated, which is not useful during snapshot testing
|
||||
s = regexp.MustCompile(`"created": .*`).ReplaceAll(s, []byte("redacted"))
|
||||
s = regexp.MustCompile(`"created":\s+"[^"]*",?`).ReplaceAll(s, []byte(""))
|
||||
|
||||
// each SBOM reports a unique documentNamespace when generated, this is not useful for snapshot testing
|
||||
s = regexp.MustCompile(`"documentNamespace": .*`).ReplaceAll(s, []byte("redacted"))
|
||||
s = regexp.MustCompile(`"documentNamespace":\s+"[^"]*",?`).ReplaceAll(s, []byte(""))
|
||||
|
||||
// the license list will be updated periodically, the value here should not be directly tested in snapshot tests
|
||||
return regexp.MustCompile(`"licenseListVersion": .*`).ReplaceAll(s, []byte("redacted"))
|
||||
return regexp.MustCompile(`"licenseListVersion":\s+"[^"]*",?`).ReplaceAll(s, []byte(""))
|
||||
}
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
"dataLicense": "CC0-1.0",
|
||||
"SPDXID": "SPDXRef-DOCUMENT",
|
||||
"name": "/some/path",
|
||||
"documentNamespace": "https://anchore.com/syft/dir/some/path-4bf54cdd-0a0f-4560-bf4f-39cac2ef7a5b",
|
||||
"documentNamespace": "https://anchore.com/syft/dir/some/path-e13c8924-4bbc-42f8-bd30-4e1554472d62",
|
||||
"creationInfo": {
|
||||
"licenseListVersion": "3.18",
|
||||
"licenseListVersion": "3.19",
|
||||
"creators": [
|
||||
"Organization: Anchore, Inc",
|
||||
"Tool: syft-v0.42.0-bogus"
|
||||
],
|
||||
"created": "2022-11-19T13:46:57Z",
|
||||
"created": "2022-12-22T23:33:52Z",
|
||||
"comment": ""
|
||||
},
|
||||
"packages": [
|
||||
@@ -62,5 +62,12 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"relationships": [
|
||||
{
|
||||
"spdxElementId": "SPDXRef-DOCUMENT",
|
||||
"relatedSpdxElement": "SPDXRef-DOCUMENT",
|
||||
"relationshipType": "DESCRIBES"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
"dataLicense": "CC0-1.0",
|
||||
"SPDXID": "SPDXRef-DOCUMENT",
|
||||
"name": "user-image-input",
|
||||
"documentNamespace": "https://anchore.com/syft/image/user-image-input-102ca7dc-3d1e-46d2-b130-28968831ebcc",
|
||||
"documentNamespace": "https://anchore.com/syft/image/user-image-input-a1cc9d58-830a-4a4b-9dcd-f41ea3001216",
|
||||
"creationInfo": {
|
||||
"licenseListVersion": "3.18",
|
||||
"licenseListVersion": "3.19",
|
||||
"creators": [
|
||||
"Organization: Anchore, Inc",
|
||||
"Tool: syft-v0.42.0-bogus"
|
||||
],
|
||||
"created": "2022-11-19T13:46:57Z",
|
||||
"created": "2022-12-22T23:33:53Z",
|
||||
"comment": ""
|
||||
},
|
||||
"packages": [
|
||||
@@ -62,5 +62,12 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"relationships": [
|
||||
{
|
||||
"spdxElementId": "SPDXRef-DOCUMENT",
|
||||
"relatedSpdxElement": "SPDXRef-DOCUMENT",
|
||||
"relationshipType": "DESCRIBES"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
"dataLicense": "CC0-1.0",
|
||||
"SPDXID": "SPDXRef-DOCUMENT",
|
||||
"name": "user-image-input",
|
||||
"documentNamespace": "https://anchore.com/syft/image/user-image-input-55ad4afc-ecdc-46a4-8bc3-36b3e72da5d1",
|
||||
"documentNamespace": "https://anchore.com/syft/image/user-image-input-fc663ee3-0f9b-402e-827f-3f29aeff164e",
|
||||
"creationInfo": {
|
||||
"licenseListVersion": "3.18",
|
||||
"licenseListVersion": "3.19",
|
||||
"creators": [
|
||||
"Organization: Anchore, Inc",
|
||||
"Tool: syft-v0.42.0-bogus"
|
||||
],
|
||||
"created": "2022-11-19T13:46:57Z",
|
||||
"created": "2022-12-22T23:33:53Z",
|
||||
"comment": ""
|
||||
},
|
||||
"packages": [
|
||||
@@ -70,7 +70,12 @@
|
||||
"fileTypes": [
|
||||
"OTHER"
|
||||
],
|
||||
"checksums": null,
|
||||
"checksums": [
|
||||
{
|
||||
"algorithm": "SHA1",
|
||||
"checksumValue": "0000000000000000000000000000000000000000"
|
||||
}
|
||||
],
|
||||
"licenseConcluded": "NOASSERTION",
|
||||
"copyrightText": ""
|
||||
},
|
||||
@@ -80,7 +85,12 @@
|
||||
"fileTypes": [
|
||||
"OTHER"
|
||||
],
|
||||
"checksums": null,
|
||||
"checksums": [
|
||||
{
|
||||
"algorithm": "SHA1",
|
||||
"checksumValue": "0000000000000000000000000000000000000000"
|
||||
}
|
||||
],
|
||||
"licenseConcluded": "NOASSERTION",
|
||||
"copyrightText": ""
|
||||
},
|
||||
@@ -90,7 +100,12 @@
|
||||
"fileTypes": [
|
||||
"OTHER"
|
||||
],
|
||||
"checksums": null,
|
||||
"checksums": [
|
||||
{
|
||||
"algorithm": "SHA1",
|
||||
"checksumValue": "0000000000000000000000000000000000000000"
|
||||
}
|
||||
],
|
||||
"licenseConcluded": "NOASSERTION",
|
||||
"copyrightText": ""
|
||||
},
|
||||
@@ -100,7 +115,12 @@
|
||||
"fileTypes": [
|
||||
"OTHER"
|
||||
],
|
||||
"checksums": null,
|
||||
"checksums": [
|
||||
{
|
||||
"algorithm": "SHA1",
|
||||
"checksumValue": "0000000000000000000000000000000000000000"
|
||||
}
|
||||
],
|
||||
"licenseConcluded": "NOASSERTION",
|
||||
"copyrightText": ""
|
||||
},
|
||||
@@ -110,7 +130,12 @@
|
||||
"fileTypes": [
|
||||
"OTHER"
|
||||
],
|
||||
"checksums": null,
|
||||
"checksums": [
|
||||
{
|
||||
"algorithm": "SHA1",
|
||||
"checksumValue": "0000000000000000000000000000000000000000"
|
||||
}
|
||||
],
|
||||
"licenseConcluded": "NOASSERTION",
|
||||
"copyrightText": ""
|
||||
},
|
||||
@@ -120,7 +145,12 @@
|
||||
"fileTypes": [
|
||||
"OTHER"
|
||||
],
|
||||
"checksums": null,
|
||||
"checksums": [
|
||||
{
|
||||
"algorithm": "SHA1",
|
||||
"checksumValue": "0000000000000000000000000000000000000000"
|
||||
}
|
||||
],
|
||||
"licenseConcluded": "NOASSERTION",
|
||||
"copyrightText": ""
|
||||
}
|
||||
@@ -155,6 +185,11 @@
|
||||
"spdxElementId": "SPDXRef-Package-python-package-1-66ba429119b8bec6",
|
||||
"relatedSpdxElement": "SPDXRef-f9e49132a4b96ccd",
|
||||
"relationshipType": "CONTAINS"
|
||||
},
|
||||
{
|
||||
"spdxElementId": "SPDXRef-DOCUMENT",
|
||||
"relatedSpdxElement": "SPDXRef-DOCUMENT",
|
||||
"relationshipType": "DESCRIBES"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -27,7 +27,7 @@
|
||||
},
|
||||
{
|
||||
"referenceCategory": "PACKAGE_MANAGER",
|
||||
"referenceLocator": "pkg:alpine/busybox@1.30.1-r5?arch=x86_64&distro=alpine-3.10.9",
|
||||
"referenceLocator": "pkg:apk/alpine/busybox@1.30.1-r5?arch=x86_64&distro=alpine-3.10.9",
|
||||
"referenceType": "purl"
|
||||
}
|
||||
],
|
||||
@@ -51,7 +51,7 @@
|
||||
},
|
||||
{
|
||||
"referenceCategory": "PACKAGE_MANAGER",
|
||||
"referenceLocator": "pkg:alpine/libssl1.1@1.1.1k-r0?arch=x86_64&distro=alpine-3.10.9",
|
||||
"referenceLocator": "pkg:apk/alpine/libssl1.1@1.1.1k-r0?arch=x86_64&distro=alpine-3.10.9",
|
||||
"referenceType": "purl"
|
||||
}
|
||||
],
|
||||
@@ -100,7 +100,7 @@
|
||||
},
|
||||
{
|
||||
"referenceCategory": "PACKAGE_MANAGER",
|
||||
"referenceLocator": "pkg:alpine/ssl_client@1.30.1-r5?arch=x86_64&upstream=busybox&distro=alpine-3.10.9",
|
||||
"referenceLocator": "pkg:apk/alpine/ssl_client@1.30.1-r5?arch=x86_64&upstream=busybox&distro=alpine-3.10.9",
|
||||
"referenceType": "purl"
|
||||
}
|
||||
],
|
||||
|
||||
@@ -19,6 +19,7 @@ func TestSPDXTagValueDirectoryEncoder(t *testing.T) {
|
||||
Format(),
|
||||
testutils.DirectoryInput(t),
|
||||
*updateSpdxTagValue,
|
||||
false,
|
||||
spdxTagValueRedactor,
|
||||
)
|
||||
}
|
||||
@@ -30,6 +31,7 @@ func TestSPDXTagValueImageEncoder(t *testing.T) {
|
||||
testutils.ImageInput(t, testImage, testutils.FromSnapshot()),
|
||||
testImage,
|
||||
*updateSpdxTagValue,
|
||||
false,
|
||||
spdxTagValueRedactor,
|
||||
)
|
||||
}
|
||||
@@ -63,6 +65,7 @@ func TestSPDXJSONSPDXIDs(t *testing.T) {
|
||||
},
|
||||
},
|
||||
*updateSpdxTagValue,
|
||||
false,
|
||||
spdxTagValueRedactor,
|
||||
)
|
||||
}
|
||||
@@ -76,6 +79,7 @@ func TestSPDXRelationshipOrder(t *testing.T) {
|
||||
s,
|
||||
testImage,
|
||||
*updateSpdxTagValue,
|
||||
false,
|
||||
spdxTagValueRedactor,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@ SPDXVersion: SPDX-2.3
|
||||
DataLicense: CC0-1.0
|
||||
SPDXID: SPDXRef-DOCUMENT
|
||||
DocumentName: foobar/baz
|
||||
DocumentNamespace: https://anchore.com/syft/dir/foobar/baz-62bc0aae-2b37-4c86-ab79-63c6fc4198ed
|
||||
LicenseListVersion: 3.18
|
||||
DocumentNamespace: https://anchore.com/syft/dir/foobar/baz-478e410d-7fad-472c-b4e9-a4068ef28160
|
||||
LicenseListVersion: 3.19
|
||||
Creator: Organization: Anchore, Inc
|
||||
Creator: Tool: syft-v0.42.0-bogus
|
||||
Created: 2022-11-19T13:48:30Z
|
||||
Created: 2022-12-21T03:39:05Z
|
||||
|
||||
##### Package: @at-sign
|
||||
|
||||
@@ -41,3 +41,7 @@ PackageLicenseConcluded: NONE
|
||||
PackageLicenseDeclared: NONE
|
||||
PackageCopyrightText: NOASSERTION
|
||||
|
||||
##### Relationships
|
||||
|
||||
Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-DOCUMENT
|
||||
|
||||
|
||||
@@ -2,42 +2,48 @@ SPDXVersion: SPDX-2.3
|
||||
DataLicense: CC0-1.0
|
||||
SPDXID: SPDXRef-DOCUMENT
|
||||
DocumentName: user-image-input
|
||||
DocumentNamespace: https://anchore.com/syft/image/user-image-input-cc20e416-9c74-401c-b4aa-245556bada5e
|
||||
LicenseListVersion: 3.18
|
||||
DocumentNamespace: https://anchore.com/syft/image/user-image-input-73433e8c-364f-42b6-b5b7-9a4da8799868
|
||||
LicenseListVersion: 3.19
|
||||
Creator: Organization: Anchore, Inc
|
||||
Creator: Tool: syft-v0.42.0-bogus
|
||||
Created: 2022-11-19T13:48:30Z
|
||||
Created: 2022-12-21T03:39:05Z
|
||||
|
||||
##### Unpackaged files
|
||||
|
||||
FileName: /f1
|
||||
SPDXID: SPDXRef-5265a4dde3edbf7c
|
||||
FileType: OTHER
|
||||
FileChecksum: SHA1: 0000000000000000000000000000000000000000
|
||||
LicenseConcluded: NOASSERTION
|
||||
|
||||
FileName: /z1/f5
|
||||
SPDXID: SPDXRef-839d99ee67d9d174
|
||||
FileType: OTHER
|
||||
FileChecksum: SHA1: 0000000000000000000000000000000000000000
|
||||
LicenseConcluded: NOASSERTION
|
||||
|
||||
FileName: /a1/f6
|
||||
SPDXID: SPDXRef-9c2f7510199b17f6
|
||||
FileType: OTHER
|
||||
FileChecksum: SHA1: 0000000000000000000000000000000000000000
|
||||
LicenseConcluded: NOASSERTION
|
||||
|
||||
FileName: /d2/f4
|
||||
SPDXID: SPDXRef-c641caa71518099f
|
||||
FileType: OTHER
|
||||
FileChecksum: SHA1: 0000000000000000000000000000000000000000
|
||||
LicenseConcluded: NOASSERTION
|
||||
|
||||
FileName: /d1/f3
|
||||
SPDXID: SPDXRef-c6f5b29dca12661f
|
||||
FileType: OTHER
|
||||
FileChecksum: SHA1: 0000000000000000000000000000000000000000
|
||||
LicenseConcluded: NOASSERTION
|
||||
|
||||
FileName: /f2
|
||||
SPDXID: SPDXRef-f9e49132a4b96ccd
|
||||
FileType: OTHER
|
||||
FileChecksum: SHA1: 0000000000000000000000000000000000000000
|
||||
LicenseConcluded: NOASSERTION
|
||||
|
||||
##### Package: package-2
|
||||
@@ -76,4 +82,5 @@ Relationship: SPDXRef-Package-python-package-1-66ba429119b8bec6 CONTAINS SPDXRef
|
||||
Relationship: SPDXRef-Package-python-package-1-66ba429119b8bec6 CONTAINS SPDXRef-c641caa71518099f
|
||||
Relationship: SPDXRef-Package-python-package-1-66ba429119b8bec6 CONTAINS SPDXRef-c6f5b29dca12661f
|
||||
Relationship: SPDXRef-Package-python-package-1-66ba429119b8bec6 CONTAINS SPDXRef-f9e49132a4b96ccd
|
||||
Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-DOCUMENT
|
||||
|
||||
|
||||
+7
-3
@@ -2,11 +2,11 @@ SPDXVersion: SPDX-2.3
|
||||
DataLicense: CC0-1.0
|
||||
SPDXID: SPDXRef-DOCUMENT
|
||||
DocumentName: /some/path
|
||||
DocumentNamespace: https://anchore.com/syft/dir/some/path-7a4b2140-6669-4a28-80dd-5c8e795c5da0
|
||||
LicenseListVersion: 3.18
|
||||
DocumentNamespace: https://anchore.com/syft/dir/some/path-1d303762-46d2-47b5-9c81-defa91387275
|
||||
LicenseListVersion: 3.19
|
||||
Creator: Organization: Anchore, Inc
|
||||
Creator: Tool: syft-v0.42.0-bogus
|
||||
Created: 2022-11-19T13:48:30Z
|
||||
Created: 2022-12-21T03:39:05Z
|
||||
|
||||
##### Package: package-2
|
||||
|
||||
@@ -36,3 +36,7 @@ PackageCopyrightText: NOASSERTION
|
||||
ExternalRef: SECURITY cpe23Type cpe:2.3:*:some:package:2:*:*:*:*:*:*:*
|
||||
ExternalRef: PACKAGE-MANAGER purl a-purl-2
|
||||
|
||||
##### Relationships
|
||||
|
||||
Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-DOCUMENT
|
||||
|
||||
|
||||
+7
-3
@@ -2,11 +2,11 @@ SPDXVersion: SPDX-2.3
|
||||
DataLicense: CC0-1.0
|
||||
SPDXID: SPDXRef-DOCUMENT
|
||||
DocumentName: user-image-input
|
||||
DocumentNamespace: https://anchore.com/syft/image/user-image-input-baff7ada-85cb-403e-90d7-05b0c6d79490
|
||||
LicenseListVersion: 3.18
|
||||
DocumentNamespace: https://anchore.com/syft/image/user-image-input-559af225-63af-4bc0-94fb-bce94913bcfa
|
||||
LicenseListVersion: 3.19
|
||||
Creator: Organization: Anchore, Inc
|
||||
Creator: Tool: syft-v0.42.0-bogus
|
||||
Created: 2022-11-19T13:48:30Z
|
||||
Created: 2022-12-21T03:39:05Z
|
||||
|
||||
##### Package: package-2
|
||||
|
||||
@@ -36,3 +36,7 @@ PackageCopyrightText: NOASSERTION
|
||||
ExternalRef: SECURITY cpe23Type cpe:2.3:*:some:package:1:*:*:*:*:*:*:*
|
||||
ExternalRef: PACKAGE-MANAGER purl a-purl-1
|
||||
|
||||
##### Relationships
|
||||
|
||||
Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-DOCUMENT
|
||||
|
||||
|
||||
BIN
Binary file not shown.
@@ -21,7 +21,7 @@ PackageLicenseDeclared: GPL-2.0-only
|
||||
PackageCopyrightText: NOASSERTION
|
||||
PackageDescription: Size optimized toolbox of many common UNIX utilities
|
||||
ExternalRef: SECURITY cpe23Type cpe:2.3:a:busybox:busybox:1.31.1-r19:*:*:*:*:*:*:*
|
||||
ExternalRef: PACKAGE-MANAGER purl pkg:alpine/busybox@1.31.1-r19?arch=x86_64&upstream=busybox&distro=alpine-3.12.5
|
||||
ExternalRef: PACKAGE-MANAGER purl pkg:apk/alpine/busybox@1.31.1-r19?arch=x86_64&upstream=busybox&distro=alpine-3.12.5
|
||||
|
||||
##### Package: my-app
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/cpe"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/formats/common/testutils"
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
@@ -19,6 +20,7 @@ func TestDirectoryEncoder(t *testing.T) {
|
||||
testutils.AssertEncoderAgainstGoldenSnapshot(t,
|
||||
Format(),
|
||||
testutils.DirectoryInput(t),
|
||||
true,
|
||||
*updateJson,
|
||||
)
|
||||
}
|
||||
@@ -29,6 +31,7 @@ func TestImageEncoder(t *testing.T) {
|
||||
Format(),
|
||||
testutils.ImageInput(t, testImage, testutils.FromSnapshot()),
|
||||
testImage,
|
||||
true,
|
||||
*updateJson,
|
||||
)
|
||||
}
|
||||
@@ -57,8 +60,8 @@ func TestEncodeFullJSONDocument(t *testing.T) {
|
||||
Files: []pkg.PythonFileRecord{},
|
||||
},
|
||||
PURL: "a-purl-1",
|
||||
CPEs: []pkg.CPE{
|
||||
pkg.MustCPE("cpe:2.3:*:some:package:1:*:*:*:*:*:*:*"),
|
||||
CPEs: []cpe.CPE{
|
||||
cpe.Must("cpe:2.3:*:some:package:1:*:*:*:*:*:*:*"),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -81,8 +84,8 @@ func TestEncodeFullJSONDocument(t *testing.T) {
|
||||
Files: []pkg.DpkgFileRecord{},
|
||||
},
|
||||
PURL: "a-purl-2",
|
||||
CPEs: []pkg.CPE{
|
||||
pkg.MustCPE("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
|
||||
CPEs: []cpe.CPE{
|
||||
cpe.Must("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -199,5 +202,6 @@ func TestEncodeFullJSONDocument(t *testing.T) {
|
||||
Format(),
|
||||
s,
|
||||
*updateJson,
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -6,12 +6,11 @@ import (
|
||||
)
|
||||
|
||||
type File struct {
|
||||
ID string `json:"id"`
|
||||
Location source.Coordinates `json:"location"`
|
||||
Metadata *FileMetadataEntry `json:"metadata,omitempty"`
|
||||
Contents string `json:"contents,omitempty"`
|
||||
Digests []file.Digest `json:"digests,omitempty"`
|
||||
Classifications []file.Classification `json:"classifications,omitempty"`
|
||||
ID string `json:"id"`
|
||||
Location source.Coordinates `json:"location"`
|
||||
Metadata *FileMetadataEntry `json:"metadata,omitempty"`
|
||||
Contents string `json:"contents,omitempty"`
|
||||
Digests []file.Digest `json:"digests,omitempty"`
|
||||
}
|
||||
|
||||
type FileMetadataEntry struct {
|
||||
|
||||
@@ -220,6 +220,14 @@ func Test_unpackMetadata(t *testing.T) {
|
||||
metadataType: pkg.GolangBinMetadataType,
|
||||
wantMetadata: pkg.GolangBinMetadata{},
|
||||
},
|
||||
{
|
||||
name: "can handle package with golang bin metadata type",
|
||||
packageData: []byte(`{
|
||||
"metadataType": "GolangBinMetadata"
|
||||
}`),
|
||||
metadataType: pkg.GolangBinMetadataType,
|
||||
wantMetadata: pkg.GolangBinMetadata{},
|
||||
},
|
||||
{
|
||||
name: "can handle package with unknonwn metadata type and missing metadata",
|
||||
packageData: []byte(`{
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
}
|
||||
},
|
||||
"schema": {
|
||||
"version": "6.0.0",
|
||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-6.0.0.json"
|
||||
"version": "6.1.0",
|
||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-6.1.0.json"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,7 +185,7 @@
|
||||
}
|
||||
},
|
||||
"schema": {
|
||||
"version": "6.0.0",
|
||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-6.0.0.json"
|
||||
"version": "6.2.0",
|
||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-6.2.0.json"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"locations": [
|
||||
{
|
||||
"path": "/somefile-1.txt",
|
||||
"layerID": "sha256:fb6beecb75b39f4bb813dbf177e501edd5ddb3e69bb45cedeb78c676ee1b7a59"
|
||||
"layerID": "sha256:6afd1cb55939d87ba4c298907d0a53059bb3742c2d65314643e2464071cf0a2d"
|
||||
}
|
||||
],
|
||||
"licenses": [
|
||||
@@ -40,7 +40,7 @@
|
||||
"locations": [
|
||||
{
|
||||
"path": "/somefile-2.txt",
|
||||
"layerID": "sha256:319b588ce64253a87b533c8ed01cf0025e0eac98e7b516e12532957e1244fdec"
|
||||
"layerID": "sha256:657997cff9a836139186239bdfe77250239a700d0ed97d57e101c295e8244319"
|
||||
}
|
||||
],
|
||||
"licenses": [],
|
||||
@@ -64,11 +64,11 @@
|
||||
],
|
||||
"artifactRelationships": [],
|
||||
"source": {
|
||||
"id": "1a678f111c8ddc66fd82687bb024e0dd6af61314404937a80e810c0cf317b796",
|
||||
"id": "c85f7ae1b0ac38342c1cf1a6f7ea2b4b1ddc49cd1b24219ebd05dc10b3303491",
|
||||
"type": "image",
|
||||
"target": {
|
||||
"userInput": "user-image-input",
|
||||
"imageID": "sha256:3c51b06feb0cda8ee62d0e3755ef2a8496a6b71f8a55b245f07f31c4bb813d31",
|
||||
"imageID": "sha256:b5c0bfa8bcf70c75d92ebebbf76af667906d56e6fad50c37e7f93df824a64b79",
|
||||
"manifestDigest": "sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368",
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||
"tags": [
|
||||
@@ -78,17 +78,17 @@
|
||||
"layers": [
|
||||
{
|
||||
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
"digest": "sha256:fb6beecb75b39f4bb813dbf177e501edd5ddb3e69bb45cedeb78c676ee1b7a59",
|
||||
"digest": "sha256:6afd1cb55939d87ba4c298907d0a53059bb3742c2d65314643e2464071cf0a2d",
|
||||
"size": 22
|
||||
},
|
||||
{
|
||||
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
"digest": "sha256:319b588ce64253a87b533c8ed01cf0025e0eac98e7b516e12532957e1244fdec",
|
||||
"digest": "sha256:657997cff9a836139186239bdfe77250239a700d0ed97d57e101c295e8244319",
|
||||
"size": 16
|
||||
}
|
||||
],
|
||||
"manifest": "eyJzY2hlbWFWZXJzaW9uIjoyLCJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmRpc3RyaWJ1dGlvbi5tYW5pZmVzdC52Mitqc29uIiwiY29uZmlnIjp7Im1lZGlhVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5kb2NrZXIuY29udGFpbmVyLmltYWdlLnYxK2pzb24iLCJzaXplIjo2NzMsImRpZ2VzdCI6InNoYTI1NjozYzUxYjA2ZmViMGNkYThlZTYyZDBlMzc1NWVmMmE4NDk2YTZiNzFmOGE1NWIyNDVmMDdmMzFjNGJiODEzZDMxIn0sImxheWVycyI6W3sibWVkaWFUeXBlIjoiYXBwbGljYXRpb24vdm5kLmRvY2tlci5pbWFnZS5yb290ZnMuZGlmZi50YXIuZ3ppcCIsInNpemUiOjIwNDgsImRpZ2VzdCI6InNoYTI1NjpmYjZiZWVjYjc1YjM5ZjRiYjgxM2RiZjE3N2U1MDFlZGQ1ZGRiM2U2OWJiNDVjZWRlYjc4YzY3NmVlMWI3YTU5In0seyJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmltYWdlLnJvb3Rmcy5kaWZmLnRhci5nemlwIiwic2l6ZSI6MjA0OCwiZGlnZXN0Ijoic2hhMjU2OjMxOWI1ODhjZTY0MjUzYTg3YjUzM2M4ZWQwMWNmMDAyNWUwZWFjOThlN2I1MTZlMTI1MzI5NTdlMTI0NGZkZWMifV19",
|
||||
"config": "eyJhcmNoaXRlY3R1cmUiOiJhbWQ2NCIsImNvbmZpZyI6eyJFbnYiOlsiUEFUSD0vdXNyL2xvY2FsL3NiaW46L3Vzci9sb2NhbC9iaW46L3Vzci9zYmluOi91c3IvYmluOi9zYmluOi9iaW4iXSwiV29ya2luZ0RpciI6Ii8iLCJPbkJ1aWxkIjpudWxsfSwiY3JlYXRlZCI6IjIwMjItMDgtMDFUMjA6MDk6MjIuNTA5NDIxNzEyWiIsImhpc3RvcnkiOlt7ImNyZWF0ZWQiOiIyMDIyLTA4LTAxVDIwOjA5OjIyLjQ4Nzg5NTUxOVoiLCJjcmVhdGVkX2J5IjoiQUREIGZpbGUtMS50eHQgL3NvbWVmaWxlLTEudHh0ICMgYnVpbGRraXQiLCJjb21tZW50IjoiYnVpbGRraXQuZG9ja2VyZmlsZS52MCJ9LHsiY3JlYXRlZCI6IjIwMjItMDgtMDFUMjA6MDk6MjIuNTA5NDIxNzEyWiIsImNyZWF0ZWRfYnkiOiJBREQgZmlsZS0yLnR4dCAvc29tZWZpbGUtMi50eHQgIyBidWlsZGtpdCIsImNvbW1lbnQiOiJidWlsZGtpdC5kb2NrZXJmaWxlLnYwIn1dLCJvcyI6ImxpbnV4Iiwicm9vdGZzIjp7InR5cGUiOiJsYXllcnMiLCJkaWZmX2lkcyI6WyJzaGEyNTY6ZmI2YmVlY2I3NWIzOWY0YmI4MTNkYmYxNzdlNTAxZWRkNWRkYjNlNjliYjQ1Y2VkZWI3OGM2NzZlZTFiN2E1OSIsInNoYTI1NjozMTliNTg4Y2U2NDI1M2E4N2I1MzNjOGVkMDFjZjAwMjVlMGVhYzk4ZTdiNTE2ZTEyNTMyOTU3ZTEyNDRmZGVjIl19fQ==",
|
||||
"manifest": "eyJzY2hlbWFWZXJzaW9uIjoyLCJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmRpc3RyaWJ1dGlvbi5tYW5pZmVzdC52Mitqc29uIiwiY29uZmlnIjp7Im1lZGlhVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5kb2NrZXIuY29udGFpbmVyLmltYWdlLnYxK2pzb24iLCJzaXplIjo2NzMsImRpZ2VzdCI6InNoYTI1NjpiNWMwYmZhOGJjZjcwYzc1ZDkyZWJlYmJmNzZhZjY2NzkwNmQ1NmU2ZmFkNTBjMzdlN2Y5M2RmODI0YTY0Yjc5In0sImxheWVycyI6W3sibWVkaWFUeXBlIjoiYXBwbGljYXRpb24vdm5kLmRvY2tlci5pbWFnZS5yb290ZnMuZGlmZi50YXIuZ3ppcCIsInNpemUiOjIwNDgsImRpZ2VzdCI6InNoYTI1Njo2YWZkMWNiNTU5MzlkODdiYTRjMjk4OTA3ZDBhNTMwNTliYjM3NDJjMmQ2NTMxNDY0M2UyNDY0MDcxY2YwYTJkIn0seyJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmltYWdlLnJvb3Rmcy5kaWZmLnRhci5nemlwIiwic2l6ZSI6MjA0OCwiZGlnZXN0Ijoic2hhMjU2OjY1Nzk5N2NmZjlhODM2MTM5MTg2MjM5YmRmZTc3MjUwMjM5YTcwMGQwZWQ5N2Q1N2UxMDFjMjk1ZTgyNDQzMTkifV19",
|
||||
"config": "eyJhcmNoaXRlY3R1cmUiOiJhbWQ2NCIsImNvbmZpZyI6eyJFbnYiOlsiUEFUSD0vdXNyL2xvY2FsL3NiaW46L3Vzci9sb2NhbC9iaW46L3Vzci9zYmluOi91c3IvYmluOi9zYmluOi9iaW4iXSwiV29ya2luZ0RpciI6Ii8iLCJPbkJ1aWxkIjpudWxsfSwiY3JlYXRlZCI6IjIwMjItMDgtMjVUMTY6MjI6MDguODkxMzY0Mjc4WiIsImhpc3RvcnkiOlt7ImNyZWF0ZWQiOiIyMDIyLTA4LTI1VDE2OjIyOjA4Ljc2MzMzMDMyM1oiLCJjcmVhdGVkX2J5IjoiQUREIGZpbGUtMS50eHQgL3NvbWVmaWxlLTEudHh0ICMgYnVpbGRraXQiLCJjb21tZW50IjoiYnVpbGRraXQuZG9ja2VyZmlsZS52MCJ9LHsiY3JlYXRlZCI6IjIwMjItMDgtMjVUMTY6MjI6MDguODkxMzY0Mjc4WiIsImNyZWF0ZWRfYnkiOiJBREQgZmlsZS0yLnR4dCAvc29tZWZpbGUtMi50eHQgIyBidWlsZGtpdCIsImNvbW1lbnQiOiJidWlsZGtpdC5kb2NrZXJmaWxlLnYwIn1dLCJvcyI6ImxpbnV4Iiwicm9vdGZzIjp7InR5cGUiOiJsYXllcnMiLCJkaWZmX2lkcyI6WyJzaGEyNTY6NmFmZDFjYjU1OTM5ZDg3YmE0YzI5ODkwN2QwYTUzMDU5YmIzNzQyYzJkNjUzMTQ2NDNlMjQ2NDA3MWNmMGEyZCIsInNoYTI1Njo2NTc5OTdjZmY5YTgzNjEzOTE4NjIzOWJkZmU3NzI1MDIzOWE3MDBkMGVkOTdkNTdlMTAxYzI5NWU4MjQ0MzE5Il19fQ==",
|
||||
"repoDigests": [],
|
||||
"architecture": "",
|
||||
"os": ""
|
||||
@@ -112,7 +112,7 @@
|
||||
}
|
||||
},
|
||||
"schema": {
|
||||
"version": "6.0.0",
|
||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-6.0.0.json"
|
||||
"version": "6.1.0",
|
||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-6.1.0.json"
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user