mirror of
https://github.com/anchore/syft.git
synced 2026-10-24 12:02:22 +02:00
Compare commits
17
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fdc1effd6e | ||
|
|
055542694f | ||
|
|
c6447cb62b | ||
|
|
bd89e22fe5 | ||
|
|
3ca797dc53 | ||
|
|
50408b52d1 | ||
|
|
c00afc7fa9 | ||
|
|
4a82e4843a | ||
|
|
d474281731 | ||
|
|
0c0890ef44 | ||
|
|
8d522efcf6 | ||
|
|
124cfd86c7 | ||
|
|
12b44af471 | ||
|
|
bcdebcadaf | ||
|
|
f6cc0c8628 | ||
|
|
c1346ad62c | ||
|
|
9ded1c4c22 |
@@ -1,398 +0,0 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
# Code generated by godownloader on 2019-12-25T12:47:14Z. DO NOT EDIT.
|
||||
#
|
||||
|
||||
usage() {
|
||||
this=$1
|
||||
cat <<EOF
|
||||
$this: download go binaries for goreleaser/goreleaser
|
||||
|
||||
Usage: $this [-b] bindir [-d] [tag]
|
||||
-b sets bindir or installation directory, Defaults to ./bin
|
||||
-d turns on debug logging
|
||||
[tag] is a tag from
|
||||
https://github.com/goreleaser/goreleaser/releases
|
||||
If tag is missing, then the latest will be used.
|
||||
|
||||
Generated by godownloader
|
||||
https://github.com/goreleaser/godownloader
|
||||
|
||||
EOF
|
||||
exit 2
|
||||
}
|
||||
|
||||
parse_args() {
|
||||
#BINDIR is ./bin unless set be ENV
|
||||
# over-ridden by flag below
|
||||
|
||||
BINDIR=${BINDIR:-./bin}
|
||||
while getopts "b:dh?x" arg; do
|
||||
case "$arg" in
|
||||
b) BINDIR="$OPTARG" ;;
|
||||
d) log_set_priority 10 ;;
|
||||
h | \?) usage "$0" ;;
|
||||
x) set -x ;;
|
||||
esac
|
||||
done
|
||||
shift $((OPTIND - 1))
|
||||
TAG=$1
|
||||
}
|
||||
# this function wraps all the destructive operations
|
||||
# if a curl|bash cuts off the end of the script due to
|
||||
# network, either nothing will happen or will syntax error
|
||||
# out preventing half-done work
|
||||
execute() {
|
||||
tmpdir=$(mktemp -d)
|
||||
log_debug "downloading files into ${tmpdir}"
|
||||
http_download "${tmpdir}/${TARBALL}" "${TARBALL_URL}"
|
||||
http_download "${tmpdir}/${CHECKSUM}" "${CHECKSUM_URL}"
|
||||
hash_sha256_verify "${tmpdir}/${TARBALL}" "${tmpdir}/${CHECKSUM}"
|
||||
srcdir="${tmpdir}"
|
||||
(cd "${tmpdir}" && untar "${TARBALL}")
|
||||
test ! -d "${BINDIR}" && install -d "${BINDIR}"
|
||||
for binexe in $BINARIES; do
|
||||
if [ "$OS" = "windows" ]; then
|
||||
binexe="${binexe}.exe"
|
||||
fi
|
||||
install "${srcdir}/${binexe}" "${BINDIR}/"
|
||||
log_info "installed ${BINDIR}/${binexe}"
|
||||
done
|
||||
rm -rf "${tmpdir}"
|
||||
}
|
||||
get_binaries() {
|
||||
case "$PLATFORM" in
|
||||
darwin/386) BINARIES="goreleaser" ;;
|
||||
darwin/amd64) BINARIES="goreleaser" ;;
|
||||
darwin/arm64) BINARIES="goreleaser" ;;
|
||||
darwin/armv6) BINARIES="goreleaser" ;;
|
||||
linux/386) BINARIES="goreleaser" ;;
|
||||
linux/amd64) BINARIES="goreleaser" ;;
|
||||
linux/arm64) BINARIES="goreleaser" ;;
|
||||
linux/armv6) BINARIES="goreleaser" ;;
|
||||
windows/386) BINARIES="goreleaser" ;;
|
||||
windows/amd64) BINARIES="goreleaser" ;;
|
||||
windows/arm64) BINARIES="goreleaser" ;;
|
||||
windows/armv6) BINARIES="goreleaser" ;;
|
||||
*)
|
||||
log_crit "platform $PLATFORM is not supported. Make sure this script is up-to-date and file request at https://github.com/${PREFIX}/issues/new"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
tag_to_version() {
|
||||
if [ -z "${TAG}" ]; then
|
||||
log_info "checking GitHub for latest tag"
|
||||
else
|
||||
log_info "checking GitHub for tag '${TAG}'"
|
||||
fi
|
||||
REALTAG=$(github_release "$OWNER/$REPO" "${TAG}") && true
|
||||
if test -z "$REALTAG"; then
|
||||
log_crit "unable to find '${TAG}' - use 'latest' or see https://github.com/${PREFIX}/releases for details"
|
||||
exit 1
|
||||
fi
|
||||
# if version starts with 'v', remove it
|
||||
TAG="$REALTAG"
|
||||
VERSION=${TAG#v}
|
||||
}
|
||||
adjust_format() {
|
||||
# change format (tar.gz or zip) based on OS
|
||||
case ${OS} in
|
||||
windows) FORMAT=zip ;;
|
||||
esac
|
||||
true
|
||||
}
|
||||
adjust_os() {
|
||||
# adjust archive name based on OS
|
||||
case ${OS} in
|
||||
386) OS=i386 ;;
|
||||
amd64) OS=x86_64 ;;
|
||||
darwin) OS=Darwin ;;
|
||||
linux) OS=Linux ;;
|
||||
windows) OS=Windows ;;
|
||||
esac
|
||||
true
|
||||
}
|
||||
adjust_arch() {
|
||||
# adjust archive name based on ARCH
|
||||
case ${ARCH} in
|
||||
386) ARCH=i386 ;;
|
||||
amd64) ARCH=x86_64 ;;
|
||||
darwin) ARCH=Darwin ;;
|
||||
linux) ARCH=Linux ;;
|
||||
windows) ARCH=Windows ;;
|
||||
esac
|
||||
true
|
||||
}
|
||||
|
||||
cat /dev/null <<EOF
|
||||
------------------------------------------------------------------------
|
||||
https://github.com/client9/shlib - portable posix shell functions
|
||||
Public domain - http://unlicense.org
|
||||
https://github.com/client9/shlib/blob/master/LICENSE.md
|
||||
but credit (and pull requests) appreciated.
|
||||
------------------------------------------------------------------------
|
||||
EOF
|
||||
is_command() {
|
||||
command -v "$1" >/dev/null
|
||||
}
|
||||
echoerr() {
|
||||
echo "$@" 1>&2
|
||||
}
|
||||
log_prefix() {
|
||||
echo "$0"
|
||||
}
|
||||
_logp=6
|
||||
log_set_priority() {
|
||||
_logp="$1"
|
||||
}
|
||||
log_priority() {
|
||||
if test -z "$1"; then
|
||||
echo "$_logp"
|
||||
return
|
||||
fi
|
||||
[ "$1" -le "$_logp" ]
|
||||
}
|
||||
log_tag() {
|
||||
case $1 in
|
||||
0) echo "emerg" ;;
|
||||
1) echo "alert" ;;
|
||||
2) echo "crit" ;;
|
||||
3) echo "err" ;;
|
||||
4) echo "warning" ;;
|
||||
5) echo "notice" ;;
|
||||
6) echo "info" ;;
|
||||
7) echo "debug" ;;
|
||||
*) echo "$1" ;;
|
||||
esac
|
||||
}
|
||||
log_debug() {
|
||||
log_priority 7 || return 0
|
||||
echoerr "$(log_prefix)" "$(log_tag 7)" "$@"
|
||||
}
|
||||
log_info() {
|
||||
log_priority 6 || return 0
|
||||
echoerr "$(log_prefix)" "$(log_tag 6)" "$@"
|
||||
}
|
||||
log_err() {
|
||||
log_priority 3 || return 0
|
||||
echoerr "$(log_prefix)" "$(log_tag 3)" "$@"
|
||||
}
|
||||
log_crit() {
|
||||
log_priority 2 || return 0
|
||||
echoerr "$(log_prefix)" "$(log_tag 2)" "$@"
|
||||
}
|
||||
uname_os() {
|
||||
os=$(uname -s | tr '[:upper:]' '[:lower:]')
|
||||
case "$os" in
|
||||
cygwin_nt*) os="windows" ;;
|
||||
mingw*) os="windows" ;;
|
||||
msys_nt*) os="windows" ;;
|
||||
esac
|
||||
echo "$os"
|
||||
}
|
||||
uname_arch() {
|
||||
arch=$(uname -m)
|
||||
case $arch in
|
||||
x86_64) arch="amd64" ;;
|
||||
x86) arch="386" ;;
|
||||
i686) arch="386" ;;
|
||||
i386) arch="386" ;;
|
||||
aarch64) arch="arm64" ;;
|
||||
armv5*) arch="armv5" ;;
|
||||
armv6*) arch="armv6" ;;
|
||||
armv7*) arch="armv7" ;;
|
||||
esac
|
||||
echo ${arch}
|
||||
}
|
||||
uname_os_check() {
|
||||
os=$(uname_os)
|
||||
case "$os" in
|
||||
darwin) return 0 ;;
|
||||
dragonfly) return 0 ;;
|
||||
freebsd) return 0 ;;
|
||||
linux) return 0 ;;
|
||||
android) return 0 ;;
|
||||
nacl) return 0 ;;
|
||||
netbsd) return 0 ;;
|
||||
openbsd) return 0 ;;
|
||||
plan9) return 0 ;;
|
||||
solaris) return 0 ;;
|
||||
windows) return 0 ;;
|
||||
esac
|
||||
log_crit "uname_os_check '$(uname -s)' got converted to '$os' which is not a GOOS value. Please file bug at https://github.com/client9/shlib"
|
||||
return 1
|
||||
}
|
||||
uname_arch_check() {
|
||||
arch=$(uname_arch)
|
||||
case "$arch" in
|
||||
386) return 0 ;;
|
||||
amd64) return 0 ;;
|
||||
arm64) return 0 ;;
|
||||
armv5) return 0 ;;
|
||||
armv6) return 0 ;;
|
||||
armv7) return 0 ;;
|
||||
ppc64) return 0 ;;
|
||||
ppc64le) return 0 ;;
|
||||
mips) return 0 ;;
|
||||
mipsle) return 0 ;;
|
||||
mips64) return 0 ;;
|
||||
mips64le) return 0 ;;
|
||||
s390x) return 0 ;;
|
||||
amd64p32) return 0 ;;
|
||||
esac
|
||||
log_crit "uname_arch_check '$(uname -m)' got converted to '$arch' which is not a GOARCH value. Please file bug report at https://github.com/client9/shlib"
|
||||
return 1
|
||||
}
|
||||
untar() {
|
||||
tarball=$1
|
||||
case "${tarball}" in
|
||||
*.tar.gz | *.tgz) tar --no-same-owner -xzf "${tarball}" ;;
|
||||
*.tar) tar --no-same-owner -xf "${tarball}" ;;
|
||||
*.zip) unzip "${tarball}" ;;
|
||||
*)
|
||||
log_err "untar unknown archive format for ${tarball}"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
http_download_curl() {
|
||||
local_file=$1
|
||||
source_url=$2
|
||||
header=$3
|
||||
if [ -z "$header" ]; then
|
||||
code=$(curl -w '%{http_code}' -sL -o "$local_file" "$source_url")
|
||||
else
|
||||
code=$(curl -w '%{http_code}' -sL -H "$header" -o "$local_file" "$source_url")
|
||||
fi
|
||||
if [ "$code" != "200" ]; then
|
||||
log_debug "http_download_curl received HTTP status $code"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
http_download_wget() {
|
||||
local_file=$1
|
||||
source_url=$2
|
||||
header=$3
|
||||
if [ -z "$header" ]; then
|
||||
wget -q -O "$local_file" "$source_url"
|
||||
else
|
||||
wget -q --header "$header" -O "$local_file" "$source_url"
|
||||
fi
|
||||
}
|
||||
http_download() {
|
||||
log_debug "http_download $2"
|
||||
if is_command curl; then
|
||||
http_download_curl "$@"
|
||||
return
|
||||
elif is_command wget; then
|
||||
http_download_wget "$@"
|
||||
return
|
||||
fi
|
||||
log_crit "http_download unable to find wget or curl"
|
||||
return 1
|
||||
}
|
||||
http_copy() {
|
||||
tmp=$(mktemp)
|
||||
http_download "${tmp}" "$1" "$2" || return 1
|
||||
body=$(cat "$tmp")
|
||||
rm -f "${tmp}"
|
||||
echo "$body"
|
||||
}
|
||||
github_release() {
|
||||
owner_repo=$1
|
||||
version=$2
|
||||
test -z "$version" && version="latest"
|
||||
giturl="https://github.com/${owner_repo}/releases/${version}"
|
||||
json=$(http_copy "$giturl" "Accept:application/json")
|
||||
test -z "$json" && return 1
|
||||
version=$(echo "$json" | tr -s '\n' ' ' | sed 's/.*"tag_name":"//' | sed 's/".*//')
|
||||
test -z "$version" && return 1
|
||||
echo "$version"
|
||||
}
|
||||
hash_sha256() {
|
||||
TARGET=${1:-/dev/stdin}
|
||||
if is_command gsha256sum; then
|
||||
hash=$(gsha256sum "$TARGET") || return 1
|
||||
echo "$hash" | cut -d ' ' -f 1
|
||||
elif is_command sha256sum; then
|
||||
hash=$(sha256sum "$TARGET") || return 1
|
||||
echo "$hash" | cut -d ' ' -f 1
|
||||
elif is_command shasum; then
|
||||
hash=$(shasum -a 256 "$TARGET" 2>/dev/null) || return 1
|
||||
echo "$hash" | cut -d ' ' -f 1
|
||||
elif is_command openssl; then
|
||||
hash=$(openssl -dst openssl dgst -sha256 "$TARGET") || return 1
|
||||
echo "$hash" | cut -d ' ' -f a
|
||||
else
|
||||
log_crit "hash_sha256 unable to find command to compute sha-256 hash"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
hash_sha256_verify() {
|
||||
TARGET=$1
|
||||
checksums=$2
|
||||
if [ -z "$checksums" ]; then
|
||||
log_err "hash_sha256_verify checksum file not specified in arg2"
|
||||
return 1
|
||||
fi
|
||||
BASENAME=${TARGET##*/}
|
||||
want=$(grep "${BASENAME}" "${checksums}" 2>/dev/null | tr '\t' ' ' | cut -d ' ' -f 1)
|
||||
if [ -z "$want" ]; then
|
||||
log_err "hash_sha256_verify unable to find checksum for '${TARGET}' in '${checksums}'"
|
||||
return 1
|
||||
fi
|
||||
got=$(hash_sha256 "$TARGET")
|
||||
if [ "$want" != "$got" ]; then
|
||||
log_err "hash_sha256_verify checksum for '$TARGET' did not verify ${want} vs $got"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
cat /dev/null <<EOF
|
||||
------------------------------------------------------------------------
|
||||
End of functions from https://github.com/client9/shlib
|
||||
------------------------------------------------------------------------
|
||||
EOF
|
||||
|
||||
PROJECT_NAME="goreleaser"
|
||||
OWNER=goreleaser
|
||||
REPO="goreleaser"
|
||||
BINARY=goreleaser
|
||||
FORMAT=tar.gz
|
||||
OS=$(uname_os)
|
||||
ARCH=$(uname_arch)
|
||||
PREFIX="$OWNER/$REPO"
|
||||
|
||||
# use in logging routines
|
||||
log_prefix() {
|
||||
echo "$PREFIX"
|
||||
}
|
||||
PLATFORM="${OS}/${ARCH}"
|
||||
GITHUB_DOWNLOAD=https://github.com/${OWNER}/${REPO}/releases/download
|
||||
|
||||
uname_os_check "$OS"
|
||||
uname_arch_check "$ARCH"
|
||||
|
||||
parse_args "$@"
|
||||
|
||||
get_binaries
|
||||
|
||||
tag_to_version
|
||||
|
||||
adjust_format
|
||||
|
||||
adjust_os
|
||||
|
||||
adjust_arch
|
||||
|
||||
log_info "found version: ${VERSION} for ${TAG}/${OS}/${ARCH}"
|
||||
|
||||
NAME=${PROJECT_NAME}_${OS}_${ARCH}
|
||||
TARBALL=${NAME}.${FORMAT}
|
||||
TARBALL_URL=${GITHUB_DOWNLOAD}/${TAG}/${TARBALL}
|
||||
CHECKSUM=checksums.txt
|
||||
CHECKSUM_URL=${GITHUB_DOWNLOAD}/${TAG}/${CHECKSUM}
|
||||
|
||||
execute
|
||||
log_err "this script is deprecated, please do not use it anymore. check https://github.com/goreleaser/godownloader/issues/207"
|
||||
@@ -31,6 +31,15 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
fetch-depth: 2
|
||||
|
||||
# If this run was triggered by a pull request event, then checkout
|
||||
# the head of the pull request instead of the merge commit.
|
||||
- run: git checkout HEAD^2
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
|
||||
@@ -96,6 +96,10 @@ jobs:
|
||||
steps:
|
||||
- uses: docker-practice/actions-setup-docker@v1
|
||||
|
||||
# note, it is important to always be auth'd into docker.io to prevent rate limiting issues
|
||||
- name: Login to Docker Hub
|
||||
run: echo ${{ secrets.TOOLBOX_DOCKER_PASS }} | docker login docker.io -u ${{ secrets.TOOLBOX_DOCKER_USER }} --password-stdin
|
||||
|
||||
- uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
@@ -232,9 +232,6 @@ jobs:
|
||||
|
||||
- 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
|
||||
@@ -308,10 +305,6 @@ jobs:
|
||||
needs: [Build-Snapshot-Artifacts]
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Restore go cache
|
||||
|
||||
+3
-5
@@ -15,7 +15,6 @@ linters:
|
||||
- dogsled
|
||||
- dupl
|
||||
- errcheck
|
||||
- exportloopref
|
||||
- funlen
|
||||
- gocognit
|
||||
- goconst
|
||||
@@ -23,6 +22,7 @@ linters:
|
||||
- gocyclo
|
||||
- gofmt
|
||||
- goimports
|
||||
- golint
|
||||
- goprintffuncname
|
||||
- gosec
|
||||
- gosimple
|
||||
@@ -31,8 +31,8 @@ linters:
|
||||
- misspell
|
||||
- nakedret
|
||||
- nolintlint
|
||||
- revive
|
||||
- rowserrcheck
|
||||
- scopelint
|
||||
- staticcheck
|
||||
- structcheck
|
||||
- stylecheck
|
||||
@@ -49,13 +49,11 @@ linters:
|
||||
# - godot
|
||||
# - godox
|
||||
# - goerr113
|
||||
# - golint # deprecated
|
||||
# - gomnd # this is too aggressive
|
||||
# - interfacer # this is a good idea, but is no longer supported and is prone to false positives
|
||||
# - lll # without a way to specify per-line exception cases, this is not usable
|
||||
# - maligned # this is an excellent linter, but tricky to optimize and we are not sensitive to memory layout optimizations
|
||||
# - nestif
|
||||
# - prealloc # following this rule isn't consistently a good idea, as it sometimes forces unnecessary allocations that result in less idiomatic code
|
||||
# - scopelint # deprecated
|
||||
# - testpackage
|
||||
# - wsl # this doens't have an auto-fixer yet and is pretty noisy (https://github.com/bombsimon/wsl/issues/90)
|
||||
# - wsl
|
||||
|
||||
+10
-64
@@ -12,10 +12,11 @@ builds:
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
# windows not supported yet (due to jotframe)
|
||||
# - windows
|
||||
- linux
|
||||
goarch:
|
||||
- amd64
|
||||
- arm64
|
||||
# Set the modified timestamp on the output binary to the git timestamp (to ensure a reproducible build)
|
||||
mod_timestamp: '{{ .CommitTimestamp }}'
|
||||
ldflags: |
|
||||
@@ -26,27 +27,7 @@ builds:
|
||||
-X github.com/anchore/syft/internal/version.gitCommit={{.Commit}}
|
||||
-X github.com/anchore/syft/internal/version.buildDate={{.Date}}
|
||||
-X github.com/anchore/syft/internal/version.gitTreeState={{.Env.BUILD_GIT_TREE_STATE}}
|
||||
|
||||
- binary: syft
|
||||
id: syft-win
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
- windows
|
||||
goarch:
|
||||
- amd64
|
||||
# Set the modified timestamp on the output binary to the git timestamp (to ensure a reproducible build)
|
||||
mod_timestamp: '{{ .CommitTimestamp }}'
|
||||
ldflags: |
|
||||
-w
|
||||
-s
|
||||
-extldflags '-static'
|
||||
-X github.com/anchore/syft/internal/version.version={{.Version}}
|
||||
-X github.com/anchore/syft/internal/version.gitCommit={{.Commit}}
|
||||
-X github.com/anchore/syft/internal/version.buildDate={{.Date}}
|
||||
-X github.com/anchore/syft/internal/version.gitTreeState={{.Env.BUILD_GIT_TREE_STATE}}
|
||||
|
||||
# For more info on this macOS build, see: https://github.com/mitchellh/gon#usage-with-goreleaser
|
||||
# For more info on this macOS build, see: https://github.com/mitchellh/gon#usage-with-goreleaser
|
||||
- binary: syft
|
||||
id: syft-macos
|
||||
env:
|
||||
@@ -55,7 +36,6 @@ builds:
|
||||
- darwin
|
||||
goarch:
|
||||
- amd64
|
||||
- arm64
|
||||
# Set the modified timestamp on the output binary to the git timestamp (to ensure a reproducible build)
|
||||
mod_timestamp: '{{ .CommitTimestamp }}'
|
||||
ldflags: |
|
||||
@@ -71,11 +51,10 @@ archives:
|
||||
- format: tar.gz
|
||||
builds:
|
||||
- syft # i.e. Linux only
|
||||
- format: zip # This is a hack for syft-macos! We don't actually intend to use _this_ ZIP file, we just need goreleaser to consider the ZIP file produced by gon (which will have the same file name) to be an artifact so we can use it downstream in publishing (e.g. to a homebrew tap)
|
||||
- format: zip # This is a hack! We don't actually intend to use _this_ ZIP file, we just need goreleaser to consider the ZIP file produced by gon (which will have the same file name) to be an artifact so we can use it downstream in publishing (e.g. to a homebrew tap)
|
||||
id: syft-zip
|
||||
builds:
|
||||
- syft-macos
|
||||
- syft-win
|
||||
|
||||
signs:
|
||||
- artifacts: checksum
|
||||
@@ -113,48 +92,15 @@ brews:
|
||||
description: *description
|
||||
|
||||
dockers:
|
||||
- image_templates:
|
||||
- dockerfile: Dockerfile
|
||||
image_templates:
|
||||
- "anchore/syft:latest"
|
||||
- "anchore/syft:{{ .Tag }}-amd64"
|
||||
- "anchore/syft:v{{ .Major }}-amd64"
|
||||
- "anchore/syft:v{{ .Major }}.{{ .Minor }}-amd64"
|
||||
dockerfile: Dockerfile
|
||||
use: buildx
|
||||
- "anchore/syft:{{ .Tag }}"
|
||||
- "anchore/syft:v{{ .Major }}"
|
||||
- "anchore/syft:v{{ .Major }}.{{ .Minor }}"
|
||||
build_flag_templates:
|
||||
- "--platform=linux/amd64"
|
||||
- "--build-arg=BUILD_DATE={{.Date}}"
|
||||
- "--build-arg=BUILD_VERSION={{.Version}}"
|
||||
- "--build-arg=VCS_REF={{.FullCommit}}"
|
||||
- "--build-arg=VCS_URL={{.GitURL}}"
|
||||
|
||||
- image_templates:
|
||||
- "anchore/syft:{{ .Tag }}-arm64v8"
|
||||
- "anchore/syft:v{{ .Major }}-arm64v8"
|
||||
- "anchore/syft:v{{ .Major }}.{{ .Minor }}-arm64v8"
|
||||
goarch: arm64
|
||||
dockerfile: Dockerfile
|
||||
use: buildx
|
||||
build_flag_templates:
|
||||
- "--platform=linux/arm64/v8"
|
||||
- "--build-arg=BUILD_DATE={{.Date}}"
|
||||
- "--build-arg=BUILD_VERSION={{.Version}}"
|
||||
- "--build-arg=VCS_REF={{.FullCommit}}"
|
||||
- "--build-arg=VCS_URL={{.GitURL}}"
|
||||
|
||||
docker_manifests:
|
||||
- name_template: anchore/syft:{{ .Tag }}
|
||||
image_templates:
|
||||
- anchore/syft:{{ .Tag }}-amd64
|
||||
- anchore/syft:v{{ .Major }}-amd64
|
||||
- anchore/syft:v{{ .Major }}.{{ .Minor }}-amd64
|
||||
- anchore/syft:{{ .Tag }}-arm64v8
|
||||
- anchore/syft:v{{ .Major }}-arm64v8
|
||||
- anchore/syft:v{{ .Major }}.{{ .Minor }}-arm64v8
|
||||
- name_template: anchore/syft:latest
|
||||
image_templates:
|
||||
- anchore/syft:{{ .Tag }}-amd64
|
||||
- anchore/syft:v{{ .Major }}-amd64
|
||||
- anchore/syft:v{{ .Major }}.{{ .Minor }}-amd64
|
||||
- anchore/syft:{{ .Tag }}-arm64v8
|
||||
- anchore/syft:v{{ .Major }}-arm64v8
|
||||
- anchore/syft:v{{ .Major }}.{{ .Minor }}-arm64v8
|
||||
use_buildx: true
|
||||
|
||||
@@ -101,10 +101,9 @@ $(TEMPDIR):
|
||||
.PHONY: bootstrap-tools
|
||||
bootstrap-tools: $(TEMPDIR)
|
||||
GO111MODULE=off GOBIN=$(shell 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)/ v1.42.1
|
||||
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(TEMPDIR)/ v1.26.0
|
||||
curl -sSfL https://raw.githubusercontent.com/wagoodman/go-bouncer/master/bouncer.sh | sh -s -- -b $(TEMPDIR)/ v0.2.0
|
||||
curl -sSfL https://raw.githubusercontent.com/anchore/chronicle/main/install.sh | sh -s -- -b $(TEMPDIR)/ v0.2.0-beta
|
||||
.github/scripts/goreleaser-install.sh -b $(TEMPDIR)/ v0.177.0
|
||||
curl -sfL https://install.goreleaser.com/github.com/goreleaser/goreleaser.sh | sh -s -- -b $(TEMPDIR)/ v0.177.0
|
||||
|
||||
.PHONY: bootstrap-go
|
||||
bootstrap-go:
|
||||
@@ -204,9 +203,7 @@ $(SNAPSHOTDIR): ## Build snapshot release binaries and packages
|
||||
cat .goreleaser.yaml >> $(TEMPDIR)/goreleaser.yaml
|
||||
|
||||
# build release snapshots
|
||||
# DOCKER_CLI_EXPERIMENTAL needed to support multi architecture builds for goreleaser
|
||||
BUILD_GIT_TREE_STATE=$(GITTREESTATE) \
|
||||
DOCKER_CLI_EXPERIMENTAL=enabled \
|
||||
$(TEMPDIR)/goreleaser release --skip-publish --skip-sign --rm-dist --snapshot --config $(TEMPDIR)/goreleaser.yaml
|
||||
|
||||
# note: we cannot clean the snapshot directory since the pipeline builds the snapshot separately
|
||||
@@ -252,16 +249,33 @@ cli: $(SNAPSHOTDIR) ## Run CLI tests
|
||||
SYFT_BINARY_LOCATION='$(SNAPSHOT_CMD)' \
|
||||
go test -count=1 -v ./test/cli
|
||||
|
||||
.PHONY: changelog-release
|
||||
.PHONY: changlog-release
|
||||
changelog-release:
|
||||
$(TEMPDIR)/chronicle --since-tag $(SECOND_TO_LAST_TAG) --until-tag $(LAST_TAG) -vv > CHANGELOG.md
|
||||
@echo "Last tag: $(SECOND_TO_LAST_TAG)"
|
||||
@docker run --rm \
|
||||
-v "$(shell pwd)":/usr/local/src/your-app \
|
||||
ferrarimarco/github-changelog-generator \
|
||||
--user anchore \
|
||||
--project $(BIN) \
|
||||
-t ${GITHUB_TOKEN} \
|
||||
--exclude-labels 'duplicate,question,invalid,wontfix,size:small,size:medium,size:large,size:x-large' \
|
||||
--no-pr-wo-labels \
|
||||
--no-issues-wo-labels \
|
||||
--since-tag $(SECOND_TO_LAST_TAG)
|
||||
|
||||
@printf '\n$(BOLD)$(CYAN)Release $(VERSION) Changelog$(RESET)\n\n'
|
||||
@cat CHANGELOG.md
|
||||
|
||||
.PHONY: changelog-unreleased
|
||||
changelog-unreleased: ## show the current changelog that will be produced on the next release (note: requires GITHUB_TOKEN set)
|
||||
$(TEMPDIR)/chronicle --since-tag $(LAST_TAG) -vv > CHANGELOG.md
|
||||
@docker run -it --rm \
|
||||
-v "$(shell pwd)":/usr/local/src/your-app \
|
||||
ferrarimarco/github-changelog-generator \
|
||||
--user anchore \
|
||||
--project $(BIN) \
|
||||
-t ${GITHUB_TOKEN} \
|
||||
--exclude-labels 'duplicate,question,invalid,wontfix,size:small,size:medium,size:large,size:x-large' \
|
||||
--since-tag $(LAST_TAG)
|
||||
|
||||
@printf '\n$(BOLD)$(CYAN)Unreleased Changes (closed PRs and issues will not be in the final changelog)$(RESET)\n'
|
||||
|
||||
@@ -287,11 +301,9 @@ release: clean-dist changelog-release ## Build and publish final binaries and pa
|
||||
cat .goreleaser.yaml >> $(TEMPDIR)/goreleaser.yaml
|
||||
|
||||
# release (note the version transformation from v0.7.0 --> 0.7.0)
|
||||
# DOCKER_CLI_EXPERIMENTAL needed to support multi architecture builds for goreleaser
|
||||
bash -c "\
|
||||
BUILD_GIT_TREE_STATE=$(GITTREESTATE) \
|
||||
VERSION=$(VERSION:v%=%) \
|
||||
DOCKER_CLI_EXPERIMENTAL=enabled \
|
||||
$(TEMPDIR)/goreleaser \
|
||||
--rm-dist \
|
||||
--config $(TEMPDIR)/goreleaser.yaml \
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
<p align="center">
|
||||
<img src="https://user-images.githubusercontent.com/5199289/136844524-1527b09f-c5cb-4aa9-be54-5aa92a6086c1.png" width="271">
|
||||
</p>
|
||||
# syft
|
||||
|
||||
[](https://github.com/anchore/syft/actions/workflows/validations.yaml)
|
||||
[](https://goreportcard.com/report/github.com/anchore/syft)
|
||||
@@ -9,11 +7,19 @@
|
||||
[](https://github.com/anchore/syft/blob/main/LICENSE)
|
||||
[](https://anchore.com/slack)
|
||||
|
||||
A CLI tool and Go library for generating a Software Bill of Materials (SBOM) from container images and filesystems. Exceptional for vulnerability detection when used with a scanner tool like [Grype](https://github.com/anchore/grype).
|
||||
A CLI tool and go library for generating a Software Bill of Materials (SBOM) from container images and filesystems. Exceptional for vulnerability detection when used with a scanner tool like [Grype](https://github.com/anchore/grype).
|
||||
|
||||
## We'll be at KubeCon 2021!
|
||||
|
||||
Attending KubeCon 2021 in person? Join us for a meetup on **Tuesday, October 12th**.
|
||||
|
||||
We’ll have free swag, giveaways, snacks, and sips. Space will be limited, so make sure to [save your seat](https://get.anchore.com/2021-kubecon-na-opensource-happy-hour/)!
|
||||
|
||||
---
|
||||
|
||||

|
||||
|
||||
## Features
|
||||
**Features**
|
||||
- Catalog container images and filesystems to discover packages and libraries.
|
||||
- Supports packages and libraries from various ecosystems (APK, DEB, RPM, Ruby Bundles, Python Wheel/Egg/requirements.txt, JavaScript NPM/Yarn, Java JAR/EAR/WAR, Jenkins plugins JPI/HPI, Go modules)
|
||||
- Linux distribution identification (supports Alpine, BusyBox, CentOS/RedHat, Debian/Ubuntu flavored distributions)
|
||||
|
||||
+3
-2
@@ -14,8 +14,9 @@ import (
|
||||
|
||||
// completionCmd represents the completion command
|
||||
var completionCmd = &cobra.Command{
|
||||
Use: "completion [bash|zsh|fish]",
|
||||
Short: "Generate a shell completion for Syft (listing local docker images)",
|
||||
Hidden: true,
|
||||
Use: "completion [bash|zsh|fish]",
|
||||
Short: "Generate a shell completion for Syft (listing local docker images)",
|
||||
Long: `To load completions (docker image list):
|
||||
|
||||
Bash:
|
||||
|
||||
+1
-3
@@ -44,9 +44,6 @@ func eventLoop(workerErrs <-chan error, signals <-chan os.Signal, subscription *
|
||||
if err := subscription.Unsubscribe(); err != nil {
|
||||
retErr = multierror.Append(retErr, err)
|
||||
}
|
||||
// the worker has exited, we may have been mid-handling events for the UI which should now be
|
||||
// ignored, in which case forcing a teardown of the UI irregardless of the state is required.
|
||||
forceTeardown = true
|
||||
}
|
||||
case e, isOpen := <-events:
|
||||
if !isOpen {
|
||||
@@ -56,6 +53,7 @@ func eventLoop(workerErrs <-chan error, signals <-chan os.Signal, subscription *
|
||||
|
||||
if err := ux.Handle(e); err != nil {
|
||||
if errors.Is(err, partybus.ErrUnsubscribe) {
|
||||
log.Warnf("unable to unsubscribe from the event bus")
|
||||
events = nil
|
||||
} else {
|
||||
retErr = multierror.Append(retErr, err)
|
||||
|
||||
+18
-32
@@ -51,6 +51,7 @@ const (
|
||||
|
||||
var (
|
||||
packagesPresenterOpt format.Option
|
||||
packagesArgs = cobra.MaximumNArgs(1)
|
||||
packagesCmd = &cobra.Command{
|
||||
Use: "packages [SOURCE]",
|
||||
Short: "Generate a package SBOM",
|
||||
@@ -59,13 +60,21 @@ var (
|
||||
"appName": internal.ApplicationName,
|
||||
"command": "packages",
|
||||
}),
|
||||
Args: validateInputArgs,
|
||||
Args: packagesArgs,
|
||||
SilenceUsage: true,
|
||||
SilenceErrors: true,
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) == 0 {
|
||||
err := cmd.Help()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("an image/directory argument is required")
|
||||
}
|
||||
|
||||
// set the presenter
|
||||
presenterOption := format.ParseOption(appConfig.Output)
|
||||
if presenterOption == format.UnknownFormatOption {
|
||||
if presenterOption == format.UnknownOption {
|
||||
return fmt.Errorf("bad --output value '%s'", appConfig.Output)
|
||||
}
|
||||
packagesPresenterOpt = presenterOption
|
||||
@@ -95,7 +104,7 @@ func init() {
|
||||
}
|
||||
|
||||
func setPackageFlags(flags *pflag.FlagSet) {
|
||||
// Formatting & Input options //////////////////////////////////////////////
|
||||
///////// Formatting & Input options //////////////////////////////////////////////
|
||||
|
||||
flags.StringP(
|
||||
"scope", "s", source.SquashedScope.String(),
|
||||
@@ -103,7 +112,7 @@ func setPackageFlags(flags *pflag.FlagSet) {
|
||||
|
||||
flags.StringP(
|
||||
"output", "o", string(format.TableOption),
|
||||
fmt.Sprintf("report output formatter, options=%v", format.AllPresenters),
|
||||
fmt.Sprintf("report output formatter, options=%v", format.AllOptions),
|
||||
)
|
||||
|
||||
flags.StringP(
|
||||
@@ -111,7 +120,7 @@ func setPackageFlags(flags *pflag.FlagSet) {
|
||||
"file to write the report output to (default is STDOUT)",
|
||||
)
|
||||
|
||||
// Upload options //////////////////////////////////////////////////////////
|
||||
///////// Upload options //////////////////////////////////////////////////////////
|
||||
flags.StringP(
|
||||
"host", "H", "",
|
||||
"the hostname or URL of the Anchore Enterprise instance to upload to",
|
||||
@@ -144,7 +153,7 @@ func setPackageFlags(flags *pflag.FlagSet) {
|
||||
}
|
||||
|
||||
func bindPackagesConfigOptions(flags *pflag.FlagSet) error {
|
||||
// Formatting & Input options //////////////////////////////////////////////
|
||||
///////// Formatting & Input options //////////////////////////////////////////////
|
||||
|
||||
if err := viper.BindPFlag("package.cataloger.scope", flags.Lookup("scope")); err != nil {
|
||||
return err
|
||||
@@ -158,7 +167,7 @@ func bindPackagesConfigOptions(flags *pflag.FlagSet) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Upload options //////////////////////////////////////////////////////////
|
||||
///////// Upload options //////////////////////////////////////////////////////////
|
||||
|
||||
if err := viper.BindPFlag("anchore.host", flags.Lookup("host")); err != nil {
|
||||
return err
|
||||
@@ -187,18 +196,6 @@ func bindPackagesConfigOptions(flags *pflag.FlagSet) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateInputArgs(cmd *cobra.Command, args []string) error {
|
||||
if len(args) == 0 {
|
||||
// in the case that no arguments are given we want to show the help text and return with a non-0 return code.
|
||||
if err := cmd.Help(); err != nil {
|
||||
return fmt.Errorf("unable to display help: %w", err)
|
||||
}
|
||||
return fmt.Errorf("an image/directory argument is required")
|
||||
}
|
||||
|
||||
return cobra.MaximumNArgs(1)(cmd, args)
|
||||
}
|
||||
|
||||
func packagesExec(_ *cobra.Command, args []string) error {
|
||||
// could be an image or a directory, with or without a scheme
|
||||
userInput := args[0]
|
||||
@@ -219,21 +216,10 @@ func packagesExec(_ *cobra.Command, args []string) error {
|
||||
setupSignals(),
|
||||
eventSubscription,
|
||||
stereoscope.Cleanup,
|
||||
ui.Select(isVerbose(), appConfig.Quiet, reporter)...,
|
||||
ui.Select(appConfig.CliOptions.Verbosity > 0, appConfig.Quiet, reporter)...,
|
||||
)
|
||||
}
|
||||
|
||||
func isVerbose() (result bool) {
|
||||
isPipedInput, err := internal.IsPipedInput()
|
||||
if err != nil {
|
||||
// since we can't tell if there was piped input we assume that there could be to disable the ETUI
|
||||
log.Warnf("unable to determine if there is piped input: %+v", err)
|
||||
return true
|
||||
}
|
||||
// verbosity should consider if there is piped input (in which case we should not show the ETUI)
|
||||
return appConfig.CliOptions.Verbosity > 0 || isPipedInput
|
||||
}
|
||||
|
||||
func packagesExecWorker(userInput string) <-chan error {
|
||||
errs := make(chan error)
|
||||
go func() {
|
||||
@@ -263,7 +249,7 @@ func packagesExecWorker(userInput string) <-chan error {
|
||||
|
||||
bus.Publish(partybus.Event{
|
||||
Type: event.PresenterReady,
|
||||
Value: packages.Presenter(packagesPresenterOpt, packages.PresenterConfig{
|
||||
Value: packages.Presenter(packagesPresenterOpt, packages.Config{
|
||||
SourceMetadata: src.Metadata,
|
||||
Catalog: catalog,
|
||||
Distro: d,
|
||||
|
||||
+10
-2
@@ -35,11 +35,19 @@ var powerUserCmd = &cobra.Command{
|
||||
"appName": internal.ApplicationName,
|
||||
"command": "power-user",
|
||||
}),
|
||||
Args: validateInputArgs,
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
Hidden: true,
|
||||
SilenceUsage: true,
|
||||
SilenceErrors: true,
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) == 0 {
|
||||
err := cmd.Help()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("an image/directory argument is required")
|
||||
}
|
||||
|
||||
if appConfig.Dev.ProfileCPU && appConfig.Dev.ProfileMem {
|
||||
return fmt.Errorf("cannot profile CPU and memory simultaneously")
|
||||
}
|
||||
@@ -83,7 +91,7 @@ func powerUserExec(_ *cobra.Command, args []string) error {
|
||||
setupSignals(),
|
||||
eventSubscription,
|
||||
stereoscope.Cleanup,
|
||||
ui.Select(isVerbose(), appConfig.Quiet, reporter)...,
|
||||
ui.Select(appConfig.CliOptions.Verbosity > 0, appConfig.Quiet, reporter)...,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -11,22 +11,18 @@ import (
|
||||
|
||||
func reportWriter() (io.Writer, func() error, error) {
|
||||
nop := func() error { return nil }
|
||||
path := strings.TrimSpace(appConfig.File)
|
||||
|
||||
path := strings.TrimSpace(appConfig.File)
|
||||
switch len(path) {
|
||||
case 0:
|
||||
return os.Stdout, nop, nil
|
||||
|
||||
default:
|
||||
reportFile, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
|
||||
|
||||
if err != nil {
|
||||
return nil, nop, fmt.Errorf("unable to create report file: %w", err)
|
||||
}
|
||||
|
||||
return reportFile, func() error {
|
||||
log.Infof("report written to file=%q", path)
|
||||
|
||||
return reportFile.Close()
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ func init() {
|
||||
flag, "q", false,
|
||||
"suppress all logging output",
|
||||
)
|
||||
|
||||
if err := viper.BindPFlag(flag, rootCmd.PersistentFlags().Lookup(flag)); err != nil {
|
||||
fmt.Printf("unable to bind flag '%s': %+v", flag, err)
|
||||
os.Exit(1)
|
||||
|
||||
@@ -11,26 +11,25 @@ require (
|
||||
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04
|
||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b
|
||||
github.com/anchore/packageurl-go v0.0.0-20210922164639-b3fa992ebd29
|
||||
github.com/anchore/stereoscope v0.0.0-20211024152658-003132a67c10
|
||||
github.com/anchore/stereoscope v0.0.0-20211005213828-538011008578
|
||||
github.com/antihax/optional v1.0.0
|
||||
github.com/bmatcuk/doublestar/v2 v2.0.4
|
||||
github.com/docker/docker v17.12.0-ce-rc1.0.20200309214505-aa6a9891b09c+incompatible
|
||||
github.com/dustin/go-humanize v1.0.0
|
||||
github.com/facebookincubator/nvdtools v0.1.4
|
||||
github.com/go-test/deep v1.0.7
|
||||
github.com/google/go-cmp v0.4.1
|
||||
github.com/google/uuid v1.2.0
|
||||
github.com/google/uuid v1.1.1
|
||||
github.com/gookit/color v1.2.7
|
||||
github.com/hashicorp/go-multierror v1.1.0
|
||||
github.com/hashicorp/go-version v1.2.0
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/mitchellh/mapstructure v1.3.1
|
||||
github.com/olekukonko/tablewriter v0.0.4
|
||||
github.com/pelletier/go-toml v1.8.1
|
||||
github.com/pelletier/go-toml v1.8.0
|
||||
github.com/pkg/profile v1.5.0
|
||||
github.com/scylladb/go-set v1.0.2
|
||||
github.com/sergi/go-diff v1.1.0
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/sirupsen/logrus v1.6.0
|
||||
github.com/spdx/tools-golang v0.1.0
|
||||
github.com/spf13/afero v1.2.2
|
||||
github.com/spf13/cobra v1.0.1-0.20200909172742-8a63648dd905
|
||||
@@ -43,8 +42,8 @@ require (
|
||||
github.com/wagoodman/jotframe v0.0.0-20200730190914-3517092dd163
|
||||
github.com/x-cray/logrus-prefixed-formatter v0.5.2
|
||||
github.com/xeipuuv/gojsonschema v1.2.0
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
|
||||
golang.org/x/mod v0.3.0
|
||||
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
gopkg.in/yaml.v2 v2.3.0
|
||||
)
|
||||
|
||||
@@ -73,6 +73,7 @@ github.com/Azure/go-autorest/autorest/validation v0.1.0/go.mod h1:Ha3z/SqBeaalWQ
|
||||
github.com/Azure/go-autorest/autorest/validation v0.2.0/go.mod h1:3EEqHnBxQGHXRYq3HT1WyXAvT7LLY3tl70hw6tQIbjI=
|
||||
github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
|
||||
github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/Djarvur/go-err113 v0.0.0-20200410182137-af658d038157/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs=
|
||||
@@ -116,8 +117,8 @@ github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b h1:e1bmaoJfZV
|
||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b/go.mod h1:Bkc+JYWjMCF8OyZ340IMSIi2Ebf3uwByOk6ho4wne1E=
|
||||
github.com/anchore/packageurl-go v0.0.0-20210922164639-b3fa992ebd29 h1:K9LfnxwhqvihqU0+MF325FNy7fsKV9EGaUxdfR4gnWk=
|
||||
github.com/anchore/packageurl-go v0.0.0-20210922164639-b3fa992ebd29/go.mod h1:Oc1UkGaJwY6ND6vtAqPSlYrptKRJngHwkwB6W7l1uP0=
|
||||
github.com/anchore/stereoscope v0.0.0-20211024152658-003132a67c10 h1:BmK/CgNlu+X9foWK2ZAIehxzYws760AZSGVNamQZpiw=
|
||||
github.com/anchore/stereoscope v0.0.0-20211024152658-003132a67c10/go.mod h1:Rqltg0KqVKoDy4kCuJMLPFDfUg7pEmSqUEA9pOO1L7c=
|
||||
github.com/anchore/stereoscope v0.0.0-20211005213828-538011008578 h1:gSpftl0RWfdTwlPmsOLgEawHIw16xwwG1mFD/wrVDRE=
|
||||
github.com/anchore/stereoscope v0.0.0-20211005213828-538011008578/go.mod h1:kL7jfbXblrDcBhu5ja/s+VTYL3Mzof4eQNMJiSqcwXQ=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||
github.com/antihax/optional v1.0.0 h1:xK2lYat7ZLaVVcIuj82J8kIro4V6kDe0AUDFboUCwcg=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
@@ -147,8 +148,6 @@ github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAw
|
||||
github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
||||
github.com/bmatcuk/doublestar/v2 v2.0.4 h1:6I6oUiT/sU27eE2OFcWqBhL1SwjyvQuOssxT4a1yidI=
|
||||
github.com/bmatcuk/doublestar/v2 v2.0.4/go.mod h1:QMmcs3H2AUQICWhfzLXz+IYln8lRQmTZRptLie8RgRw=
|
||||
github.com/bmatcuk/doublestar/v4 v4.0.2 h1:X0krlUVAVmtr2cRoTqR8aDMrDqnB36ht8wpWTiQ3jsA=
|
||||
github.com/bmatcuk/doublestar/v4 v4.0.2/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
|
||||
github.com/bombsimon/wsl/v2 v2.0.0/go.mod h1:mf25kr/SqFEPhhcxW1+7pxzGlW+hIl/hYTKY95VwV8U=
|
||||
github.com/bombsimon/wsl/v2 v2.2.0/go.mod h1:Azh8c3XGEJl9LyX0/sFC+CKMc7Ssgua0g+6abzXN4Pg=
|
||||
github.com/bombsimon/wsl/v3 v3.0.0/go.mod h1:st10JtZYLE4D5sC7b8xV4zTKZwAQjCH/Hy2Pm1FNZIc=
|
||||
@@ -375,9 +374,8 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4
|
||||
github.com/google/rpmpack v0.0.0-20191226140753-aa36bfddb3a0/go.mod h1:RaTPr0KUf2K7fnZYLNDrr8rxAamWs3iNywJLtQ2AzBg=
|
||||
github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
|
||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
|
||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/wire v0.3.0/go.mod h1:i1DMg/Lu8Sz5yYl25iOdmc5CT5qusaa+zmRWs16741s=
|
||||
github.com/google/wire v0.4.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU=
|
||||
github.com/googleapis/gax-go v2.0.2+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
||||
@@ -481,6 +479,7 @@ github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e/go.mod h1:Pj4uuM52
|
||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
@@ -596,9 +595,8 @@ github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pelletier/go-toml v1.8.0 h1:Keo9qb7iRJs2voHvunFtuuYFsbWeOBh8/P9v/kVMFtw=
|
||||
github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs=
|
||||
github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM=
|
||||
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||
github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d/go.mod h1:3OzsM7FXDQlpCiw2j81fOmAwQLnZnLGXVKUzeKQXIAw=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
@@ -662,9 +660,8 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx
|
||||
github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/assertions v1.0.0 h1:UVQPSSmc3qtTi+zPPkCXvZX9VvW/xT/NsRvKfwY81a8=
|
||||
github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
|
||||
@@ -803,8 +800,9 @@ golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 h1:cg5LA/zNPRzIXIWSCxQW10Rvpy94aQh3LT/ShoCpkHw=
|
||||
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
@@ -946,7 +944,6 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -1141,9 +1138,8 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
||||
|
||||
@@ -8,7 +8,8 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/anchore/syft/internal/formats/syftjson"
|
||||
"github.com/anchore/syft/internal/formats"
|
||||
"github.com/anchore/syft/syft/format"
|
||||
|
||||
"github.com/wagoodman/go-progress"
|
||||
|
||||
@@ -24,10 +25,10 @@ type packageSBOMImportAPI interface {
|
||||
ImportImagePackages(context.Context, string, external.ImagePackageManifest) (external.ImageImportContentResponse, *http.Response, error)
|
||||
}
|
||||
|
||||
func packageSbomModel(s source.Metadata, catalog *pkg.Catalog, d *distro.Distro, scope source.Scope) (*external.ImagePackageManifest, error) {
|
||||
func packageSbomModel(s source.Metadata, catalog *pkg.Catalog, d *distro.Distro, _ source.Scope) (*external.ImagePackageManifest, error) {
|
||||
var buf bytes.Buffer
|
||||
|
||||
err := syftjson.Format().Presenter(catalog, &s, d, scope).Present(&buf)
|
||||
pres := formats.ByOption(format.JSONOption).Presenter(catalog, &s, d)
|
||||
err := pres.Present(&buf)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to serialize results: %w", err)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package anchore
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@@ -10,9 +9,10 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/client-go/pkg/external"
|
||||
"github.com/anchore/syft/internal/formats/syftjson"
|
||||
syftjsonModel "github.com/anchore/syft/internal/formats/syftjson/model"
|
||||
"github.com/anchore/syft/syft"
|
||||
"github.com/anchore/syft/syft/distro"
|
||||
"github.com/anchore/syft/syft/format"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"github.com/docker/docker/pkg/ioutils"
|
||||
@@ -84,15 +84,14 @@ func TestPackageSbomToModel(t *testing.T) {
|
||||
t.Fatalf("unable to marshal model: %+v", err)
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
pres := syftjson.Format().Presenter(c, &m, &d, source.AllLayersScope)
|
||||
if err := pres.Present(&buf); err != nil {
|
||||
by, err := syft.Encode(c, &m, &d, format.JSONOption)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get expected json: %+v", err)
|
||||
}
|
||||
|
||||
// unmarshal expected result
|
||||
var expectedDoc syftjsonModel.Document
|
||||
if err := json.Unmarshal(buf.Bytes(), &expectedDoc); err != nil {
|
||||
if err := json.Unmarshal(by, &expectedDoc); err != nil {
|
||||
t.Fatalf("unable to parse json doc: %+v", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -89,18 +89,42 @@ func (cfg Application) loadDefaultValues(v *viper.Viper) {
|
||||
}
|
||||
}
|
||||
|
||||
// build inflates simple config values into syft native objects (or other complex objects) after the config is fully read in.
|
||||
func (cfg *Application) parseConfigValues() error {
|
||||
// parse application config options
|
||||
for _, optionFn := range []func() error{
|
||||
cfg.parseUploadOptions,
|
||||
cfg.parseLogLevelOption,
|
||||
} {
|
||||
if err := optionFn(); err != nil {
|
||||
return err
|
||||
if cfg.Quiet {
|
||||
// TODO: this is bad: quiet option trumps all other logging options
|
||||
// we should be able to quiet the console logging and leave file logging alone...
|
||||
// ... this will be an enhancement for later
|
||||
cfg.Log.LevelOpt = logrus.PanicLevel
|
||||
} else {
|
||||
if cfg.Log.Level != "" {
|
||||
if cfg.CliOptions.Verbosity > 0 {
|
||||
return fmt.Errorf("cannot explicitly set log level (cfg file or env var) and use -v flag together")
|
||||
}
|
||||
|
||||
lvl, err := logrus.ParseLevel(strings.ToLower(cfg.Log.Level))
|
||||
if err != nil {
|
||||
return fmt.Errorf("bad log level configured (%q): %w", cfg.Log.Level, err)
|
||||
}
|
||||
// set the log level explicitly
|
||||
cfg.Log.LevelOpt = lvl
|
||||
} else {
|
||||
// set the log level implicitly
|
||||
switch v := cfg.CliOptions.Verbosity; {
|
||||
case v == 1:
|
||||
cfg.Log.LevelOpt = logrus.InfoLevel
|
||||
case v >= 2:
|
||||
cfg.Log.LevelOpt = logrus.DebugLevel
|
||||
default:
|
||||
cfg.Log.LevelOpt = logrus.WarnLevel
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// parse nested config options
|
||||
if cfg.Anchore.Host == "" && cfg.Anchore.Dockerfile != "" {
|
||||
return fmt.Errorf("cannot provide dockerfile option without enabling upload")
|
||||
}
|
||||
|
||||
// for each field in the configuration struct, see if the field implements the parser interface
|
||||
// note: the app config is a pointer, so we need to grab the elements explicitly (to traverse the address)
|
||||
value := reflect.ValueOf(cfg).Elem()
|
||||
@@ -113,48 +137,6 @@ func (cfg *Application) parseConfigValues() error {
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cfg *Application) parseUploadOptions() error {
|
||||
if cfg.Anchore.Host == "" && cfg.Anchore.Dockerfile != "" {
|
||||
return fmt.Errorf("cannot provide dockerfile option without enabling upload")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cfg *Application) parseLogLevelOption() error {
|
||||
switch {
|
||||
case cfg.Quiet:
|
||||
// TODO: this is bad: quiet option trumps all other logging options (such as to a file on disk)
|
||||
// we should be able to quiet the console logging and leave file logging alone...
|
||||
// ... this will be an enhancement for later
|
||||
cfg.Log.LevelOpt = logrus.PanicLevel
|
||||
case cfg.Log.Level != "":
|
||||
if cfg.CliOptions.Verbosity > 0 {
|
||||
return fmt.Errorf("cannot explicitly set log level (cfg file or env var) and use -v flag together")
|
||||
}
|
||||
|
||||
lvl, err := logrus.ParseLevel(strings.ToLower(cfg.Log.Level))
|
||||
if err != nil {
|
||||
return fmt.Errorf("bad log level configured (%q): %w", cfg.Log.Level, err)
|
||||
}
|
||||
|
||||
cfg.Log.LevelOpt = lvl
|
||||
if cfg.Log.LevelOpt >= logrus.InfoLevel {
|
||||
cfg.CliOptions.Verbosity = 1
|
||||
}
|
||||
default:
|
||||
|
||||
switch v := cfg.CliOptions.Verbosity; {
|
||||
case v == 1:
|
||||
cfg.Log.LevelOpt = logrus.InfoLevel
|
||||
case v >= 2:
|
||||
cfg.Log.LevelOpt = logrus.DebugLevel
|
||||
default:
|
||||
cfg.Log.LevelOpt = logrus.ErrorLevel
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package spdxhelpers
|
||||
|
||||
import "github.com/anchore/syft/syft/pkg"
|
||||
|
||||
func Description(p *pkg.Package) string {
|
||||
switch metadata := p.Metadata.(type) {
|
||||
case pkg.ApkMetadata:
|
||||
return metadata.Description
|
||||
case pkg.NpmPackageJSONMetadata:
|
||||
return metadata.Description
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package spdxhelpers
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_Description(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input pkg.Package
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
// note: since this is an optional field, no value is preferred over NONE or NOASSERTION
|
||||
name: "no metadata",
|
||||
input: pkg.Package{},
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "from apk",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.ApkMetadata{
|
||||
Description: "a description!",
|
||||
},
|
||||
},
|
||||
expected: "a description!",
|
||||
},
|
||||
{
|
||||
name: "from npm",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Description: "a description!",
|
||||
},
|
||||
},
|
||||
expected: "a description!",
|
||||
},
|
||||
{
|
||||
// note: since this is an optional field, no value is preferred over NONE or NOASSERTION
|
||||
name: "empty",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Homepage: "",
|
||||
},
|
||||
},
|
||||
expected: "",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
assert.Equal(t, test.expected, Description(&test.input))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package spdxhelpers
|
||||
|
||||
import "github.com/anchore/syft/syft/pkg"
|
||||
|
||||
func DownloadLocation(p *pkg.Package) string {
|
||||
// 3.7: Package Download Location
|
||||
// Cardinality: mandatory, one
|
||||
// NONE if there is no download location whatsoever.
|
||||
// NOASSERTION if:
|
||||
// (i) the SPDX file creator has attempted to but cannot reach a reasonable objective determination;
|
||||
// (ii) the SPDX file creator has made no attempt to determine this field; or
|
||||
// (iii) the SPDX file creator has intentionally provided no information (no meaning should be implied by doing so).
|
||||
|
||||
switch metadata := p.Metadata.(type) {
|
||||
case pkg.ApkMetadata:
|
||||
return NoneIfEmpty(metadata.URL)
|
||||
case pkg.NpmPackageJSONMetadata:
|
||||
return NoneIfEmpty(metadata.URL)
|
||||
default:
|
||||
return "NOASSERTION"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package spdxhelpers
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_DownloadLocation(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input pkg.Package
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "no metadata",
|
||||
input: pkg.Package{},
|
||||
expected: "NOASSERTION",
|
||||
},
|
||||
{
|
||||
name: "from apk",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.ApkMetadata{
|
||||
URL: "http://a-place.gov",
|
||||
},
|
||||
},
|
||||
expected: "http://a-place.gov",
|
||||
},
|
||||
{
|
||||
name: "from npm",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
URL: "http://a-place.gov",
|
||||
},
|
||||
},
|
||||
expected: "http://a-place.gov",
|
||||
},
|
||||
{
|
||||
name: "empty",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
URL: "",
|
||||
},
|
||||
},
|
||||
expected: "NONE",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
assert.Equal(t, test.expected, DownloadLocation(&test.input))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package spdxhelpers
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/internal/formats/spdx22json/model"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
func ExternalRefs(p *pkg.Package) (externalRefs []model.ExternalRef) {
|
||||
externalRefs = make([]model.ExternalRef, 0)
|
||||
for _, c := range p.CPEs {
|
||||
externalRefs = append(externalRefs, model.ExternalRef{
|
||||
ReferenceCategory: model.SecurityReferenceCategory,
|
||||
ReferenceLocator: c.BindToFmtString(),
|
||||
ReferenceType: model.Cpe23ExternalRefType,
|
||||
})
|
||||
}
|
||||
|
||||
if p.PURL != "" {
|
||||
externalRefs = append(externalRefs, model.ExternalRef{
|
||||
ReferenceCategory: model.PackageManagerReferenceCategory,
|
||||
ReferenceLocator: p.PURL,
|
||||
ReferenceType: model.PurlExternalRefType,
|
||||
})
|
||||
}
|
||||
return externalRefs
|
||||
}
|
||||
|
||||
func ExtractPURL(refs []model.ExternalRef) string {
|
||||
for _, r := range refs {
|
||||
if r.ReferenceType == model.PurlExternalRefType {
|
||||
return r.ReferenceLocator
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func ExtractCPEs(refs []model.ExternalRef) (cpes []pkg.CPE) {
|
||||
for _, r := range refs {
|
||||
if r.ReferenceType == model.Cpe23ExternalRefType {
|
||||
cpe, err := pkg.NewCPE(r.ReferenceLocator)
|
||||
if err != nil {
|
||||
log.Warnf("unable to extract SPDX CPE=%q: %+v", r.ReferenceLocator, err)
|
||||
continue
|
||||
}
|
||||
cpes = append(cpes, cpe)
|
||||
}
|
||||
}
|
||||
return cpes
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package spdxhelpers
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/internal/formats/common/testutils"
|
||||
"github.com/anchore/syft/internal/formats/spdx22json/model"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_ExternalRefs(t *testing.T) {
|
||||
testCPE := testutils.MustCPE(pkg.NewCPE("cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*"))
|
||||
tests := []struct {
|
||||
name string
|
||||
input pkg.Package
|
||||
expected []model.ExternalRef
|
||||
}{
|
||||
{
|
||||
name: "cpe + purl",
|
||||
input: pkg.Package{
|
||||
CPEs: []pkg.CPE{
|
||||
testCPE,
|
||||
},
|
||||
PURL: "a-purl",
|
||||
},
|
||||
expected: []model.ExternalRef{
|
||||
{
|
||||
ReferenceCategory: model.SecurityReferenceCategory,
|
||||
ReferenceLocator: testCPE.BindToFmtString(),
|
||||
ReferenceType: model.Cpe23ExternalRefType,
|
||||
},
|
||||
{
|
||||
ReferenceCategory: model.PackageManagerReferenceCategory,
|
||||
ReferenceLocator: "a-purl",
|
||||
ReferenceType: model.PurlExternalRefType,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
assert.ElementsMatch(t, test.expected, ExternalRefs(&test.input))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package spdxhelpers
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/anchore/syft/internal/formats/spdx22json/model"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
func Files(packageSpdxID string, p *pkg.Package) (files []model.File, fileIDs []string, relationships []model.Relationship) {
|
||||
files = make([]model.File, 0)
|
||||
fileIDs = make([]string, 0)
|
||||
relationships = make([]model.Relationship, 0)
|
||||
|
||||
pkgFileOwner, ok := p.Metadata.(pkg.FileOwner)
|
||||
if !ok {
|
||||
return files, fileIDs, relationships
|
||||
}
|
||||
|
||||
for _, ownedFilePath := range pkgFileOwner.OwnedFiles() {
|
||||
baseFileName := filepath.Base(ownedFilePath)
|
||||
pathHash := sha256.Sum256([]byte(ownedFilePath))
|
||||
fileSpdxID := model.ElementID(fmt.Sprintf("File-%s-%x", p.Name, pathHash)).String()
|
||||
|
||||
fileIDs = append(fileIDs, fileSpdxID)
|
||||
|
||||
files = append(files, model.File{
|
||||
FileName: ownedFilePath,
|
||||
Item: model.Item{
|
||||
Element: model.Element{
|
||||
SPDXID: fileSpdxID,
|
||||
Name: baseFileName,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
relationships = append(relationships, model.Relationship{
|
||||
SpdxElementID: packageSpdxID,
|
||||
RelationshipType: model.ContainsRelationship,
|
||||
RelatedSpdxElement: fileSpdxID,
|
||||
})
|
||||
}
|
||||
|
||||
return files, fileIDs, relationships
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package spdxhelpers
|
||||
|
||||
import "github.com/anchore/syft/syft/pkg"
|
||||
|
||||
func Homepage(p *pkg.Package) string {
|
||||
switch metadata := p.Metadata.(type) {
|
||||
case pkg.GemMetadata:
|
||||
return metadata.Homepage
|
||||
case pkg.NpmPackageJSONMetadata:
|
||||
return metadata.Homepage
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package spdxhelpers
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_Homepage(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input pkg.Package
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
// note: since this is an optional field, no value is preferred over NONE or NOASSERTION
|
||||
name: "no metadata",
|
||||
input: pkg.Package{},
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "from gem",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.GemMetadata{
|
||||
Homepage: "http://a-place.gov",
|
||||
},
|
||||
},
|
||||
expected: "http://a-place.gov",
|
||||
},
|
||||
{
|
||||
name: "from npm",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Homepage: "http://a-place.gov",
|
||||
},
|
||||
},
|
||||
expected: "http://a-place.gov",
|
||||
},
|
||||
{
|
||||
// note: since this is an optional field, no value is preferred over NONE or NOASSERTION
|
||||
name: "empty",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Homepage: "",
|
||||
},
|
||||
},
|
||||
expected: "",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
assert.Equal(t, test.expected, Homepage(&test.input))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package spdxhelpers
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/internal/spdxlicense"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
func License(p *pkg.Package) string {
|
||||
// source: https://spdx.github.io/spdx-spec/3-package-information/#313-concluded-license
|
||||
// The options to populate this field are limited to:
|
||||
// A valid SPDX License Expression as defined in Appendix IV;
|
||||
// NONE, if the SPDX file creator concludes there is no license available for this package; or
|
||||
// NOASSERTION if:
|
||||
// (i) the SPDX file creator has attempted to but cannot reach a reasonable objective determination;
|
||||
// (ii) the SPDX file creator has made no attempt to determine this field; or
|
||||
// (iii) the SPDX file creator has intentionally provided no information (no meaning should be implied by doing so).
|
||||
|
||||
if len(p.Licenses) == 0 {
|
||||
return "NONE"
|
||||
}
|
||||
|
||||
// take all licenses and assume an AND expression; for information about license expressions see https://spdx.github.io/spdx-spec/appendix-IV-SPDX-license-expressions/
|
||||
var parsedLicenses []string
|
||||
for _, l := range p.Licenses {
|
||||
if value, exists := spdxlicense.ID(l); exists {
|
||||
parsedLicenses = append(parsedLicenses, value)
|
||||
}
|
||||
}
|
||||
|
||||
if len(parsedLicenses) == 0 {
|
||||
return "NOASSERTION"
|
||||
}
|
||||
|
||||
return strings.Join(parsedLicenses, " AND ")
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package spdxhelpers
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_License(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input pkg.Package
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "no licenses",
|
||||
input: pkg.Package{},
|
||||
expected: "NONE",
|
||||
},
|
||||
{
|
||||
name: "no SPDX licenses",
|
||||
input: pkg.Package{
|
||||
Licenses: []string{
|
||||
"made-up",
|
||||
},
|
||||
},
|
||||
expected: "NOASSERTION",
|
||||
},
|
||||
{
|
||||
name: "with SPDX license",
|
||||
input: pkg.Package{
|
||||
Licenses: []string{
|
||||
"MIT",
|
||||
},
|
||||
},
|
||||
expected: "MIT",
|
||||
},
|
||||
{
|
||||
name: "with SPDX license expression",
|
||||
input: pkg.Package{
|
||||
Licenses: []string{
|
||||
"MIT",
|
||||
"GPL-3.0",
|
||||
},
|
||||
},
|
||||
expected: "MIT AND GPL-3.0",
|
||||
},
|
||||
{
|
||||
name: "cap insensitive",
|
||||
input: pkg.Package{
|
||||
Licenses: []string{
|
||||
"gpl-3.0",
|
||||
},
|
||||
},
|
||||
expected: "GPL-3.0",
|
||||
},
|
||||
{
|
||||
name: "debian to spdx conversion",
|
||||
input: pkg.Package{
|
||||
Licenses: []string{
|
||||
"GPL-2",
|
||||
},
|
||||
},
|
||||
expected: "GPL-2.0",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
assert.Equal(t, test.expected, License(&test.input))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package spdxhelpers
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func NoneIfEmpty(value string) string {
|
||||
if strings.TrimSpace(value) == "" {
|
||||
return "NONE"
|
||||
}
|
||||
return value
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package spdxhelpers
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_noneIfEmpty(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
value string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "non-zero value",
|
||||
value: "something",
|
||||
expected: "something",
|
||||
},
|
||||
{
|
||||
name: "empty",
|
||||
value: "",
|
||||
expected: "NONE",
|
||||
},
|
||||
{
|
||||
name: "space",
|
||||
value: " ",
|
||||
expected: "NONE",
|
||||
},
|
||||
{
|
||||
name: "tab",
|
||||
value: "\t",
|
||||
expected: "NONE",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
assert.Equal(t, test.expected, NoneIfEmpty(test.value))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package spdxhelpers
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_Originator(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input pkg.Package
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
// note: since this is an optional field, no value is preferred over NONE or NOASSERTION
|
||||
name: "no metadata",
|
||||
input: pkg.Package{},
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "from gem",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.GemMetadata{
|
||||
Authors: []string{
|
||||
"auth1",
|
||||
"auth2",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: "auth1",
|
||||
},
|
||||
{
|
||||
name: "from npm",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Author: "auth",
|
||||
},
|
||||
},
|
||||
expected: "auth",
|
||||
},
|
||||
{
|
||||
name: "from apk",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.ApkMetadata{
|
||||
Maintainer: "auth",
|
||||
},
|
||||
},
|
||||
expected: "auth",
|
||||
},
|
||||
{
|
||||
name: "from python - just name",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.PythonPackageMetadata{
|
||||
Author: "auth",
|
||||
},
|
||||
},
|
||||
expected: "auth",
|
||||
},
|
||||
{
|
||||
name: "from python - just email",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.PythonPackageMetadata{
|
||||
AuthorEmail: "auth@auth.gov",
|
||||
},
|
||||
},
|
||||
expected: "auth@auth.gov",
|
||||
},
|
||||
{
|
||||
name: "from python - both name and email",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.PythonPackageMetadata{
|
||||
Author: "auth",
|
||||
AuthorEmail: "auth@auth.gov",
|
||||
},
|
||||
},
|
||||
expected: "auth <auth@auth.gov>",
|
||||
},
|
||||
{
|
||||
name: "from rpm",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.RpmdbMetadata{
|
||||
Vendor: "auth",
|
||||
},
|
||||
},
|
||||
expected: "auth",
|
||||
},
|
||||
{
|
||||
name: "from dpkg",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.DpkgMetadata{
|
||||
Maintainer: "auth",
|
||||
},
|
||||
},
|
||||
expected: "auth",
|
||||
},
|
||||
{
|
||||
// note: since this is an optional field, no value is preferred over NONE or NOASSERTION
|
||||
name: "empty",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Author: "",
|
||||
},
|
||||
},
|
||||
expected: "",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
assert.Equal(t, test.expected, Originator(&test.input))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package spdxhelpers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
func Originator(p *pkg.Package) string {
|
||||
switch metadata := p.Metadata.(type) {
|
||||
case pkg.ApkMetadata:
|
||||
return metadata.Maintainer
|
||||
case pkg.NpmPackageJSONMetadata:
|
||||
return metadata.Author
|
||||
case pkg.PythonPackageMetadata:
|
||||
author := metadata.Author
|
||||
if author == "" {
|
||||
return metadata.AuthorEmail
|
||||
}
|
||||
if metadata.AuthorEmail != "" {
|
||||
author += fmt.Sprintf(" <%s>", metadata.AuthorEmail)
|
||||
}
|
||||
return author
|
||||
case pkg.GemMetadata:
|
||||
if len(metadata.Authors) > 0 {
|
||||
return metadata.Authors[0]
|
||||
}
|
||||
return ""
|
||||
case pkg.RpmdbMetadata:
|
||||
return metadata.Vendor
|
||||
case pkg.DpkgMetadata:
|
||||
return metadata.Maintainer
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package spdxhelpers
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
func SourceInfo(p *pkg.Package) string {
|
||||
answer := ""
|
||||
switch p.Type {
|
||||
case pkg.RpmPkg:
|
||||
answer = "acquired package info from RPM DB"
|
||||
case pkg.ApkPkg:
|
||||
answer = "acquired package info from APK DB"
|
||||
case pkg.DebPkg:
|
||||
answer = "acquired package info from DPKG DB"
|
||||
case pkg.NpmPkg:
|
||||
answer = "acquired package info from installed node module manifest file"
|
||||
case pkg.PythonPkg:
|
||||
answer = "acquired package info from installed python package manifest file"
|
||||
case pkg.JavaPkg, pkg.JenkinsPluginPkg:
|
||||
answer = "acquired package info from installed java archive"
|
||||
case pkg.GemPkg:
|
||||
answer = "acquired package info from installed gem metadata file"
|
||||
case pkg.GoModulePkg:
|
||||
answer = "acquired package info from go module information"
|
||||
case pkg.RustPkg:
|
||||
answer = "acquired package info from rust cargo manifest"
|
||||
default:
|
||||
answer = "acquired package info from the following paths"
|
||||
}
|
||||
var paths []string
|
||||
for _, l := range p.Locations {
|
||||
paths = append(paths, l.RealPath)
|
||||
}
|
||||
|
||||
return answer + ": " + strings.Join(paths, ", ")
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
package spdxhelpers
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_SourceInfo(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input pkg.Package
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "locations are captured",
|
||||
input: pkg.Package{
|
||||
// note: no type given
|
||||
Locations: []source.Location{
|
||||
{
|
||||
RealPath: "/a-place",
|
||||
VirtualPath: "/b-place",
|
||||
},
|
||||
{
|
||||
RealPath: "/c-place",
|
||||
VirtualPath: "/d-place",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []string{
|
||||
"from the following paths",
|
||||
"/a-place",
|
||||
"/c-place",
|
||||
},
|
||||
},
|
||||
{
|
||||
// note: no specific support for this
|
||||
input: pkg.Package{
|
||||
Type: pkg.KbPkg,
|
||||
},
|
||||
expected: []string{
|
||||
"from the following paths",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: pkg.Package{
|
||||
Type: pkg.RpmPkg,
|
||||
},
|
||||
expected: []string{
|
||||
"from RPM DB",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: pkg.Package{
|
||||
Type: pkg.ApkPkg,
|
||||
},
|
||||
expected: []string{
|
||||
"from APK DB",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: pkg.Package{
|
||||
Type: pkg.DebPkg,
|
||||
},
|
||||
expected: []string{
|
||||
"from DPKG DB",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: pkg.Package{
|
||||
Type: pkg.NpmPkg,
|
||||
},
|
||||
expected: []string{
|
||||
"from installed node module manifest file",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: pkg.Package{
|
||||
Type: pkg.PythonPkg,
|
||||
},
|
||||
expected: []string{
|
||||
"from installed python package manifest file",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: pkg.Package{
|
||||
Type: pkg.JavaPkg,
|
||||
},
|
||||
expected: []string{
|
||||
"from installed java archive",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: pkg.Package{
|
||||
Type: pkg.JenkinsPluginPkg,
|
||||
},
|
||||
expected: []string{
|
||||
"from installed java archive",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: pkg.Package{
|
||||
Type: pkg.GemPkg,
|
||||
},
|
||||
expected: []string{
|
||||
"from installed gem metadata file",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: pkg.Package{
|
||||
Type: pkg.GoModulePkg,
|
||||
},
|
||||
expected: []string{
|
||||
"from go module information",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: pkg.Package{
|
||||
Type: pkg.RustPkg,
|
||||
},
|
||||
expected: []string{
|
||||
"from rust cargo manifest",
|
||||
},
|
||||
},
|
||||
}
|
||||
var pkgTypes []pkg.Type
|
||||
for _, test := range tests {
|
||||
t.Run(test.name+" "+string(test.input.Type), func(t *testing.T) {
|
||||
if test.input.Type != "" {
|
||||
pkgTypes = append(pkgTypes, test.input.Type)
|
||||
}
|
||||
actual := SourceInfo(&test.input)
|
||||
for _, expected := range test.expected {
|
||||
assert.Contains(t, actual, expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
assert.ElementsMatch(t, pkg.AllPkgs, pkgTypes, "missing one or more package types to test against (maybe a package type was added?)")
|
||||
}
|
||||
@@ -18,6 +18,13 @@ import (
|
||||
|
||||
type redactor func(s []byte) []byte
|
||||
|
||||
func MustCPE(c pkg.CPE, e error) pkg.CPE {
|
||||
if e != nil {
|
||||
panic(e)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func AssertPresenterAgainstGoldenImageSnapshot(t *testing.T, pres presenter.Presenter, testImage string, updateSnapshot bool, redactors ...redactor) {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
@@ -105,7 +112,7 @@ func ImageInput(t testing.TB, testImage string) (*pkg.Catalog, source.Metadata,
|
||||
},
|
||||
PURL: "a-purl-1",
|
||||
CPEs: []pkg.CPE{
|
||||
pkg.MustCPE("cpe:2.3:*:some:package:1:*:*:*:*:*:*:*"),
|
||||
MustCPE(pkg.NewCPE("cpe:2.3:*:some:package:1:*:*:*:*:*:*:*")),
|
||||
},
|
||||
})
|
||||
catalog.Add(pkg.Package{
|
||||
@@ -124,7 +131,7 @@ func ImageInput(t testing.TB, testImage string) (*pkg.Catalog, source.Metadata,
|
||||
},
|
||||
PURL: "a-purl-2",
|
||||
CPEs: []pkg.CPE{
|
||||
pkg.MustCPE("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
|
||||
MustCPE(pkg.NewCPE("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*")),
|
||||
},
|
||||
})
|
||||
|
||||
@@ -167,7 +174,7 @@ func DirectoryInput(t testing.TB) (*pkg.Catalog, source.Metadata, *distro.Distro
|
||||
},
|
||||
PURL: "a-purl-2",
|
||||
CPEs: []pkg.CPE{
|
||||
pkg.MustCPE("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
|
||||
MustCPE(pkg.NewCPE("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*")),
|
||||
},
|
||||
})
|
||||
catalog.Add(pkg.Package{
|
||||
@@ -186,7 +193,7 @@ func DirectoryInput(t testing.TB) (*pkg.Catalog, source.Metadata, *distro.Distro
|
||||
},
|
||||
PURL: "a-purl-2",
|
||||
CPEs: []pkg.CPE{
|
||||
pkg.MustCPE("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
|
||||
MustCPE(pkg.NewCPE("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*")),
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
package formats
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/anchore/syft/internal/formats/table"
|
||||
|
||||
"github.com/anchore/syft/internal/formats/spdx22json"
|
||||
"github.com/anchore/syft/internal/formats/syftjson"
|
||||
"github.com/anchore/syft/syft/format"
|
||||
)
|
||||
@@ -12,17 +9,16 @@ import (
|
||||
// TODO: eventually this is the source of truth for all formatters
|
||||
func All() []format.Format {
|
||||
return []format.Format{
|
||||
spdx22json.Format(),
|
||||
syftjson.Format(),
|
||||
table.Format(),
|
||||
}
|
||||
}
|
||||
|
||||
func Identify(by []byte) (*format.Format, error) {
|
||||
for _, f := range All() {
|
||||
if err := f.Validate(bytes.NewReader(by)); err != nil {
|
||||
continue
|
||||
if f.Detect(by) {
|
||||
return &f, nil
|
||||
}
|
||||
return &f, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
)
|
||||
|
||||
func TestIdentify(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
fixture string
|
||||
expected format.Option
|
||||
@@ -18,6 +19,10 @@ func TestIdentify(t *testing.T) {
|
||||
fixture: "test-fixtures/alpine-syft.json",
|
||||
expected: format.JSONOption,
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/alpine-syft-spdx.json",
|
||||
expected: format.SPDXJSONOption,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.fixture, func(t *testing.T) {
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package spdx22json
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/anchore/syft/internal/formats/spdx22json/model"
|
||||
"github.com/anchore/syft/syft/distro"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func decoder(reader io.Reader) (*pkg.Catalog, *source.Metadata, *distro.Distro, error) {
|
||||
dec := json.NewDecoder(reader)
|
||||
|
||||
var doc model.Document
|
||||
err := dec.Decode(&doc)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("unable to decode spdx-json: %w", err)
|
||||
}
|
||||
|
||||
return toSyftModel(doc)
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package spdx22json
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/internal/formats/common/testutils"
|
||||
"github.com/go-test/deep"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestEncodeDecodeCycle(t *testing.T) {
|
||||
testImage := "image-simple"
|
||||
originalCatalog, originalMetadata, _ := testutils.ImageInput(t, testImage)
|
||||
|
||||
var buf bytes.Buffer
|
||||
assert.NoError(t, encoder(&buf, originalCatalog, &originalMetadata, nil))
|
||||
|
||||
actualCatalog, actualMetadata, _, err := decoder(bytes.NewReader(buf.Bytes()))
|
||||
assert.NoError(t, err)
|
||||
|
||||
for _, d := range deep.Equal(originalMetadata, *actualMetadata) {
|
||||
t.Errorf("metadata difference: %+v", d)
|
||||
}
|
||||
|
||||
actualPackages := actualCatalog.Sorted()
|
||||
for idx, p := range originalCatalog.Sorted() {
|
||||
if !assert.Equal(t, p.Name, actualPackages[idx].Name) {
|
||||
t.Errorf("different package at idx=%d: %s vs %s", idx, p.Name, actualPackages[idx].Name)
|
||||
continue
|
||||
}
|
||||
|
||||
// ids will never be equal
|
||||
p.ID = ""
|
||||
actualPackages[idx].ID = ""
|
||||
|
||||
for _, d := range deep.Equal(*p, *actualPackages[idx]) {
|
||||
if strings.Contains(d, ".VirtualPath: ") {
|
||||
// location.Virtual path is not exposed in the json output
|
||||
continue
|
||||
}
|
||||
if strings.HasSuffix(d, "<nil slice> != []") {
|
||||
// semantically the same
|
||||
continue
|
||||
}
|
||||
t.Errorf("package difference (%s): %+v", p.Name, d)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package spdx22json
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
|
||||
"github.com/anchore/syft/syft/distro"
|
||||
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
const anchoreNamespace = "https://anchore.com/syft"
|
||||
|
||||
func encoder(output io.Writer, catalog *pkg.Catalog, srcMetadata *source.Metadata, d *distro.Distro) error {
|
||||
doc := toFormatModel(catalog, srcMetadata, d)
|
||||
|
||||
enc := json.NewEncoder(output)
|
||||
// prevent > and < from being escaped in the payload
|
||||
enc.SetEscapeHTML(false)
|
||||
enc.SetIndent("", " ")
|
||||
|
||||
return enc.Encode(&doc)
|
||||
}
|
||||
+6
-5
@@ -1,4 +1,4 @@
|
||||
package packages
|
||||
package spdx22json
|
||||
|
||||
import (
|
||||
"flag"
|
||||
@@ -6,14 +6,15 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/internal/formats/common/testutils"
|
||||
"github.com/anchore/syft/syft/format"
|
||||
)
|
||||
|
||||
var updateSpdxJson = flag.Bool("update-spdx-json", false, "update the *.golden files for spdx-json presenters")
|
||||
|
||||
func TestSPDXJSONDirectoryPresenter(t *testing.T) {
|
||||
catalog, metadata, _ := testutils.DirectoryInput(t)
|
||||
catalog, metadata, distro := testutils.DirectoryInput(t)
|
||||
testutils.AssertPresenterAgainstGoldenSnapshot(t,
|
||||
NewSPDXJSONPresenter(catalog, metadata),
|
||||
format.NewPresenter(encoder, catalog, &metadata, distro),
|
||||
*updateSpdxJson,
|
||||
spdxJsonRedactor,
|
||||
)
|
||||
@@ -21,9 +22,9 @@ func TestSPDXJSONDirectoryPresenter(t *testing.T) {
|
||||
|
||||
func TestSPDXJSONImagePresenter(t *testing.T) {
|
||||
testImage := "image-simple"
|
||||
catalog, metadata, _ := testutils.ImageInput(t, testImage)
|
||||
catalog, metadata, distro := testutils.ImageInput(t, testImage)
|
||||
testutils.AssertPresenterAgainstGoldenImageSnapshot(t,
|
||||
NewSPDXJSONPresenter(catalog, metadata),
|
||||
format.NewPresenter(encoder, catalog, &metadata, distro),
|
||||
testImage,
|
||||
*updateSpdxJson,
|
||||
spdxJsonRedactor,
|
||||
@@ -1,12 +1,12 @@
|
||||
package table
|
||||
package spdx22json
|
||||
|
||||
import "github.com/anchore/syft/syft/format"
|
||||
|
||||
func Format() format.Format {
|
||||
return format.NewFormat(
|
||||
format.TableOption,
|
||||
format.SPDXJSONOption,
|
||||
encoder,
|
||||
nil,
|
||||
nil,
|
||||
decoder,
|
||||
validator,
|
||||
)
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package spdx22
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package spdx22
|
||||
package model
|
||||
|
||||
type Checksum struct {
|
||||
// Identifies the algorithm used to produce the subject Checksum. One of: "SHA256", "SHA1", "SHA384", "MD2", "MD4", "SHA512", "MD6", "MD5", "SHA224"
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
package spdx22
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
@@ -10,7 +10,7 @@ type CreationInfo struct {
|
||||
Created time.Time `json:"created"`
|
||||
// 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
|
||||
//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.
|
||||
Creators []string `json:"creators"`
|
||||
+7
-1
@@ -1,4 +1,6 @@
|
||||
package spdx22
|
||||
package model
|
||||
|
||||
import "github.com/anchore/syft/syft/source"
|
||||
|
||||
// derived from:
|
||||
// - https://spdx.github.io/spdx-spec/appendix-III-RDF-data-model-implementation-and-identifier-syntax/
|
||||
@@ -11,6 +13,10 @@ type Document struct {
|
||||
// One instance is required for each SPDX file produced. It provides the necessary information for forward
|
||||
// and backward compatibility for processing tools.
|
||||
CreationInfo CreationInfo `json:"creationInfo"`
|
||||
// SyftSourceData contains information about what is being described in this SPDX document (e.g. a container image, a directory, etc)
|
||||
SyftSourceData *source.Metadata `json:"syftSourceData,omitempty"`
|
||||
// SyftDistroData contains information about the linux distribution discovered
|
||||
SyftDistroData *SyftDistroData `json:"syftDistroData,omitempty"`
|
||||
// 2.2: Data License; should be "CC0-1.0"
|
||||
// Cardinality: mandatory, one
|
||||
// License expression for dataLicense. Compliance with the SPDX specification includes populating the SPDX
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package spdx22
|
||||
package model
|
||||
|
||||
type Element struct {
|
||||
SPDXID string `json:"SPDXID"`
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package spdx22
|
||||
package model
|
||||
|
||||
// ElementID represents the identifier string portion of an SPDX element
|
||||
// identifier. DocElementID should be used for any attributes which can
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package spdx22
|
||||
package model
|
||||
|
||||
type ExternalDocumentRef struct {
|
||||
// externalDocumentId is a string containing letters, numbers, ., - and/or + which uniquely identifies an external document within this document.
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package spdx22
|
||||
package model
|
||||
|
||||
type ReferenceCategory string
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package spdx22
|
||||
package model
|
||||
|
||||
type FileType string
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package spdx22
|
||||
package model
|
||||
|
||||
type HasExtractedLicensingInfo struct {
|
||||
// Verbatim license or licensing notice text that was discovered.
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package spdx22
|
||||
package model
|
||||
|
||||
type Item struct {
|
||||
Element
|
||||
+4
-1
@@ -1,4 +1,4 @@
|
||||
package spdx22
|
||||
package model
|
||||
|
||||
type Package struct {
|
||||
Item
|
||||
@@ -47,4 +47,7 @@ type Package struct {
|
||||
Supplier string `json:"supplier,omitempty"`
|
||||
// Provides an indication of the version of the package that is described by this SpdxDocument.
|
||||
VersionInfo string `json:"versionInfo,omitempty"`
|
||||
// SyftPackageData provides a spot to add syft-specific data that is not available in-spec or is not easily decodable from
|
||||
// other SPDX fields.
|
||||
SyftPackageData *SyftPackageData `json:"syftPackageData,omitempty"`
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package spdx22
|
||||
package model
|
||||
|
||||
// Why are there two package identifier fields Package Checksum and Package Verification?
|
||||
// Although the values of the two fields Package Checksum and Package Verification are similar, they each serve a
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package spdx22
|
||||
package model
|
||||
|
||||
type Relationship struct {
|
||||
// Id to which the SPDX element is related
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package spdx22
|
||||
package model
|
||||
|
||||
type StartPointer struct {
|
||||
Offset int `json:"offset,omitempty"`
|
||||
@@ -0,0 +1,7 @@
|
||||
package model
|
||||
|
||||
type SyftDistroData struct {
|
||||
Name string `json:"name"` // Name of the Linux distribution
|
||||
Version string `json:"version"` // Version of the Linux distribution (major or major.minor version)
|
||||
IDLike string `json:"idLike"` // the ID_LIKE field found within the /etc/os-release file
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
type SyftPackageData struct {
|
||||
SyftPackageBasicData
|
||||
SyftPackageCustomData
|
||||
}
|
||||
|
||||
type SyftPackageCustomData struct {
|
||||
MetadataType pkg.MetadataType `json:"metadataType,omitempty"`
|
||||
Metadata interface{} `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
type SyftPackageBasicData struct {
|
||||
PackageType pkg.Type `json:"type,omitempty"`
|
||||
FoundBy string `json:"foundBy,omitempty"`
|
||||
Locations []source.Location `json:"locations,omitempty"`
|
||||
Licenses []string `json:"licenses,omitempty"`
|
||||
Language pkg.Language `json:"language,omitempty"`
|
||||
}
|
||||
|
||||
// syftPackageMetadataUnpacker is all values needed from Package to disambiguate ambiguous fields during json unmarshaling.
|
||||
type syftPackageMetadataUnpacker struct {
|
||||
MetadataType pkg.MetadataType `json:"metadataType"`
|
||||
Metadata json.RawMessage `json:"metadata"`
|
||||
}
|
||||
|
||||
func (p *syftPackageMetadataUnpacker) String() string {
|
||||
return fmt.Sprintf("metadataType: %s, metadata: %s", p.MetadataType, string(p.Metadata))
|
||||
}
|
||||
|
||||
// UnmarshalJSON is a custom unmarshaller for handling basic values and values with ambiguous types.
|
||||
// nolint:funlen
|
||||
func (p *SyftPackageData) UnmarshalJSON(b []byte) error {
|
||||
var basic SyftPackageBasicData
|
||||
if err := json.Unmarshal(b, &basic); err != nil {
|
||||
return err
|
||||
}
|
||||
p.SyftPackageBasicData = basic
|
||||
|
||||
var unpacker syftPackageMetadataUnpacker
|
||||
if err := json.Unmarshal(b, &unpacker); err != nil {
|
||||
log.Warnf("failed to unmarshall into syftPackageMetadataUnpacker: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
p.MetadataType = unpacker.MetadataType
|
||||
|
||||
switch p.MetadataType {
|
||||
case pkg.ApkMetadataType:
|
||||
var payload pkg.ApkMetadata
|
||||
if err := json.Unmarshal(unpacker.Metadata, &payload); err != nil {
|
||||
return err
|
||||
}
|
||||
p.Metadata = payload
|
||||
case pkg.RpmdbMetadataType:
|
||||
var payload pkg.RpmdbMetadata
|
||||
if err := json.Unmarshal(unpacker.Metadata, &payload); err != nil {
|
||||
return err
|
||||
}
|
||||
p.Metadata = payload
|
||||
case pkg.DpkgMetadataType:
|
||||
var payload pkg.DpkgMetadata
|
||||
if err := json.Unmarshal(unpacker.Metadata, &payload); err != nil {
|
||||
return err
|
||||
}
|
||||
p.Metadata = payload
|
||||
case pkg.JavaMetadataType:
|
||||
var payload pkg.JavaMetadata
|
||||
if err := json.Unmarshal(unpacker.Metadata, &payload); err != nil {
|
||||
return err
|
||||
}
|
||||
p.Metadata = payload
|
||||
case pkg.RustCargoPackageMetadataType:
|
||||
var payload pkg.CargoPackageMetadata
|
||||
if err := json.Unmarshal(unpacker.Metadata, &payload); err != nil {
|
||||
return err
|
||||
}
|
||||
p.Metadata = payload
|
||||
case pkg.GemMetadataType:
|
||||
var payload pkg.GemMetadata
|
||||
if err := json.Unmarshal(unpacker.Metadata, &payload); err != nil {
|
||||
return err
|
||||
}
|
||||
p.Metadata = payload
|
||||
case pkg.KbPackageMetadataType:
|
||||
var payload pkg.KbPackageMetadata
|
||||
if err := json.Unmarshal(unpacker.Metadata, &payload); err != nil {
|
||||
return err
|
||||
}
|
||||
p.Metadata = payload
|
||||
case pkg.PythonPackageMetadataType:
|
||||
var payload pkg.PythonPackageMetadata
|
||||
if err := json.Unmarshal(unpacker.Metadata, &payload); err != nil {
|
||||
return err
|
||||
}
|
||||
p.Metadata = payload
|
||||
case pkg.NpmPackageJSONMetadataType:
|
||||
var payload pkg.NpmPackageJSONMetadata
|
||||
if err := json.Unmarshal(unpacker.Metadata, &payload); err != nil {
|
||||
return err
|
||||
}
|
||||
p.Metadata = payload
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
package spdx22
|
||||
package model
|
||||
|
||||
const Version = "SPDX-2.2"
|
||||
@@ -0,0 +1,4 @@
|
||||
# Note: changes to this file will result in updating several test values. Consider making a new image fixture instead of editing this one.
|
||||
FROM scratch
|
||||
ADD file-1.txt /somefile-1.txt
|
||||
ADD file-2.txt /somefile-2.txt
|
||||
@@ -0,0 +1 @@
|
||||
this file has contents
|
||||
@@ -0,0 +1 @@
|
||||
file-2 contents!
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
{
|
||||
"SPDXID": "SPDXRef-DOCUMENT",
|
||||
"name": "/some/path",
|
||||
"spdxVersion": "SPDX-2.2",
|
||||
"creationInfo": {
|
||||
"created": "2021-10-11T01:54:40.609143Z",
|
||||
"creators": [
|
||||
"Organization: Anchore, Inc",
|
||||
"Tool: syft-[not provided]"
|
||||
],
|
||||
"licenseListVersion": "3.14"
|
||||
},
|
||||
"syftSourceData": {
|
||||
"Scheme": "DirectoryScheme",
|
||||
"ImageMetadata": {
|
||||
"userInput": "",
|
||||
"imageID": "",
|
||||
"manifestDigest": "",
|
||||
"mediaType": "",
|
||||
"tags": null,
|
||||
"imageSize": 0,
|
||||
"layers": null,
|
||||
"manifest": null,
|
||||
"config": null,
|
||||
"repoDigests": null
|
||||
},
|
||||
"Path": "/some/path"
|
||||
},
|
||||
"syftDistroData": {
|
||||
"name": "debian",
|
||||
"version": "1.2.3",
|
||||
"idLike": "like!"
|
||||
},
|
||||
"dataLicense": "CC0-1.0",
|
||||
"documentNamespace": "https:/anchore.com/syft/dir/some/path-e94d6e43-5de5-4849-8e21-983ada9103c5",
|
||||
"packages": [
|
||||
{
|
||||
"SPDXID": "SPDXRef-Package-python-package-1-1.0.1",
|
||||
"name": "package-1",
|
||||
"licenseConcluded": "MIT",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
"externalRefs": [
|
||||
{
|
||||
"referenceCategory": "SECURITY",
|
||||
"referenceLocator": "cpe:2.3:*:some:package:2:*:*:*:*:*:*:*",
|
||||
"referenceType": "cpe23Type"
|
||||
},
|
||||
{
|
||||
"referenceCategory": "PACKAGE_MANAGER",
|
||||
"referenceLocator": "a-purl-2",
|
||||
"referenceType": "purl"
|
||||
}
|
||||
],
|
||||
"filesAnalyzed": false,
|
||||
"hasFiles": [
|
||||
"SPDXRef-File-package-1-04cd22424378dcd6c77fce08beb52493b5494a60ea5e1f9bdf9b16dc0cacffe9"
|
||||
],
|
||||
"licenseDeclared": "MIT",
|
||||
"sourceInfo": "acquired package info from installed python package manifest file: /some/path/pkg1",
|
||||
"versionInfo": "1.0.1",
|
||||
"syftPackageData": {
|
||||
"type": "python",
|
||||
"foundBy": "the-cataloger-1",
|
||||
"locations": [
|
||||
{
|
||||
"path": "/some/path/pkg1"
|
||||
}
|
||||
],
|
||||
"licenses": [
|
||||
"MIT"
|
||||
],
|
||||
"language": "python",
|
||||
"metadataType": "PythonPackageMetadata",
|
||||
"metadata": {
|
||||
"name": "package-1",
|
||||
"version": "1.0.1",
|
||||
"license": "",
|
||||
"author": "",
|
||||
"authorEmail": "",
|
||||
"platform": "",
|
||||
"files": [
|
||||
{
|
||||
"path": "/some/path/pkg1/depedencies/foo"
|
||||
}
|
||||
],
|
||||
"sitePackagesRootPath": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"SPDXID": "SPDXRef-Package-deb-package-2-2.0.1",
|
||||
"name": "package-2",
|
||||
"licenseConcluded": "NONE",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
"externalRefs": [
|
||||
{
|
||||
"referenceCategory": "SECURITY",
|
||||
"referenceLocator": "cpe:2.3:*:some:package:2:*:*:*:*:*:*:*",
|
||||
"referenceType": "cpe23Type"
|
||||
},
|
||||
{
|
||||
"referenceCategory": "PACKAGE_MANAGER",
|
||||
"referenceLocator": "a-purl-2",
|
||||
"referenceType": "purl"
|
||||
}
|
||||
],
|
||||
"filesAnalyzed": false,
|
||||
"licenseDeclared": "NONE",
|
||||
"sourceInfo": "acquired package info from DPKG DB: /some/path/pkg1",
|
||||
"versionInfo": "2.0.1",
|
||||
"syftPackageData": {
|
||||
"type": "deb",
|
||||
"foundBy": "the-cataloger-2",
|
||||
"locations": [
|
||||
{
|
||||
"path": "/some/path/pkg1"
|
||||
}
|
||||
],
|
||||
"metadataType": "DpkgMetadata",
|
||||
"metadata": {
|
||||
"package": "package-2",
|
||||
"source": "",
|
||||
"version": "2.0.1",
|
||||
"sourceVersion": "",
|
||||
"architecture": "",
|
||||
"maintainer": "",
|
||||
"installedSize": 0,
|
||||
"files": null
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"files": [
|
||||
{
|
||||
"SPDXID": "SPDXRef-File-package-1-04cd22424378dcd6c77fce08beb52493b5494a60ea5e1f9bdf9b16dc0cacffe9",
|
||||
"name": "foo",
|
||||
"licenseConcluded": "",
|
||||
"fileName": "/some/path/pkg1/depedencies/foo"
|
||||
}
|
||||
],
|
||||
"relationships": [
|
||||
{
|
||||
"spdxElementId": "SPDXRef-Package-python-package-1-1.0.1",
|
||||
"relationshipType": "CONTAINS",
|
||||
"relatedSpdxElement": "SPDXRef-File-package-1-04cd22424378dcd6c77fce08beb52493b5494a60ea5e1f9bdf9b16dc0cacffe9"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
{
|
||||
"SPDXID": "SPDXRef-DOCUMENT",
|
||||
"name": "user-image-input",
|
||||
"spdxVersion": "SPDX-2.2",
|
||||
"creationInfo": {
|
||||
"created": "2021-10-11T01:54:40.61361Z",
|
||||
"creators": [
|
||||
"Organization: Anchore, Inc",
|
||||
"Tool: syft-[not provided]"
|
||||
],
|
||||
"licenseListVersion": "3.14"
|
||||
},
|
||||
"syftSourceData": {
|
||||
"Scheme": "ImageScheme",
|
||||
"ImageMetadata": {
|
||||
"userInput": "user-image-input",
|
||||
"imageID": "sha256:2480160b55bec40c44d3b145c7b2c1c47160db8575c3dcae086d76b9370ae7ca",
|
||||
"manifestDigest": "sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368",
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||
"tags": [
|
||||
"stereoscope-fixture-image-simple:85066c51088bdd274f7a89e99e00490f666c49e72ffc955707cd6e18f0e22c5b"
|
||||
],
|
||||
"imageSize": 38,
|
||||
"layers": [
|
||||
{
|
||||
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
"digest": "sha256:fb6beecb75b39f4bb813dbf177e501edd5ddb3e69bb45cedeb78c676ee1b7a59",
|
||||
"size": 22
|
||||
},
|
||||
{
|
||||
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
"digest": "sha256:319b588ce64253a87b533c8ed01cf0025e0eac98e7b516e12532957e1244fdec",
|
||||
"size": 16
|
||||
}
|
||||
],
|
||||
"manifest": "eyJzY2hlbWFWZXJzaW9uIjoyLCJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmRpc3RyaWJ1dGlvbi5tYW5pZmVzdC52Mitqc29uIiwiY29uZmlnIjp7Im1lZGlhVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5kb2NrZXIuY29udGFpbmVyLmltYWdlLnYxK2pzb24iLCJzaXplIjo2NjcsImRpZ2VzdCI6InNoYTI1NjoyNDgwMTYwYjU1YmVjNDBjNDRkM2IxNDVjN2IyYzFjNDcxNjBkYjg1NzVjM2RjYWUwODZkNzZiOTM3MGFlN2NhIn0sImxheWVycyI6W3sibWVkaWFUeXBlIjoiYXBwbGljYXRpb24vdm5kLmRvY2tlci5pbWFnZS5yb290ZnMuZGlmZi50YXIuZ3ppcCIsInNpemUiOjIwNDgsImRpZ2VzdCI6InNoYTI1NjpmYjZiZWVjYjc1YjM5ZjRiYjgxM2RiZjE3N2U1MDFlZGQ1ZGRiM2U2OWJiNDVjZWRlYjc4YzY3NmVlMWI3YTU5In0seyJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmltYWdlLnJvb3Rmcy5kaWZmLnRhci5nemlwIiwic2l6ZSI6MjA0OCwiZGlnZXN0Ijoic2hhMjU2OjMxOWI1ODhjZTY0MjUzYTg3YjUzM2M4ZWQwMWNmMDAyNWUwZWFjOThlN2I1MTZlMTI1MzI5NTdlMTI0NGZkZWMifV19",
|
||||
"config": "eyJhcmNoaXRlY3R1cmUiOiJhbWQ2NCIsImNvbmZpZyI6eyJFbnYiOlsiUEFUSD0vdXNyL2xvY2FsL3NiaW46L3Vzci9sb2NhbC9iaW46L3Vzci9zYmluOi91c3IvYmluOi9zYmluOi9iaW4iXSwiV29ya2luZ0RpciI6Ii8iLCJPbkJ1aWxkIjpudWxsfSwiY3JlYXRlZCI6IjIwMjEtMTAtMDRUMTE6NDA6MDAuNjM4Mzk0NVoiLCJoaXN0b3J5IjpbeyJjcmVhdGVkIjoiMjAyMS0xMC0wNFQxMTo0MDowMC41OTA3MzE2WiIsImNyZWF0ZWRfYnkiOiJBREQgZmlsZS0xLnR4dCAvc29tZWZpbGUtMS50eHQgIyBidWlsZGtpdCIsImNvbW1lbnQiOiJidWlsZGtpdC5kb2NrZXJmaWxlLnYwIn0seyJjcmVhdGVkIjoiMjAyMS0xMC0wNFQxMTo0MDowMC42MzgzOTQ1WiIsImNyZWF0ZWRfYnkiOiJBREQgZmlsZS0yLnR4dCAvc29tZWZpbGUtMi50eHQgIyBidWlsZGtpdCIsImNvbW1lbnQiOiJidWlsZGtpdC5kb2NrZXJmaWxlLnYwIn1dLCJvcyI6ImxpbnV4Iiwicm9vdGZzIjp7InR5cGUiOiJsYXllcnMiLCJkaWZmX2lkcyI6WyJzaGEyNTY6ZmI2YmVlY2I3NWIzOWY0YmI4MTNkYmYxNzdlNTAxZWRkNWRkYjNlNjliYjQ1Y2VkZWI3OGM2NzZlZTFiN2E1OSIsInNoYTI1NjozMTliNTg4Y2U2NDI1M2E4N2I1MzNjOGVkMDFjZjAwMjVlMGVhYzk4ZTdiNTE2ZTEyNTMyOTU3ZTEyNDRmZGVjIl19fQ==",
|
||||
"repoDigests": []
|
||||
},
|
||||
"Path": ""
|
||||
},
|
||||
"syftDistroData": {
|
||||
"name": "debian",
|
||||
"version": "1.2.3",
|
||||
"idLike": "like!"
|
||||
},
|
||||
"dataLicense": "CC0-1.0",
|
||||
"documentNamespace": "https:/anchore.com/syft/image/user-image-input-9badd1fb-a067-4207-addf-243c5b7705fe",
|
||||
"packages": [
|
||||
{
|
||||
"SPDXID": "SPDXRef-Package-python-package-1-1.0.1",
|
||||
"name": "package-1",
|
||||
"licenseConcluded": "MIT",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
"externalRefs": [
|
||||
{
|
||||
"referenceCategory": "SECURITY",
|
||||
"referenceLocator": "cpe:2.3:*:some:package:1:*:*:*:*:*:*:*",
|
||||
"referenceType": "cpe23Type"
|
||||
},
|
||||
{
|
||||
"referenceCategory": "PACKAGE_MANAGER",
|
||||
"referenceLocator": "a-purl-1",
|
||||
"referenceType": "purl"
|
||||
}
|
||||
],
|
||||
"filesAnalyzed": false,
|
||||
"licenseDeclared": "MIT",
|
||||
"sourceInfo": "acquired package info from installed python package manifest file: /somefile-1.txt",
|
||||
"versionInfo": "1.0.1",
|
||||
"syftPackageData": {
|
||||
"type": "python",
|
||||
"foundBy": "the-cataloger-1",
|
||||
"locations": [
|
||||
{
|
||||
"path": "/somefile-1.txt",
|
||||
"layerID": "sha256:fb6beecb75b39f4bb813dbf177e501edd5ddb3e69bb45cedeb78c676ee1b7a59"
|
||||
}
|
||||
],
|
||||
"licenses": [
|
||||
"MIT"
|
||||
],
|
||||
"language": "python",
|
||||
"metadataType": "PythonPackageMetadata",
|
||||
"metadata": {
|
||||
"name": "package-1",
|
||||
"version": "1.0.1",
|
||||
"license": "",
|
||||
"author": "",
|
||||
"authorEmail": "",
|
||||
"platform": "",
|
||||
"sitePackagesRootPath": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"SPDXID": "SPDXRef-Package-deb-package-2-2.0.1",
|
||||
"name": "package-2",
|
||||
"licenseConcluded": "NONE",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
"externalRefs": [
|
||||
{
|
||||
"referenceCategory": "SECURITY",
|
||||
"referenceLocator": "cpe:2.3:*:some:package:2:*:*:*:*:*:*:*",
|
||||
"referenceType": "cpe23Type"
|
||||
},
|
||||
{
|
||||
"referenceCategory": "PACKAGE_MANAGER",
|
||||
"referenceLocator": "a-purl-2",
|
||||
"referenceType": "purl"
|
||||
}
|
||||
],
|
||||
"filesAnalyzed": false,
|
||||
"licenseDeclared": "NONE",
|
||||
"sourceInfo": "acquired package info from DPKG DB: /somefile-2.txt",
|
||||
"versionInfo": "2.0.1",
|
||||
"syftPackageData": {
|
||||
"type": "deb",
|
||||
"foundBy": "the-cataloger-2",
|
||||
"locations": [
|
||||
{
|
||||
"path": "/somefile-2.txt",
|
||||
"layerID": "sha256:319b588ce64253a87b533c8ed01cf0025e0eac98e7b516e12532957e1244fdec"
|
||||
}
|
||||
],
|
||||
"metadataType": "DpkgMetadata",
|
||||
"metadata": {
|
||||
"package": "package-2",
|
||||
"source": "",
|
||||
"version": "2.0.1",
|
||||
"sourceVersion": "",
|
||||
"architecture": "",
|
||||
"maintainer": "",
|
||||
"installedSize": 0,
|
||||
"files": null
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
Binary file not shown.
@@ -0,0 +1,164 @@
|
||||
package spdx22json
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/anchore/syft/syft/distro"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/internal/formats/common/spdxhelpers"
|
||||
"github.com/anchore/syft/internal/formats/spdx22json/model"
|
||||
"github.com/anchore/syft/internal/spdxlicense"
|
||||
"github.com/anchore/syft/internal/version"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// toFormatModel creates and populates a new JSON document struct that follows the SPDX 2.2 spec from the given cataloging results.
|
||||
func toFormatModel(catalog *pkg.Catalog, srcMetadata *source.Metadata, d *distro.Distro) model.Document {
|
||||
name := documentName(srcMetadata)
|
||||
packages, files, relationships := extractFromCatalog(catalog)
|
||||
|
||||
return model.Document{
|
||||
Element: model.Element{
|
||||
SPDXID: model.ElementID("DOCUMENT").String(),
|
||||
Name: name,
|
||||
},
|
||||
SPDXVersion: model.Version,
|
||||
CreationInfo: model.CreationInfo{
|
||||
Created: time.Now().UTC(),
|
||||
Creators: []string{
|
||||
// note: key-value format derived from the JSON example document examples: https://github.com/spdx/spdx-spec/blob/v2.2/examples/SPDXJSONExample-v2.2.spdx.json
|
||||
"Organization: Anchore, Inc",
|
||||
"Tool: " + internal.ApplicationName + "-" + version.FromBuild().Version,
|
||||
},
|
||||
LicenseListVersion: spdxlicense.Version,
|
||||
},
|
||||
DataLicense: "CC0-1.0",
|
||||
DocumentNamespace: documentNamespace(name, srcMetadata),
|
||||
Packages: packages,
|
||||
Files: files,
|
||||
Relationships: relationships,
|
||||
SyftSourceData: srcMetadata,
|
||||
SyftDistroData: toSyftDistroData(d),
|
||||
}
|
||||
}
|
||||
|
||||
func toSyftDistroData(d *distro.Distro) *model.SyftDistroData {
|
||||
if d == nil {
|
||||
return nil
|
||||
}
|
||||
return &model.SyftDistroData{
|
||||
Name: d.Name(),
|
||||
Version: d.FullVersion(),
|
||||
IDLike: d.IDLike,
|
||||
}
|
||||
}
|
||||
|
||||
func documentName(srcMetadata *source.Metadata) string {
|
||||
if srcMetadata != nil {
|
||||
switch srcMetadata.Scheme {
|
||||
case source.ImageScheme:
|
||||
return cleanSPDXName(srcMetadata.ImageMetadata.UserInput)
|
||||
case source.DirectoryScheme:
|
||||
return cleanSPDXName(srcMetadata.Path)
|
||||
}
|
||||
}
|
||||
// TODO: is this alright?
|
||||
return uuid.Must(uuid.NewRandom()).String()
|
||||
}
|
||||
|
||||
func documentNamespace(name string, srcMetadata *source.Metadata) string {
|
||||
input := "unknown-source-type"
|
||||
if srcMetadata != nil {
|
||||
switch srcMetadata.Scheme {
|
||||
case source.ImageScheme:
|
||||
input = "image"
|
||||
case source.DirectoryScheme:
|
||||
input = "dir"
|
||||
}
|
||||
}
|
||||
|
||||
uniqueID := uuid.Must(uuid.NewRandom())
|
||||
identifier := path.Join(input, uniqueID.String())
|
||||
if name != "." {
|
||||
identifier = path.Join(input, fmt.Sprintf("%s-%s", name, uniqueID.String()))
|
||||
}
|
||||
|
||||
return path.Join(anchoreNamespace, identifier)
|
||||
}
|
||||
|
||||
func extractFromCatalog(catalog *pkg.Catalog) ([]model.Package, []model.File, []model.Relationship) {
|
||||
packages := make([]model.Package, 0)
|
||||
relationships := make([]model.Relationship, 0)
|
||||
files := make([]model.File, 0)
|
||||
|
||||
for _, p := range catalog.Sorted() {
|
||||
license := spdxhelpers.License(p)
|
||||
packageSpdxID := model.ElementID(fmt.Sprintf("Package-%+v-%s-%s", p.Type, p.Name, p.Version)).String()
|
||||
|
||||
packageFiles, fileIDs, packageFileRelationships := spdxhelpers.Files(packageSpdxID, p)
|
||||
files = append(files, packageFiles...)
|
||||
|
||||
relationships = append(relationships, packageFileRelationships...)
|
||||
|
||||
// note: the license concluded and declared should be the same since we are collecting license information
|
||||
// from the project data itself (the installed package files).
|
||||
packages = append(packages, model.Package{
|
||||
Description: spdxhelpers.Description(p),
|
||||
DownloadLocation: spdxhelpers.DownloadLocation(p),
|
||||
ExternalRefs: spdxhelpers.ExternalRefs(p),
|
||||
FilesAnalyzed: false,
|
||||
HasFiles: fileIDs,
|
||||
Homepage: spdxhelpers.Homepage(p),
|
||||
LicenseDeclared: license, // The Declared License is what the authors of a project believe govern the package
|
||||
Originator: spdxhelpers.Originator(p),
|
||||
SourceInfo: spdxhelpers.SourceInfo(p),
|
||||
VersionInfo: p.Version,
|
||||
Item: model.Item{
|
||||
LicenseConcluded: license, // The Concluded License field is the license the SPDX file creator believes governs the package
|
||||
Element: model.Element{
|
||||
SPDXID: packageSpdxID,
|
||||
Name: p.Name,
|
||||
},
|
||||
},
|
||||
SyftPackageData: toSyftPackageData(p),
|
||||
})
|
||||
}
|
||||
|
||||
return packages, files, relationships
|
||||
}
|
||||
|
||||
func toSyftPackageData(p *pkg.Package) *model.SyftPackageData {
|
||||
if p == nil {
|
||||
return nil
|
||||
}
|
||||
return &model.SyftPackageData{
|
||||
SyftPackageBasicData: model.SyftPackageBasicData{
|
||||
PackageType: p.Type,
|
||||
FoundBy: p.FoundBy,
|
||||
Locations: p.Locations,
|
||||
Language: p.Language,
|
||||
Licenses: p.Licenses,
|
||||
},
|
||||
SyftPackageCustomData: model.SyftPackageCustomData{
|
||||
MetadataType: p.MetadataType,
|
||||
Metadata: p.Metadata,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func cleanSPDXName(name string) string {
|
||||
// remove # according to specification
|
||||
name = strings.ReplaceAll(name, "#", "-")
|
||||
|
||||
// remove : for url construction
|
||||
name = strings.ReplaceAll(name, ":", "-")
|
||||
|
||||
// clean relative pathing
|
||||
return path.Clean(name)
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package spdx22json
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/internal/formats/common/spdxhelpers"
|
||||
"github.com/anchore/syft/internal/formats/spdx22json/model"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/distro"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func toSyftModel(doc model.Document) (*pkg.Catalog, *source.Metadata, *distro.Distro, error) {
|
||||
d, err := toSyftDistro(doc.SyftDistroData)
|
||||
if err != nil {
|
||||
log.Warnf("unable to parse distro info=%+v: %+v", d, err)
|
||||
d = nil
|
||||
}
|
||||
|
||||
return toSyftCatalog(doc.Packages), doc.SyftSourceData, d, nil
|
||||
}
|
||||
|
||||
func toSyftCatalog(pkgs []model.Package) *pkg.Catalog {
|
||||
catalog := pkg.NewCatalog()
|
||||
for _, p := range pkgs {
|
||||
catalog.Add(toSyftPackage(p))
|
||||
}
|
||||
return catalog
|
||||
}
|
||||
|
||||
func toSyftPackage(p model.Package) pkg.Package {
|
||||
syftPkg := pkg.Package{
|
||||
Name: p.Name,
|
||||
Version: p.VersionInfo,
|
||||
CPEs: spdxhelpers.ExtractCPEs(p.ExternalRefs),
|
||||
PURL: spdxhelpers.ExtractPURL(p.ExternalRefs),
|
||||
}
|
||||
|
||||
if extra := p.SyftPackageData; extra != nil {
|
||||
syftPkg.Type = extra.PackageType
|
||||
syftPkg.FoundBy = extra.FoundBy
|
||||
syftPkg.Locations = extra.Locations
|
||||
syftPkg.Language = extra.Language
|
||||
syftPkg.Licenses = extra.Licenses
|
||||
syftPkg.MetadataType = extra.MetadataType
|
||||
syftPkg.Metadata = extra.Metadata
|
||||
}
|
||||
|
||||
//if syftPkg.Type == "" && syftPkg.PURL != "" {
|
||||
// // TODO: extract package type from purl --this is useful for ingesting from tools other than syft and is important for grype
|
||||
//}
|
||||
|
||||
return syftPkg
|
||||
}
|
||||
|
||||
func toSyftDistro(d *model.SyftDistroData) (*distro.Distro, error) {
|
||||
if d == nil {
|
||||
return nil, nil
|
||||
}
|
||||
newDistro, err := distro.NewDistro(distro.Type(d.Name), d.Version, d.IDLike)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &newDistro, nil
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package spdx22json
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
func validator(reader io.Reader) error {
|
||||
type Document struct {
|
||||
SPDXID string `json:"SPDXID"`
|
||||
}
|
||||
|
||||
dec := json.NewDecoder(reader)
|
||||
|
||||
var doc Document
|
||||
err := dec.Decode(&doc)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to decode: %w", err)
|
||||
}
|
||||
|
||||
if doc.SPDXID != "" {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("could not extract document SPDXID")
|
||||
}
|
||||
@@ -11,13 +11,13 @@ import (
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func decoder(reader io.Reader) (*pkg.Catalog, *source.Metadata, *distro.Distro, source.Scope, error) {
|
||||
func decoder(reader io.Reader) (*pkg.Catalog, *source.Metadata, *distro.Distro, error) {
|
||||
dec := json.NewDecoder(reader)
|
||||
|
||||
var doc model.Document
|
||||
err := dec.Decode(&doc)
|
||||
if err != nil {
|
||||
return nil, nil, nil, source.UnknownScope, fmt.Errorf("unable to decode syft-json: %w", err)
|
||||
return nil, nil, nil, fmt.Errorf("unable to decode syft-json: %w", err)
|
||||
}
|
||||
|
||||
return toSyftModel(doc)
|
||||
|
||||
@@ -5,8 +5,6 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/syft/source"
|
||||
|
||||
"github.com/anchore/syft/internal/formats/common/testutils"
|
||||
"github.com/go-test/deep"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -17,9 +15,9 @@ func TestEncodeDecodeCycle(t *testing.T) {
|
||||
originalCatalog, originalMetadata, _ := testutils.ImageInput(t, testImage)
|
||||
|
||||
var buf bytes.Buffer
|
||||
assert.NoError(t, encoder(&buf, originalCatalog, &originalMetadata, nil, source.SquashedScope))
|
||||
assert.NoError(t, encoder(&buf, originalCatalog, &originalMetadata, nil))
|
||||
|
||||
actualCatalog, actualMetadata, _, _, err := decoder(bytes.NewReader(buf.Bytes()))
|
||||
actualCatalog, actualMetadata, _, err := decoder(bytes.NewReader(buf.Bytes()))
|
||||
assert.NoError(t, err)
|
||||
|
||||
for _, d := range deep.Equal(originalMetadata, *actualMetadata) {
|
||||
|
||||
@@ -10,9 +10,9 @@ import (
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func encoder(output io.Writer, catalog *pkg.Catalog, srcMetadata *source.Metadata, d *distro.Distro, scope source.Scope) error {
|
||||
func encoder(output io.Writer, catalog *pkg.Catalog, srcMetadata *source.Metadata, d *distro.Distro) error {
|
||||
// TODO: application config not available yet
|
||||
doc := ToFormatModel(catalog, srcMetadata, d, scope, nil)
|
||||
doc := ToFormatModel(catalog, srcMetadata, d, nil)
|
||||
|
||||
enc := json.NewEncoder(output)
|
||||
// prevent > and < from being escaped in the payload
|
||||
|
||||
@@ -4,27 +4,25 @@ import (
|
||||
"flag"
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/syft/source"
|
||||
|
||||
"github.com/anchore/syft/internal/formats/common/testutils"
|
||||
"github.com/anchore/syft/syft/format"
|
||||
)
|
||||
|
||||
var updateJson = flag.Bool("update-json", false, "update the *.golden files for json presenters")
|
||||
|
||||
func TestDirectoryPresenter(t *testing.T) {
|
||||
func TestJSONDirectoryPresenter(t *testing.T) {
|
||||
catalog, metadata, distro := testutils.DirectoryInput(t)
|
||||
testutils.AssertPresenterAgainstGoldenSnapshot(t,
|
||||
format.NewPresenter(encoder, catalog, &metadata, distro, source.SquashedScope),
|
||||
format.NewPresenter(encoder, catalog, &metadata, distro),
|
||||
*updateJson,
|
||||
)
|
||||
}
|
||||
|
||||
func TestImagePresenter(t *testing.T) {
|
||||
func TestJSONImagePresenter(t *testing.T) {
|
||||
testImage := "image-simple"
|
||||
catalog, metadata, distro := testutils.ImageInput(t, testImage)
|
||||
testutils.AssertPresenterAgainstGoldenImageSnapshot(t,
|
||||
format.NewPresenter(encoder, catalog, &metadata, distro, source.SquashedScope),
|
||||
format.NewPresenter(encoder, catalog, &metadata, distro),
|
||||
testImage,
|
||||
*updateJson,
|
||||
)
|
||||
|
||||
@@ -3,7 +3,6 @@ package model
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
@@ -20,11 +19,6 @@ type sourceUnpacker struct {
|
||||
Target json.RawMessage `json:"target"`
|
||||
}
|
||||
|
||||
type ImageSource struct {
|
||||
source.ImageMetadata
|
||||
Scope source.Scope `json:"scope"`
|
||||
}
|
||||
|
||||
// UnmarshalJSON populates a source object from JSON bytes.
|
||||
func (s *Source) UnmarshalJSON(b []byte) error {
|
||||
var unpacker sourceUnpacker
|
||||
@@ -36,19 +30,13 @@ func (s *Source) UnmarshalJSON(b []byte) error {
|
||||
|
||||
switch s.Type {
|
||||
case "directory":
|
||||
if target, err := strconv.Unquote(string(unpacker.Target)); err == nil {
|
||||
s.Target = target
|
||||
} else {
|
||||
s.Target = string(unpacker.Target[:])
|
||||
}
|
||||
|
||||
s.Target = string(unpacker.Target[:])
|
||||
case "image":
|
||||
var payload ImageSource
|
||||
var payload source.ImageMetadata
|
||||
if err := json.Unmarshal(unpacker.Target, &payload); err != nil {
|
||||
return err
|
||||
}
|
||||
s.Target = payload
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unsupported package metadata type: %+v", s.Type)
|
||||
}
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@
|
||||
"platform": "",
|
||||
"files": [
|
||||
{
|
||||
"path": "/some/path/pkg1/dependencies/foo"
|
||||
"path": "/some/path/pkg1/depedencies/foo"
|
||||
}
|
||||
],
|
||||
"sitePackagesRootPath": ""
|
||||
+7
-7
@@ -9,7 +9,7 @@
|
||||
"locations": [
|
||||
{
|
||||
"path": "/somefile-1.txt",
|
||||
"layerID": "sha256:fb6beecb75b39f4bb813dbf177e501edd5ddb3e69bb45cedeb78c676ee1b7a59"
|
||||
"layerID": "sha256:ffb5e9eaa453a002110719d12c294960117ca2903953d1faa40f01dc3f77045c"
|
||||
}
|
||||
],
|
||||
"licenses": [
|
||||
@@ -40,7 +40,7 @@
|
||||
"locations": [
|
||||
{
|
||||
"path": "/somefile-2.txt",
|
||||
"layerID": "sha256:319b588ce64253a87b533c8ed01cf0025e0eac98e7b516e12532957e1244fdec"
|
||||
"layerID": "sha256:8463854829fc53d47b9dcdf7ee79fe7eb4ca7933c910f67f8521412f7a2f5c21"
|
||||
}
|
||||
],
|
||||
"licenses": [],
|
||||
@@ -67,7 +67,7 @@
|
||||
"type": "image",
|
||||
"target": {
|
||||
"userInput": "user-image-input",
|
||||
"imageID": "sha256:2480160b55bec40c44d3b145c7b2c1c47160db8575c3dcae086d76b9370ae7ca",
|
||||
"imageID": "sha256:112851310e48e604f7379e2a3acddab50e91ce926edacb598a532e60ff6b776a",
|
||||
"manifestDigest": "sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368",
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||
"tags": [
|
||||
@@ -77,17 +77,17 @@
|
||||
"layers": [
|
||||
{
|
||||
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
"digest": "sha256:fb6beecb75b39f4bb813dbf177e501edd5ddb3e69bb45cedeb78c676ee1b7a59",
|
||||
"digest": "sha256:ffb5e9eaa453a002110719d12c294960117ca2903953d1faa40f01dc3f77045c",
|
||||
"size": 22
|
||||
},
|
||||
{
|
||||
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
"digest": "sha256:319b588ce64253a87b533c8ed01cf0025e0eac98e7b516e12532957e1244fdec",
|
||||
"digest": "sha256:8463854829fc53d47b9dcdf7ee79fe7eb4ca7933c910f67f8521412f7a2f5c21",
|
||||
"size": 16
|
||||
}
|
||||
],
|
||||
"manifest": "eyJzY2hlbWFWZXJzaW9uIjoyLCJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmRpc3RyaWJ1dGlvbi5tYW5pZmVzdC52Mitqc29uIiwiY29uZmlnIjp7Im1lZGlhVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5kb2NrZXIuY29udGFpbmVyLmltYWdlLnYxK2pzb24iLCJzaXplIjo2NjcsImRpZ2VzdCI6InNoYTI1NjoyNDgwMTYwYjU1YmVjNDBjNDRkM2IxNDVjN2IyYzFjNDcxNjBkYjg1NzVjM2RjYWUwODZkNzZiOTM3MGFlN2NhIn0sImxheWVycyI6W3sibWVkaWFUeXBlIjoiYXBwbGljYXRpb24vdm5kLmRvY2tlci5pbWFnZS5yb290ZnMuZGlmZi50YXIuZ3ppcCIsInNpemUiOjIwNDgsImRpZ2VzdCI6InNoYTI1NjpmYjZiZWVjYjc1YjM5ZjRiYjgxM2RiZjE3N2U1MDFlZGQ1ZGRiM2U2OWJiNDVjZWRlYjc4YzY3NmVlMWI3YTU5In0seyJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmltYWdlLnJvb3Rmcy5kaWZmLnRhci5nemlwIiwic2l6ZSI6MjA0OCwiZGlnZXN0Ijoic2hhMjU2OjMxOWI1ODhjZTY0MjUzYTg3YjUzM2M4ZWQwMWNmMDAyNWUwZWFjOThlN2I1MTZlMTI1MzI5NTdlMTI0NGZkZWMifV19",
|
||||
"config": "eyJhcmNoaXRlY3R1cmUiOiJhbWQ2NCIsImNvbmZpZyI6eyJFbnYiOlsiUEFUSD0vdXNyL2xvY2FsL3NiaW46L3Vzci9sb2NhbC9iaW46L3Vzci9zYmluOi91c3IvYmluOi9zYmluOi9iaW4iXSwiV29ya2luZ0RpciI6Ii8iLCJPbkJ1aWxkIjpudWxsfSwiY3JlYXRlZCI6IjIwMjEtMTAtMDRUMTE6NDA6MDAuNjM4Mzk0NVoiLCJoaXN0b3J5IjpbeyJjcmVhdGVkIjoiMjAyMS0xMC0wNFQxMTo0MDowMC41OTA3MzE2WiIsImNyZWF0ZWRfYnkiOiJBREQgZmlsZS0xLnR4dCAvc29tZWZpbGUtMS50eHQgIyBidWlsZGtpdCIsImNvbW1lbnQiOiJidWlsZGtpdC5kb2NrZXJmaWxlLnYwIn0seyJjcmVhdGVkIjoiMjAyMS0xMC0wNFQxMTo0MDowMC42MzgzOTQ1WiIsImNyZWF0ZWRfYnkiOiJBREQgZmlsZS0yLnR4dCAvc29tZWZpbGUtMi50eHQgIyBidWlsZGtpdCIsImNvbW1lbnQiOiJidWlsZGtpdC5kb2NrZXJmaWxlLnYwIn1dLCJvcyI6ImxpbnV4Iiwicm9vdGZzIjp7InR5cGUiOiJsYXllcnMiLCJkaWZmX2lkcyI6WyJzaGEyNTY6ZmI2YmVlY2I3NWIzOWY0YmI4MTNkYmYxNzdlNTAxZWRkNWRkYjNlNjliYjQ1Y2VkZWI3OGM2NzZlZTFiN2E1OSIsInNoYTI1NjozMTliNTg4Y2U2NDI1M2E4N2I1MzNjOGVkMDFjZjAwMjVlMGVhYzk4ZTdiNTE2ZTEyNTMyOTU3ZTEyNDRmZGVjIl19fQ==",
|
||||
"manifest": "eyJzY2hlbWFWZXJzaW9uIjoyLCJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmRpc3RyaWJ1dGlvbi5tYW5pZmVzdC52Mitqc29uIiwiY29uZmlnIjp7Im1lZGlhVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5kb2NrZXIuY29udGFpbmVyLmltYWdlLnYxK2pzb24iLCJzaXplIjo2NzMsImRpZ2VzdCI6InNoYTI1NjoxMTI4NTEzMTBlNDhlNjA0ZjczNzllMmEzYWNkZGFiNTBlOTFjZTkyNmVkYWNiNTk4YTUzMmU2MGZmNmI3NzZhIn0sImxheWVycyI6W3sibWVkaWFUeXBlIjoiYXBwbGljYXRpb24vdm5kLmRvY2tlci5pbWFnZS5yb290ZnMuZGlmZi50YXIuZ3ppcCIsInNpemUiOjIwNDgsImRpZ2VzdCI6InNoYTI1NjpmZmI1ZTllYWE0NTNhMDAyMTEwNzE5ZDEyYzI5NDk2MDExN2NhMjkwMzk1M2QxZmFhNDBmMDFkYzNmNzcwNDVjIn0seyJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmltYWdlLnJvb3Rmcy5kaWZmLnRhci5nemlwIiwic2l6ZSI6MjA0OCwiZGlnZXN0Ijoic2hhMjU2Ojg0NjM4NTQ4MjlmYzUzZDQ3YjlkY2RmN2VlNzlmZTdlYjRjYTc5MzNjOTEwZjY3Zjg1MjE0MTJmN2EyZjVjMjEifV19",
|
||||
"config": "eyJhcmNoaXRlY3R1cmUiOiJhbWQ2NCIsImNvbmZpZyI6eyJFbnYiOlsiUEFUSD0vdXNyL2xvY2FsL3NiaW46L3Vzci9sb2NhbC9iaW46L3Vzci9zYmluOi91c3IvYmluOi9zYmluOi9iaW4iXSwiV29ya2luZ0RpciI6Ii8iLCJPbkJ1aWxkIjpudWxsfSwiY3JlYXRlZCI6IjIwMjEtMDktMDhUMTc6MjE6NTguODk2NTI5MTkyWiIsImhpc3RvcnkiOlt7ImNyZWF0ZWQiOiIyMDIxLTA5LTA4VDE3OjIxOjU4Ljg3OTY5MDgyNFoiLCJjcmVhdGVkX2J5IjoiQUREIGZpbGUtMS50eHQgL3NvbWVmaWxlLTEudHh0ICMgYnVpbGRraXQiLCJjb21tZW50IjoiYnVpbGRraXQuZG9ja2VyZmlsZS52MCJ9LHsiY3JlYXRlZCI6IjIwMjEtMDktMDhUMTc6MjE6NTguODk2NTI5MTkyWiIsImNyZWF0ZWRfYnkiOiJBREQgZmlsZS0yLnR4dCAvc29tZWZpbGUtMi50eHQgIyBidWlsZGtpdCIsImNvbW1lbnQiOiJidWlsZGtpdC5kb2NrZXJmaWxlLnYwIn1dLCJvcyI6ImxpbnV4Iiwicm9vdGZzIjp7InR5cGUiOiJsYXllcnMiLCJkaWZmX2lkcyI6WyJzaGEyNTY6ZmZiNWU5ZWFhNDUzYTAwMjExMDcxOWQxMmMyOTQ5NjAxMTdjYTI5MDM5NTNkMWZhYTQwZjAxZGMzZjc3MDQ1YyIsInNoYTI1Njo4NDYzODU0ODI5ZmM1M2Q0N2I5ZGNkZjdlZTc5ZmU3ZWI0Y2E3OTMzYzkxMGY2N2Y4NTIxNDEyZjdhMmY1YzIxIl19fQ==",
|
||||
"repoDigests": [],
|
||||
"scope": "Squashed"
|
||||
}
|
||||
@@ -12,9 +12,14 @@ import (
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
//// NewJSONDocument creates and populates a new JSON document struct from the given cataloging results.
|
||||
//func NewJSONDocument(catalog *pkg.Catalog, srcMetadata source.Metadata, d *distro.Distro, scope source.Scope, configuration interface{}) (Document, error) {
|
||||
//
|
||||
//}
|
||||
|
||||
// TODO: this is export4ed for the use of the power-user command (temp)
|
||||
func ToFormatModel(catalog *pkg.Catalog, srcMetadata *source.Metadata, d *distro.Distro, scope source.Scope, applicationConfig interface{}) model.Document {
|
||||
src, err := toSourceModel(srcMetadata, scope)
|
||||
func ToFormatModel(catalog *pkg.Catalog, srcMetadata *source.Metadata, d *distro.Distro, applicationConfig interface{}) model.Document {
|
||||
src, err := toSourceModel(srcMetadata)
|
||||
if err != nil {
|
||||
log.Warnf("unable to create syft-json source object: %+v", err)
|
||||
}
|
||||
@@ -99,15 +104,12 @@ func toRelationshipModel(relationships []pkg.Relationship) []model.Relationship
|
||||
}
|
||||
|
||||
// toSourceModel creates a new source object to be represented into JSON.
|
||||
func toSourceModel(src *source.Metadata, scope source.Scope) (model.Source, error) {
|
||||
func toSourceModel(src *source.Metadata) (model.Source, error) {
|
||||
switch src.Scheme {
|
||||
case source.ImageScheme:
|
||||
return model.Source{
|
||||
Type: "image",
|
||||
Target: model.ImageSource{
|
||||
ImageMetadata: src.ImageMetadata,
|
||||
Scope: scope,
|
||||
},
|
||||
Type: "image",
|
||||
Target: src.ImageMetadata,
|
||||
}, nil
|
||||
case source.DirectoryScheme:
|
||||
return model.Source{
|
||||
|
||||
@@ -8,32 +8,29 @@ import (
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func toSyftModel(doc model.Document) (*pkg.Catalog, *source.Metadata, *distro.Distro, source.Scope, error) {
|
||||
func toSyftModel(doc model.Document) (*pkg.Catalog, *source.Metadata, *distro.Distro, error) {
|
||||
dist, err := distro.NewDistro(distro.Type(doc.Distro.Name), doc.Distro.Version, doc.Distro.IDLike)
|
||||
if err != nil {
|
||||
return nil, nil, nil, source.UnknownScope, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
srcMetadata, scope := toSyftSourceData(doc.Source)
|
||||
|
||||
return toSyftCatalog(doc.Artifacts), srcMetadata, &dist, scope, nil
|
||||
return toSyftCatalog(doc.Artifacts), toSyftSourceMetadata(doc.Source), &dist, nil
|
||||
}
|
||||
|
||||
func toSyftSourceData(s model.Source) (*source.Metadata, source.Scope) {
|
||||
func toSyftSourceMetadata(s model.Source) *source.Metadata {
|
||||
switch s.Type {
|
||||
case "directory":
|
||||
return &source.Metadata{
|
||||
Scheme: source.DirectoryScheme,
|
||||
Path: s.Target.(string),
|
||||
}, source.UnknownScope
|
||||
}
|
||||
case "image":
|
||||
parsedSource := s.Target.(model.ImageSource)
|
||||
return &source.Metadata{
|
||||
Scheme: source.ImageScheme,
|
||||
ImageMetadata: parsedSource.ImageMetadata,
|
||||
}, parsedSource.Scope
|
||||
ImageMetadata: s.Target.(source.ImageMetadata),
|
||||
}
|
||||
}
|
||||
return nil, source.UnknownScope
|
||||
return nil
|
||||
}
|
||||
|
||||
func toSyftCatalog(pkgs []model.Package) *pkg.Catalog {
|
||||
|
||||
@@ -22,7 +22,7 @@ func validator(reader io.Reader) error {
|
||||
return fmt.Errorf("unable to decode: %w", err)
|
||||
}
|
||||
|
||||
// note: we accept all schema versions
|
||||
// note: we accept al schema versions
|
||||
// TODO: add per-schema version parsing
|
||||
if strings.Contains(doc.Schema.URL, "anchore/syft") {
|
||||
return nil
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,16 +0,0 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
// IsPipedInput returns true if there is no input device, which means the user **may** be providing input via a pipe.
|
||||
func IsPipedInput() (bool, error) {
|
||||
fi, err := os.Stdin.Stat()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("unable to determine if there is piped input: %w", err)
|
||||
}
|
||||
|
||||
return fi.Mode()&os.ModeCharDevice == 0, nil
|
||||
}
|
||||
@@ -1,207 +0,0 @@
|
||||
package packages
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/internal/presenter/packages/model/spdx22"
|
||||
"github.com/anchore/syft/internal/spdxlicense"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
func getSPDXExternalRefs(p *pkg.Package) (externalRefs []spdx22.ExternalRef) {
|
||||
externalRefs = make([]spdx22.ExternalRef, 0)
|
||||
for _, c := range p.CPEs {
|
||||
externalRefs = append(externalRefs, spdx22.ExternalRef{
|
||||
ReferenceCategory: spdx22.SecurityReferenceCategory,
|
||||
ReferenceLocator: c.BindToFmtString(),
|
||||
ReferenceType: spdx22.Cpe23ExternalRefType,
|
||||
})
|
||||
}
|
||||
|
||||
if p.PURL != "" {
|
||||
externalRefs = append(externalRefs, spdx22.ExternalRef{
|
||||
ReferenceCategory: spdx22.PackageManagerReferenceCategory,
|
||||
ReferenceLocator: p.PURL,
|
||||
ReferenceType: spdx22.PurlExternalRefType,
|
||||
})
|
||||
}
|
||||
return externalRefs
|
||||
}
|
||||
|
||||
func getSPDXFiles(packageSpdxID string, p *pkg.Package) (files []spdx22.File, fileIDs []string, relationships []spdx22.Relationship) {
|
||||
files = make([]spdx22.File, 0)
|
||||
fileIDs = make([]string, 0)
|
||||
relationships = make([]spdx22.Relationship, 0)
|
||||
|
||||
pkgFileOwner, ok := p.Metadata.(pkg.FileOwner)
|
||||
if !ok {
|
||||
return files, fileIDs, relationships
|
||||
}
|
||||
|
||||
for _, ownedFilePath := range pkgFileOwner.OwnedFiles() {
|
||||
baseFileName := filepath.Base(ownedFilePath)
|
||||
pathHash := sha256.Sum256([]byte(ownedFilePath))
|
||||
fileSpdxID := spdx22.ElementID(fmt.Sprintf("File-%s-%x", p.Name, pathHash)).String()
|
||||
|
||||
fileIDs = append(fileIDs, fileSpdxID)
|
||||
|
||||
files = append(files, spdx22.File{
|
||||
FileName: ownedFilePath,
|
||||
Item: spdx22.Item{
|
||||
Element: spdx22.Element{
|
||||
SPDXID: fileSpdxID,
|
||||
Name: baseFileName,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
relationships = append(relationships, spdx22.Relationship{
|
||||
SpdxElementID: packageSpdxID,
|
||||
RelationshipType: spdx22.ContainsRelationship,
|
||||
RelatedSpdxElement: fileSpdxID,
|
||||
})
|
||||
}
|
||||
|
||||
return files, fileIDs, relationships
|
||||
}
|
||||
|
||||
func getSPDXLicense(p *pkg.Package) string {
|
||||
// source: https://spdx.github.io/spdx-spec/3-package-information/#313-concluded-license
|
||||
// The options to populate this field are limited to:
|
||||
// A valid SPDX License Expression as defined in Appendix IV;
|
||||
// NONE, if the SPDX file creator concludes there is no license available for this package; or
|
||||
// NOASSERTION if:
|
||||
// (i) the SPDX file creator has attempted to but cannot reach a reasonable objective determination;
|
||||
// (ii) the SPDX file creator has made no attempt to determine this field; or
|
||||
// (iii) the SPDX file creator has intentionally provided no information (no meaning should be implied by doing so).
|
||||
|
||||
if len(p.Licenses) == 0 {
|
||||
return "NONE"
|
||||
}
|
||||
|
||||
// take all licenses and assume an AND expression; for information about license expressions see https://spdx.github.io/spdx-spec/appendix-IV-SPDX-license-expressions/
|
||||
var parsedLicenses []string
|
||||
for _, l := range p.Licenses {
|
||||
if value, exists := spdxlicense.ID(l); exists {
|
||||
parsedLicenses = append(parsedLicenses, value)
|
||||
}
|
||||
}
|
||||
|
||||
if len(parsedLicenses) == 0 {
|
||||
return "NOASSERTION"
|
||||
}
|
||||
|
||||
return strings.Join(parsedLicenses, " AND ")
|
||||
}
|
||||
|
||||
func noneIfEmpty(value string) string {
|
||||
if strings.TrimSpace(value) == "" {
|
||||
return "NONE"
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func getSPDXDownloadLocation(p *pkg.Package) string {
|
||||
// 3.7: Package Download Location
|
||||
// Cardinality: mandatory, one
|
||||
// NONE if there is no download location whatsoever.
|
||||
// NOASSERTION if:
|
||||
// (i) the SPDX file creator has attempted to but cannot reach a reasonable objective determination;
|
||||
// (ii) the SPDX file creator has made no attempt to determine this field; or
|
||||
// (iii) the SPDX file creator has intentionally provided no information (no meaning should be implied by doing so).
|
||||
|
||||
switch metadata := p.Metadata.(type) {
|
||||
case pkg.ApkMetadata:
|
||||
return noneIfEmpty(metadata.URL)
|
||||
case pkg.NpmPackageJSONMetadata:
|
||||
return noneIfEmpty(metadata.URL)
|
||||
default:
|
||||
return "NOASSERTION"
|
||||
}
|
||||
}
|
||||
|
||||
func getSPDXHomepage(p *pkg.Package) string {
|
||||
switch metadata := p.Metadata.(type) {
|
||||
case pkg.GemMetadata:
|
||||
return metadata.Homepage
|
||||
case pkg.NpmPackageJSONMetadata:
|
||||
return metadata.Homepage
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func getSPDXSourceInfo(p *pkg.Package) string {
|
||||
answer := ""
|
||||
switch p.Type {
|
||||
case pkg.RpmPkg:
|
||||
answer = "acquired package info from RPM DB"
|
||||
case pkg.ApkPkg:
|
||||
answer = "acquired package info from APK DB"
|
||||
case pkg.DebPkg:
|
||||
answer = "acquired package info from DPKG DB"
|
||||
case pkg.NpmPkg:
|
||||
answer = "acquired package info from installed node module manifest file"
|
||||
case pkg.PythonPkg:
|
||||
answer = "acquired package info from installed python package manifest file"
|
||||
case pkg.JavaPkg, pkg.JenkinsPluginPkg:
|
||||
answer = "acquired package info from installed java archive"
|
||||
case pkg.GemPkg:
|
||||
answer = "acquired package info from installed gem metadata file"
|
||||
case pkg.GoModulePkg:
|
||||
answer = "acquired package info from go module information"
|
||||
case pkg.RustPkg:
|
||||
answer = "acquired package info from rust cargo manifest"
|
||||
default:
|
||||
answer = "acquired package info from the following paths"
|
||||
}
|
||||
var paths []string
|
||||
for _, l := range p.Locations {
|
||||
paths = append(paths, l.RealPath)
|
||||
}
|
||||
|
||||
return answer + ": " + strings.Join(paths, ", ")
|
||||
}
|
||||
|
||||
func getSPDXOriginator(p *pkg.Package) string {
|
||||
switch metadata := p.Metadata.(type) {
|
||||
case pkg.ApkMetadata:
|
||||
return metadata.Maintainer
|
||||
case pkg.NpmPackageJSONMetadata:
|
||||
return metadata.Author
|
||||
case pkg.PythonPackageMetadata:
|
||||
author := metadata.Author
|
||||
if author == "" {
|
||||
return metadata.AuthorEmail
|
||||
}
|
||||
if metadata.AuthorEmail != "" {
|
||||
author += fmt.Sprintf(" <%s>", metadata.AuthorEmail)
|
||||
}
|
||||
return author
|
||||
case pkg.GemMetadata:
|
||||
if len(metadata.Authors) > 0 {
|
||||
return metadata.Authors[0]
|
||||
}
|
||||
return ""
|
||||
case pkg.RpmdbMetadata:
|
||||
return metadata.Vendor
|
||||
case pkg.DpkgMetadata:
|
||||
return metadata.Maintainer
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func getSPDXDescription(p *pkg.Package) string {
|
||||
switch metadata := p.Metadata.(type) {
|
||||
case pkg.ApkMetadata:
|
||||
return metadata.Description
|
||||
case pkg.NpmPackageJSONMetadata:
|
||||
return metadata.Description
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
@@ -1,527 +0,0 @@
|
||||
package packages
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/syft/source"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/anchore/syft/internal/presenter/packages/model/spdx22"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
func Test_getSPDXExternalRefs(t *testing.T) {
|
||||
testCPE := pkg.MustCPE("cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*")
|
||||
tests := []struct {
|
||||
name string
|
||||
input pkg.Package
|
||||
expected []spdx22.ExternalRef
|
||||
}{
|
||||
{
|
||||
name: "cpe + purl",
|
||||
input: pkg.Package{
|
||||
CPEs: []pkg.CPE{
|
||||
testCPE,
|
||||
},
|
||||
PURL: "a-purl",
|
||||
},
|
||||
expected: []spdx22.ExternalRef{
|
||||
{
|
||||
ReferenceCategory: spdx22.SecurityReferenceCategory,
|
||||
ReferenceLocator: testCPE.BindToFmtString(),
|
||||
ReferenceType: spdx22.Cpe23ExternalRefType,
|
||||
},
|
||||
{
|
||||
ReferenceCategory: spdx22.PackageManagerReferenceCategory,
|
||||
ReferenceLocator: "a-purl",
|
||||
ReferenceType: spdx22.PurlExternalRefType,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
assert.ElementsMatch(t, test.expected, getSPDXExternalRefs(&test.input))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_getSPDXLicense(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input pkg.Package
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "no licenses",
|
||||
input: pkg.Package{},
|
||||
expected: "NONE",
|
||||
},
|
||||
{
|
||||
name: "no SPDX licenses",
|
||||
input: pkg.Package{
|
||||
Licenses: []string{
|
||||
"made-up",
|
||||
},
|
||||
},
|
||||
expected: "NOASSERTION",
|
||||
},
|
||||
{
|
||||
name: "with SPDX license",
|
||||
input: pkg.Package{
|
||||
Licenses: []string{
|
||||
"MIT",
|
||||
},
|
||||
},
|
||||
expected: "MIT",
|
||||
},
|
||||
{
|
||||
name: "with SPDX license expression",
|
||||
input: pkg.Package{
|
||||
Licenses: []string{
|
||||
"MIT",
|
||||
"GPL-3.0",
|
||||
},
|
||||
},
|
||||
expected: "MIT AND GPL-3.0",
|
||||
},
|
||||
{
|
||||
name: "cap insensitive",
|
||||
input: pkg.Package{
|
||||
Licenses: []string{
|
||||
"gpl-3.0",
|
||||
},
|
||||
},
|
||||
expected: "GPL-3.0",
|
||||
},
|
||||
{
|
||||
name: "debian to spdx conversion",
|
||||
input: pkg.Package{
|
||||
Licenses: []string{
|
||||
"GPL-2",
|
||||
},
|
||||
},
|
||||
expected: "GPL-2.0",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
assert.Equal(t, test.expected, getSPDXLicense(&test.input))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_noneIfEmpty(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
value string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "non-zero value",
|
||||
value: "something",
|
||||
expected: "something",
|
||||
},
|
||||
{
|
||||
name: "empty",
|
||||
value: "",
|
||||
expected: "NONE",
|
||||
},
|
||||
{
|
||||
name: "space",
|
||||
value: " ",
|
||||
expected: "NONE",
|
||||
},
|
||||
{
|
||||
name: "tab",
|
||||
value: "\t",
|
||||
expected: "NONE",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
assert.Equal(t, test.expected, noneIfEmpty(test.value))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_getSPDXDownloadLocation(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input pkg.Package
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "no metadata",
|
||||
input: pkg.Package{},
|
||||
expected: "NOASSERTION",
|
||||
},
|
||||
{
|
||||
name: "from apk",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.ApkMetadata{
|
||||
URL: "http://a-place.gov",
|
||||
},
|
||||
},
|
||||
expected: "http://a-place.gov",
|
||||
},
|
||||
{
|
||||
name: "from npm",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
URL: "http://a-place.gov",
|
||||
},
|
||||
},
|
||||
expected: "http://a-place.gov",
|
||||
},
|
||||
{
|
||||
name: "empty",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
URL: "",
|
||||
},
|
||||
},
|
||||
expected: "NONE",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
assert.Equal(t, test.expected, getSPDXDownloadLocation(&test.input))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_getSPDXHomepage(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input pkg.Package
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
// note: since this is an optional field, no value is preferred over NONE or NOASSERTION
|
||||
name: "no metadata",
|
||||
input: pkg.Package{},
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "from gem",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.GemMetadata{
|
||||
Homepage: "http://a-place.gov",
|
||||
},
|
||||
},
|
||||
expected: "http://a-place.gov",
|
||||
},
|
||||
{
|
||||
name: "from npm",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Homepage: "http://a-place.gov",
|
||||
},
|
||||
},
|
||||
expected: "http://a-place.gov",
|
||||
},
|
||||
{
|
||||
// note: since this is an optional field, no value is preferred over NONE or NOASSERTION
|
||||
name: "empty",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Homepage: "",
|
||||
},
|
||||
},
|
||||
expected: "",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
assert.Equal(t, test.expected, getSPDXHomepage(&test.input))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_getSPDXSourceInfo(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input pkg.Package
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "locations are captured",
|
||||
input: pkg.Package{
|
||||
// note: no type given
|
||||
Locations: []source.Location{
|
||||
{
|
||||
RealPath: "/a-place",
|
||||
VirtualPath: "/b-place",
|
||||
},
|
||||
{
|
||||
RealPath: "/c-place",
|
||||
VirtualPath: "/d-place",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []string{
|
||||
"from the following paths",
|
||||
"/a-place",
|
||||
"/c-place",
|
||||
},
|
||||
},
|
||||
{
|
||||
// note: no specific support for this
|
||||
input: pkg.Package{
|
||||
Type: pkg.KbPkg,
|
||||
},
|
||||
expected: []string{
|
||||
"from the following paths",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: pkg.Package{
|
||||
Type: pkg.RpmPkg,
|
||||
},
|
||||
expected: []string{
|
||||
"from RPM DB",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: pkg.Package{
|
||||
Type: pkg.ApkPkg,
|
||||
},
|
||||
expected: []string{
|
||||
"from APK DB",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: pkg.Package{
|
||||
Type: pkg.DebPkg,
|
||||
},
|
||||
expected: []string{
|
||||
"from DPKG DB",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: pkg.Package{
|
||||
Type: pkg.NpmPkg,
|
||||
},
|
||||
expected: []string{
|
||||
"from installed node module manifest file",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: pkg.Package{
|
||||
Type: pkg.PythonPkg,
|
||||
},
|
||||
expected: []string{
|
||||
"from installed python package manifest file",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: pkg.Package{
|
||||
Type: pkg.JavaPkg,
|
||||
},
|
||||
expected: []string{
|
||||
"from installed java archive",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: pkg.Package{
|
||||
Type: pkg.JenkinsPluginPkg,
|
||||
},
|
||||
expected: []string{
|
||||
"from installed java archive",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: pkg.Package{
|
||||
Type: pkg.GemPkg,
|
||||
},
|
||||
expected: []string{
|
||||
"from installed gem metadata file",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: pkg.Package{
|
||||
Type: pkg.GoModulePkg,
|
||||
},
|
||||
expected: []string{
|
||||
"from go module information",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: pkg.Package{
|
||||
Type: pkg.RustPkg,
|
||||
},
|
||||
expected: []string{
|
||||
"from rust cargo manifest",
|
||||
},
|
||||
},
|
||||
}
|
||||
var pkgTypes []pkg.Type
|
||||
for _, test := range tests {
|
||||
t.Run(test.name+" "+string(test.input.Type), func(t *testing.T) {
|
||||
if test.input.Type != "" {
|
||||
pkgTypes = append(pkgTypes, test.input.Type)
|
||||
}
|
||||
actual := getSPDXSourceInfo(&test.input)
|
||||
for _, expected := range test.expected {
|
||||
assert.Contains(t, actual, expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
assert.ElementsMatch(t, pkg.AllPkgs, pkgTypes, "missing one or more package types to test against (maybe a package type was added?)")
|
||||
}
|
||||
|
||||
func Test_getSPDXOriginator(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input pkg.Package
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
// note: since this is an optional field, no value is preferred over NONE or NOASSERTION
|
||||
name: "no metadata",
|
||||
input: pkg.Package{},
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "from gem",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.GemMetadata{
|
||||
Authors: []string{
|
||||
"auth1",
|
||||
"auth2",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: "auth1",
|
||||
},
|
||||
{
|
||||
name: "from npm",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Author: "auth",
|
||||
},
|
||||
},
|
||||
expected: "auth",
|
||||
},
|
||||
{
|
||||
name: "from apk",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.ApkMetadata{
|
||||
Maintainer: "auth",
|
||||
},
|
||||
},
|
||||
expected: "auth",
|
||||
},
|
||||
{
|
||||
name: "from python - just name",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.PythonPackageMetadata{
|
||||
Author: "auth",
|
||||
},
|
||||
},
|
||||
expected: "auth",
|
||||
},
|
||||
{
|
||||
name: "from python - just email",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.PythonPackageMetadata{
|
||||
AuthorEmail: "auth@auth.gov",
|
||||
},
|
||||
},
|
||||
expected: "auth@auth.gov",
|
||||
},
|
||||
{
|
||||
name: "from python - both name and email",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.PythonPackageMetadata{
|
||||
Author: "auth",
|
||||
AuthorEmail: "auth@auth.gov",
|
||||
},
|
||||
},
|
||||
expected: "auth <auth@auth.gov>",
|
||||
},
|
||||
{
|
||||
name: "from rpm",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.RpmdbMetadata{
|
||||
Vendor: "auth",
|
||||
},
|
||||
},
|
||||
expected: "auth",
|
||||
},
|
||||
{
|
||||
name: "from dpkg",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.DpkgMetadata{
|
||||
Maintainer: "auth",
|
||||
},
|
||||
},
|
||||
expected: "auth",
|
||||
},
|
||||
{
|
||||
// note: since this is an optional field, no value is preferred over NONE or NOASSERTION
|
||||
name: "empty",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Author: "",
|
||||
},
|
||||
},
|
||||
expected: "",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
assert.Equal(t, test.expected, getSPDXOriginator(&test.input))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_getSPDXDescription(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input pkg.Package
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
// note: since this is an optional field, no value is preferred over NONE or NOASSERTION
|
||||
name: "no metadata",
|
||||
input: pkg.Package{},
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "from apk",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.ApkMetadata{
|
||||
Description: "a description!",
|
||||
},
|
||||
},
|
||||
expected: "a description!",
|
||||
},
|
||||
{
|
||||
name: "from npm",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Description: "a description!",
|
||||
},
|
||||
},
|
||||
expected: "a description!",
|
||||
},
|
||||
{
|
||||
// note: since this is an optional field, no value is preferred over NONE or NOASSERTION
|
||||
name: "empty",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Homepage: "",
|
||||
},
|
||||
},
|
||||
expected: "",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
assert.Equal(t, test.expected, getSPDXDescription(&test.input))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
package packages
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/internal/presenter/packages/model/spdx22"
|
||||
"github.com/anchore/syft/internal/spdxlicense"
|
||||
"github.com/anchore/syft/internal/version"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const anchoreNamespace = "https://anchore.com/syft"
|
||||
|
||||
// SPDXJsonPresenter is a SPDX presentation object for the syft results (see https://github.com/spdx/spdx-spec)
|
||||
type SPDXJsonPresenter struct {
|
||||
catalog *pkg.Catalog
|
||||
srcMetadata source.Metadata
|
||||
}
|
||||
|
||||
// NewSPDXJSONPresenter creates a new JSON presenter object for the given cataloging results.
|
||||
func NewSPDXJSONPresenter(catalog *pkg.Catalog, srcMetadata source.Metadata) *SPDXJsonPresenter {
|
||||
return &SPDXJsonPresenter{
|
||||
catalog: catalog,
|
||||
srcMetadata: srcMetadata,
|
||||
}
|
||||
}
|
||||
|
||||
// Present the catalog results to the given writer.
|
||||
func (pres *SPDXJsonPresenter) Present(output io.Writer) error {
|
||||
doc := newSPDXJsonDocument(pres.catalog, pres.srcMetadata)
|
||||
|
||||
enc := json.NewEncoder(output)
|
||||
// prevent > and < from being escaped in the payload
|
||||
enc.SetEscapeHTML(false)
|
||||
enc.SetIndent("", " ")
|
||||
return enc.Encode(&doc)
|
||||
}
|
||||
|
||||
// newSPDXJsonDocument creates and populates a new JSON document struct that follows the SPDX 2.2 spec from the given cataloging results.
|
||||
func newSPDXJsonDocument(catalog *pkg.Catalog, srcMetadata source.Metadata) spdx22.Document {
|
||||
uniqueID := uuid.Must(uuid.NewRandom())
|
||||
|
||||
var name, input, identifier string
|
||||
switch srcMetadata.Scheme {
|
||||
case source.ImageScheme:
|
||||
name = cleanSPDXName(srcMetadata.ImageMetadata.UserInput)
|
||||
input = "image"
|
||||
case source.DirectoryScheme:
|
||||
name = cleanSPDXName(srcMetadata.Path)
|
||||
input = "dir"
|
||||
}
|
||||
|
||||
if name != "." {
|
||||
identifier = path.Join(input, fmt.Sprintf("%s-%s", name, uniqueID.String()))
|
||||
} else {
|
||||
identifier = path.Join(input, uniqueID.String())
|
||||
}
|
||||
|
||||
namespace := path.Join(anchoreNamespace, identifier)
|
||||
packages, files, relationships := newSPDXJsonElements(catalog)
|
||||
|
||||
return spdx22.Document{
|
||||
Element: spdx22.Element{
|
||||
SPDXID: spdx22.ElementID("DOCUMENT").String(),
|
||||
Name: name,
|
||||
},
|
||||
SPDXVersion: spdx22.Version,
|
||||
CreationInfo: spdx22.CreationInfo{
|
||||
Created: time.Now().UTC(),
|
||||
Creators: []string{
|
||||
// note: key-value format derived from the JSON example document examples: https://github.com/spdx/spdx-spec/blob/v2.2/examples/SPDXJSONExample-v2.2.spdx.json
|
||||
"Organization: Anchore, Inc",
|
||||
"Tool: " + internal.ApplicationName + "-" + version.FromBuild().Version,
|
||||
},
|
||||
LicenseListVersion: spdxlicense.Version,
|
||||
},
|
||||
DataLicense: "CC0-1.0",
|
||||
DocumentNamespace: namespace,
|
||||
Packages: packages,
|
||||
Files: files,
|
||||
Relationships: relationships,
|
||||
}
|
||||
}
|
||||
|
||||
func newSPDXJsonElements(catalog *pkg.Catalog) ([]spdx22.Package, []spdx22.File, []spdx22.Relationship) {
|
||||
packages := make([]spdx22.Package, 0)
|
||||
relationships := make([]spdx22.Relationship, 0)
|
||||
files := make([]spdx22.File, 0)
|
||||
|
||||
for _, p := range catalog.Sorted() {
|
||||
license := getSPDXLicense(p)
|
||||
packageSpdxID := spdx22.ElementID(fmt.Sprintf("Package-%+v-%s-%s", p.Type, p.Name, p.Version)).String()
|
||||
|
||||
packageFiles, fileIDs, packageFileRelationships := getSPDXFiles(packageSpdxID, p)
|
||||
files = append(files, packageFiles...)
|
||||
|
||||
relationships = append(relationships, packageFileRelationships...)
|
||||
|
||||
// note: the license concluded and declared should be the same since we are collecting license information
|
||||
// from the project data itself (the installed package files).
|
||||
packages = append(packages, spdx22.Package{
|
||||
Description: getSPDXDescription(p),
|
||||
DownloadLocation: getSPDXDownloadLocation(p),
|
||||
ExternalRefs: getSPDXExternalRefs(p),
|
||||
FilesAnalyzed: false,
|
||||
HasFiles: fileIDs,
|
||||
Homepage: getSPDXHomepage(p),
|
||||
LicenseDeclared: license, // The Declared License is what the authors of a project believe govern the package
|
||||
Originator: getSPDXOriginator(p),
|
||||
SourceInfo: getSPDXSourceInfo(p),
|
||||
VersionInfo: p.Version,
|
||||
Item: spdx22.Item{
|
||||
LicenseConcluded: license, // The Concluded License field is the license the SPDX file creator believes governs the package
|
||||
Element: spdx22.Element{
|
||||
SPDXID: packageSpdxID,
|
||||
Name: p.Name,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return packages, files, relationships
|
||||
}
|
||||
|
||||
func cleanSPDXName(name string) string {
|
||||
// remove # according to specification
|
||||
name = strings.ReplaceAll(name, "#", "-")
|
||||
|
||||
// remove : for url construction
|
||||
name = strings.ReplaceAll(name, ":", "-")
|
||||
|
||||
// clean relative pathing
|
||||
return path.Clean(name)
|
||||
}
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/anchore/syft/internal/formats/common/spdxhelpers"
|
||||
|
||||
"github.com/anchore/syft/internal/spdxlicense"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
@@ -114,7 +116,7 @@ func (pres *SPDXTagValuePresenter) packages() map[spdx.ElementID]*spdx.Package2_
|
||||
// If the Concluded License is not the same as the Declared License, a written explanation should be provided
|
||||
// in the Comments on License field (section 3.16). With respect to NOASSERTION, a written explanation in
|
||||
// the Comments on License field (section 3.16) is preferred.
|
||||
license := getSPDXLicense(p)
|
||||
license := spdxhelpers.License(p)
|
||||
|
||||
results[spdx.ElementID(id)] = &spdx.Package2_2{
|
||||
|
||||
@@ -274,7 +276,7 @@ func (pres *SPDXTagValuePresenter) packages() map[spdx.ElementID]*spdx.Package2_
|
||||
}
|
||||
|
||||
func formatSPDXExternalRefs(p *pkg.Package) (refs []*spdx.PackageExternalReference2_2) {
|
||||
for _, ref := range getSPDXExternalRefs(p) {
|
||||
for _, ref := range spdxhelpers.ExternalRefs(p) {
|
||||
refs = append(refs, &spdx.PackageExternalReference2_2{
|
||||
Category: string(ref.ReferenceCategory),
|
||||
RefType: string(ref.ReferenceType),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package table
|
||||
package packages
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -8,16 +8,24 @@ import (
|
||||
|
||||
"github.com/olekukonko/tablewriter"
|
||||
|
||||
"github.com/anchore/syft/syft/distro"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func encoder(output io.Writer, catalog *pkg.Catalog, _ *source.Metadata, _ *distro.Distro, _ source.Scope) error {
|
||||
var rows [][]string
|
||||
type TablePresenter struct {
|
||||
catalog *pkg.Catalog
|
||||
}
|
||||
|
||||
func NewTablePresenter(catalog *pkg.Catalog) *TablePresenter {
|
||||
return &TablePresenter{
|
||||
catalog: catalog,
|
||||
}
|
||||
}
|
||||
|
||||
func (pres *TablePresenter) Present(output io.Writer) error {
|
||||
rows := make([][]string, 0)
|
||||
|
||||
columns := []string{"Name", "Version", "Type"}
|
||||
for _, p := range catalog.Sorted() {
|
||||
for _, p := range pres.catalog.Sorted() {
|
||||
row := []string{
|
||||
p.Name,
|
||||
p.Version,
|
||||
@@ -27,8 +35,8 @@ func encoder(output io.Writer, catalog *pkg.Catalog, _ *source.Metadata, _ *dist
|
||||
}
|
||||
|
||||
if len(rows) == 0 {
|
||||
_, err := fmt.Fprintln(output, "No packages discovered")
|
||||
return err
|
||||
fmt.Fprintln(output, "No packages discovered")
|
||||
return nil
|
||||
}
|
||||
|
||||
// sort by name, version, then type
|
||||
+9
-8
@@ -1,22 +1,23 @@
|
||||
package table
|
||||
package packages
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/internal/formats/common/testutils"
|
||||
"github.com/anchore/syft/syft/format"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
|
||||
"github.com/go-test/deep"
|
||||
)
|
||||
|
||||
var updateTableGoldenFiles = flag.Bool("update-table", false, "update the *.golden files for table format")
|
||||
var updateTablePresenterGoldenFiles = flag.Bool("update-table", false, "update the *.golden files for table presenters")
|
||||
|
||||
func TestTablePresenter(t *testing.T) {
|
||||
catalog, metadata, distro := testutils.DirectoryInput(t)
|
||||
testutils.AssertPresenterAgainstGoldenSnapshot(t,
|
||||
format.NewPresenter(encoder, catalog, &metadata, distro, source.SquashedScope),
|
||||
*updateTableGoldenFiles,
|
||||
testImage := "image-simple"
|
||||
catalog, _, _ := testutils.ImageInput(t, testImage)
|
||||
testutils.AssertPresenterAgainstGoldenImageSnapshot(t,
|
||||
NewTablePresenter(catalog),
|
||||
testImage,
|
||||
*updateTablePresenterGoldenFiles,
|
||||
)
|
||||
}
|
||||
|
||||
-79
@@ -1,79 +0,0 @@
|
||||
{
|
||||
"SPDXID": "SPDXRef-DOCUMENT",
|
||||
"name": "/some/path",
|
||||
"spdxVersion": "SPDX-2.2",
|
||||
"creationInfo": {
|
||||
"created": "2021-10-12T18:40:22.948394Z",
|
||||
"creators": [
|
||||
"Organization: Anchore, Inc",
|
||||
"Tool: syft-[not provided]"
|
||||
],
|
||||
"licenseListVersion": "3.14"
|
||||
},
|
||||
"dataLicense": "CC0-1.0",
|
||||
"documentNamespace": "https:/anchore.com/syft/dir/some/path-98ae71fb-f276-4c5c-acf7-25770bf7bca2",
|
||||
"packages": [
|
||||
{
|
||||
"SPDXID": "SPDXRef-Package-python-package-1-1.0.1",
|
||||
"name": "package-1",
|
||||
"licenseConcluded": "MIT",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
"externalRefs": [
|
||||
{
|
||||
"referenceCategory": "SECURITY",
|
||||
"referenceLocator": "cpe:2.3:*:some:package:2:*:*:*:*:*:*:*",
|
||||
"referenceType": "cpe23Type"
|
||||
},
|
||||
{
|
||||
"referenceCategory": "PACKAGE_MANAGER",
|
||||
"referenceLocator": "a-purl-2",
|
||||
"referenceType": "purl"
|
||||
}
|
||||
],
|
||||
"filesAnalyzed": false,
|
||||
"hasFiles": [
|
||||
"SPDXRef-File-package-1-efae7fecc76ca25da40f79d7ef5b8933510434914835832c7976f3e866aa756a"
|
||||
],
|
||||
"licenseDeclared": "MIT",
|
||||
"sourceInfo": "acquired package info from installed python package manifest file: /some/path/pkg1",
|
||||
"versionInfo": "1.0.1"
|
||||
},
|
||||
{
|
||||
"SPDXID": "SPDXRef-Package-deb-package-2-2.0.1",
|
||||
"name": "package-2",
|
||||
"licenseConcluded": "NONE",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
"externalRefs": [
|
||||
{
|
||||
"referenceCategory": "SECURITY",
|
||||
"referenceLocator": "cpe:2.3:*:some:package:2:*:*:*:*:*:*:*",
|
||||
"referenceType": "cpe23Type"
|
||||
},
|
||||
{
|
||||
"referenceCategory": "PACKAGE_MANAGER",
|
||||
"referenceLocator": "a-purl-2",
|
||||
"referenceType": "purl"
|
||||
}
|
||||
],
|
||||
"filesAnalyzed": false,
|
||||
"licenseDeclared": "NONE",
|
||||
"sourceInfo": "acquired package info from DPKG DB: /some/path/pkg1",
|
||||
"versionInfo": "2.0.1"
|
||||
}
|
||||
],
|
||||
"files": [
|
||||
{
|
||||
"SPDXID": "SPDXRef-File-package-1-efae7fecc76ca25da40f79d7ef5b8933510434914835832c7976f3e866aa756a",
|
||||
"name": "foo",
|
||||
"licenseConcluded": "",
|
||||
"fileName": "/some/path/pkg1/dependencies/foo"
|
||||
}
|
||||
],
|
||||
"relationships": [
|
||||
{
|
||||
"spdxElementId": "SPDXRef-Package-python-package-1-1.0.1",
|
||||
"relationshipType": "CONTAINS",
|
||||
"relatedSpdxElement": "SPDXRef-File-package-1-efae7fecc76ca25da40f79d7ef5b8933510434914835832c7976f3e866aa756a"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
{
|
||||
"SPDXID": "SPDXRef-DOCUMENT",
|
||||
"name": "user-image-input",
|
||||
"spdxVersion": "SPDX-2.2",
|
||||
"creationInfo": {
|
||||
"created": "2021-10-12T18:40:22.953633Z",
|
||||
"creators": [
|
||||
"Organization: Anchore, Inc",
|
||||
"Tool: syft-[not provided]"
|
||||
],
|
||||
"licenseListVersion": "3.14"
|
||||
},
|
||||
"dataLicense": "CC0-1.0",
|
||||
"documentNamespace": "https:/anchore.com/syft/image/user-image-input-149edbad-3c01-4ee0-b3a0-75232312bf51",
|
||||
"packages": [
|
||||
{
|
||||
"SPDXID": "SPDXRef-Package-python-package-1-1.0.1",
|
||||
"name": "package-1",
|
||||
"licenseConcluded": "MIT",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
"externalRefs": [
|
||||
{
|
||||
"referenceCategory": "SECURITY",
|
||||
"referenceLocator": "cpe:2.3:*:some:package:1:*:*:*:*:*:*:*",
|
||||
"referenceType": "cpe23Type"
|
||||
},
|
||||
{
|
||||
"referenceCategory": "PACKAGE_MANAGER",
|
||||
"referenceLocator": "a-purl-1",
|
||||
"referenceType": "purl"
|
||||
}
|
||||
],
|
||||
"filesAnalyzed": false,
|
||||
"licenseDeclared": "MIT",
|
||||
"sourceInfo": "acquired package info from installed python package manifest file: /somefile-1.txt",
|
||||
"versionInfo": "1.0.1"
|
||||
},
|
||||
{
|
||||
"SPDXID": "SPDXRef-Package-deb-package-2-2.0.1",
|
||||
"name": "package-2",
|
||||
"licenseConcluded": "NONE",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
"externalRefs": [
|
||||
{
|
||||
"referenceCategory": "SECURITY",
|
||||
"referenceLocator": "cpe:2.3:*:some:package:2:*:*:*:*:*:*:*",
|
||||
"referenceType": "cpe23Type"
|
||||
},
|
||||
{
|
||||
"referenceCategory": "PACKAGE_MANAGER",
|
||||
"referenceLocator": "a-purl-2",
|
||||
"referenceType": "purl"
|
||||
}
|
||||
],
|
||||
"filesAnalyzed": false,
|
||||
"licenseDeclared": "NONE",
|
||||
"sourceInfo": "acquired package info from DPKG DB: /somefile-2.txt",
|
||||
"versionInfo": "2.0.1"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
[Image]
|
||||
Layer: 0
|
||||
Digest: sha256:fb6beecb75b39f4bb813dbf177e501edd5ddb3e69bb45cedeb78c676ee1b7a59
|
||||
Digest: sha256:ffb5e9eaa453a002110719d12c294960117ca2903953d1faa40f01dc3f77045c
|
||||
Size: 22
|
||||
MediaType: application/vnd.docker.image.rootfs.diff.tar.gzip
|
||||
|
||||
Layer: 1
|
||||
Digest: sha256:319b588ce64253a87b533c8ed01cf0025e0eac98e7b516e12532957e1244fdec
|
||||
Digest: sha256:8463854829fc53d47b9dcdf7ee79fe7eb4ca7933c910f67f8521412f7a2f5c21
|
||||
Size: 16
|
||||
MediaType: application/vnd.docker.image.rootfs.diff.tar.gzip
|
||||
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -32,7 +32,7 @@ func (pres *TextPresenter) Present(output io.Writer) error {
|
||||
|
||||
switch pres.srcMetadata.Scheme {
|
||||
case source.DirectoryScheme:
|
||||
fmt.Fprintf(w, "[Path: %s]\n", pres.srcMetadata.Path)
|
||||
fmt.Fprintln(w, fmt.Sprintf("[Path: %s]", pres.srcMetadata.Path))
|
||||
case source.ImageScheme:
|
||||
fmt.Fprintln(w, "[Image]")
|
||||
|
||||
@@ -51,7 +51,7 @@ func (pres *TextPresenter) Present(output io.Writer) error {
|
||||
// populate artifacts...
|
||||
rows := 0
|
||||
for _, p := range pres.catalog.Sorted() {
|
||||
fmt.Fprintf(w, "[%s]\n", p.Name)
|
||||
fmt.Fprintln(w, fmt.Sprintf("[%s]", p.Name))
|
||||
fmt.Fprintln(w, " Version:\t", p.Version)
|
||||
fmt.Fprintln(w, " Type:\t", string(p.Type))
|
||||
fmt.Fprintln(w, " Found by:\t", p.FoundBy)
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
)
|
||||
|
||||
type JSONDocument struct {
|
||||
// note: poweruser.JSONDocument is meant to always be a superset of packages.JSONDocument, any additional fields
|
||||
// note: poweruser.JSONDocument is meant to always be a superset of packages.Document, any additional fields
|
||||
// here should be optional by supplying "omitempty" on these fields hint to the jsonschema generator to not
|
||||
// require these fields. As an accepted rule in this repo all collections should still be initialized in the
|
||||
// context of being used in a JSON document.
|
||||
@@ -19,6 +19,8 @@ type JSONDocument struct {
|
||||
|
||||
// NewJSONDocument creates and populates a new JSON document struct from the given cataloging results.
|
||||
func NewJSONDocument(config JSONDocumentConfig) (JSONDocument, error) {
|
||||
pkgsDoc := syftjson.ToFormatModel(config.PackageCatalog, &config.SourceMetadata, config.Distro, config.ApplicationConfig)
|
||||
|
||||
fileMetadata, err := NewJSONFileMetadata(config.FileMetadata, config.FileDigests)
|
||||
if err != nil {
|
||||
return JSONDocument{}, err
|
||||
@@ -29,6 +31,6 @@ func NewJSONDocument(config JSONDocumentConfig) (JSONDocument, error) {
|
||||
FileContents: NewJSONFileContents(config.FileContents),
|
||||
FileMetadata: fileMetadata,
|
||||
Secrets: NewJSONSecrets(config.Secrets),
|
||||
Document: syftjson.ToFormatModel(config.PackageCatalog, &config.SourceMetadata, config.Distro, config.ApplicationConfig.Package.Cataloger.ScopeOpt, config.ApplicationConfig),
|
||||
Document: pkgsDoc,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -50,21 +49,14 @@ type LicenseList struct {
|
||||
}
|
||||
|
||||
func main() {
|
||||
if err := run(); err != nil {
|
||||
fmt.Println(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func run() error {
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to get licenses list: %+v", err)
|
||||
log.Fatalf("unable to get licenses list: %+v", err)
|
||||
}
|
||||
|
||||
var result LicenseList
|
||||
if err = json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
return fmt.Errorf("unable to decode license list: %+v", err)
|
||||
log.Fatalf("unable to decode license list: %+v", err)
|
||||
}
|
||||
defer func() {
|
||||
if err := resp.Body.Close(); err != nil {
|
||||
@@ -74,7 +66,7 @@ func run() error {
|
||||
|
||||
f, err := os.Create(source)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create %q: %+v", source, err)
|
||||
log.Fatalf("unable to create %q: %+v", source, err)
|
||||
}
|
||||
defer func() {
|
||||
if err := f.Close(); err != nil {
|
||||
@@ -97,9 +89,8 @@ func run() error {
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to generate template: %+v", err)
|
||||
log.Fatalf("unable to generate template: %+v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Parsing the provided SPDX license list necessitates a two pass approach.
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
syftEventParsers "github.com/anchore/syft/syft/event/parsers"
|
||||
"github.com/wagoodman/go-partybus"
|
||||
)
|
||||
|
||||
// handleCatalogerPresenterReady is a UI function for processing the CatalogerFinished bus event, displaying the catalog
|
||||
// via the given presenter to stdout.
|
||||
func handleCatalogerPresenterReady(event partybus.Event, reportOutput io.Writer) error {
|
||||
// show the report to stdout
|
||||
pres, err := syftEventParsers.ParsePresenterReady(event)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bad CatalogerFinished event: %w", err)
|
||||
}
|
||||
|
||||
if err := pres.Present(reportOutput); err != nil {
|
||||
return fmt.Errorf("unable to show package catalog report: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,6 +1,3 @@
|
||||
//go:build linux || darwin
|
||||
// +build linux darwin
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user