mirror of
https://github.com/anchore/syft.git
synced 2026-10-12 14:17:20 +02:00
Compare commits
275
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab69742d25 | ||
|
|
bf8ded3cf8 | ||
|
|
d53a8d283f | ||
|
|
13c6591cb2 | ||
|
|
4ffde9f891 | ||
|
|
06451adc43 | ||
|
|
ecf6b9eff5 | ||
|
|
d61c2cada3 | ||
|
|
0bbcff1ae3 | ||
|
|
f21e439a63 | ||
|
|
c511d509ef | ||
|
|
fdeb78fc19 | ||
|
|
6ac7afb439 | ||
|
|
b23e461731 | ||
|
|
8c63a2403c | ||
|
|
e4ff7f5973 | ||
|
|
37d45de9ff | ||
|
|
26927eab02 | ||
|
|
1dc16fbac4 | ||
|
|
d0e1e7be05 | ||
|
|
57bd6a5f9c | ||
|
|
c60aabad37 | ||
|
|
c0072873c9 | ||
|
|
236ab25393 | ||
|
|
fa6ea7b10c | ||
|
|
ae7843e17c | ||
|
|
a4d3aedf4e | ||
|
|
50dbb33e66 | ||
|
|
10af8342c0 | ||
|
|
f7f3c6efc3 | ||
|
|
a8d4fb845f | ||
|
|
ff45b9ea63 | ||
|
|
fb55856e33 | ||
|
|
a76b5fa36b | ||
|
|
643bf2c24d | ||
|
|
c5baac0e94 | ||
|
|
19341fd0a2 | ||
|
|
ec8f035584 | ||
|
|
9d39701d50 | ||
|
|
82718d36ca | ||
|
|
cc1bb1f28c | ||
|
|
14e96fcbf8 | ||
|
|
af508550e6 | ||
|
|
409ac78f62 | ||
|
|
41870fec7f | ||
|
|
97fd939355 | ||
|
|
3f4105a657 | ||
|
|
b49f0172f6 | ||
|
|
46fd83817f | ||
|
|
a2ed08dfdd | ||
|
|
34e7b0df98 | ||
|
|
3b41acb0ff | ||
|
|
3325493831 | ||
|
|
02ba369d13 | ||
|
|
74073aa481 | ||
|
|
0b234b3256 | ||
|
|
b4ee6e4696 | ||
|
|
2be26a1af9 | ||
|
|
30e9e65608 | ||
|
|
e6d364437a | ||
|
|
c55e1cf2fd | ||
|
|
356e601cd5 | ||
|
|
ee05a595b6 | ||
|
|
05c6817c07 | ||
|
|
d85c72a08e | ||
|
|
1ca1fa73d3 | ||
|
|
a675c81143 | ||
|
|
875daec82b | ||
|
|
fb59c4e65c | ||
|
|
c4800b2393 | ||
|
|
eb58f698bd | ||
|
|
de98400c39 | ||
|
|
9e520d1442 | ||
|
|
3ec2e33d73 | ||
|
|
40a4ec72b9 | ||
|
|
268897ca82 | ||
|
|
031de8ddab | ||
|
|
1d25dfe4da | ||
|
|
0c049806bd | ||
|
|
9addda3da5 | ||
|
|
767c83d513 | ||
|
|
392539dc28 | ||
|
|
3dd674c53b | ||
|
|
fd7517bbba | ||
|
|
c935b71c9e | ||
|
|
77031752fa | ||
|
|
12d5544b2a | ||
|
|
cc13d1dbf1 | ||
|
|
af937d74c0 | ||
|
|
4017979670 | ||
|
|
82d273fac5 | ||
|
|
1417c1989b | ||
|
|
91a0032987 | ||
|
|
f91bf4528b | ||
|
|
c5fc6998d3 | ||
|
|
d3734ddeb2 | ||
|
|
bf82010f3c | ||
|
|
7ca1f22395 | ||
|
|
93cf89363b | ||
|
|
34ef7dcbfe | ||
|
|
29edf90038 | ||
|
|
766907e6ed | ||
|
|
e820f2d21a | ||
|
|
39c9c690df | ||
|
|
c4597a08a2 | ||
|
|
236755a6b1 | ||
|
|
168eb64235 | ||
|
|
490732aa58 | ||
|
|
fd4796b0d8 | ||
|
|
78f39ee3a5 | ||
|
|
5c6cf08a8c | ||
|
|
8bb3b5eed1 | ||
|
|
21cca30e5b | ||
|
|
ed76e96749 | ||
|
|
ab508169e6 | ||
|
|
360dbc04aa | ||
|
|
58a033f924 | ||
|
|
04a6fa1b41 | ||
|
|
d63c774fa9 | ||
|
|
3eee102f63 | ||
|
|
20cc3df387 | ||
|
|
4782a94029 | ||
|
|
2293641e3b | ||
|
|
9dd9ce0bdd | ||
|
|
949ac70369 | ||
|
|
07fb23487f | ||
|
|
f45586b457 | ||
|
|
0fe98ed1f2 | ||
|
|
5f67883fe8 | ||
|
|
d288dd67ae | ||
|
|
da745b13e8 | ||
|
|
68da404bd7 | ||
|
|
35bf33bcf0 | ||
|
|
7f73d6a603 | ||
|
|
ef40eec38d | ||
|
|
80212d87c2 | ||
|
|
b14123ecc1 | ||
|
|
e00aeb16e4 | ||
|
|
fa6f6d70f0 | ||
|
|
0975d80dc0 | ||
|
|
4d823eaaa7 | ||
|
|
138e42b428 | ||
|
|
ecd8fb7740 | ||
|
|
dd639c09b2 | ||
|
|
31a352d030 | ||
|
|
08e913a2ce | ||
|
|
5ef5b1badc | ||
|
|
b236f8c1f7 | ||
|
|
476ce60768 | ||
|
|
16223e6dd7 | ||
|
|
1286689419 | ||
|
|
295454945c | ||
|
|
86baeeb481 | ||
|
|
2dcf5163b8 | ||
|
|
12b8ba47fb | ||
|
|
138d9ce2a0 | ||
|
|
8a229b16c4 | ||
|
|
9af0098a68 | ||
|
|
2840ea6653 | ||
|
|
b38e40e3e6 | ||
|
|
a6eedecd31 | ||
|
|
691b1357b2 | ||
|
|
c890e7f17f | ||
|
|
29fd7d0dec | ||
|
|
289137ff3c | ||
|
|
22ffd5209d | ||
|
|
a0852ebf98 | ||
|
|
d380ad08b5 | ||
|
|
ae9534203d | ||
|
|
dc1ef98237 | ||
|
|
36a3af41d9 | ||
|
|
3e2bc6ed09 | ||
|
|
5e11775c8b | ||
|
|
8ed534c32b | ||
|
|
aff2c247db | ||
|
|
ed499fd2d0 | ||
|
|
ad828d659f | ||
|
|
2805655ab0 | ||
|
|
552ddbfadb | ||
|
|
f6b5d3e736 | ||
|
|
4312699dd6 | ||
|
|
fabd760914 | ||
|
|
e854078fb0 | ||
|
|
987ae7f26a | ||
|
|
76ede661db | ||
|
|
da77ea388a | ||
|
|
3cc3ecb003 | ||
|
|
7745e450fa | ||
|
|
bd0917fe93 | ||
|
|
1dcac54b0e | ||
|
|
3252b6f806 | ||
|
|
c4a0b6f647 | ||
|
|
5b630ec61c | ||
|
|
e8abab2e77 | ||
|
|
002a326c41 | ||
|
|
3e399256b3 | ||
|
|
ebac92a86e | ||
|
|
aebdf4bff4 | ||
|
|
f1457f2554 | ||
|
|
e4f5df936c | ||
|
|
8c0d21e147 | ||
|
|
6f5c83a558 | ||
|
|
ee48c01c06 | ||
|
|
f6bf15bb3c | ||
|
|
312f2532d0 | ||
|
|
f2c9463e76 | ||
|
|
4eb61fc104 | ||
|
|
832d96c1dd | ||
|
|
5a7a72b53e | ||
|
|
df1fc98e7a | ||
|
|
656a4d46d7 | ||
|
|
148fe572bc | ||
|
|
deee79411a | ||
|
|
e7f1a803e7 | ||
|
|
a34f86fba1 | ||
|
|
e388b5249d | ||
|
|
1746e96ad3 | ||
|
|
956858fc11 | ||
|
|
37fee88b5c | ||
|
|
1143c12a97 | ||
|
|
b15c5dbfe2 | ||
|
|
35d56bfb99 | ||
|
|
abf6d78dfc | ||
|
|
fe42bcec38 | ||
|
|
fea4a50124 | ||
|
|
5eefd73ac7 | ||
|
|
684c7018be | ||
|
|
f827f91ec1 | ||
|
|
e9af7d218c | ||
|
|
506ad5d6a7 | ||
|
|
1f4f9332c5 | ||
|
|
52a4c3b594 | ||
|
|
9c321691d4 | ||
|
|
0e8d6deabe | ||
|
|
deb2fd92ef | ||
|
|
80d3b62de4 | ||
|
|
b71afc87fc | ||
|
|
efe3174b5f | ||
|
|
58e4dbbf01 | ||
|
|
b70fa899cb | ||
|
|
951fbd454a | ||
|
|
92ae4d44c5 | ||
|
|
8d48a8b8c2 | ||
|
|
cff5a05681 | ||
|
|
00ca43d24a | ||
|
|
6a27678036 | ||
|
|
89773c0a12 | ||
|
|
b08d3c2970 | ||
|
|
63232bf725 | ||
|
|
908eb57890 | ||
|
|
c5c423ab37 | ||
|
|
d4496b05aa | ||
|
|
adc55cdb3a | ||
|
|
00d0bb59cc | ||
|
|
f474308783 | ||
|
|
bf67072246 | ||
|
|
9673f867e5 | ||
|
|
a4fb2c0396 | ||
|
|
cf2ce643c3 | ||
|
|
524a44b70d | ||
|
|
4e86715c1a | ||
|
|
e8c6b7151e | ||
|
|
0fb8762f41 | ||
|
|
58ddf74140 | ||
|
|
b5d828ee14 | ||
|
|
1c4394fed0 | ||
|
|
f5c1a0befc | ||
|
|
b1287d45d8 | ||
|
|
d97216ff70 | ||
|
|
c09a009bda | ||
|
|
d61af0abab | ||
|
|
89cda82263 | ||
|
|
ee6ace36d1 | ||
|
|
e2e5e223ab | ||
|
|
4579d11abc |
+7
-104
@@ -1,67 +1,10 @@
|
||||
# only pull in version updates that were released more than a week ago (low-pass filter for quickly-retracted releases)
|
||||
cooldown: 7d
|
||||
|
||||
# Most tools (binny, chronicle, cosign, golangci-lint, goreleaser, gosimports,
|
||||
# bouncer, quill, syft, task, gh) are inherited from anchore/go-make's embedded
|
||||
# .binny.yaml — see https://github.com/anchore/go-make. Only syft-specific tools
|
||||
# or version overrides should live here.
|
||||
tools:
|
||||
## internal tools ############################################################################
|
||||
|
||||
# we want to use a pinned version of binny to manage the toolchain (so binny manages itself!)
|
||||
- name: binny
|
||||
version:
|
||||
want: v0.13.0
|
||||
method: github-release
|
||||
with:
|
||||
repo: anchore/binny
|
||||
|
||||
# used to produce SBOMs during release
|
||||
- name: syft
|
||||
version:
|
||||
want: v1.42.3
|
||||
method: github-release
|
||||
with:
|
||||
repo: anchore/syft
|
||||
|
||||
# used to sign mac binaries at release
|
||||
- name: quill
|
||||
version:
|
||||
want: v0.7.1
|
||||
method: github-release
|
||||
with:
|
||||
repo: anchore/quill
|
||||
|
||||
# used at release to generate the changelog
|
||||
- name: chronicle
|
||||
version:
|
||||
want: v0.8.0
|
||||
method: github-release
|
||||
with:
|
||||
repo: anchore/chronicle
|
||||
|
||||
## external tools ############################################################################
|
||||
|
||||
# used for linting
|
||||
- name: golangci-lint
|
||||
version:
|
||||
want: v2.11.4
|
||||
method: github-release
|
||||
with:
|
||||
repo: golangci/golangci-lint
|
||||
|
||||
# used for showing the changelog at release
|
||||
- name: glow
|
||||
version:
|
||||
want: v2.1.1
|
||||
method: github-release
|
||||
with:
|
||||
repo: charmbracelet/glow
|
||||
|
||||
# used for signing the checksums file at release
|
||||
- name: cosign
|
||||
version:
|
||||
want: v3.0.5
|
||||
method: github-release
|
||||
with:
|
||||
repo: sigstore/cosign
|
||||
|
||||
# used in integration tests to verify JSON schemas
|
||||
- name: yajsv
|
||||
version:
|
||||
@@ -70,58 +13,18 @@ tools:
|
||||
with:
|
||||
repo: neilpa/yajsv
|
||||
|
||||
# used to release all artifacts
|
||||
- name: goreleaser
|
||||
version:
|
||||
want: v2.15.2
|
||||
method: github-release
|
||||
with:
|
||||
repo: goreleaser/goreleaser
|
||||
|
||||
# used for organizing imports during static analysis
|
||||
- name: gosimports
|
||||
version:
|
||||
want: v0.3.8
|
||||
method: github-release
|
||||
with:
|
||||
repo: rinchsan/gosimports
|
||||
|
||||
# used during static analysis for license compliance
|
||||
- name: bouncer
|
||||
version:
|
||||
want: v0.4.0
|
||||
method: github-release
|
||||
with:
|
||||
repo: wagoodman/go-bouncer
|
||||
|
||||
# used for running all local and CI tasks
|
||||
- name: task
|
||||
version:
|
||||
want: v3.49.1
|
||||
method: github-release
|
||||
with:
|
||||
repo: go-task/task
|
||||
|
||||
# used for triggering a release
|
||||
- name: gh
|
||||
version:
|
||||
want: v2.89.0
|
||||
method: github-release
|
||||
with:
|
||||
repo: cli/cli
|
||||
|
||||
# used to upload test fixture cache
|
||||
- name: oras
|
||||
version:
|
||||
want: v1.3.1
|
||||
want: v1.3.4
|
||||
method: github-release
|
||||
with:
|
||||
repo: oras-project/oras
|
||||
|
||||
# used to upload test fixture cache
|
||||
# used to parse JSON/YAML annotations on the fixture cache image
|
||||
- name: yq
|
||||
version:
|
||||
want: v4.52.5
|
||||
want: v4.53.6
|
||||
method: github-release
|
||||
with:
|
||||
repo: mikefarah/yq
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
# check out text files with lf on every platform (regardless of core.autocrlf), letting git detect binary files
|
||||
* text=auto eol=lf
|
||||
|
||||
# fixtures that must keep their crlf line endings byte for byte
|
||||
syft/linux/testdata/unprintable -text
|
||||
syft/pkg/cataloger/bitnami/testdata/no-rel/opt/bitnami/redis/.spdx-redis.spdx -text
|
||||
syft/pkg/cataloger/java/testdata/jar-metadata/**/META-INF/MANIFEST.MF -text
|
||||
@@ -1,55 +1,45 @@
|
||||
name: "Bootstrap"
|
||||
description: "Bootstrap all tools and dependencies"
|
||||
description: "Bootstrap all syft tools and dependencies on top of go-make's setup action"
|
||||
|
||||
# This action is a thin wrapper around anchore/go-make/.github/actions/setup which
|
||||
# already handles checkout, setup-go, restore-only build/mod cache, and tool cache.
|
||||
# We add the syft-specific extras here: apt packages and the test fixture cache.
|
||||
|
||||
inputs:
|
||||
go-version:
|
||||
description: "Go version to install"
|
||||
description: "Go version to install (passed to go-make/setup)"
|
||||
required: true
|
||||
default: "1.26.2"
|
||||
go-dependencies:
|
||||
description: "Download go dependencies"
|
||||
cache-key-prefix:
|
||||
description: "Prefix all cache keys with this value (passed to go-make/setup)"
|
||||
required: true
|
||||
default: "v1"
|
||||
cache-enabled:
|
||||
description: "Enable build/mod and tool caching (passed to go-make/setup)"
|
||||
required: true
|
||||
default: "true"
|
||||
cache-key-prefix:
|
||||
description: "Prefix all cache keys with this value"
|
||||
required: true
|
||||
default: "53ac821810"
|
||||
download-test-fixture-cache:
|
||||
description: "Download test fixture cache from OCI and github actions"
|
||||
required: true
|
||||
default: "false"
|
||||
tools:
|
||||
description: "whether to install tools"
|
||||
default: "true"
|
||||
bootstrap-apt-packages:
|
||||
description: "Space delimited list of tools to install via apt"
|
||||
default: "libxml2-utils"
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
# note: go mod and build is automatically cached on default with v4+
|
||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||
if: inputs.go-version != ''
|
||||
- name: Setup go + go-make tooling
|
||||
uses: anchore/go-make/.github/actions/setup@fa5421b3bf24c9d18f11a736c5eb4172f2b7d683 # v0.8.1
|
||||
with:
|
||||
go-version: ${{ inputs.go-version }}
|
||||
check-latest: true
|
||||
- name: Restore tool cache
|
||||
if: inputs.tools == 'true'
|
||||
id: tool-cache
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: ${{ github.workspace }}/.tool
|
||||
key: ${{ inputs.cache-key-prefix }}-${{ runner.os }}-tool-${{ hashFiles('.binny.yaml') }}
|
||||
- name: Install project tools
|
||||
cache-key-prefix: ${{ inputs.cache-key-prefix }}
|
||||
cache-enabled: ${{ inputs.cache-enabled }}
|
||||
|
||||
- name: Install binny-managed tools
|
||||
shell: bash
|
||||
if: inputs.tools == 'true'
|
||||
run: |
|
||||
make tools
|
||||
.tool/binny list
|
||||
.tool/binny check
|
||||
- name: Install go dependencies
|
||||
if: inputs.go-dependencies == 'true'
|
||||
shell: bash
|
||||
run: make ci-bootstrap-go
|
||||
run: make binny:install
|
||||
|
||||
- name: Install apt packages
|
||||
if: inputs.bootstrap-apt-packages != ''
|
||||
shell: bash
|
||||
@@ -57,13 +47,34 @@ runs:
|
||||
APT_PACKAGES: ${{ inputs.bootstrap-apt-packages }}
|
||||
run: |
|
||||
IFS=' ' read -ra packages <<< "$APT_PACKAGES"
|
||||
DEBIAN_FRONTEND=noninteractive sudo apt update && sudo -E apt install -y "${packages[@]}"
|
||||
# the AMI already ships most of these, and `apt update` costs ~40MB of index (minutes,
|
||||
# when archive.ubuntu.com throttles us) to find that out. only touch apt if we must.
|
||||
missing=()
|
||||
for pkg in "${packages[@]}"; do
|
||||
dpkg -s "$pkg" >/dev/null 2>&1 || missing+=("$pkg")
|
||||
done
|
||||
if [ ${#missing[@]} -eq 0 ]; then
|
||||
echo "all apt packages already present: ${packages[*]}"
|
||||
exit 0
|
||||
fi
|
||||
DEBIAN_FRONTEND=noninteractive sudo apt update && sudo -E apt install -y "${missing[@]}"
|
||||
|
||||
# ORAS cache: restore-only on non-default branches / forks
|
||||
- name: Restore ORAS cache from github actions
|
||||
if: inputs.download-test-fixture-cache == 'true'
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
if: ${{ inputs.download-test-fixture-cache == 'true' && (github.ref != format('refs/heads/{0}', github.event.repository.default_branch) || github.event.repository.fork == true) }}
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
path: ${{ github.workspace }}/.tmp/oras-cache
|
||||
key: ${{ inputs.cache-key-prefix }}-oras-cache
|
||||
|
||||
# ORAS cache: restore + save on the default branch of the canonical repo only.
|
||||
- name: Restore and save ORAS cache from github actions
|
||||
if: ${{ inputs.download-test-fixture-cache == 'true' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && github.event.repository.fork == false }}
|
||||
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
path: ${{ github.workspace }}/.tmp/oras-cache
|
||||
key: ${{ inputs.cache-key-prefix }}-oras-cache
|
||||
|
||||
- name: Download test fixture cache
|
||||
if: inputs.download-test-fixture-cache == 'true'
|
||||
shell: bash
|
||||
|
||||
+3
-26
@@ -1,20 +1,13 @@
|
||||
# Dependabot configuration
|
||||
#
|
||||
# Grouping behavior (see inline comments for details):
|
||||
# - Minor + patch updates: grouped into a single PR per ecosystem
|
||||
# - Major version bumps: individual PR per dependency
|
||||
# - Security updates: individual PR per dependency
|
||||
#
|
||||
# Note: "patch" refers to semver version bumps (1.2.3 -> 1.2.4), not security fixes.
|
||||
# Security updates are identified separately via GitHub's Advisory Database and
|
||||
# can be any version bump (patch, minor, or major) that fixes a known CVE.
|
||||
|
||||
version: 2
|
||||
|
||||
updates:
|
||||
|
||||
- package-ecosystem: gomod
|
||||
directory: "/"
|
||||
directories:
|
||||
- "/"
|
||||
- "/.make"
|
||||
cooldown:
|
||||
default-days: 7
|
||||
schedule:
|
||||
@@ -28,14 +21,6 @@ updates:
|
||||
- dependency-name: "github.com/aquasecurity/go-version"
|
||||
- dependency-name: "github.com/knqyf263/go-apk-version"
|
||||
- dependency-name: "github.com/knqyf263/go-deb-version"
|
||||
groups:
|
||||
go-minor-patch:
|
||||
applies-to: version-updates # security updates get individual PRs
|
||||
patterns:
|
||||
- "*"
|
||||
update-types: # major omitted, gets individual PRs
|
||||
- "minor"
|
||||
- "patch"
|
||||
|
||||
- package-ecosystem: "github-actions"
|
||||
directories:
|
||||
@@ -49,11 +34,3 @@ updates:
|
||||
open-pull-requests-limit: 10
|
||||
labels:
|
||||
- "dependencies"
|
||||
groups:
|
||||
actions-minor-patch:
|
||||
applies-to: version-updates # security updates get individual PRs
|
||||
patterns:
|
||||
- "*"
|
||||
update-types: # major omitted, gets individual PRs
|
||||
- "minor"
|
||||
- "patch"
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
red=$(tput setaf 1)
|
||||
bold=$(tput bold)
|
||||
normal=$(tput sgr0)
|
||||
|
||||
# assert we are running in CI (or die!)
|
||||
if [[ -z "$CI" ]]; then
|
||||
echo "${bold}${red}This step should ONLY be run in CI. Exiting...${normal}"
|
||||
exit 1
|
||||
fi
|
||||
@@ -1,36 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import subprocess
|
||||
import sys
|
||||
import shlex
|
||||
|
||||
|
||||
class bcolors:
|
||||
HEADER = '\033[95m'
|
||||
OKBLUE = '\033[94m'
|
||||
OKCYAN = '\033[96m'
|
||||
OKGREEN = '\033[92m'
|
||||
WARNING = '\033[93m'
|
||||
FAIL = '\033[91m'
|
||||
ENDC = '\033[0m'
|
||||
BOLD = '\033[1m'
|
||||
UNDERLINE = '\033[4m'
|
||||
|
||||
|
||||
if len(sys.argv) < 3:
|
||||
print("Usage: coverage.py [threshold] [go-coverage-report]")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
threshold = float(sys.argv[1])
|
||||
report = sys.argv[2]
|
||||
|
||||
|
||||
args = shlex.split(f"go tool cover -func {report}")
|
||||
p = subprocess.run(args, capture_output=True, text=True)
|
||||
|
||||
percent_coverage = float(p.stdout.splitlines()[-1].split()[-1].replace("%", ""))
|
||||
print(f"{bcolors.BOLD}Coverage: {percent_coverage}%{bcolors.ENDC}")
|
||||
|
||||
if percent_coverage < threshold:
|
||||
print(f"{bcolors.BOLD}{bcolors.FAIL}Coverage below threshold of {threshold}%{bcolors.ENDC}")
|
||||
sys.exit(1)
|
||||
@@ -1,35 +1,45 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import glob
|
||||
import sys
|
||||
import json
|
||||
import hashlib
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
IGNORED_PREFIXES = []
|
||||
|
||||
|
||||
def find_fingerprints_and_check_dirs(base_dir):
|
||||
all_fingerprints = set(glob.glob(os.path.join(base_dir, '**', 'test*', '**', '*.fingerprint'), recursive=True))
|
||||
all_fingerprints = set(
|
||||
glob.glob(
|
||||
os.path.join(base_dir, "**", "test*", "**", "*.fingerprint"), recursive=True
|
||||
)
|
||||
)
|
||||
|
||||
all_fingerprints = {os.path.relpath(fp) for fp in all_fingerprints
|
||||
if not any(fp.startswith(prefix) for prefix in IGNORED_PREFIXES)}
|
||||
all_fingerprints = {
|
||||
os.path.relpath(fp)
|
||||
for fp in all_fingerprints
|
||||
if not any(fp.startswith(prefix) for prefix in IGNORED_PREFIXES)
|
||||
}
|
||||
|
||||
if not all_fingerprints:
|
||||
show("No .fingerprint files or cache directories found.")
|
||||
exit(1)
|
||||
|
||||
missing_content = []
|
||||
orphan_fingerprints = []
|
||||
empty_content = []
|
||||
valid_paths = set()
|
||||
fingerprint_contents = []
|
||||
|
||||
for fingerprint in all_fingerprints:
|
||||
path = fingerprint.replace('.fingerprint', '')
|
||||
path = fingerprint.replace(".fingerprint", "")
|
||||
|
||||
if not os.path.exists(path):
|
||||
missing_content.append(path)
|
||||
# paired content path is entirely missing — the .fingerprint is likely
|
||||
# leftover from a moved/deleted source (testdata trees are git-ignored,
|
||||
# so they persist locally across rename refactors)
|
||||
orphan_fingerprints.append(fingerprint)
|
||||
continue
|
||||
|
||||
if not os.path.isdir(path):
|
||||
@@ -39,13 +49,13 @@ def find_fingerprints_and_check_dirs(base_dir):
|
||||
if os.listdir(path):
|
||||
valid_paths.add(path)
|
||||
else:
|
||||
missing_content.append(path)
|
||||
empty_content.append(path)
|
||||
|
||||
with open(fingerprint, 'r') as f:
|
||||
with open(fingerprint, "r") as f:
|
||||
content = f.read().strip()
|
||||
fingerprint_contents.append((fingerprint, content))
|
||||
|
||||
return sorted(valid_paths), missing_content, fingerprint_contents
|
||||
return sorted(valid_paths), empty_content, orphan_fingerprints, fingerprint_contents
|
||||
|
||||
|
||||
def parse_fingerprint_contents(fingerprint_content):
|
||||
@@ -59,7 +69,9 @@ def parse_fingerprint_contents(fingerprint_content):
|
||||
def calculate_sha256(fingerprint_contents):
|
||||
sorted_fingerprint_contents = sorted(fingerprint_contents, key=lambda x: x[0])
|
||||
|
||||
concatenated_contents = ''.join(content for _, content in sorted_fingerprint_contents)
|
||||
concatenated_contents = "".join(
|
||||
content for _, content in sorted_fingerprint_contents
|
||||
)
|
||||
|
||||
sha256_hash = hashlib.sha256(concatenated_contents.encode()).hexdigest()
|
||||
|
||||
@@ -68,7 +80,7 @@ def calculate_sha256(fingerprint_contents):
|
||||
|
||||
def calculate_file_sha256(file_path):
|
||||
sha256_hash = hashlib.sha256()
|
||||
with open(file_path, 'rb') as f:
|
||||
with open(file_path, "rb") as f:
|
||||
for byte_block in iter(lambda: f.read(4096), b""):
|
||||
sha256_hash.update(byte_block)
|
||||
return sha256_hash.hexdigest()
|
||||
@@ -79,17 +91,28 @@ def show(*s: str):
|
||||
|
||||
|
||||
def main(file_path: str | None):
|
||||
base_dir = '.'
|
||||
valid_paths, missing_content, fingerprint_contents = find_fingerprints_and_check_dirs(base_dir)
|
||||
base_dir = "."
|
||||
valid_paths, empty_content, orphan_fingerprints, fingerprint_contents = (
|
||||
find_fingerprints_and_check_dirs(base_dir)
|
||||
)
|
||||
|
||||
if missing_content:
|
||||
show("The following paths are missing or have no content, but have corresponding .fingerprint files:")
|
||||
for path in sorted(missing_content):
|
||||
if empty_content:
|
||||
show(
|
||||
"The following paths exist but are empty, and have corresponding .fingerprint files:"
|
||||
)
|
||||
for path in sorted(empty_content):
|
||||
show(f"- {path}")
|
||||
# when adding new cache directories there is a time where it is not possible to have this directory without
|
||||
# running the tests first... but this step is a prerequisite for running the tests. We should not block on this.
|
||||
# show("Please ensure these paths exist and have content if they are directories.")
|
||||
# exit(1)
|
||||
|
||||
if orphan_fingerprints:
|
||||
show(
|
||||
"The following .fingerprint files reference paths that no longer exist "
|
||||
"(likely leftover from a moved/deleted cataloger — safe to delete, "
|
||||
"or run `task prune-orphan-fingerprints`):"
|
||||
)
|
||||
for fp in sorted(orphan_fingerprints):
|
||||
show(f"- {fp}")
|
||||
|
||||
sha256_hash = calculate_sha256(fingerprint_contents)
|
||||
|
||||
@@ -101,30 +124,24 @@ def main(file_path: str | None):
|
||||
file_digest = calculate_file_sha256(fingerprint_file)
|
||||
|
||||
# Parse the fingerprint file to get the digest/path tuples
|
||||
with open(fingerprint_file, 'r') as f:
|
||||
with open(fingerprint_file, "r") as f:
|
||||
fingerprint_content = f.read().strip()
|
||||
input_map = parse_fingerprint_contents(fingerprint_content)
|
||||
|
||||
paths_with_digests.append({
|
||||
"path": path,
|
||||
"digest": file_digest,
|
||||
"input": input_map
|
||||
})
|
||||
paths_with_digests.append(
|
||||
{"path": path, "digest": file_digest, "input": input_map}
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
show(f"Error processing {fingerprint_file}: {e}")
|
||||
raise e
|
||||
|
||||
|
||||
output = {
|
||||
"digest": sha256_hash,
|
||||
"paths": paths_with_digests
|
||||
}
|
||||
output = {"digest": sha256_hash, "paths": paths_with_digests}
|
||||
|
||||
content = json.dumps(output, indent=2, sort_keys=True)
|
||||
|
||||
if file_path:
|
||||
with open(file_path, 'w') as f:
|
||||
with open(file_path, "w") as f:
|
||||
f.write(content)
|
||||
|
||||
print(content)
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eu
|
||||
|
||||
ORIGINAL_STATE_DIR=$(mktemp -d "TEMP-original-state-XXXXXXXXX")
|
||||
TIDY_STATE_DIR=$(mktemp -d "TEMP-tidy-state-XXXXXXXXX")
|
||||
|
||||
trap "cp -p ${ORIGINAL_STATE_DIR}/* ./ && git update-index -q --refresh && rm -fR ${ORIGINAL_STATE_DIR} ${TIDY_STATE_DIR}" EXIT
|
||||
|
||||
# capturing original state of files...
|
||||
cp go.mod go.sum "${ORIGINAL_STATE_DIR}"
|
||||
|
||||
# capturing state of go.mod and go.sum after running go mod tidy...
|
||||
go mod tidy
|
||||
cp go.mod go.sum "${TIDY_STATE_DIR}"
|
||||
|
||||
set +e
|
||||
|
||||
# detect difference between the git HEAD state and the go mod tidy state
|
||||
DIFF_MOD=$(diff -u "${ORIGINAL_STATE_DIR}/go.mod" "${TIDY_STATE_DIR}/go.mod")
|
||||
DIFF_SUM=$(diff -u "${ORIGINAL_STATE_DIR}/go.sum" "${TIDY_STATE_DIR}/go.sum")
|
||||
|
||||
if [[ -n "${DIFF_MOD}" || -n "${DIFF_SUM}" ]]; then
|
||||
echo "go.mod diff:"
|
||||
echo "${DIFF_MOD}"
|
||||
echo "go.sum diff:"
|
||||
echo "${DIFF_SUM}"
|
||||
echo ""
|
||||
printf "FAILED! go.mod and/or go.sum are NOT tidy; please run 'go mod tidy'.\n\n"
|
||||
exit 1
|
||||
fi
|
||||
Executable
+102
@@ -0,0 +1,102 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Remove orphan *.fingerprint files left behind by moved/deleted catalogers.
|
||||
|
||||
A fingerprint is considered orphaned when:
|
||||
1. its paired content path (the fingerprint path with `.fingerprint` stripped)
|
||||
does not exist, AND
|
||||
2. the nearest ancestor `testdata/` directory has no `Makefile` claiming
|
||||
responsibility for generating that path.
|
||||
|
||||
The second condition is the safety check: if there is a Makefile, the
|
||||
fingerprint is "live" and might just be waiting for fixtures to be built —
|
||||
leave it alone. Without a Makefile, nothing in-repo will ever regenerate
|
||||
the content, so the fingerprint is dead weight that triggers spurious
|
||||
"missing path" warnings.
|
||||
|
||||
Empty parent directories are also pruned after removing the fingerprint.
|
||||
|
||||
Use --dry-run to preview without deleting.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import glob
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def find_ancestor_testdata(path: str) -> str | None:
|
||||
d = os.path.dirname(path)
|
||||
while d and d not in (".", os.sep):
|
||||
if os.path.basename(d) == "testdata":
|
||||
return d
|
||||
d = os.path.dirname(d)
|
||||
return None
|
||||
|
||||
|
||||
def is_orphan(fingerprint: str) -> bool:
|
||||
paired = fingerprint[: -len(".fingerprint")]
|
||||
if os.path.exists(paired):
|
||||
return False
|
||||
|
||||
testdata_dir = find_ancestor_testdata(fingerprint)
|
||||
if testdata_dir and os.path.isfile(os.path.join(testdata_dir, "Makefile")):
|
||||
# a Makefile exists that may regenerate this — not safe to prune
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def prune_empty_parents(start: str, stop_at: str = ".") -> list[str]:
|
||||
removed = []
|
||||
d = os.path.dirname(start)
|
||||
stop_at = os.path.abspath(stop_at)
|
||||
while d and os.path.abspath(d) != stop_at:
|
||||
try:
|
||||
if not os.listdir(d):
|
||||
os.rmdir(d)
|
||||
removed.append(d)
|
||||
d = os.path.dirname(d)
|
||||
else:
|
||||
break
|
||||
except OSError:
|
||||
break
|
||||
return removed
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument(
|
||||
"--dry-run",
|
||||
action="store_true",
|
||||
help="Show what would be removed without deleting anything",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
all_fingerprints = glob.glob("**/test*/**/*.fingerprint", recursive=True)
|
||||
orphans = sorted(fp for fp in all_fingerprints if is_orphan(fp))
|
||||
|
||||
if not orphans:
|
||||
print("no orphan fingerprints found")
|
||||
return 0
|
||||
|
||||
verb = "would remove" if args.dry_run else "removing"
|
||||
print(f"{verb} {len(orphans)} orphan fingerprint(s):")
|
||||
for fp in orphans:
|
||||
print(f"- {fp}")
|
||||
if args.dry_run:
|
||||
continue
|
||||
try:
|
||||
os.remove(fp)
|
||||
except OSError as e:
|
||||
print(f" ! failed to remove: {e}", file=sys.stderr)
|
||||
continue
|
||||
for d in prune_empty_parents(fp):
|
||||
print(f" (also removed empty dir {d})")
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -1,57 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eu
|
||||
|
||||
bold=$(tput bold)
|
||||
normal=$(tput sgr0)
|
||||
|
||||
GH_CLI=.tool/gh
|
||||
|
||||
if ! [ -x "$(command -v $GH_CLI)" ]; then
|
||||
echo "The GitHub CLI could not be found. run: make bootstrap"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
$GH_CLI auth status
|
||||
|
||||
# set the default repo in cases where multiple remotes are defined
|
||||
$GH_CLI repo set-default anchore/syft
|
||||
|
||||
export GITHUB_TOKEN="${GITHUB_TOKEN-"$($GH_CLI auth token)"}"
|
||||
|
||||
# we need all of the git state to determine the next version. Since tagging is done by
|
||||
# the release pipeline it is possible to not have all of the tags from previous releases.
|
||||
git fetch --tags
|
||||
|
||||
# populates the CHANGELOG.md and VERSION files
|
||||
echo "${bold}Generating changelog...${normal}"
|
||||
make changelog 2> /dev/null
|
||||
|
||||
NEXT_VERSION=$(cat VERSION)
|
||||
|
||||
if [[ "$NEXT_VERSION" == "" || "${NEXT_VERSION}" == "(Unreleased)" ]]; then
|
||||
echo "Could not determine the next version to release. Exiting..."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
while true; do
|
||||
read -p "${bold}Do you want to trigger a release for version '${NEXT_VERSION}'?${normal} [y/n] " yn
|
||||
case $yn in
|
||||
[Yy]* ) echo; break;;
|
||||
[Nn]* ) echo; echo "Cancelling release..."; exit;;
|
||||
* ) echo "Please answer yes or no.";;
|
||||
esac
|
||||
done
|
||||
|
||||
echo "${bold}Kicking off release for ${NEXT_VERSION}${normal}..."
|
||||
echo
|
||||
$GH_CLI workflow run release.yaml -f version=${NEXT_VERSION}
|
||||
|
||||
echo
|
||||
echo "${bold}Waiting for release to start...${normal}"
|
||||
sleep 10
|
||||
|
||||
set +e
|
||||
|
||||
echo "${bold}Head to the release workflow to monitor the release:${normal} $($GH_CLI run list --workflow=release.yaml --limit=1 --json url --jq '.[].url')"
|
||||
id=$($GH_CLI run list --workflow=release.yaml --limit=1 --json databaseId --jq '.[].databaseId')
|
||||
$GH_CLI run watch $id --exit-status || (echo ; echo "${bold}Logs of failed step:${normal}" && GH_PAGER="" $GH_CLI run view $id --log-failed)
|
||||
@@ -13,7 +13,7 @@ permissions: {}
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
uses: anchore/workflows/.github/workflows/codeql.yaml@e8cee3a5916cebb68cda68b54c180f43394c1910 # v0.5.0
|
||||
uses: anchore/workflows/.github/workflows/codeql.yaml@7212994dc8fc3a53fe9c8e766ab5b4ddd16ea3d4 # v0.8.0
|
||||
permissions:
|
||||
security-events: write
|
||||
packages: read
|
||||
|
||||
@@ -21,20 +21,27 @@ on:
|
||||
options:
|
||||
- "all"
|
||||
- "install-script-only"
|
||||
skip-checks:
|
||||
description: skip the check-gate (release even if checks haven't passed on main)
|
||||
type: boolean
|
||||
default: false
|
||||
required: false
|
||||
|
||||
jobs:
|
||||
|
||||
version-available:
|
||||
if: ${{ github.event.inputs.phase == 'all' }}
|
||||
uses: anchore/workflows/.github/workflows/check-version-available.yaml@8b2b1caf40e03933c6807e03b99e883e2ceb5ac8 # v0.4.0
|
||||
permissions:
|
||||
contents: read # required for fetching tags
|
||||
uses: anchore/workflows/.github/workflows/check-version-available.yaml@7212994dc8fc3a53fe9c8e766ab5b4ddd16ea3d4 # v0.8.0
|
||||
with:
|
||||
version: ${{ github.event.inputs.version }}
|
||||
|
||||
check-gate:
|
||||
if: ${{ github.event.inputs.phase == 'all' }}
|
||||
if: ${{ github.event.inputs.phase == 'all' && !inputs.skip-checks }}
|
||||
permissions:
|
||||
checks: read # required for getting the status of specific check names
|
||||
uses: anchore/workflows/.github/workflows/check-gate.yaml@8b2b1caf40e03933c6807e03b99e883e2ceb5ac8 # v0.4.0
|
||||
contents: read
|
||||
checks: read # required for getting the status of specific check names
|
||||
uses: anchore/workflows/.github/workflows/check-gate.yaml@7212994dc8fc3a53fe9c8e766ab5b4ddd16ea3d4 # v0.8.0
|
||||
with:
|
||||
# these are checks that should be run on pull-request and merges to main.
|
||||
# we do NOT want to kick off a release if these have not been verified on main.
|
||||
@@ -42,21 +49,33 @@ jobs:
|
||||
checks: '["Acceptance tests (Linux)", "Acceptance tests (Mac)", "Build snapshot artifacts", "CLI tests (Linux)", "Integration tests", "Static analysis", "Unit tests"]'
|
||||
|
||||
release:
|
||||
needs: [ check-gate, version-available ]
|
||||
if: ${{ github.event.inputs.phase == 'all' }}
|
||||
needs: [check-gate, version-available]
|
||||
# run even when check-gate is skipped, but never when version-available
|
||||
# failed/was skipped, nor when check-gate failed or was cancelled. note:
|
||||
# always() disables the implicit success() gate on ALL needs, so the
|
||||
# version-available requirement must be re-asserted explicitly here.
|
||||
if: >-
|
||||
${{ always()
|
||||
&& needs.version-available.result == 'success'
|
||||
&& !contains(fromJSON('["failure", "cancelled"]'), needs.check-gate.result) }}
|
||||
environment: release
|
||||
# runs-on.com: compute instances for parallel builds
|
||||
# spot disabled: reliability for build workflows (used for releases too)
|
||||
# goreleaser uses parallelism of 12, so we need more CPUs
|
||||
# s3-cache: faster actions cache
|
||||
# tmpfs: faster io-intensive workflows
|
||||
runs-on: runs-on=${{ github.run_id }}/cpu=16+32/ram=32+128/family=c5+c6+c7+c8/spot=false/extras=s3-cache+tmpfs
|
||||
# volume: enlarge dist/ disk -- the default 40GB EBS root fills up during the multi-arch
|
||||
# image + binary build and OOMs (runs out of disk); size it well above peak usage.
|
||||
# provisioned throughput/iops above the gp3 free baseline (125mbs/3000iops) approximate
|
||||
# the tmpfs IO speed on disk, for ~1c/run.
|
||||
# note: tmpfs intentionally omitted -- a RAM-backed dist/ never touches the enlarged
|
||||
# volume and competes with the build's memory peak, so keep dist/ on disk.
|
||||
runs-on: runs-on=${{ github.run_id }}/cpu=16+32/ram=32+128/family=c5+c6+c7+c8/spot=false/extras=s3-cache/volume=120gb:gp3:500mbs:4000iops
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
# required for goreleaser signs section with cosign
|
||||
id-token: write
|
||||
contents: write # required for creating the GitHub release and pushing the version tag
|
||||
packages: write # required for publishing release artifacts to GitHub packages
|
||||
id-token: write # required for keyless signing (cosign/sigstore OIDC)
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #v7.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: true
|
||||
@@ -64,32 +83,30 @@ jobs:
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
|
||||
# dockers_v2 builds multi-platform manifests in a single buildx invocation, which requires a
|
||||
# docker-container driver builder (the default builder on the runner cannot build multi-platform).
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 #v4.1.0
|
||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f #v4.6.0
|
||||
with:
|
||||
username: ${{ secrets.ANCHOREOSSWRITE_DH_USERNAME }}
|
||||
password: ${{ secrets.ANCHOREOSSWRITE_DH_PAT }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 #v4.1.0
|
||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f #v4.6.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Tag release
|
||||
run: |
|
||||
git config --global user.name "anchoreci"
|
||||
git config --global user.email "anchoreci@users.noreply.github.com"
|
||||
git tag -a "$VERSION" -m "Release $VERSION"
|
||||
git push origin --tags
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
VERSION: ${{ github.event.inputs.version }}
|
||||
|
||||
- name: Build & publish release artifacts
|
||||
run: make ci-release
|
||||
env:
|
||||
# for pushing tags (does not inherit workflow permissions)
|
||||
TAG_TOKEN: ${{ secrets.TAG_TOKEN }}
|
||||
RELEASE_VERSION: ${{ github.event.inputs.version }}
|
||||
# for mac signing and notarization...
|
||||
QUILL_SIGN_P12: ${{ secrets.ANCHORE_APPLE_DEVELOPER_ID_CERT_CHAIN }}
|
||||
QUILL_SIGN_PASSWORD: ${{ secrets.ANCHORE_APPLE_DEVELOPER_ID_CERT_PASS }}
|
||||
@@ -101,36 +118,18 @@ jobs:
|
||||
# for updating brew formula in anchore/homebrew-syft
|
||||
GITHUB_BREW_TOKEN: ${{ secrets.ANCHOREOPS_GITHUB_OSS_WRITE_TOKEN }}
|
||||
|
||||
- uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 #v0.24.0
|
||||
- uses: anchore/sbom-action@3ad7283483fc7af8ff2b4ea19663c2d5ca935e26 #v0.24.2
|
||||
continue-on-error: true
|
||||
with:
|
||||
file: go.mod
|
||||
artifact-name: sbom.spdx.json
|
||||
|
||||
- name: Notify Slack of new release
|
||||
uses: slackapi/slack-github-action@45a88b9581bfab2566dc881e2cd66d334e621e2c #v3.0.3
|
||||
continue-on-error: true
|
||||
with:
|
||||
webhook: ${{ secrets.SLACK_TOOLBOX_WEBHOOK_URL }}
|
||||
webhook-type: incoming-webhook
|
||||
payload: |
|
||||
text: "A new Syft release has been published: https://github.com/anchore/syft/releases/tag/${{ github.event.inputs.version }}"
|
||||
blocks:
|
||||
- type: section
|
||||
text:
|
||||
type: mrkdwn
|
||||
text: |
|
||||
*A new Syft release has been published* :rocket:
|
||||
• Release: <https://github.com/anchore/syft/releases/tag/${{ github.event.inputs.version }}|${{ github.event.inputs.version }}>
|
||||
• Repo: `${{ github.repository }}`
|
||||
• Workflow: `${{ github.workflow }}`
|
||||
• Event: `${{ github.event_name }}`
|
||||
if: ${{ success() }}
|
||||
|
||||
release-install-script:
|
||||
needs: [ release ]
|
||||
needs: [release]
|
||||
if: ${{ always() && (needs.release.result == 'success' || github.event.inputs.phase == 'install-script-only') }}
|
||||
uses: "anchore/workflows/.github/workflows/release-install-script.yaml@main"
|
||||
permissions:
|
||||
contents: read # required for the reusable workflow to check out the repo and publish the install script
|
||||
uses: anchore/workflows/.github/workflows/release-install-script.yaml@7212994dc8fc3a53fe9c8e766ab5b4ddd16ea3d4 # v0.8.0
|
||||
with:
|
||||
tag: ${{ github.event.inputs.version }}
|
||||
secrets:
|
||||
|
||||
@@ -20,12 +20,12 @@ jobs:
|
||||
contents: read
|
||||
security-events: write # for uploading SARIF results
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: "Run zizmor"
|
||||
uses: zizmorcore/zizmor-action@b1d7e1fb5de872772f31590499237e7cce841e8e # v0.5.3
|
||||
uses: zizmorcore/zizmor-action@cc914d7f3750a2d13d75c7f184a1060aa0e9d482 # v0.6.4
|
||||
with:
|
||||
# there is a pass/fail gate as a repo ruleset (if there is no ruleset configured then the action will pass by default)
|
||||
advanced-security: true
|
||||
|
||||
@@ -21,13 +21,17 @@ jobs:
|
||||
name: "Static analysis"
|
||||
# runs-on.com: memory & general purpose instances for testing
|
||||
# spot enabled: ok to interrupt non-production workloads
|
||||
# s3-cache: faster actions cache
|
||||
# tmpfs: faster io-intensive workflows
|
||||
runs-on: &test-runner "runs-on=${{ github.run_id }}/cpu=4+8/ram=32+128/family=r5+r6+r7+r8+m4+m5+m6+m7+m8/spot=price-capacity-optimized/extras=s3-cache+tmpfs"
|
||||
# tmpfs intentionally omitted -- it RAM-backs /home/runner, /tmp and /var/lib/docker at
|
||||
# 100% of instance memory, starving `make unit` of headroom to link with -race.
|
||||
# volume: the caches and docker images tmpfs used to hold. gp3 baseline throughput is
|
||||
# enough; a 4 vCPU instance can't pull much more than that anyway.
|
||||
# note: s3-cache intentionally omitted -- PR runs are untrusted and must not write to the
|
||||
# shared cache backend that the trusted release workflow reads from (cache poisoning).
|
||||
runs-on: &test-runner "runs-on=${{ github.run_id }}/cpu=4+8/ram=16+32/family=m6+m7+m8+c6+c7+c8/spot=price-capacity-optimized/volume=100gb"
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -45,11 +49,18 @@ jobs:
|
||||
Unit-Test:
|
||||
# Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline
|
||||
name: "Unit tests"
|
||||
runs-on: ubuntu-latest
|
||||
needs: [Unit-Test-Linux, Unit-Test-Windows]
|
||||
steps:
|
||||
- run: echo All unit tests passed!
|
||||
|
||||
Unit-Test-Linux:
|
||||
name: "Unit tests (Linux)"
|
||||
runs-on: *test-runner
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -64,6 +75,27 @@ jobs:
|
||||
- name: Check for capability drift
|
||||
run: make check-capability-drift
|
||||
|
||||
Unit-Test-Windows:
|
||||
name: "Unit tests (Windows)"
|
||||
runs-on: windows-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
# go-make's setup only (not ./.github/actions/bootstrap): no apt, binny tools, or fixture cache are needed
|
||||
- name: Setup go + go-make tooling
|
||||
uses: anchore/go-make/.github/actions/setup@fa5421b3bf24c9d18f11a736c5eb4172f2b7d683 # v0.8.1
|
||||
with:
|
||||
# keep in sync with the go-version default in ./.github/actions/bootstrap
|
||||
go-version: "1.26.2"
|
||||
|
||||
# the full unit suite leans on linux docker fixtures, so this runs the windows-relevant subset
|
||||
- name: Run unit tests
|
||||
run: make unit:windows
|
||||
|
||||
Integration-Test:
|
||||
# Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline
|
||||
name: "Integration tests"
|
||||
@@ -71,7 +103,7 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -91,16 +123,20 @@ jobs:
|
||||
# runs-on.com: compute instances for parallel builds
|
||||
# spot disabled: reliability for build workflows (used for releases too)
|
||||
# goreleaser uses parallelism of 12, so we need more CPUs
|
||||
# s3-cache: faster actions cache
|
||||
# tmpfs: faster io-intensive workflows
|
||||
runs-on: "runs-on=${{ github.run_id }}/cpu=16+32/ram=32+128/family=c5+c6+c7+c8/spot=false/extras=s3-cache+tmpfs"
|
||||
# tmpfs intentionally omitted -- a RAM-backed dist/ competes with the goreleaser
|
||||
# build's memory peak (parallel binaries + multi-arch image assembly) and OOMs the
|
||||
# instance; keep dist/ on disk.
|
||||
# note: s3-cache intentionally omitted -- PR runs are untrusted and must not write to the
|
||||
# shared cache backend that the trusted release workflow reads from (cache poisoning).
|
||||
# volume: enlarge dist/ disk -- the default 40GB EBS root fills up during the multi-arch
|
||||
# image + binary build and OOMs (runs out of disk); size it well above peak usage.
|
||||
# provisioned throughput/iops above the gp3 free baseline (125mbs/3000iops) approximate
|
||||
# the tmpfs IO speed on disk, for ~1c/run.
|
||||
runs-on: "runs-on=${{ github.run_id }}/cpu=16+32/ram=32+128/family=c5+c6+c7+c8/spot=false/volume=120gb:gp3:500mbs:4000iops"
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
# required for magic-cache from runs-on to function with artifact upload/download (see https://runs-on.com/caching/magic-cache/#actionsupload-artifact-compatibility)
|
||||
- uses: runs-on/action@742bf56072eb4845a0f94b3394673e4903c90ff0 # v2.1.0
|
||||
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -130,10 +166,7 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
# required for magic-cache from runs-on to function with artifact upload/download (see https://runs-on.com/caching/magic-cache/#actionsupload-artifact-compatibility)
|
||||
- uses: runs-on/action@742bf56072eb4845a0f94b3394673e4903c90ff0 # v2.1.0
|
||||
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -175,9 +208,9 @@ jobs:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Install Cosign
|
||||
uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4.1.1
|
||||
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
|
||||
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -211,10 +244,7 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
# required for magic-cache from runs-on to function with artifact upload/download (see https://runs-on.com/caching/magic-cache/#actionsupload-artifact-compatibility)
|
||||
- uses: runs-on/action@742bf56072eb4845a0f94b3394673e4903c90ff0 # v2.1.0
|
||||
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
rules:
|
||||
unpinned-uses:
|
||||
config:
|
||||
policies:
|
||||
# anchore/workflows is an internal repository; using @main is acceptable
|
||||
anchore/*: any
|
||||
dangerous-triggers:
|
||||
ignore:
|
||||
# Safe use of pull_request_target - only runs trusted scripts from base repo,
|
||||
|
||||
+5
-2
@@ -3,6 +3,8 @@ go.work
|
||||
go.work.sum
|
||||
.tool-versions
|
||||
.python-version
|
||||
.mise.toml
|
||||
.env
|
||||
|
||||
# app configuration
|
||||
/.syft.yaml
|
||||
@@ -18,8 +20,11 @@ bin/
|
||||
/.tool
|
||||
/.task
|
||||
/generate
|
||||
/spec
|
||||
/specs
|
||||
mise.toml
|
||||
.make/.make
|
||||
.conductor
|
||||
|
||||
# changelog generation
|
||||
CHANGELOG.md
|
||||
@@ -76,5 +81,3 @@ cosign.pub
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
|
||||
|
||||
+26
-3
@@ -7,6 +7,7 @@ linters:
|
||||
- asciicheck
|
||||
- bodyclose
|
||||
- copyloopvar
|
||||
- depguard
|
||||
- dogsled
|
||||
- dupl
|
||||
- errcheck
|
||||
@@ -29,9 +30,20 @@ linters:
|
||||
- unused
|
||||
- whitespace
|
||||
settings:
|
||||
depguard:
|
||||
rules:
|
||||
yaml:
|
||||
deny:
|
||||
# gopkg.in/yaml.v3 is unmaintained and panics on some malformed input (see #5327)
|
||||
- pkg: gopkg.in/yaml.v3
|
||||
desc: use go.yaml.in/yaml/v3 instead
|
||||
funlen:
|
||||
lines: 70
|
||||
statements: 50
|
||||
goconst:
|
||||
# the default of 3 turns every repeated literal in a lookup table or a test fixture into a finding.
|
||||
# 5 keeps the ones worth hoisting without flagging data that reads better written out.
|
||||
min-occurrences: 5
|
||||
gocritic:
|
||||
enabled-checks:
|
||||
- deferInLoop
|
||||
@@ -54,6 +66,12 @@ linters:
|
||||
- linters:
|
||||
- revive
|
||||
text: "var-naming: avoid package names that conflict"
|
||||
# cpegenerate is almost entirely lookup tables mapping artifact names to vendors, products, and
|
||||
# maven group ids. The repeated strings are the data itself, so hoisting them to constants would
|
||||
# replace a readable table with indirection for no gain.
|
||||
- linters:
|
||||
- goconst
|
||||
path: syft/pkg/cataloger/internal/cpegenerate/
|
||||
paths:
|
||||
- third_party$
|
||||
- builtin$
|
||||
@@ -61,7 +79,6 @@ linters:
|
||||
|
||||
# do not enable...
|
||||
# - deadcode # The owner seems to have abandoned the linter. Replaced by "unused".
|
||||
# - depguard # We don't have a configuration for this yet
|
||||
# - goprintffuncname # does not catch all cases and there are exceptions
|
||||
# - nakedret # does not catch all cases and should not fail a build
|
||||
# - gochecknoglobals
|
||||
@@ -70,7 +87,6 @@ linters:
|
||||
# - godot
|
||||
# - godox
|
||||
# - goerr113
|
||||
# - goimports # we're using gosimports now instead to account for extra whitespaces (see https://github.com/golang/go/issues/20818)
|
||||
# - golint # deprecated
|
||||
# - gomnd # this is too aggressive
|
||||
# - interfacer # this is a good idea, but is no longer supported and is prone to false positives
|
||||
@@ -96,11 +112,18 @@ issues:
|
||||
|
||||
formatters:
|
||||
enable:
|
||||
- gci
|
||||
- gofmt
|
||||
- goimports
|
||||
exclusions:
|
||||
generated: lax
|
||||
paths:
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
settings:
|
||||
gci:
|
||||
# See https://golangci-lint.run/docs/formatters/configuration/#gci
|
||||
sections:
|
||||
- standard # Standard section: captures all standard packages.
|
||||
- default # Default section: contains all imports that could not be matched to another section type.
|
||||
- prefix(github.com/anchore)
|
||||
|
||||
+76
-294
@@ -73,306 +73,88 @@ brews:
|
||||
description: *description
|
||||
license: "Apache License 2.0"
|
||||
|
||||
dockers:
|
||||
# production images...
|
||||
- image_templates:
|
||||
- anchore/syft:{{.Tag}}-amd64
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-amd64
|
||||
goarch: amd64
|
||||
dockers_v2:
|
||||
# production images (scratch base, root)
|
||||
- id: production
|
||||
dockerfile: Dockerfile
|
||||
use: buildx
|
||||
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}}"
|
||||
ids: &docker-ids
|
||||
- linux-build
|
||||
images: &docker-images
|
||||
- anchore/syft
|
||||
- ghcr.io/anchore/syft
|
||||
platforms: &docker-platforms
|
||||
- linux/amd64
|
||||
- linux/arm64
|
||||
- linux/ppc64le
|
||||
- linux/riscv64
|
||||
- linux/s390x
|
||||
labels: &docker-labels
|
||||
"org.opencontainers.image.created": "{{.Date}}"
|
||||
"org.opencontainers.image.title": "syft"
|
||||
"org.opencontainers.image.description": "CLI tool and library for generating a Software Bill of Materials from container images and filesystems"
|
||||
"org.opencontainers.image.source": "{{.GitURL}}"
|
||||
"org.opencontainers.image.revision": "{{.FullCommit}}"
|
||||
"org.opencontainers.image.vendor": "Anchore, Inc."
|
||||
"org.opencontainers.image.version": "{{.Version}}"
|
||||
"org.opencontainers.image.licenses": "Apache-2.0"
|
||||
"io.artifacthub.package.readme-url": "https://raw.githubusercontent.com/anchore/syft/main/README.md"
|
||||
"io.artifacthub.package.logo-url": "https://user-images.githubusercontent.com/5199289/136844524-1527b09f-c5cb-4aa9-be54-5aa92a6086c1.png"
|
||||
"io.artifacthub.package.license": "Apache-2.0"
|
||||
tags:
|
||||
- latest
|
||||
- "{{.Tag}}"
|
||||
# DEBIAN_VERSION 13 (trixie) is the first distroless release with a riscv64 base image
|
||||
# and is a superset of debian 12 for the other targeted arches; docker_v2 builds all
|
||||
# platforms in a single buildx invocation, so build_args cannot vary per platform.
|
||||
build_args: &docker-build-args
|
||||
DEBIAN_VERSION: "13"
|
||||
# disable provenance attestations to keep the manifest free of unknown/unknown entries
|
||||
flags: &docker-flags
|
||||
- "--provenance=false"
|
||||
# SBOMs are produced separately for the archives (see the sboms section)
|
||||
sbom: &docker-sbom "false"
|
||||
|
||||
- image_templates:
|
||||
- anchore/syft:{{.Tag}}-arm64v8
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-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}}"
|
||||
|
||||
- image_templates:
|
||||
- anchore/syft:{{.Tag}}-ppc64le
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-ppc64le
|
||||
goarch: ppc64le
|
||||
dockerfile: Dockerfile
|
||||
use: buildx
|
||||
build_flag_templates:
|
||||
- "--platform=linux/ppc64le"
|
||||
- "--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}}-riscv64
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-riscv64
|
||||
goarch: riscv64
|
||||
dockerfile: Dockerfile
|
||||
use: buildx
|
||||
build_flag_templates:
|
||||
- "--platform=linux/riscv64"
|
||||
- "--build-arg=DEBIAN_VERSION=13"
|
||||
- "--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}}-s390x
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-s390x
|
||||
goarch: s390x
|
||||
dockerfile: Dockerfile
|
||||
use: buildx
|
||||
build_flag_templates:
|
||||
- "--platform=linux/s390x"
|
||||
- "--build-arg=BUILD_DATE={{.Date}}"
|
||||
- "--build-arg=BUILD_VERSION={{.Version}}"
|
||||
- "--build-arg=VCS_REF={{.FullCommit}}"
|
||||
- "--build-arg=VCS_URL={{.GitURL}}"
|
||||
|
||||
# nonroot images...
|
||||
- image_templates:
|
||||
- anchore/syft:{{.Tag}}-nonroot-amd64
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-amd64
|
||||
goarch: amd64
|
||||
# nonroot images
|
||||
- id: nonroot
|
||||
dockerfile: Dockerfile.nonroot
|
||||
use: buildx
|
||||
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}}"
|
||||
ids: *docker-ids
|
||||
images: *docker-images
|
||||
platforms: *docker-platforms
|
||||
labels: *docker-labels
|
||||
tags:
|
||||
- nonroot
|
||||
- "{{.Tag}}-nonroot"
|
||||
build_args: *docker-build-args
|
||||
flags: *docker-flags
|
||||
sbom: *docker-sbom
|
||||
|
||||
- image_templates:
|
||||
- anchore/syft:{{.Tag}}-nonroot-arm64v8
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-arm64v8
|
||||
goarch: arm64
|
||||
dockerfile: Dockerfile.nonroot
|
||||
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}}"
|
||||
|
||||
- image_templates:
|
||||
- anchore/syft:{{.Tag}}-nonroot-ppc64le
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-ppc64le
|
||||
goarch: ppc64le
|
||||
dockerfile: Dockerfile.nonroot
|
||||
use: buildx
|
||||
build_flag_templates:
|
||||
- "--platform=linux/ppc64le"
|
||||
- "--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}}-nonroot-riscv64
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-riscv64
|
||||
goarch: riscv64
|
||||
dockerfile: Dockerfile.nonroot
|
||||
use: buildx
|
||||
build_flag_templates:
|
||||
- "--platform=linux/riscv64"
|
||||
- "--build-arg=DEBIAN_VERSION=13"
|
||||
- "--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}}-nonroot-s390x
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-s390x
|
||||
goarch: s390x
|
||||
dockerfile: Dockerfile.nonroot
|
||||
use: buildx
|
||||
build_flag_templates:
|
||||
- "--platform=linux/s390x"
|
||||
- "--build-arg=BUILD_DATE={{.Date}}"
|
||||
- "--build-arg=BUILD_VERSION={{.Version}}"
|
||||
- "--build-arg=VCS_REF={{.FullCommit}}"
|
||||
- "--build-arg=VCS_URL={{.GitURL}}"
|
||||
|
||||
# debug images...
|
||||
- image_templates:
|
||||
- anchore/syft:{{.Tag}}-debug-amd64
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-debug-amd64
|
||||
goarch: amd64
|
||||
# debug images (root)
|
||||
- id: debug
|
||||
dockerfile: Dockerfile.debug
|
||||
use: buildx
|
||||
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}}"
|
||||
ids: *docker-ids
|
||||
images: *docker-images
|
||||
platforms: *docker-platforms
|
||||
labels: *docker-labels
|
||||
tags:
|
||||
- debug
|
||||
- "{{.Tag}}-debug"
|
||||
build_args: *docker-build-args
|
||||
flags: *docker-flags
|
||||
sbom: *docker-sbom
|
||||
|
||||
- image_templates:
|
||||
- anchore/syft:{{.Tag}}-debug-arm64v8
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-debug-arm64v8
|
||||
goarch: arm64
|
||||
dockerfile: Dockerfile.debug
|
||||
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}}"
|
||||
|
||||
- image_templates:
|
||||
- anchore/syft:{{.Tag}}-debug-ppc64le
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-debug-ppc64le
|
||||
goarch: ppc64le
|
||||
dockerfile: Dockerfile.debug
|
||||
use: buildx
|
||||
build_flag_templates:
|
||||
- "--platform=linux/ppc64le"
|
||||
- "--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}}-debug-riscv64
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-debug-riscv64
|
||||
goarch: riscv64
|
||||
dockerfile: Dockerfile.debug
|
||||
use: buildx
|
||||
build_flag_templates:
|
||||
- "--platform=linux/riscv64"
|
||||
- "--build-arg=DEBIAN_VERSION=13"
|
||||
- "--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}}-debug-s390x
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-debug-s390x
|
||||
goarch: s390x
|
||||
dockerfile: Dockerfile.debug
|
||||
use: buildx
|
||||
build_flag_templates:
|
||||
- "--platform=linux/s390x"
|
||||
- "--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:latest
|
||||
image_templates:
|
||||
- anchore/syft:{{.Tag}}-amd64
|
||||
- anchore/syft:{{.Tag}}-arm64v8
|
||||
- anchore/syft:{{.Tag}}-ppc64le
|
||||
- anchore/syft:{{.Tag}}-riscv64
|
||||
- anchore/syft:{{.Tag}}-s390x
|
||||
|
||||
- name_template: ghcr.io/anchore/syft:latest
|
||||
image_templates:
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-amd64
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-arm64v8
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-ppc64le
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-riscv64
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-s390x
|
||||
|
||||
- name_template: anchore/syft:{{.Tag}}
|
||||
image_templates:
|
||||
- anchore/syft:{{.Tag}}-amd64
|
||||
- anchore/syft:{{.Tag}}-arm64v8
|
||||
- anchore/syft:{{.Tag}}-ppc64le
|
||||
- anchore/syft:{{.Tag}}-riscv64
|
||||
- anchore/syft:{{.Tag}}-s390x
|
||||
|
||||
- name_template: ghcr.io/anchore/syft:{{.Tag}}
|
||||
image_templates:
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-amd64
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-arm64v8
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-ppc64le
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-riscv64
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-s390x
|
||||
|
||||
# nonroot images...
|
||||
- name_template: anchore/syft:nonroot
|
||||
image_templates:
|
||||
- anchore/syft:{{.Tag}}-nonroot-amd64
|
||||
- anchore/syft:{{.Tag}}-nonroot-arm64v8
|
||||
- anchore/syft:{{.Tag}}-nonroot-ppc64le
|
||||
- anchore/syft:{{.Tag}}-nonroot-riscv64
|
||||
- anchore/syft:{{.Tag}}-nonroot-s390x
|
||||
|
||||
- name_template: ghcr.io/anchore/syft:nonroot
|
||||
image_templates:
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-amd64
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-arm64v8
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-ppc64le
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-riscv64
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-s390x
|
||||
|
||||
- name_template: anchore/syft:{{.Tag}}-nonroot
|
||||
image_templates:
|
||||
- anchore/syft:{{.Tag}}-nonroot-amd64
|
||||
- anchore/syft:{{.Tag}}-nonroot-arm64v8
|
||||
- anchore/syft:{{.Tag}}-nonroot-ppc64le
|
||||
- anchore/syft:{{.Tag}}-nonroot-riscv64
|
||||
- anchore/syft:{{.Tag}}-nonroot-s390x
|
||||
|
||||
- name_template: ghcr.io/anchore/syft:{{.Tag}}-nonroot
|
||||
image_templates:
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-amd64
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-arm64v8
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-ppc64le
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-riscv64
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-s390x
|
||||
|
||||
# debug images...
|
||||
- name_template: anchore/syft:debug
|
||||
image_templates:
|
||||
- anchore/syft:{{.Tag}}-debug-amd64
|
||||
- anchore/syft:{{.Tag}}-debug-arm64v8
|
||||
- anchore/syft:{{.Tag}}-debug-ppc64le
|
||||
- anchore/syft:{{.Tag}}-debug-riscv64
|
||||
- anchore/syft:{{.Tag}}-debug-s390x
|
||||
|
||||
- name_template: ghcr.io/anchore/syft:debug
|
||||
image_templates:
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-debug-amd64
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-debug-arm64v8
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-debug-ppc64le
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-debug-riscv64
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-debug-s390x
|
||||
|
||||
- name_template: anchore/syft:{{.Tag}}-debug
|
||||
image_templates:
|
||||
- anchore/syft:{{.Tag}}-debug-amd64
|
||||
- anchore/syft:{{.Tag}}-debug-arm64v8
|
||||
- anchore/syft:{{.Tag}}-debug-ppc64le
|
||||
- anchore/syft:{{.Tag}}-debug-riscv64
|
||||
- anchore/syft:{{.Tag}}-debug-s390x
|
||||
|
||||
- name_template: ghcr.io/anchore/syft:{{.Tag}}-debug
|
||||
image_templates:
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-debug-amd64
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-debug-arm64v8
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-debug-ppc64le
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-debug-riscv64
|
||||
- ghcr.io/anchore/syft:{{.Tag}}-debug-s390x
|
||||
# debug-nonroot images
|
||||
- id: debug-nonroot
|
||||
dockerfile: Dockerfile.debug-nonroot
|
||||
ids: *docker-ids
|
||||
images: *docker-images
|
||||
platforms: *docker-platforms
|
||||
labels: *docker-labels
|
||||
tags:
|
||||
- debug-nonroot
|
||||
- "{{.Tag}}-debug-nonroot"
|
||||
build_args: *docker-build-args
|
||||
flags: *docker-flags
|
||||
sbom: *docker-sbom
|
||||
|
||||
sboms:
|
||||
- artifacts: archive
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
module github.com/anchore/syft/.make
|
||||
|
||||
go 1.25.8
|
||||
|
||||
require github.com/anchore/go-make v0.8.1
|
||||
|
||||
require (
|
||||
github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect
|
||||
github.com/goccy/go-yaml v1.19.2 // indirect
|
||||
golang.org/x/mod v0.40.0 // indirect
|
||||
golang.org/x/sys v0.47.0 // indirect
|
||||
)
|
||||
@@ -0,0 +1,10 @@
|
||||
github.com/anchore/go-make v0.8.1 h1:GAhPXR+9qVQhFB1pfVlHFOzxAt3dTDTUIA7Tp7nx9tg=
|
||||
github.com/anchore/go-make v0.8.1/go.mod h1:xALbVsPsDoJnJxdkjPlxOLZV7GbAdT5Pv3gd0kP1Lz0=
|
||||
github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs=
|
||||
github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
|
||||
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
|
||||
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||
golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs=
|
||||
golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE=
|
||||
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
|
||||
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
+213
@@ -0,0 +1,213 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
|
||||
. "github.com/anchore/go-make"
|
||||
"github.com/anchore/go-make/config"
|
||||
"github.com/anchore/go-make/file"
|
||||
"github.com/anchore/go-make/lang"
|
||||
"github.com/anchore/go-make/run"
|
||||
"github.com/anchore/go-make/tasks/golint"
|
||||
"github.com/anchore/go-make/tasks/goreleaser"
|
||||
"github.com/anchore/go-make/tasks/gotask"
|
||||
"github.com/anchore/go-make/tasks/gotest"
|
||||
)
|
||||
|
||||
func main() {
|
||||
Makefile(
|
||||
// shared anchore tasks
|
||||
golint.Tasks(),
|
||||
goreleaser.Tasks(),
|
||||
|
||||
// unit tests: exclude packages under any test/ directory (matches the syft
|
||||
// Taskfile's prior `grep -v` against test paths). Coverage threshold of 62%
|
||||
// preserves the prior coverage gate that used to live in scripts/coverage.py.
|
||||
gotest.Tasks(
|
||||
gotest.Name("unit"),
|
||||
gotest.ExcludeGlob("**/test/**"),
|
||||
gotest.CoverageThreshold(62),
|
||||
race(),
|
||||
),
|
||||
|
||||
// integration tests: run `go test` directly instead of via gotest.Tasks(), which
|
||||
// has no way to set a timeout. The suite is a single package of ~36 sequential
|
||||
// tests over 18 docker fixture images, so with a cold fixture cache (every image
|
||||
// built + saved inline, under -race) it runs well past `go test`'s default 10m
|
||||
// timeout -- which is exactly the case when regenerating the fixture cache from
|
||||
// scratch. -count=1 keeps the go test cache from short-circuiting a run whose
|
||||
// side effect (the built fixtures) is the thing we're after.
|
||||
//
|
||||
// The race-detector smoke against a real image stays bundled here (RunsOn
|
||||
// integration) so `make integration` behaves like the Taskfile version did.
|
||||
Task{
|
||||
Name: "integration",
|
||||
Description: "run integration tests",
|
||||
RunsOn: lang.List("test"),
|
||||
Run: func() {
|
||||
raceFlag := ""
|
||||
if raceEnabled() {
|
||||
raceFlag = " -race"
|
||||
}
|
||||
Run(
|
||||
"go test -count=1 -timeout=30m"+raceFlag+" ./cmd/syft/internal/test/integration/...",
|
||||
run.Env("GODEBUG", "dontfreezetheworld=1"),
|
||||
)
|
||||
},
|
||||
},
|
||||
Task{
|
||||
Name: "integration:race-smoke",
|
||||
Description: "exercise the CLI with the race detector",
|
||||
RunsOn: lang.List("integration"),
|
||||
Run: func() {
|
||||
if !raceEnabled() {
|
||||
Log("race detector disabled (RACE=false); skipping race smoke")
|
||||
return
|
||||
}
|
||||
Run("go run -race cmd/syft/main.go anchore/test_images:grype-quality-dotnet-69f15d2")
|
||||
},
|
||||
},
|
||||
|
||||
// cli tests: native go-make Task. Runs SYFT_BINARY_LOCATION at an *absolute*
|
||||
// path to the snapshot binary. Builds the snapshot only when the binary is
|
||||
// missing rather than depending on the snapshot task unconditionally: in
|
||||
// validations.yaml CI we download a pre-built snapshot artifact, so the binary
|
||||
// already exists and rebuilding would both burn ~10m and clobber it. When
|
||||
// `make test` runs cold (e.g. the release pipeline) or locally with no
|
||||
// snapshot, we build a single-target snapshot (current OS/arch only) since
|
||||
// that's all the CLI tests need.
|
||||
Task{
|
||||
Name: "cli",
|
||||
Description: "Run CLI tests",
|
||||
RunsOn: lang.List("test"),
|
||||
Run: func() {
|
||||
bin := snapshotBinPath()
|
||||
if !file.Exists(bin) {
|
||||
Log("snapshot binary not found at %s; building single-target snapshot", bin)
|
||||
Run("make snapshot:single-target")
|
||||
}
|
||||
Log("testing binary: %s", bin)
|
||||
Run(
|
||||
"go test -count=1 -timeout=15m -v ./test/cli",
|
||||
run.Env("SYFT_BINARY_LOCATION", bin),
|
||||
)
|
||||
},
|
||||
},
|
||||
|
||||
// windows unit tests: deliberately not hooked into "test" (no RunsOn). The full unit suite leans on
|
||||
// linux docker fixture images and posix-only assumptions, so this covers the packages that own
|
||||
// host path handling (where windows bugs live) and skips the tests that need docker image fixtures.
|
||||
// ponytail: curated package list, widen it as more of the suite is made windows-safe.
|
||||
Task{
|
||||
Name: "unit:windows",
|
||||
Description: "run the windows-relevant subset of unit tests",
|
||||
Run: func() {
|
||||
Run("go test -count=1"+
|
||||
` -skip "Image|Squash|AllLayers|MixFileTypes|GivenCoordinates"`+
|
||||
" ./syft/internal/windows/... ./syft/internal/fileresolver/... ./syft/source/directorysource/... ./syft/source/filesource/..."+
|
||||
" ./syft/file ./syft/file/cataloger/filedigest/... ./syft/file/cataloger/filecontent/...",
|
||||
run.Stdout(os.Stderr),
|
||||
)
|
||||
},
|
||||
},
|
||||
|
||||
// default validation pipeline (replaces Taskfile `default`/`pr-validations`/`validations`).
|
||||
Task{
|
||||
Name: "default",
|
||||
Description: "Run all validation tasks",
|
||||
Dependencies: Deps("static-analysis", "test", "install-test"),
|
||||
},
|
||||
|
||||
// --- everything else is implemented in Taskfile.yaml. gotask.Tasks()
|
||||
// discovers every (non-internal) Taskfile task — including the namespaced
|
||||
// `generate:cpe-index:*` tasks from the included task.d file — and surfaces
|
||||
// them as first-class go-make tasks (with their canonical names and `desc:`)
|
||||
// that forward to `task <name>`. Descriptions live in Taskfile.yaml as the
|
||||
// single source of truth; no per-task wrapping needed here.
|
||||
gotask.Tasks(),
|
||||
|
||||
// gotask.Tasks() discovers canonical task names only, not Taskfile aliases,
|
||||
// so re-expose `refresh-fixtures`'s `fixtures` alias for manual use.
|
||||
Task{
|
||||
Name: "fixtures",
|
||||
Description: "Clear and fetch all test fixture cache (alias of refresh-fixtures)",
|
||||
Dependencies: Deps("refresh-fixtures"),
|
||||
},
|
||||
|
||||
// gotask.Tasks() can't attach RunsOn labels, so wire the syft-specific
|
||||
// Taskfile tasks into go-make's native phases here. These thin hooks have
|
||||
// no body and no description (hidden from `make help`); they only pull the
|
||||
// discovered tasks in when the labeled phase runs.
|
||||
Task{
|
||||
Name: "static-analysis:syft",
|
||||
RunsOn: lang.List("static-analysis"),
|
||||
Dependencies: Deps("check-json-schema-drift", "check-binary-fixture-size"),
|
||||
},
|
||||
Task{
|
||||
Name: "test:syft",
|
||||
RunsOn: lang.List("test"),
|
||||
Dependencies: Deps("validate-cyclonedx-schema", "test-utils", "check-docker-cache"),
|
||||
},
|
||||
// refresh-fixtures hooks into "unit" so `make unit` triggers the stale-cache
|
||||
// detection + download just like `task unit` did on main (its
|
||||
// `deps: [tmpdir, fixtures]` is what kept the fixture cache fresh).
|
||||
Task{
|
||||
Name: "unit:syft",
|
||||
RunsOn: lang.List("unit"),
|
||||
Dependencies: Deps("refresh-fixtures"),
|
||||
},
|
||||
Task{
|
||||
Name: "clean:syft",
|
||||
RunsOn: lang.List("clean"),
|
||||
Dependencies: Deps(
|
||||
"clean-snapshot",
|
||||
"clean-docker-cache",
|
||||
"clean-oras-cache",
|
||||
"clean-cache",
|
||||
"clean-test-observations",
|
||||
),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// raceEnabled is the single switch for the race detector across every test suite.
|
||||
// Unset it and we keep go-make's behavior (on in CI, off locally and on windows);
|
||||
// set RACE=false to turn it off everywhere -- worth doing when rebuilding the test
|
||||
// fixture cache from scratch, where every suite is dominated by building docker
|
||||
// fixtures and the race detector only adds wall clock. RACE=true forces it on.
|
||||
func raceEnabled() bool {
|
||||
if enabled, err := strconv.ParseBool(config.Env("RACE", "")); err == nil {
|
||||
return enabled
|
||||
}
|
||||
return config.CI && !config.Windows
|
||||
}
|
||||
|
||||
// race applies raceEnabled() to a gotest suite. gotest exposes no functional option
|
||||
// for the race detector, but gotest.Config.Race is exported.
|
||||
func race() gotest.Option {
|
||||
return func(c *gotest.Config) {
|
||||
c.Race = raceEnabled()
|
||||
}
|
||||
}
|
||||
|
||||
// snapshotBinPath replicates the SNAPSHOT_BIN computation from the prior Taskfile:
|
||||
// <repoRoot>/snapshot/<os>-build_<os>_<arch>/syft, where arch maps amd64->amd64_v1
|
||||
// and arm64->arm64_v8.0 to match goreleaser's per-target output directory naming.
|
||||
// Returns an absolute path: the cli tests' getSyftBinaryLocation contract requires
|
||||
// SYFT_BINARY_LOCATION to be absolute because subtests run with cmd.Dir = t.TempDir().
|
||||
func snapshotBinPath() string {
|
||||
osName := runtime.GOOS
|
||||
var arch string
|
||||
switch runtime.GOARCH {
|
||||
case "amd64":
|
||||
arch = "amd64_v1"
|
||||
case "arm64":
|
||||
arch = "arm64_v8.0"
|
||||
default:
|
||||
arch = runtime.GOARCH
|
||||
}
|
||||
return filepath.Join(RootDir(), "snapshot", osName+"-build_"+osName+"_"+arch, "syft")
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/go-make/config"
|
||||
"github.com/anchore/go-make/tasks/gotest"
|
||||
)
|
||||
|
||||
func TestRaceEnabled(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
env string // "" means RACE is unset
|
||||
ci bool
|
||||
want bool
|
||||
}{
|
||||
{name: "default in CI", ci: true, want: true},
|
||||
{name: "default locally", ci: false, want: false},
|
||||
{name: "explicitly off in CI", env: "false", ci: true, want: false},
|
||||
{name: "explicitly on locally", env: "true", ci: false, want: true},
|
||||
{name: "unparseable falls back to default", env: "yes-please", ci: true, want: true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// always go through t.Setenv (it registers the restore) so an ambient
|
||||
// RACE from the caller's environment can't leak into the unset cases
|
||||
t.Setenv("RACE", tt.env)
|
||||
if tt.env == "" {
|
||||
os.Unsetenv("RACE")
|
||||
}
|
||||
orig := config.CI
|
||||
config.CI = tt.ci
|
||||
t.Cleanup(func() { config.CI = orig })
|
||||
|
||||
if got := raceEnabled(); got != tt.want {
|
||||
t.Errorf("raceEnabled() = %v, want %v", got, tt.want)
|
||||
}
|
||||
|
||||
// the gotest suites get the same answer through the functional option
|
||||
var cfg gotest.Config
|
||||
race()(&cfg)
|
||||
if cfg.Race != tt.want {
|
||||
t.Errorf("race() set Race = %v, want %v", cfg.Race, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
+3
-19
@@ -1,4 +1,4 @@
|
||||
ARG DEBIAN_VERSION=12
|
||||
ARG DEBIAN_VERSION=13
|
||||
FROM gcr.io/distroless/static-debian${DEBIAN_VERSION}:latest AS build
|
||||
|
||||
FROM scratch
|
||||
@@ -8,23 +8,7 @@ COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certifica
|
||||
# create the /tmp dir, which is needed for image content cache
|
||||
WORKDIR /tmp
|
||||
|
||||
COPY syft /
|
||||
|
||||
ARG BUILD_DATE
|
||||
ARG BUILD_VERSION
|
||||
ARG VCS_REF
|
||||
ARG VCS_URL
|
||||
|
||||
LABEL org.opencontainers.image.created=$BUILD_DATE
|
||||
LABEL org.opencontainers.image.title="syft"
|
||||
LABEL org.opencontainers.image.description="CLI tool and library for generating a Software Bill of Materials from container images and filesystems"
|
||||
LABEL org.opencontainers.image.source=$VCS_URL
|
||||
LABEL org.opencontainers.image.revision=$VCS_REF
|
||||
LABEL org.opencontainers.image.vendor="Anchore, Inc."
|
||||
LABEL org.opencontainers.image.version=$BUILD_VERSION
|
||||
LABEL org.opencontainers.image.licenses="Apache-2.0"
|
||||
LABEL io.artifacthub.package.readme-url="https://raw.githubusercontent.com/anchore/syft/main/README.md"
|
||||
LABEL io.artifacthub.package.logo-url="https://user-images.githubusercontent.com/5199289/136844524-1527b09f-c5cb-4aa9-be54-5aa92a6086c1.png"
|
||||
LABEL io.artifacthub.package.license="Apache-2.0"
|
||||
ARG TARGETPLATFORM
|
||||
COPY ${TARGETPLATFORM}/syft /
|
||||
|
||||
ENTRYPOINT ["/syft"]
|
||||
|
||||
+4
-22
@@ -1,28 +1,10 @@
|
||||
ARG DEBIAN_VERSION=12
|
||||
FROM gcr.io/distroless/static-debian${DEBIAN_VERSION}:debug-nonroot
|
||||
ARG DEBIAN_VERSION=13
|
||||
FROM gcr.io/distroless/static-debian${DEBIAN_VERSION}:debug
|
||||
|
||||
# create the /tmp dir, which is needed for image content cache
|
||||
WORKDIR /tmp
|
||||
|
||||
COPY syft /
|
||||
|
||||
USER nonroot
|
||||
|
||||
ARG BUILD_DATE
|
||||
ARG BUILD_VERSION
|
||||
ARG VCS_REF
|
||||
ARG VCS_URL
|
||||
|
||||
LABEL org.opencontainers.image.created=$BUILD_DATE
|
||||
LABEL org.opencontainers.image.title="syft"
|
||||
LABEL org.opencontainers.image.description="CLI tool and library for generating a Software Bill of Materials from container images and filesystems"
|
||||
LABEL org.opencontainers.image.source=$VCS_URL
|
||||
LABEL org.opencontainers.image.revision=$VCS_REF
|
||||
LABEL org.opencontainers.image.vendor="Anchore, Inc."
|
||||
LABEL org.opencontainers.image.version=$BUILD_VERSION
|
||||
LABEL org.opencontainers.image.licenses="Apache-2.0"
|
||||
LABEL io.artifacthub.package.readme-url="https://raw.githubusercontent.com/anchore/syft/main/README.md"
|
||||
LABEL io.artifacthub.package.logo-url="https://user-images.githubusercontent.com/5199289/136844524-1527b09f-c5cb-4aa9-be54-5aa92a6086c1.png"
|
||||
LABEL io.artifacthub.package.license="Apache-2.0"
|
||||
ARG TARGETPLATFORM
|
||||
COPY ${TARGETPLATFORM}/syft /
|
||||
|
||||
ENTRYPOINT ["/syft"]
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
ARG DEBIAN_VERSION=13
|
||||
FROM gcr.io/distroless/static-debian${DEBIAN_VERSION}:debug-nonroot
|
||||
|
||||
# create the /tmp dir, which is needed for image content cache
|
||||
WORKDIR /tmp
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
COPY ${TARGETPLATFORM}/syft /
|
||||
|
||||
USER nonroot
|
||||
|
||||
ENTRYPOINT ["/syft"]
|
||||
+3
-19
@@ -1,28 +1,12 @@
|
||||
ARG DEBIAN_VERSION=12
|
||||
ARG DEBIAN_VERSION=13
|
||||
FROM gcr.io/distroless/static-debian${DEBIAN_VERSION}:nonroot
|
||||
|
||||
# create the /tmp dir, which is needed for image content cache
|
||||
WORKDIR /tmp
|
||||
|
||||
COPY syft /
|
||||
ARG TARGETPLATFORM
|
||||
COPY ${TARGETPLATFORM}/syft /
|
||||
|
||||
USER nonroot
|
||||
|
||||
ARG BUILD_DATE
|
||||
ARG BUILD_VERSION
|
||||
ARG VCS_REF
|
||||
ARG VCS_URL
|
||||
|
||||
LABEL org.opencontainers.image.created=$BUILD_DATE
|
||||
LABEL org.opencontainers.image.title="syft"
|
||||
LABEL org.opencontainers.image.description="CLI tool and library for generating a Software Bill of Materials from container images and filesystems"
|
||||
LABEL org.opencontainers.image.source=$VCS_URL
|
||||
LABEL org.opencontainers.image.revision=$VCS_REF
|
||||
LABEL org.opencontainers.image.vendor="Anchore, Inc."
|
||||
LABEL org.opencontainers.image.version=$BUILD_VERSION
|
||||
LABEL org.opencontainers.image.licenses="Apache-2.0"
|
||||
LABEL io.artifacthub.package.readme-url="https://raw.githubusercontent.com/anchore/syft/main/README.md"
|
||||
LABEL io.artifacthub.package.logo-url="https://user-images.githubusercontent.com/5199289/136844524-1527b09f-c5cb-4aa9-be54-5aa92a6086c1.png"
|
||||
LABEL io.artifacthub.package.license="Apache-2.0"
|
||||
|
||||
ENTRYPOINT ["/syft"]
|
||||
|
||||
@@ -1,46 +1,18 @@
|
||||
OWNER = anchore
|
||||
PROJECT = syft
|
||||
# `test` and `snapshot` have matching directory names in this repo, so make would
|
||||
# refuse to run them without an explicit .PHONY (Nothing to be done for ...).
|
||||
.PHONY: test snapshot
|
||||
test:
|
||||
@go run -C .make . test
|
||||
|
||||
TOOL_DIR = .tool
|
||||
BINNY = $(TOOL_DIR)/binny
|
||||
TASK = $(TOOL_DIR)/task
|
||||
snapshot:
|
||||
@go run -C .make . snapshot
|
||||
|
||||
.DEFAULT_GOAL := make-default
|
||||
.PHONY: *
|
||||
.DEFAULT_GOAL: make-default
|
||||
|
||||
## Bootstrapping targets #################################
|
||||
make-default:
|
||||
@go run -C .make .
|
||||
|
||||
# note: we need to assume that binny and task have not already been installed
|
||||
$(BINNY):
|
||||
@mkdir -p $(TOOL_DIR)
|
||||
@curl -sSfL https://get.anchore.io/binny | sh -s -- -b $(TOOL_DIR)
|
||||
|
||||
# note: we need to assume that binny and task have not already been installed
|
||||
.PHONY: task
|
||||
$(TASK) task: $(BINNY)
|
||||
@$(BINNY) install task -q
|
||||
|
||||
.PHONY: ci-bootstrap-go
|
||||
ci-bootstrap-go:
|
||||
go mod download
|
||||
|
||||
# this is a bootstrapping catch-all, where if the target doesn't exist, we'll ensure the tools are installed and then try again
|
||||
.DEFAULT:
|
||||
%:
|
||||
@make --silent $(TASK)
|
||||
@$(TASK) $@
|
||||
|
||||
## Shim targets #################################
|
||||
|
||||
.PHONY: make-default
|
||||
make-default: $(TASK)
|
||||
@# run the default task in the taskfile
|
||||
@$(TASK)
|
||||
|
||||
# for those of us that can't seem to kick the habit of typing `make ...` lets wrap the superior `task` tool
|
||||
TASKS := $(shell bash -c "test -f $(TASK) && NO_COLOR=1 $(TASK) -l | grep '^\* ' | cut -d' ' -f2 | tr -d ':' | tr '\n' ' '" ) $(shell bash -c "test -f $(TASK) && NO_COLOR=1 $(TASK) -l | grep 'aliases:' | cut -d ':' -f 3 | tr '\n' ' ' | tr -d ','")
|
||||
|
||||
.PHONY: $(TASKS)
|
||||
$(TASKS): $(TASK)
|
||||
@$(TASK) $@
|
||||
|
||||
help: $(TASK)
|
||||
@$(TASK) -l
|
||||
@go run -C .make . $@
|
||||
|
||||
@@ -8,10 +8,9 @@
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/anchore/syft/actions/workflows/validations.yaml" target="_blank"><img alt="Validations" src="https://github.com/anchore/syft/actions/workflows/validations.yaml/badge.svg"></a>
|
||||
<a href="https://goreportcard.com/report/github.com/anchore/syft" target="_blank"><img alt="Go Report Card" src="https://goreportcard.com/badge/github.com/anchore/syft"></a>
|
||||
<a href="https://github.com/anchore/syft/releases/latest" target="_blank"><img alt="GitHub release" src="https://img.shields.io/github/release/anchore/syft.svg"></a>
|
||||
<a href="https://github.com/anchore/syft" target="_blank"><img alt="GitHub go.mod Go version" src="https://img.shields.io/github/go-mod/go-version/anchore/syft.svg"></a>
|
||||
<a href="" target="_blank"><img alt="License: Apache-2.0" src="https://img.shields.io/badge/License-Apache%202.0-blue.svg"></a>
|
||||
<a href="https://github.com/anchore/syft/blob/main/LICENSE" target="_blank"><img alt="License: Apache-2.0" src="https://img.shields.io/badge/License-Apache%202.0-blue.svg"></a>
|
||||
<a href="https://anchore.com/discourse" target="_blank"><img alt="Join our Discourse" src="https://img.shields.io/badge/Discourse-Join-blue?logo=discourse"/></a>
|
||||
<a rel="me" href="https://fosstodon.org/@syft"><img alt="Follow on Mastodon" src="https://img.shields.io/badge/Mastodon-Follow-blue?logoColor=white&logo=mastodon"/></a>
|
||||
</p>
|
||||
|
||||
+55
-262
@@ -1,6 +1,11 @@
|
||||
|
||||
version: "3"
|
||||
|
||||
# NOTE: most generic tasks (static-analysis, format, lint, unit, snapshot, release,
|
||||
# changelog, ci-release, etc.) are now provided natively by anchore/go-make and
|
||||
# defined in .make/main.go. This file holds the syft-specific tasks, which go-make
|
||||
# auto-discovers via gotask.Tasks() and surfaces as `make <name>` — keep
|
||||
# descriptions (`desc:`) populated so they show up in `make help`.
|
||||
|
||||
includes:
|
||||
generate:cpe-index: ./task.d/generate/cpe-index.yaml
|
||||
|
||||
@@ -25,11 +30,7 @@ vars:
|
||||
YQ: "{{ .TOOL_DIR }}/yq"
|
||||
TASK: "{{ .TOOL_DIR }}/task"
|
||||
|
||||
# used for changelog generation
|
||||
CHANGELOG: CHANGELOG.md
|
||||
NEXT_VERSION: VERSION
|
||||
|
||||
# used for snapshot builds
|
||||
# used for snapshot bin discovery in compare/install tasks
|
||||
OS:
|
||||
sh: uname -s | tr '[:upper:]' '[:lower:]'
|
||||
ARCH:
|
||||
@@ -38,15 +39,16 @@ vars:
|
||||
PROJECT_ROOT:
|
||||
sh: echo $PWD
|
||||
|
||||
# docker platform suffix goreleaser appends to snapshot image tags (e.g. latest-amd64).
|
||||
# snapshot builds load per-arch images rather than a multi-arch manifest, so the smoke
|
||||
# test must run the host-native tag to avoid emulation (and red-local/green-CI splits).
|
||||
DOCKER_ARCH:
|
||||
sh: go env GOARCH
|
||||
|
||||
# note: the snapshot dir must be a relative path starting with ./
|
||||
# e.g. when installing snapshot debs from a local path, ./ forces the deb to be installed in the current working directory instead of referencing a package name
|
||||
SNAPSHOT_DIR: ./snapshot
|
||||
SNAPSHOT_BIN: "{{ .PROJECT_ROOT }}/{{ .SNAPSHOT_DIR }}/{{ .OS }}-build_{{ .OS }}_{{ .ARCH }}/{{ .PROJECT }}"
|
||||
SNAPSHOT_CMD: "{{ .TOOL_DIR }}/goreleaser release --config {{ .TMP_DIR }}/goreleaser.yaml --clean --snapshot --skip=publish --skip=sign"
|
||||
BUILD_CMD: "{{ .TOOL_DIR }}/goreleaser build --config {{ .TMP_DIR }}/goreleaser.yaml --clean --snapshot --single-target"
|
||||
RELEASE_CMD: "{{ .TOOL_DIR }}/goreleaser release --clean --release-notes {{ .CHANGELOG }}"
|
||||
VERSION:
|
||||
sh: git describe --dirty --always --tags
|
||||
|
||||
# used for install and acceptance testing
|
||||
COMPARE_DIR: ./test/compare
|
||||
@@ -57,43 +59,10 @@ env:
|
||||
|
||||
tasks:
|
||||
|
||||
## High-level tasks #################################
|
||||
|
||||
default:
|
||||
desc: Run all validation tasks
|
||||
aliases:
|
||||
- pr-validations
|
||||
- validations
|
||||
cmds:
|
||||
- task: static-analysis
|
||||
- task: test
|
||||
- task: install-test
|
||||
|
||||
static-analysis:
|
||||
desc: Run all static analysis tasks
|
||||
cmds:
|
||||
- task: check-go-mod-tidy
|
||||
- task: check-licenses
|
||||
- task: lint
|
||||
- task: check-json-schema-drift
|
||||
- task: check-binary-fixture-size
|
||||
|
||||
test:
|
||||
desc: Run all levels of test
|
||||
cmds:
|
||||
- task: unit
|
||||
- task: integration
|
||||
- task: validate-cyclonedx-schema
|
||||
- task: test-utils
|
||||
- task: snapshot
|
||||
- task: cli
|
||||
- task: check-docker-cache
|
||||
|
||||
## Bootstrap tasks #################################
|
||||
## Bootstrap (internal helpers used by other Taskfile tasks) ###############
|
||||
|
||||
binny:
|
||||
internal: true
|
||||
# desc: Get the binny tool
|
||||
generates:
|
||||
- "{{ .TOOL_DIR }}/binny"
|
||||
status:
|
||||
@@ -102,10 +71,8 @@ tasks:
|
||||
silent: true
|
||||
|
||||
tools:
|
||||
desc: Install all tools needed for CI and local development
|
||||
internal: true
|
||||
deps: [binny]
|
||||
aliases:
|
||||
- bootstrap
|
||||
generates:
|
||||
- ".binny.yaml"
|
||||
- "{{ .TOOL_DIR }}/*"
|
||||
@@ -114,79 +81,14 @@ tasks:
|
||||
cmd: "{{ .TOOL_DIR }}/binny install -v"
|
||||
silent: true
|
||||
|
||||
update-tools:
|
||||
desc: Update pinned versions of all tools to their latest available versions
|
||||
deps: [binny]
|
||||
generates:
|
||||
- ".binny.yaml"
|
||||
- "{{ .TOOL_DIR }}/*"
|
||||
cmd: "{{ .TOOL_DIR }}/binny update -v"
|
||||
silent: true
|
||||
|
||||
list-tools:
|
||||
desc: List all tools needed for CI and local development
|
||||
deps: [binny]
|
||||
cmd: "{{ .TOOL_DIR }}/binny list"
|
||||
silent: true
|
||||
|
||||
list-tool-updates:
|
||||
desc: List all tools that are not up to date relative to the binny config
|
||||
deps: [binny]
|
||||
cmd: "{{ .TOOL_DIR }}/binny list --updates"
|
||||
silent: true
|
||||
|
||||
tmpdir:
|
||||
internal: true
|
||||
silent: true
|
||||
generates:
|
||||
- "{{ .TMP_DIR }}"
|
||||
cmd: "mkdir -p {{ .TMP_DIR }}"
|
||||
|
||||
## Static analysis tasks #################################
|
||||
|
||||
format:
|
||||
desc: Auto-format all source code
|
||||
deps: [tools]
|
||||
cmds:
|
||||
- gofmt -w -s .
|
||||
- "{{ .TOOL_DIR }}/gosimports -local github.com/anchore -w ."
|
||||
- go mod tidy
|
||||
|
||||
lint-fix:
|
||||
desc: Auto-format all source code + run golangci lint fixers
|
||||
deps: [tools]
|
||||
cmds:
|
||||
- task: format
|
||||
- "{{ .TOOL_DIR }}/golangci-lint run --tests=false --fix"
|
||||
|
||||
lint:
|
||||
desc: Run gofmt + golangci lint checks
|
||||
vars:
|
||||
BAD_FMT_FILES:
|
||||
sh: gofmt -l -s .
|
||||
BAD_FILE_NAMES:
|
||||
sh: "find . | grep -e ':' || true"
|
||||
deps: [tools]
|
||||
cmds:
|
||||
# ensure there are no go fmt differences
|
||||
- cmd: 'test -z "{{ .BAD_FMT_FILES }}" || (echo "files with gofmt issues: [{{ .BAD_FMT_FILES }}]"; exit 1)'
|
||||
silent: true
|
||||
# ensure there are no files with ":" in it (a known back case in the go ecosystem)
|
||||
- cmd: 'test -z "{{ .BAD_FILE_NAMES }}" || (echo "files with bad names: [{{ .BAD_FILE_NAMES }}]"; exit 1)'
|
||||
silent: true
|
||||
# run linting
|
||||
- "{{ .TOOL_DIR }}/golangci-lint run --tests=false"
|
||||
|
||||
|
||||
check-licenses:
|
||||
# desc: Ensure transitive dependencies are compliant with the current license policy
|
||||
deps: [tools]
|
||||
cmd: "{{ .TOOL_DIR }}/bouncer check ./..."
|
||||
|
||||
check-go-mod-tidy:
|
||||
# desc: Ensure go.mod and go.sum are up to date
|
||||
cmds:
|
||||
- cmd: .github/scripts/go-mod-tidy-check.sh && echo "go.mod and go.sum are tidy!"
|
||||
silent: true
|
||||
## Static analysis extras #################################################
|
||||
|
||||
check-json-schema-drift:
|
||||
desc: Ensure there is no drift between the JSON schema and the code
|
||||
@@ -203,8 +105,8 @@ tasks:
|
||||
cmds:
|
||||
- .github/scripts/check_binary_fixture_size.sh syft/pkg/cataloger/binary/testdata/classifiers/snippets
|
||||
|
||||
## Test extras ############################################################
|
||||
|
||||
## Testing tasks #################################
|
||||
update-format-golden-files:
|
||||
desc: "Update golden (i.e. snapshot) files used by unit tests"
|
||||
cmds:
|
||||
@@ -214,60 +116,35 @@ tasks:
|
||||
- go test ./syft/format/cyclonedxjson -update-cyclonedx-json
|
||||
- go test ./syft/format/syftjson -update-json
|
||||
|
||||
unit:
|
||||
desc: Run unit tests
|
||||
deps:
|
||||
- tmpdir
|
||||
- fixtures
|
||||
vars:
|
||||
TEST_PKGS:
|
||||
sh: "go list ./... | grep -v {{ .OWNER }}/{{ .PROJECT }}/test | grep -v {{ .OWNER }}/{{ .PROJECT }}/cmd/syft/internal/test | tr '\n' ' '"
|
||||
|
||||
# unit test coverage threshold (in % coverage)
|
||||
COVERAGE_THRESHOLD: 62
|
||||
cmds:
|
||||
- task: clean-test-observations
|
||||
- "go test -coverprofile {{ .TMP_DIR }}/unit-coverage-details.txt {{ .TEST_PKGS }}"
|
||||
- cmd: ".github/scripts/coverage.py {{ .COVERAGE_THRESHOLD }} {{ .TMP_DIR }}/unit-coverage-details.txt"
|
||||
silent: true
|
||||
|
||||
integration:
|
||||
desc: Run integration tests
|
||||
cmds:
|
||||
- "go test -v ./cmd/syft/internal/test/integration"
|
||||
# exercise most of the CLI with the data race detector
|
||||
# we use a larger image to ensure we're using multiple catalogers at a time
|
||||
- "go run -race cmd/syft/main.go anchore/test_images:grype-quality-dotnet-69f15d2"
|
||||
|
||||
validate-cyclonedx-schema:
|
||||
desc: Validate that Syft produces valid CycloneDX documents
|
||||
cmds:
|
||||
- "cd schema/cyclonedx && make"
|
||||
|
||||
cli:
|
||||
desc: Run CLI tests
|
||||
deps: [tools]
|
||||
cmds:
|
||||
- cmd: "echo 'testing binary: {{ .SNAPSHOT_BIN }}'"
|
||||
silent: true
|
||||
- cmd: "test -f {{ .SNAPSHOT_BIN }} || (find {{ .SNAPSHOT_DIR }} && echo '\nno snapshot found for {{ .SNAPSHOT_BIN }}' && false)"
|
||||
silent: true
|
||||
|
||||
- "go test -count=1 -timeout=15m -v ./test/cli"
|
||||
env:
|
||||
SYFT_BINARY_LOCATION: "{{ .SNAPSHOT_BIN }}"
|
||||
|
||||
test-utils:
|
||||
desc: Run tests for pipeline utils
|
||||
cmds:
|
||||
- cmd: .github/scripts/labeler_test.py
|
||||
- cmd: go test -C .make ./...
|
||||
|
||||
snapshot-smoke-test:
|
||||
desc: Run a smoke test on the snapshot builds + docker images
|
||||
cmds:
|
||||
- cmd: "echo 'testing snapshot binary: {{ .SNAPSHOT_BIN }}'"
|
||||
silent: true
|
||||
- cmd: "test -f {{ .SNAPSHOT_BIN }} || (find {{ .SNAPSHOT_DIR }} && echo '\nno snapshot found for {{ .SNAPSHOT_BIN }}' && false)"
|
||||
silent: true
|
||||
- "{{ .SNAPSHOT_BIN }} version"
|
||||
- "{{ .SNAPSHOT_BIN }} scan alpine:latest"
|
||||
- docker run --pull=never --rm anchore/syft:latest-{{ .DOCKER_ARCH }} version
|
||||
- docker run --pull=never --rm anchore/syft:latest-{{ .DOCKER_ARCH }} scan alpine:latest
|
||||
|
||||
## Test-fixture-related targets #################################
|
||||
## Test-fixture-related targets ###########################################
|
||||
|
||||
fingerprints:
|
||||
desc: Generate fingerprints for all non-docker test fixture
|
||||
desc: Generate fingerprints for all non-docker test fixtures
|
||||
silent: true
|
||||
deps: [tmpdir]
|
||||
# this will look for `testdata/Makefile` and invoke the `fingerprint` target to calculate all cache input fingerprint files
|
||||
generates:
|
||||
- '**/testdata/**/*.fingerprint'
|
||||
@@ -281,16 +158,10 @@ tasks:
|
||||
echo -e "${YELLOW}creating fingerprint files for non-docker fixtures...${RESET}"
|
||||
for dir in $(find . -type d -name 'testdata'); do
|
||||
if [ -f "$dir/Makefile" ]; then
|
||||
# for debugging...
|
||||
#echo -e "${YELLOW}• calculating fingerprints in $dir... ${RESET}"
|
||||
|
||||
(make -C "$dir" fingerprint)
|
||||
fi
|
||||
done
|
||||
|
||||
# for debugging...
|
||||
# echo -e "generated all fixture fingerprints"
|
||||
|
||||
- .github/scripts/fingerprint_docker_fixtures.py
|
||||
- |
|
||||
# if DOWNLOAD_TEST_FIXTURE_CACHE is set to 'false', then we don't need to calculate the fingerprint for the cache
|
||||
@@ -432,6 +303,7 @@ tasks:
|
||||
eval $oras_command
|
||||
|
||||
show-test-image-cache:
|
||||
desc: Print the on-disk + docker daemon state of the stereoscope fixture image cache
|
||||
silent: true
|
||||
cmds:
|
||||
- "echo 'Docker daemon cache:'"
|
||||
@@ -452,28 +324,36 @@ tasks:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
## install.sh testing targets #################################
|
||||
## install.sh testing targets #############################################
|
||||
|
||||
install-test:
|
||||
desc: Run install.sh test suite (delegates to test/install/Makefile)
|
||||
cmds:
|
||||
- "cd test/install && make"
|
||||
|
||||
install-test-cache-save:
|
||||
desc: Save the install.sh test image cache (delegates to test/install/Makefile)
|
||||
cmds:
|
||||
- "cd test/install && make save"
|
||||
|
||||
install-test-cache-load:
|
||||
desc: Load the install.sh test image cache (delegates to test/install/Makefile)
|
||||
cmds:
|
||||
- "cd test/install && make load"
|
||||
|
||||
install-test-ci-mac:
|
||||
desc: Run install.sh CI test suite on macOS (delegates to test/install/Makefile)
|
||||
cmds:
|
||||
- "cd test/install && make ci-test-mac"
|
||||
|
||||
## Compare-test targets ###################################################
|
||||
|
||||
generate-compare-file:
|
||||
desc: Generate the acceptance comparison reference JSON for the current compare image
|
||||
cmd: "go run ./cmd/syft {{ .COMPARE_TEST_IMAGE }} -o json > {{ .COMPARE_DIR }}/testdata/acceptance-{{ .COMPARE_TEST_IMAGE }}.json"
|
||||
|
||||
compare-mac:
|
||||
desc: Run macOS install + acceptance comparison against the snapshot build
|
||||
deps: [tmpdir]
|
||||
cmd: |
|
||||
{{ .COMPARE_DIR }}/mac.sh \
|
||||
@@ -483,11 +363,13 @@ tasks:
|
||||
{{ .TMP_DIR }}
|
||||
|
||||
compare-linux:
|
||||
desc: Run Linux install + acceptance comparison (deb + rpm) against the snapshot build
|
||||
cmds:
|
||||
- task: compare-test-deb-package-install
|
||||
- task: compare-test-rpm-package-install
|
||||
|
||||
compare-test-deb-package-install:
|
||||
desc: Run Linux .deb install + acceptance comparison against the snapshot build
|
||||
deps: [tmpdir]
|
||||
cmd: |
|
||||
{{ .COMPARE_DIR }}/deb.sh \
|
||||
@@ -497,6 +379,7 @@ tasks:
|
||||
{{ .TMP_DIR }}
|
||||
|
||||
compare-test-rpm-package-install:
|
||||
desc: Run Linux .rpm install + acceptance comparison against the snapshot build
|
||||
deps: [tmpdir]
|
||||
cmd: |
|
||||
{{ .COMPARE_DIR }}/rpm.sh \
|
||||
@@ -506,7 +389,7 @@ tasks:
|
||||
{{ .TMP_DIR }}
|
||||
|
||||
|
||||
## Code and data generation targets #################################
|
||||
## Code and data generation targets ######################################
|
||||
|
||||
generate:
|
||||
desc: Add data generation tasks
|
||||
@@ -556,104 +439,7 @@ tasks:
|
||||
- "SYFT_ENABLE_COMPLETENESS_TESTS=true go test -p 1 ./internal/capabilities/... -count=1"
|
||||
|
||||
|
||||
## Build-related targets #################################
|
||||
|
||||
build:
|
||||
desc: Build the project
|
||||
deps: [tools, tmpdir]
|
||||
generates:
|
||||
- "{{ .PROJECT }}"
|
||||
cmds:
|
||||
- silent: true
|
||||
cmd: |
|
||||
echo "dist: {{ .SNAPSHOT_DIR }}" > {{ .TMP_DIR }}/goreleaser.yaml
|
||||
cat .goreleaser.yaml >> {{ .TMP_DIR }}/goreleaser.yaml
|
||||
|
||||
- "{{ .BUILD_CMD }}"
|
||||
|
||||
snapshot:
|
||||
desc: Create a snapshot release
|
||||
aliases:
|
||||
- build
|
||||
deps: [tools, tmpdir]
|
||||
sources:
|
||||
- cmd/**/*.go
|
||||
- syft/**/*.go
|
||||
- internal/**/*.go
|
||||
method: checksum
|
||||
generates:
|
||||
- "{{ .SNAPSHOT_BIN }}"
|
||||
cmds:
|
||||
- silent: true
|
||||
cmd: |
|
||||
echo "dist: {{ .SNAPSHOT_DIR }}" > {{ .TMP_DIR }}/goreleaser.yaml
|
||||
cat .goreleaser.yaml >> {{ .TMP_DIR }}/goreleaser.yaml
|
||||
|
||||
- "{{ .SNAPSHOT_CMD }}"
|
||||
|
||||
snapshot-smoke-test:
|
||||
desc: Run a smoke test on the snapshot builds + docker images
|
||||
cmds:
|
||||
- cmd: "echo 'testing snapshot binary: {{ .SNAPSHOT_BIN }}'"
|
||||
silent: true
|
||||
- cmd: "test -f {{ .SNAPSHOT_BIN }} || (find {{ .SNAPSHOT_DIR }} && echo '\nno snapshot found for {{ .SNAPSHOT_BIN }}' && false)"
|
||||
silent: true
|
||||
- "{{ .SNAPSHOT_BIN }} version"
|
||||
- "{{ .SNAPSHOT_BIN }} scan alpine:latest"
|
||||
- docker run --rm anchore/syft:latest version
|
||||
- docker run --rm anchore/syft:latest scan alpine:latest
|
||||
|
||||
changelog:
|
||||
desc: Generate a changelog
|
||||
deps: [tools]
|
||||
generates:
|
||||
- "{{ .CHANGELOG }}"
|
||||
- "{{ .NEXT_VERSION }}"
|
||||
cmds:
|
||||
- "{{ .TOOL_DIR }}/chronicle -vv -n --version-file {{ .NEXT_VERSION }} > {{ .CHANGELOG }}"
|
||||
- "{{ .TOOL_DIR }}/glow -w 0 {{ .CHANGELOG }}"
|
||||
|
||||
|
||||
## Release targets #################################
|
||||
|
||||
release:
|
||||
desc: Create a release
|
||||
interactive: true
|
||||
deps: [tools]
|
||||
cmds:
|
||||
- cmd: .github/scripts/trigger-release.sh
|
||||
silent: true
|
||||
|
||||
|
||||
## CI-only targets #################################
|
||||
|
||||
ci-check:
|
||||
# desc: "[CI only] Are you in CI?"
|
||||
cmds:
|
||||
- cmd: .github/scripts/ci-check.sh
|
||||
silent: true
|
||||
|
||||
ci-release:
|
||||
# desc: "[CI only] Create a release"
|
||||
deps: [tools]
|
||||
cmds:
|
||||
- task: ci-check
|
||||
- "{{ .TOOL_DIR }}/chronicle -vvv > CHANGELOG.md"
|
||||
- cmd: "cat CHANGELOG.md"
|
||||
silent: true
|
||||
- "{{ .RELEASE_CMD }}"
|
||||
|
||||
|
||||
## Cleanup targets #################################
|
||||
|
||||
clean:
|
||||
desc: Remove all cache files and old builds
|
||||
cmds:
|
||||
- task: clean-snapshot
|
||||
- task: clean-cache
|
||||
- task: clean-test-observations
|
||||
- task: clean-docker-cache
|
||||
- task: clean-oras-cache
|
||||
## Cleanup targets ########################################################
|
||||
|
||||
clean-snapshot:
|
||||
desc: Remove any snapshot builds
|
||||
@@ -675,6 +461,7 @@ tasks:
|
||||
desc: Remove all image docker tar cache, images from the docker daemon, and ephemeral test fixtures
|
||||
cmds:
|
||||
- task: clean-docker-cache
|
||||
- task: prune-orphan-fingerprints
|
||||
- |
|
||||
BOLD='\033[1m'
|
||||
YELLOW='\033[0;33m'
|
||||
@@ -690,6 +477,12 @@ tasks:
|
||||
echo -e "${BOLD}Deleted all ephemeral test fixtures${RESET}"
|
||||
- rm -f {{ .LAST_CACHE_PULL_FILE }} {{ .CACHE_PATHS_FILE }}
|
||||
|
||||
prune-orphan-fingerprints:
|
||||
desc: Remove *.fingerprint files left behind by moved/deleted catalogers
|
||||
silent: true
|
||||
cmds:
|
||||
- .github/scripts/prune_orphan_fingerprints.py
|
||||
|
||||
clean-test-observations:
|
||||
desc: Remove all test observations (i.e. testdata/test-observations.json)
|
||||
cmds:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
[TestHandler_handleAttestationStarted/attesting_in_progress/task_line - 1]
|
||||
⠋ Creating a thing running a thing
|
||||
⠋ Creating a thing running a thing
|
||||
---
|
||||
|
||||
[TestHandler_handleAttestationStarted/attesting_in_progress/log - 1]
|
||||
@@ -11,7 +11,7 @@
|
||||
---
|
||||
|
||||
[TestHandler_handleAttestationStarted/attesting_complete/task_line - 1]
|
||||
✔ Created a thing running a thing
|
||||
✔ Created a thing running a thing
|
||||
---
|
||||
|
||||
[TestHandler_handleAttestationStarted/attesting_complete/log - 1]
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
|
||||
[TestHandler_handleFetchImage/fetch_image_in_progress - 1]
|
||||
⠋ Loading image ━━━━━━━━━━━━━━━━━━━━ [current] the-image
|
||||
⠋ Loading image ━━━━━━━━━━━━━━━━━━━━ [current] the-image
|
||||
---
|
||||
|
||||
[TestHandler_handleFetchImage/fetch_image_complete - 1]
|
||||
✔ Loaded image the-image
|
||||
✔ Loaded image the-image
|
||||
---
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
|
||||
[TestHandler_handleFileIndexingStarted/cataloging_in_progress - 1]
|
||||
⠋ Indexing file system ━━━━━━━━━━━━━━━━━━━━ [current] /some/path
|
||||
⠋ Indexing file system ━━━━━━━━━━━━━━━━━━━━ [current] /some/path
|
||||
---
|
||||
|
||||
[TestHandler_handleFileIndexingStarted/cataloging_complete - 1]
|
||||
✔ Indexed file system /some/path
|
||||
✔ Indexed file system /some/path
|
||||
---
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
|
||||
[TestHandler_handlePullSourceStarted/snap_download_in_progress - 1]
|
||||
⠋ Downloading snap file... ━━━━━━━━━━━━━━━━━━━━ example-app_1.0_amd64.snap
|
||||
⠋ Downloading snap file... ━━━━━━━━━━━━━━━━━━━━ example-app_1.0_amd64.snap
|
||||
---
|
||||
|
||||
[TestHandler_handlePullSourceStarted/snap_download_complete - 1]
|
||||
✔ Snap downloaded successfully example-app_1.0_amd64.snap
|
||||
✔ Snap downloaded successfully example-app_1.0_amd64.snap
|
||||
---
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
|
||||
[TestHandler_handleReadImage/read_image_in_progress - 1]
|
||||
⠋ Parsing image ━━━━━━━━━━━━━━━━━━━━ id
|
||||
⠋ Parsing image ━━━━━━━━━━━━━━━━━━━━ id
|
||||
---
|
||||
|
||||
[TestHandler_handleReadImage/read_image_complete - 1]
|
||||
✔ Parsed image id
|
||||
✔ Parsed image id
|
||||
---
|
||||
|
||||
@@ -158,6 +158,11 @@ func (l *backgroundLineReader) read(reader io.Reader, stage *progress.Stage) {
|
||||
|
||||
l.lock.Unlock()
|
||||
}
|
||||
if err := s.Err(); err != nil {
|
||||
log.WithFields("error", err).Debug("unable to read attestation output")
|
||||
// keep draining so the writer never blocks on a full pipe
|
||||
_, _ = io.Copy(io.Discard, reader)
|
||||
}
|
||||
}
|
||||
|
||||
func (l backgroundLineReader) Lines() []string {
|
||||
|
||||
@@ -60,6 +60,7 @@ type (
|
||||
Packages []detectorPackageInfo `json:"packages,omitempty"`
|
||||
Comment string `json:"comment,omitempty"`
|
||||
PackageTypes []string `json:"package_types,omitempty"`
|
||||
PURLTypes []string `json:"purl_types,omitempty"`
|
||||
JSONSchemaTypes []string `json:"json_schema_types,omitempty"`
|
||||
Capabilities capabilities.CapabilitySet `json:"capabilities,omitempty"`
|
||||
}
|
||||
@@ -235,7 +236,7 @@ func renderCatalogerInfoJSON(doc *capabilities.Document, catalogers []capabiliti
|
||||
// if no parsers, use detectors instead
|
||||
if len(info.Patterns) == 0 {
|
||||
info.Capabilities = cat.Capabilities
|
||||
info.Patterns = convertDetectorsToPatterns(cat.Detectors, cat.PackageTypes, cat.JSONSchemaTypes)
|
||||
info.Patterns = convertDetectorsToPatterns(cat.Detectors, cat.PackageTypes, cat.PURLTypes, cat.JSONSchemaTypes)
|
||||
}
|
||||
|
||||
info.Config = getConfigInfoFromDocument(doc, cat.Config)
|
||||
@@ -278,6 +279,7 @@ func convertParsersToPatterns(parsers []capabilities.Parser) []patternInfo {
|
||||
Packages: convertDetectorPackages(parser.Detector.Packages),
|
||||
Comment: parser.Detector.Comment,
|
||||
PackageTypes: parser.PackageTypes,
|
||||
PURLTypes: parser.PURLTypes,
|
||||
JSONSchemaTypes: parser.JSONSchemaTypes,
|
||||
Capabilities: parser.Capabilities,
|
||||
})
|
||||
@@ -286,7 +288,7 @@ func convertParsersToPatterns(parsers []capabilities.Parser) []patternInfo {
|
||||
}
|
||||
|
||||
// convertDetectorsToPatterns converts detector entries to pattern info for JSON output (for non-parser catalogers)
|
||||
func convertDetectorsToPatterns(detectors []capabilities.Detector, packageTypes, jsonSchemaTypes []string) []patternInfo {
|
||||
func convertDetectorsToPatterns(detectors []capabilities.Detector, packageTypes, purlTypes, jsonSchemaTypes []string) []patternInfo {
|
||||
var patterns []patternInfo
|
||||
for _, det := range detectors {
|
||||
patterns = append(patterns, patternInfo{
|
||||
@@ -296,6 +298,7 @@ func convertDetectorsToPatterns(detectors []capabilities.Detector, packageTypes,
|
||||
Packages: convertDetectorPackages(det.Packages),
|
||||
Comment: det.Comment,
|
||||
PackageTypes: packageTypes,
|
||||
PURLTypes: purlTypes,
|
||||
JSONSchemaTypes: jsonSchemaTypes,
|
||||
})
|
||||
}
|
||||
@@ -346,14 +349,14 @@ func renderCatalogerInfoTable(_ *capabilities.Document, catalogers []capabilitie
|
||||
})),
|
||||
tablewriter.WithConfig(tablewriter.Config{
|
||||
Row: tw.CellConfig{
|
||||
Formatting: tw.CellFormatting{MergeMode: tw.MergeHierarchical},
|
||||
Alignment: tw.CellAlignment{Global: tw.AlignLeft},
|
||||
Merging: tw.CellMerging{Mode: tw.MergeHierarchical},
|
||||
Alignment: tw.CellAlignment{Global: tw.AlignLeft},
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
// set headers
|
||||
table.Header("ECOSYSTEM", "CATALOGER", "CRITERIA", "LICENSE", "NODES", "EDGES", "KINDS", "LISTING", "DIGESTS", "HASH")
|
||||
table.Header("ECOSYSTEM", "CATALOGER", "CRITERIA", "PURL", "LICENSE", "NODES", "EDGES", "KINDS", "LISTING", "DIGESTS", "HASH")
|
||||
|
||||
// build rows for each cataloger
|
||||
var data [][]string
|
||||
@@ -367,13 +370,13 @@ func renderCatalogerInfoTable(_ *capabilities.Document, catalogers []capabilitie
|
||||
// generic catalogers: one row per parser
|
||||
for _, parser := range cat.Parsers {
|
||||
criteria := formatCriteria([]capabilities.Detector{parser.Detector})
|
||||
row := buildTableRowFromCapabilities(ecosystem, cat.Name, criteria, parser.Capabilities)
|
||||
row := buildTableRowFromCapabilities(ecosystem, cat.Name, criteria, parser.PURLTypes, parser.Capabilities)
|
||||
data = append(data, row)
|
||||
}
|
||||
} else {
|
||||
// custom catalogers: one row with all detectors
|
||||
criteria := formatCriteria(cat.Detectors)
|
||||
row := buildTableRowFromCapabilities(ecosystem, cat.Name, criteria, cat.Capabilities)
|
||||
row := buildTableRowFromCapabilities(ecosystem, cat.Name, criteria, cat.PURLTypes, cat.Capabilities)
|
||||
data = append(data, row)
|
||||
}
|
||||
}
|
||||
@@ -385,7 +388,7 @@ func renderCatalogerInfoTable(_ *capabilities.Document, catalogers []capabilitie
|
||||
}
|
||||
|
||||
// buildTableRowFromCapabilities builds a table row from capability values
|
||||
func buildTableRowFromCapabilities(ecosystem, name, criteria string, caps capabilities.CapabilitySet) []string {
|
||||
func buildTableRowFromCapabilities(ecosystem, name, criteria string, purlTypes []string, caps capabilities.CapabilitySet) []string {
|
||||
// extract capability default values
|
||||
license := extractBoolCapability(caps, "license")
|
||||
nodes := extractNodesCapability(caps)
|
||||
@@ -399,6 +402,7 @@ func buildTableRowFromCapabilities(ecosystem, name, criteria string, caps capabi
|
||||
ecosystem,
|
||||
name,
|
||||
criteria,
|
||||
formatPURLTypes(purlTypes),
|
||||
license,
|
||||
nodes,
|
||||
edges,
|
||||
@@ -409,6 +413,14 @@ func buildTableRowFromCapabilities(ecosystem, name, criteria string, caps capabi
|
||||
}
|
||||
}
|
||||
|
||||
// formatPURLTypes renders the PURL types as a comma-separated list, or a placeholder when empty
|
||||
func formatPURLTypes(purlTypes []string) string {
|
||||
if len(purlTypes) == 0 {
|
||||
return noStyle.Render("·")
|
||||
}
|
||||
return strings.Join(purlTypes, ", ")
|
||||
}
|
||||
|
||||
// extractBoolCapability extracts a boolean capability value and formats it
|
||||
func extractBoolCapability(caps capabilities.CapabilitySet, name string) string {
|
||||
for _, cap := range caps {
|
||||
|
||||
@@ -280,12 +280,15 @@ func Test_catalogerInfoReport(t *testing.T) {
|
||||
Detectors: []capabilities.Detector{
|
||||
{Method: capabilities.GlobDetection, Criteria: []string{"**/*.test"}},
|
||||
},
|
||||
PURLTypes: []string{"npm"},
|
||||
},
|
||||
},
|
||||
assertions: func(t *testing.T, got string) {
|
||||
assert.Contains(t, got, "test-cataloger")
|
||||
assert.Contains(t, got, "ECOSYSTEM")
|
||||
assert.Contains(t, got, "CATALOGER")
|
||||
assert.Contains(t, got, "PURL")
|
||||
assert.Contains(t, got, "npm")
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -298,12 +301,17 @@ func Test_catalogerInfoReport(t *testing.T) {
|
||||
Ecosystem: "test",
|
||||
Type: "custom",
|
||||
Selectors: []string{"test", "custom"},
|
||||
Detectors: []capabilities.Detector{
|
||||
{Method: capabilities.GlobDetection, Criteria: []string{"**/*.test"}},
|
||||
},
|
||||
PURLTypes: []string{"npm"},
|
||||
},
|
||||
},
|
||||
assertions: func(t *testing.T, got string) {
|
||||
assert.Contains(t, got, `"name":"test-cataloger"`)
|
||||
assert.Contains(t, got, `"ecosystem":"test"`)
|
||||
assert.Contains(t, got, `"type":"custom"`)
|
||||
assert.Contains(t, got, `"purl_types":["npm"]`)
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,24 +1,34 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/anchore/clio"
|
||||
"github.com/anchore/go-homedir"
|
||||
"github.com/anchore/syft/cmd/syft/internal/options"
|
||||
"github.com/anchore/syft/cmd/syft/internal/ui"
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/internal/bus"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/spillbuf"
|
||||
"github.com/anchore/syft/internal/tmpdir"
|
||||
"github.com/anchore/syft/syft/format"
|
||||
"github.com/anchore/syft/syft/format/syftjson"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
)
|
||||
|
||||
const (
|
||||
convertExample = ` {{.appName}} {{.command}} img.syft.json -o spdx-json convert a syft SBOM to spdx-json, output goes to stdout
|
||||
{{.appName}} {{.command}} img.syft.json -o cyclonedx-json=img.cdx.json convert a syft SBOM to CycloneDX, output is written to the file "img.cdx.json"
|
||||
{{.appName}} {{.command}} - -o spdx-json convert an SBOM from STDIN to spdx-json
|
||||
SYFT_CONVERT_PASSTHROUGH_EXACT_FORMAT=true {{.appName}} {{.command}} img.syft.json -o syft-json=out.syft.json copy the input to "out.syft.json" unchanged if it is already syft-json at the current schema version
|
||||
`
|
||||
)
|
||||
|
||||
@@ -26,6 +36,7 @@ type ConvertOptions struct {
|
||||
options.Config `yaml:",inline" mapstructure:",squash"`
|
||||
options.Output `yaml:",inline" mapstructure:",squash"`
|
||||
options.UpdateCheck `yaml:",inline" mapstructure:",squash"`
|
||||
Convert options.Convert `yaml:"convert" json:"convert" mapstructure:"convert"`
|
||||
}
|
||||
|
||||
func Convert(app clio.Application) *cobra.Command {
|
||||
@@ -34,6 +45,7 @@ func Convert(app clio.Application) *cobra.Command {
|
||||
opts := &ConvertOptions{
|
||||
UpdateCheck: options.DefaultUpdateCheck(),
|
||||
Output: options.DefaultOutput(),
|
||||
Convert: options.DefaultConvert(),
|
||||
}
|
||||
|
||||
return app.SetupCommand(&cobra.Command{
|
||||
@@ -46,11 +58,11 @@ func Convert(app clio.Application) *cobra.Command {
|
||||
}),
|
||||
Args: validateConvertArgs,
|
||||
PreRunE: applicationUpdateCheck(id, &opts.UpdateCheck),
|
||||
RunE: func(_ *cobra.Command, args []string) error {
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
restoreStdout := ui.CaptureStdoutToTraceLog()
|
||||
defer restoreStdout()
|
||||
|
||||
return RunConvert(opts, args[0])
|
||||
return RunConvert(cmd.Context(), opts, args[0])
|
||||
},
|
||||
}, opts)
|
||||
}
|
||||
@@ -59,30 +71,63 @@ func validateConvertArgs(cmd *cobra.Command, args []string) error {
|
||||
return validateArgs(cmd, args, "an SBOM argument is required")
|
||||
}
|
||||
|
||||
func RunConvert(opts *ConvertOptions, userInput string) error {
|
||||
func RunConvert(ctx context.Context, opts *ConvertOptions, userInput string) error {
|
||||
log.Warn("convert is an experimental feature, run `syft convert -h` for help")
|
||||
|
||||
writer, err := opts.SBOMWriter()
|
||||
// a temp dir is only needed when STDIN is a pipe and has to be spilled to disk; it is created lazily on
|
||||
// first use (under the user's TMPDIR), so a file input never touches it. Mirrors syft.CreateSBOM, which
|
||||
// also honors a TempDir already attached to the context.
|
||||
td := tmpdir.FromContext(ctx)
|
||||
if td == nil {
|
||||
_, td = tmpdir.Root(ctx, "syft-convert")
|
||||
defer func() {
|
||||
_ = td.Cleanup()
|
||||
}()
|
||||
}
|
||||
|
||||
reader, inputInfo, err := openConvertInput(userInput, td)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = reader.Close()
|
||||
}()
|
||||
|
||||
var reader io.ReadSeekCloser
|
||||
outputs := opts.Outputs
|
||||
var unchanged []string
|
||||
if opts.Convert.PassthroughExactFormat {
|
||||
log.Warn("convert.passthrough-exact-format is enabled: syft-json input that already exactly matches a requested output format and schema version will be copied to that output unchanged (this does not apply to any other input format)")
|
||||
|
||||
if userInput == "-" {
|
||||
// though os.Stdin is an os.File, it does not support seeking
|
||||
// you will get errors such as "seek /dev/stdin: illegal seek".
|
||||
// We need to buffer what we read.
|
||||
reader = internal.NewBufferedSeeker(os.Stdin)
|
||||
} else {
|
||||
f, err := os.Open(userInput)
|
||||
unchanged, outputs, err = partitionOutputsBySourceFormat(opts.Output, reader)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open SBOM file: %w", err)
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = f.Close()
|
||||
}()
|
||||
reader = f
|
||||
}
|
||||
|
||||
// resolve (and validate) every output that needs converting before writing anything, so that
|
||||
// a bad -o value fails the command without leaving partial results behind
|
||||
var writer sbom.Writer
|
||||
if len(outputs) > 0 {
|
||||
outputOpts := opts.Output
|
||||
outputOpts.Outputs = outputs
|
||||
writer, err = outputOpts.SBOMWriter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, output := range unchanged {
|
||||
if err := writeUnchanged(output, opts.LegacyFile, inputInfo, reader); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if writer == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, err := reader.Seek(0, io.SeekStart); err != nil {
|
||||
return fmt.Errorf("unable to seek to start of SBOM: %w", err)
|
||||
}
|
||||
|
||||
s, _, _, err := format.Decode(reader)
|
||||
@@ -100,3 +145,171 @@ func RunConvert(opts *ConvertOptions, userInput string) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// openConvertInput opens the SBOM document at the given path, or STDIN when "-". The returned reader is seekable so
|
||||
// the document can be identified, copied, and decoded in turn without being loaded into memory. The returned
|
||||
// os.FileInfo describes the file the document is being read from, so that writing back over it can be recognized;
|
||||
// it is nil when the input is not a file (a pipe or a terminal on STDIN).
|
||||
func openConvertInput(userInput string, td *tmpdir.TempDir) (io.ReadSeekCloser, os.FileInfo, error) {
|
||||
if userInput == "-" {
|
||||
return openStdin(td)
|
||||
}
|
||||
|
||||
f, err := os.Open(userInput)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to open SBOM file: %w", err)
|
||||
}
|
||||
|
||||
info, err := f.Stat()
|
||||
if err != nil {
|
||||
// the document is readable, we just cannot recognize it as an output destination
|
||||
log.WithFields("error", err, "path", userInput).Debug("unable to stat SBOM file")
|
||||
}
|
||||
return f, info, nil
|
||||
}
|
||||
|
||||
// openStdin returns a seekable view of STDIN. When STDIN is a regular file (such as a shell redirect) it is read in
|
||||
// place. A pipe or terminal cannot seek (you will get errors such as "seek /dev/stdin: illegal seek"), so its
|
||||
// contents are drained into a spill buffer: a small head stays in memory and everything beyond it goes to a temp
|
||||
// file under td, so an arbitrarily large piped SBOM never has to be held in memory to be made seekable.
|
||||
func openStdin(td *tmpdir.TempDir) (io.ReadSeekCloser, os.FileInfo, error) {
|
||||
if info, err := os.Stdin.Stat(); err == nil && info.Mode().IsRegular() {
|
||||
if start, err := os.Stdin.Seek(0, io.SeekCurrent); err == nil {
|
||||
// a redirect reads the file in place, so it is just as much an input file as a path argument is
|
||||
return readSeekNopCloser{io.NewSectionReader(os.Stdin, start, info.Size()-start)}, info, nil
|
||||
}
|
||||
}
|
||||
|
||||
buf := spillbuf.New(td)
|
||||
if _, err := io.Copy(io.NewOffsetWriter(buf, 0), os.Stdin); err != nil {
|
||||
_ = buf.Close()
|
||||
return nil, nil, fmt.Errorf("failed to read SBOM from STDIN: %w", err)
|
||||
}
|
||||
|
||||
return &spilledReader{
|
||||
SectionReader: io.NewSectionReader(buf, 0, buf.Size()),
|
||||
buf: buf,
|
||||
}, nil, nil
|
||||
}
|
||||
|
||||
type readSeekNopCloser struct {
|
||||
io.ReadSeeker
|
||||
}
|
||||
|
||||
func (readSeekNopCloser) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// spilledReader is a seekable view over a spill buffer; closing it releases the buffer's memory and spill file.
|
||||
type spilledReader struct {
|
||||
*io.SectionReader
|
||||
buf *spillbuf.Buffer
|
||||
}
|
||||
|
||||
func (r *spilledReader) Close() error {
|
||||
return r.buf.Close()
|
||||
}
|
||||
|
||||
// partitionOutputsBySourceFormat splits the requested outputs (in "<format>[@<version>][=<path>]" form) into those
|
||||
// that exactly match the format and schema version of a syft-json input document (which can be written out
|
||||
// unchanged) and those that still require conversion. Input in any other format is always converted. Outputs that
|
||||
// cannot be resolved to an encoder are left for the writer to report on.
|
||||
func partitionOutputsBySourceFormat(output options.Output, reader io.ReadSeeker) (unchanged []string, toConvert []string, err error) {
|
||||
if _, err := reader.Seek(0, io.SeekStart); err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to seek to start of SBOM: %w", err)
|
||||
}
|
||||
|
||||
id, version := format.Identify(reader)
|
||||
if id == "" || version == "" {
|
||||
// let decoding surface the appropriate error
|
||||
return nil, output.Outputs, nil
|
||||
}
|
||||
|
||||
if id != syftjson.ID {
|
||||
log.WithFields("format", id, "version", version).Debug("input is not syft-json, passthrough does not apply")
|
||||
return nil, output.Outputs, nil
|
||||
}
|
||||
|
||||
encoders, err := output.Encoders()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
collection := format.NewEncoderCollection(encoders...)
|
||||
|
||||
for _, out := range output.Outputs {
|
||||
name, _, _ := strings.Cut(strings.TrimSpace(out), "=")
|
||||
enc := collection.GetByString(name)
|
||||
if enc != nil && enc.ID() == id && enc.Version() == version {
|
||||
log.WithFields("format", id, "version", version, "output", out).Warn("input already exactly matches the requested output format and schema version, copying it unchanged instead of converting it")
|
||||
unchanged = append(unchanged, out)
|
||||
continue
|
||||
}
|
||||
toConvert = append(toConvert, out)
|
||||
}
|
||||
|
||||
return unchanged, toConvert, nil
|
||||
}
|
||||
|
||||
// writeUnchanged copies the SBOM document as-is to the destination described by the given "<format>[=<path>]"
|
||||
// output value, falling back to the (deprecated) --file path, and finally to the report bus (STDOUT). When the
|
||||
// destination is the input file itself there is nothing to do.
|
||||
func writeUnchanged(output string, defaultFile string, inputInfo os.FileInfo, reader io.ReadSeeker) error {
|
||||
if _, err := reader.Seek(0, io.SeekStart); err != nil {
|
||||
return fmt.Errorf("unable to seek to start of SBOM: %w", err)
|
||||
}
|
||||
|
||||
_, path, _ := strings.Cut(strings.TrimSpace(output), "=")
|
||||
if path == "" {
|
||||
path = defaultFile
|
||||
}
|
||||
|
||||
if path == "" {
|
||||
// the report bus only carries strings, so STDOUT output must be materialized in memory
|
||||
content, err := io.ReadAll(reader) //nolint:gocritic // the whole SBOM is the report; there is no meaningful bound
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to read SBOM: %w", err)
|
||||
}
|
||||
bus.Report(string(content))
|
||||
return nil
|
||||
}
|
||||
|
||||
expandedPath, err := homedir.Expand(path)
|
||||
if err != nil {
|
||||
log.Warnf("could not expand given writer output path=%q: %w", path, err)
|
||||
expandedPath = path
|
||||
}
|
||||
|
||||
if isSameFile(inputInfo, expandedPath) {
|
||||
log.WithFields("path", expandedPath).Info("output is the input file, leaving it as-is")
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(filepath.Dir(expandedPath), 0755); err != nil {
|
||||
return fmt.Errorf("unable to create report directory: %w", err)
|
||||
}
|
||||
|
||||
f, err := os.OpenFile(expandedPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create report file: %w", err)
|
||||
}
|
||||
|
||||
if _, err := io.Copy(f, reader); err != nil {
|
||||
_ = f.Close()
|
||||
return fmt.Errorf("unable to write report file: %w", err)
|
||||
}
|
||||
|
||||
return f.Close()
|
||||
}
|
||||
|
||||
// isSameFile reports whether the given path names the file the input is being read from. Comparing the input's
|
||||
// os.FileInfo rather than its path covers a shell redirect onto STDIN, which has no path to compare.
|
||||
func isSameFile(inputInfo os.FileInfo, path string) bool {
|
||||
if inputInfo == nil {
|
||||
return false
|
||||
}
|
||||
pathInfo, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return os.SameFile(inputInfo, pathInfo)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,448 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/wagoodman/go-partybus"
|
||||
|
||||
"github.com/anchore/syft/cmd/syft/internal/options"
|
||||
"github.com/anchore/syft/internal/bus"
|
||||
"github.com/anchore/syft/internal/spillbuf"
|
||||
"github.com/anchore/syft/syft/event"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/format"
|
||||
"github.com/anchore/syft/syft/format/cyclonedxjson"
|
||||
"github.com/anchore/syft/syft/format/spdxjson"
|
||||
"github.com/anchore/syft/syft/format/syftjson"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func convertTestSBOM() sbom.SBOM {
|
||||
catalog := pkg.NewCollection()
|
||||
catalog.Add(pkg.Package{
|
||||
Name: "musl-utils",
|
||||
Version: "1.2.3-r4",
|
||||
Type: pkg.ApkPkg,
|
||||
FoundBy: "apk-db-cataloger",
|
||||
Locations: file.NewLocationSet(
|
||||
file.NewLocation("/lib/apk/db/installed"),
|
||||
),
|
||||
PURL: "pkg:apk/alpine/musl-utils@1.2.3-r4",
|
||||
})
|
||||
|
||||
return sbom.SBOM{
|
||||
Artifacts: sbom.Artifacts{
|
||||
Packages: catalog,
|
||||
},
|
||||
Source: source.Description{
|
||||
ID: "some-id",
|
||||
Name: "some-dir",
|
||||
Version: "latest",
|
||||
Metadata: source.DirectoryMetadata{
|
||||
Path: "/some/path",
|
||||
},
|
||||
},
|
||||
Descriptor: sbom.Descriptor{
|
||||
Name: "syft",
|
||||
Version: "v0.0.0-test",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func mustConvertEncoder(enc sbom.FormatEncoder, err error) sbom.FormatEncoder {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return enc
|
||||
}
|
||||
|
||||
func encodeConvertTestSBOM(t *testing.T, enc sbom.FormatEncoder) []byte {
|
||||
buf := &bytes.Buffer{}
|
||||
require.NoError(t, enc.Encode(buf, convertTestSBOM()))
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// prettySyftJSON returns the test SBOM as pretty-printed syft-json. Pretty input makes a verbatim copy
|
||||
// distinguishable from a re-encode, since re-encoding always produces compact output here.
|
||||
func prettySyftJSON(t *testing.T) []byte {
|
||||
cfg := syftjson.DefaultEncoderConfig()
|
||||
cfg.Pretty = true
|
||||
return encodeConvertTestSBOM(t, mustConvertEncoder(syftjson.NewFormatEncoderWithConfig(cfg)))
|
||||
}
|
||||
|
||||
func Test_partitionOutputsBySourceFormat(t *testing.T) {
|
||||
syftEncoder := mustConvertEncoder(syftjson.NewFormatEncoderWithConfig(syftjson.DefaultEncoderConfig()))
|
||||
syftVersion := syftEncoder.Version()
|
||||
syftContent := encodeConvertTestSBOM(t, syftEncoder)
|
||||
|
||||
// the same document claiming an older schema version
|
||||
olderSyftContent := bytes.ReplaceAll(syftContent, []byte(`"version":"`+syftVersion+`"`), []byte(`"version":"16.0.0"`))
|
||||
require.NotEqual(t, syftContent, olderSyftContent)
|
||||
|
||||
cdxContent := encodeConvertTestSBOM(t, mustConvertEncoder(cyclonedxjson.NewFormatEncoderWithConfig(cyclonedxjson.DefaultEncoderConfig())))
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
content []byte
|
||||
outputs []string
|
||||
wantUnchanged []string
|
||||
wantToConvert []string
|
||||
}{
|
||||
{
|
||||
name: "syft-json matches the bare format name",
|
||||
content: syftContent,
|
||||
outputs: []string{"syft-json"},
|
||||
wantUnchanged: []string{"syft-json"},
|
||||
},
|
||||
{
|
||||
name: "syft-json matches its alias",
|
||||
content: syftContent,
|
||||
outputs: []string{"json"},
|
||||
wantUnchanged: []string{"json"},
|
||||
},
|
||||
{
|
||||
name: "syft-json matches an explicit version and file path",
|
||||
content: syftContent,
|
||||
outputs: []string{"syft-json@" + syftVersion + "=out/some.syft.json"},
|
||||
wantUnchanged: []string{"syft-json@" + syftVersion + "=out/some.syft.json"},
|
||||
},
|
||||
{
|
||||
name: "syft-json at an older schema version requires conversion",
|
||||
content: olderSyftContent,
|
||||
outputs: []string{"syft-json"},
|
||||
wantToConvert: []string{"syft-json"},
|
||||
},
|
||||
{
|
||||
name: "a different output format requires conversion",
|
||||
content: syftContent,
|
||||
outputs: []string{"spdx-json"},
|
||||
wantToConvert: []string{"spdx-json"},
|
||||
},
|
||||
{
|
||||
name: "mixed outputs are partitioned",
|
||||
content: syftContent,
|
||||
outputs: []string{"syft-json=a.json", "spdx-json=b.json", "cyclonedx-xml"},
|
||||
wantUnchanged: []string{"syft-json=a.json"},
|
||||
wantToConvert: []string{"spdx-json=b.json", "cyclonedx-xml"},
|
||||
},
|
||||
{
|
||||
name: "unknown format is left for the writer to report",
|
||||
content: syftContent,
|
||||
outputs: []string{"bogus"},
|
||||
wantToConvert: []string{"bogus"},
|
||||
},
|
||||
{
|
||||
name: "non syft-json input is always converted, even when it matches the output",
|
||||
content: cdxContent,
|
||||
outputs: []string{"cyclonedx-json"},
|
||||
wantToConvert: []string{"cyclonedx-json"},
|
||||
},
|
||||
{
|
||||
name: "unidentifiable content is left for the decoder to report",
|
||||
content: []byte("definitely not an sbom"),
|
||||
outputs: []string{"syft-json"},
|
||||
wantToConvert: []string{"syft-json"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
output := options.DefaultOutput()
|
||||
output.Outputs = tt.outputs
|
||||
|
||||
unchanged, toConvert, err := partitionOutputsBySourceFormat(output, bytes.NewReader(tt.content))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tt.wantUnchanged, unchanged)
|
||||
assert.Equal(t, tt.wantToConvert, toConvert)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_RunConvert_passthroughExactFormat(t *testing.T) {
|
||||
syftContent := prettySyftJSON(t)
|
||||
|
||||
newOpts := func(passthrough bool, outputs ...string) *ConvertOptions {
|
||||
opts := &ConvertOptions{
|
||||
Output: options.DefaultOutput(),
|
||||
Convert: options.Convert{
|
||||
PassthroughExactFormat: passthrough,
|
||||
},
|
||||
}
|
||||
opts.Outputs = outputs
|
||||
return opts
|
||||
}
|
||||
|
||||
writeInput := func(t *testing.T, content []byte) string {
|
||||
input := filepath.Join(t.TempDir(), "input.json")
|
||||
require.NoError(t, os.WriteFile(input, content, 0600))
|
||||
return input
|
||||
}
|
||||
|
||||
t.Run("matching output is copied unchanged while others are converted", func(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
syftOut := filepath.Join(dir, "out.syft.json")
|
||||
spdxOut := filepath.Join(dir, "out.spdx.json")
|
||||
|
||||
opts := newOpts(true, "syft-json="+syftOut, "spdx-json="+spdxOut)
|
||||
require.NoError(t, RunConvert(context.Background(), opts, writeInput(t, syftContent)))
|
||||
|
||||
got, err := os.ReadFile(syftOut)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, syftContent, got)
|
||||
|
||||
spdx, err := os.ReadFile(spdxOut)
|
||||
require.NoError(t, err)
|
||||
id, _ := format.Identify(bytes.NewReader(spdx))
|
||||
assert.Equal(t, spdxjson.ID, id)
|
||||
})
|
||||
|
||||
t.Run("STDIN input is copied unchanged while others are converted", func(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
syftOut := filepath.Join(dir, "out.syft.json")
|
||||
spdxOut := filepath.Join(dir, "out.spdx.json")
|
||||
|
||||
stdin, err := os.Open(writeInput(t, syftContent))
|
||||
require.NoError(t, err)
|
||||
defer stdin.Close()
|
||||
|
||||
originalStdin := os.Stdin
|
||||
os.Stdin = stdin
|
||||
t.Cleanup(func() { os.Stdin = originalStdin })
|
||||
|
||||
opts := newOpts(true, "syft-json="+syftOut, "spdx-json="+spdxOut)
|
||||
require.NoError(t, RunConvert(context.Background(), opts, "-"))
|
||||
|
||||
got, err := os.ReadFile(syftOut)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, syftContent, got)
|
||||
|
||||
spdx, err := os.ReadFile(spdxOut)
|
||||
require.NoError(t, err)
|
||||
id, _ := format.Identify(bytes.NewReader(spdx))
|
||||
assert.Equal(t, spdxjson.ID, id)
|
||||
})
|
||||
|
||||
t.Run("piped STDIN input is copied unchanged while others are converted", func(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
syftOut := filepath.Join(dir, "out.syft.json")
|
||||
spdxOut := filepath.Join(dir, "out.spdx.json")
|
||||
|
||||
pipeReader, pipeWriter, err := os.Pipe()
|
||||
require.NoError(t, err)
|
||||
defer pipeReader.Close()
|
||||
go func() {
|
||||
defer pipeWriter.Close()
|
||||
_, _ = pipeWriter.Write(syftContent)
|
||||
}()
|
||||
|
||||
originalStdin := os.Stdin
|
||||
os.Stdin = pipeReader
|
||||
t.Cleanup(func() { os.Stdin = originalStdin })
|
||||
|
||||
opts := newOpts(true, "syft-json="+syftOut, "spdx-json="+spdxOut)
|
||||
require.NoError(t, RunConvert(context.Background(), opts, "-"))
|
||||
|
||||
got, err := os.ReadFile(syftOut)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, syftContent, got)
|
||||
|
||||
spdx, err := os.ReadFile(spdxOut)
|
||||
require.NoError(t, err)
|
||||
id, _ := format.Identify(bytes.NewReader(spdx))
|
||||
assert.Equal(t, spdxjson.ID, id)
|
||||
})
|
||||
|
||||
t.Run("without the option the matching output is re-encoded", func(t *testing.T) {
|
||||
syftOut := filepath.Join(t.TempDir(), "out.syft.json")
|
||||
|
||||
opts := newOpts(false, "syft-json="+syftOut)
|
||||
require.NoError(t, RunConvert(context.Background(), opts, writeInput(t, syftContent)))
|
||||
|
||||
got, err := os.ReadFile(syftOut)
|
||||
require.NoError(t, err)
|
||||
id, _ := format.Identify(bytes.NewReader(got))
|
||||
assert.Equal(t, syftjson.ID, id)
|
||||
// the input was pretty-printed and the encoder is not configured to be, so a re-encode cannot reproduce it
|
||||
assert.NotEqual(t, syftContent, got)
|
||||
})
|
||||
|
||||
t.Run("non syft-json input is re-encoded even when it matches the output", func(t *testing.T) {
|
||||
cdxContent := encodeConvertTestSBOM(t, mustConvertEncoder(cyclonedxjson.NewFormatEncoderWithConfig(cyclonedxjson.DefaultEncoderConfig())))
|
||||
cdxOut := filepath.Join(t.TempDir(), "out.cdx.json")
|
||||
|
||||
opts := newOpts(true, "cyclonedx-json="+cdxOut)
|
||||
require.NoError(t, RunConvert(context.Background(), opts, writeInput(t, cdxContent)))
|
||||
|
||||
got, err := os.ReadFile(cdxOut)
|
||||
require.NoError(t, err)
|
||||
id, _ := format.Identify(bytes.NewReader(got))
|
||||
assert.Equal(t, cyclonedxjson.ID, id)
|
||||
// a fresh serial number is minted on every encode, so a round trip never reproduces the input
|
||||
assert.NotEqual(t, cdxContent, got)
|
||||
})
|
||||
|
||||
t.Run("the deprecated --file path is honored", func(t *testing.T) {
|
||||
syftOut := filepath.Join(t.TempDir(), "out.syft.json")
|
||||
|
||||
opts := newOpts(true, "syft-json")
|
||||
opts.LegacyFile = syftOut
|
||||
require.NoError(t, RunConvert(context.Background(), opts, writeInput(t, syftContent)))
|
||||
|
||||
got, err := os.ReadFile(syftOut)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, syftContent, got)
|
||||
})
|
||||
|
||||
t.Run("writing over the input file leaves it intact", func(t *testing.T) {
|
||||
input := writeInput(t, syftContent)
|
||||
|
||||
opts := newOpts(true, "syft-json="+input)
|
||||
require.NoError(t, RunConvert(context.Background(), opts, input))
|
||||
|
||||
got, err := os.ReadFile(input)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, syftContent, got)
|
||||
})
|
||||
|
||||
t.Run("writing over a redirected STDIN input leaves it intact", func(t *testing.T) {
|
||||
input := writeInput(t, syftContent)
|
||||
|
||||
stdin, err := os.Open(input)
|
||||
require.NoError(t, err)
|
||||
defer stdin.Close()
|
||||
|
||||
originalStdin := os.Stdin
|
||||
os.Stdin = stdin
|
||||
t.Cleanup(func() { os.Stdin = originalStdin })
|
||||
|
||||
opts := newOpts(true, "syft-json="+input)
|
||||
require.NoError(t, RunConvert(context.Background(), opts, "-"))
|
||||
|
||||
got, err := os.ReadFile(input)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, syftContent, got)
|
||||
})
|
||||
|
||||
t.Run("an invalid output fails before anything is written", func(t *testing.T) {
|
||||
syftOut := filepath.Join(t.TempDir(), "out.syft.json")
|
||||
|
||||
opts := newOpts(true, "syft-json="+syftOut, "bogus")
|
||||
require.Error(t, RunConvert(context.Background(), opts, writeInput(t, syftContent)))
|
||||
|
||||
assert.NoFileExists(t, syftOut)
|
||||
})
|
||||
|
||||
t.Run("a missing input file is reported", func(t *testing.T) {
|
||||
syftOut := filepath.Join(t.TempDir(), "out.syft.json")
|
||||
|
||||
opts := newOpts(true, "syft-json="+syftOut)
|
||||
err := RunConvert(context.Background(), opts, filepath.Join(t.TempDir(), "does-not-exist.json"))
|
||||
require.ErrorContains(t, err, "failed to open SBOM file")
|
||||
|
||||
assert.NoFileExists(t, syftOut)
|
||||
})
|
||||
|
||||
t.Run("unrecognized input is left to the decoder to reject", func(t *testing.T) {
|
||||
syftOut := filepath.Join(t.TempDir(), "out.syft.json")
|
||||
|
||||
opts := newOpts(true, "syft-json="+syftOut)
|
||||
err := RunConvert(context.Background(), opts, writeInput(t, []byte("definitely not an sbom")))
|
||||
require.ErrorContains(t, err, "failed to decode SBOM")
|
||||
})
|
||||
}
|
||||
|
||||
// largeSyftJSON returns a pretty-printed syft-json document of at least minBytes, built from many small packages.
|
||||
func largeSyftJSON(t *testing.T, minBytes int64) []byte {
|
||||
catalog := pkg.NewCollection()
|
||||
for i := 0; i < int(minBytes/100); i++ {
|
||||
catalog.Add(pkg.Package{
|
||||
Name: fmt.Sprintf("pkg-%d", i),
|
||||
Version: "1.0.0",
|
||||
Type: pkg.ApkPkg,
|
||||
PURL: fmt.Sprintf("pkg:apk/alpine/pkg-%d@1.0.0", i),
|
||||
})
|
||||
}
|
||||
s := convertTestSBOM()
|
||||
s.Artifacts.Packages = catalog
|
||||
|
||||
cfg := syftjson.DefaultEncoderConfig()
|
||||
cfg.Pretty = true
|
||||
enc := mustConvertEncoder(syftjson.NewFormatEncoderWithConfig(cfg))
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
require.NoError(t, enc.Encode(buf, s))
|
||||
require.Greater(t, int64(buf.Len()), minBytes)
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
func Test_RunConvert_pipedInputLargerThanMemoryLimitSpillsToDisk(t *testing.T) {
|
||||
// a piped document bigger than the spill buffer's in-memory tier is written to a temp file under TMPDIR;
|
||||
// the conversion must identify, copy and clean up from there rather than holding the document in memory
|
||||
dir := t.TempDir()
|
||||
syftOut := filepath.Join(dir, "out.syft.json")
|
||||
spillRoot := filepath.Join(dir, "spill")
|
||||
require.NoError(t, os.Mkdir(spillRoot, 0o755))
|
||||
t.Setenv("TMPDIR", spillRoot)
|
||||
|
||||
content := largeSyftJSON(t, 2*spillbuf.DefaultMemLimit)
|
||||
|
||||
pipeReader, pipeWriter, err := os.Pipe()
|
||||
require.NoError(t, err)
|
||||
defer pipeReader.Close()
|
||||
go func() {
|
||||
defer pipeWriter.Close()
|
||||
_, _ = pipeWriter.Write(content)
|
||||
}()
|
||||
|
||||
originalStdin := os.Stdin
|
||||
os.Stdin = pipeReader
|
||||
t.Cleanup(func() { os.Stdin = originalStdin })
|
||||
|
||||
opts := &ConvertOptions{
|
||||
Output: options.DefaultOutput(),
|
||||
Convert: options.Convert{
|
||||
PassthroughExactFormat: true,
|
||||
},
|
||||
}
|
||||
opts.Outputs = []string{"syft-json=" + syftOut}
|
||||
require.NoError(t, RunConvert(context.Background(), opts, "-"))
|
||||
|
||||
got, err := os.ReadFile(syftOut)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, content, got)
|
||||
|
||||
entries, err := os.ReadDir(spillRoot)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, entries, "the spill directory must be removed once the conversion finishes")
|
||||
}
|
||||
|
||||
func Test_writeUnchanged_stdout(t *testing.T) {
|
||||
// with no output path the document is published on the report bus, which is how the CLI writes to STDOUT
|
||||
b := partybus.NewBus()
|
||||
subscription := b.Subscribe()
|
||||
bus.Set(b)
|
||||
t.Cleanup(func() {
|
||||
bus.Set(nil)
|
||||
})
|
||||
|
||||
content := []byte(`{"some":"document"}`)
|
||||
require.NoError(t, writeUnchanged("syft-json", "", nil, bytes.NewReader(content)))
|
||||
|
||||
select {
|
||||
case e := <-subscription.Events():
|
||||
assert.Equal(t, event.CLIReport, e.Type)
|
||||
assert.Equal(t, string(content), e.Value)
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("no report was published")
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -171,6 +172,11 @@ func validateArgs(cmd *cobra.Command, args []string, err string) error {
|
||||
}
|
||||
|
||||
func runScan(ctx context.Context, id clio.Identification, opts *scanOptions, userInput string) error {
|
||||
scanStart := time.Now()
|
||||
defer func() {
|
||||
log.Infof("scan completed in %s", time.Since(scanStart).Round(time.Millisecond))
|
||||
}()
|
||||
|
||||
writer, err := opts.SBOMWriter()
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"github.com/anchore/syft/syft/file/cataloger/executable"
|
||||
"github.com/anchore/syft/syft/file/cataloger/filecontent"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/binary"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/cpp"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/dotnet"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/golang"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/java"
|
||||
@@ -44,6 +45,7 @@ type Catalog struct {
|
||||
Enrich []string `yaml:"enrich" json:"enrich" mapstructure:"enrich"`
|
||||
|
||||
// ecosystem-specific cataloger configuration
|
||||
Cpp cppConfig `yaml:"cpp" json:"cpp" mapstructure:"cpp"`
|
||||
Dotnet dotnetConfig `yaml:"dotnet" json:"dotnet" mapstructure:"dotnet"`
|
||||
Golang golangConfig `yaml:"golang" json:"golang" mapstructure:"golang"`
|
||||
Java javaConfig `yaml:"java" json:"java" mapstructure:"java"`
|
||||
@@ -80,6 +82,7 @@ func DefaultCatalog() Catalog {
|
||||
JavaScript: defaultJavaScriptConfig(),
|
||||
Python: defaultPythonConfig(),
|
||||
Nix: defaultNixConfig(),
|
||||
Cpp: defaultCppConfig(),
|
||||
Dotnet: defaultDotnetConfig(),
|
||||
Golang: defaultGolangConfig(),
|
||||
Java: defaultJavaConfig(),
|
||||
@@ -172,11 +175,15 @@ func (cfg Catalog) ToPackagesConfig() pkgcataloging.Config {
|
||||
}
|
||||
return pkgcataloging.Config{
|
||||
Binary: binary.DefaultClassifierCatalogerConfig(),
|
||||
Cpp: cpp.DefaultCatalogerConfig().
|
||||
WithVcpkgAllowGitClone(*multiLevelOption(false, enrichmentEnabled(cfg.Enrich, task.Cpp, task.Vcpkg), cfg.Cpp.VcpkgAllowGitClone)),
|
||||
Dotnet: dotnet.DefaultCatalogerConfig().
|
||||
WithDepPackagesMustHaveDLL(cfg.Dotnet.DepPackagesMustHaveDLL).
|
||||
WithDepPackagesMustClaimDLL(cfg.Dotnet.DepPackagesMustClaimDLL).
|
||||
WithPropagateDLLClaimsToParents(cfg.Dotnet.PropagateDLLClaimsToParents).
|
||||
WithRelaxDLLClaimsWhenBundlingDetected(cfg.Dotnet.RelaxDLLClaimsWhenBundlingDetected),
|
||||
WithRelaxDLLClaimsWhenBundlingDetected(cfg.Dotnet.RelaxDLLClaimsWhenBundlingDetected).
|
||||
WithExcludeProjectReferences(cfg.Dotnet.ExcludeProjectReferences),
|
||||
|
||||
Golang: golang.DefaultCatalogerConfig().
|
||||
WithSearchLocalModCacheLicenses(*multiLevelOption(false, enrichmentEnabled(cfg.Enrich, task.Go, task.Golang), cfg.Golang.SearchLocalModCacheLicenses)).
|
||||
WithLocalModCacheDir(cfg.Golang.LocalModCacheDir).
|
||||
@@ -191,7 +198,9 @@ func (cfg Catalog) ToPackagesConfig() pkgcataloging.Config {
|
||||
WithFromBuildSettings(cfg.Golang.MainModuleVersion.FromBuildSettings).
|
||||
WithFromLDFlags(cfg.Golang.MainModuleVersion.FromLDFlags),
|
||||
).
|
||||
WithUsePackagesLib(*multiLevelOption(true, enrichmentEnabled(cfg.Enrich, task.Go, task.Golang), cfg.Golang.UsePackagesLib)),
|
||||
WithUsePackagesLib(*multiLevelOption(true, enrichmentEnabled(cfg.Enrich, task.Go, task.Golang), cfg.Golang.UsePackagesLib)).
|
||||
WithCaptureSymbols(cfg.Golang.CaptureSymbols).
|
||||
WithCaptureSymbolsModules(cfg.Golang.CaptureSymbolsModules),
|
||||
JavaScript: javascript.DefaultCatalogerConfig().
|
||||
WithIncludeDevDependencies(*multiLevelOption(false, cfg.JavaScript.IncludeDevDependencies)).
|
||||
WithSearchRemoteLicenses(*multiLevelOption(false, enrichmentEnabled(cfg.Enrich, task.JavaScript, task.Node, task.NPM), cfg.JavaScript.SearchRemoteLicenses)).
|
||||
@@ -299,6 +308,7 @@ var publicisedEnrichmentOptions = []string{
|
||||
task.Java,
|
||||
task.JavaScript,
|
||||
task.Python,
|
||||
task.Vcpkg,
|
||||
}
|
||||
|
||||
func enrichmentEnabled(enrichDirectives []string, features ...string) *bool {
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package options
|
||||
|
||||
import (
|
||||
"github.com/anchore/clio"
|
||||
)
|
||||
|
||||
var _ clio.FieldDescriber = (*Convert)(nil)
|
||||
|
||||
// Convert holds the options specific to the convert command. These are deliberately configuration-only (config file
|
||||
// or environment) rather than flags: they change how the command behaves for every invocation in a pipeline and are
|
||||
// not expected to vary from one call to the next.
|
||||
type Convert struct {
|
||||
PassthroughExactFormat bool `yaml:"passthrough-exact-format" json:"passthrough-exact-format" mapstructure:"passthrough-exact-format"`
|
||||
}
|
||||
|
||||
func DefaultConvert() Convert {
|
||||
return Convert{
|
||||
PassthroughExactFormat: false,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Convert) DescribeFields(descriptions clio.FieldDescriptionSet) {
|
||||
descriptions.Add(&o.PassthroughExactFormat, `when a syft-json input SBOM already exactly matches the requested output format and schema version, copy it to that output unchanged instead of decoding and re-encoding it.
|
||||
Note: this only applies to syft-json input, and only when the document's schema version is identical to the schema version this version of syft produces. Any other input is always converted.
|
||||
Note: output written this way is a byte-for-byte copy of the input, so format options (such as 'format.pretty' or 'format.json.legacy') are not applied to it.
|
||||
Note: to keep memory use minimal on large documents, give the input as a file path (or a shell redirect) and write the output with '-o <format>=<path>'. Piped input, or output to STDOUT, requires the whole document to be held in memory.`)
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package options
|
||||
|
||||
import (
|
||||
"github.com/anchore/clio"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/cpp"
|
||||
)
|
||||
|
||||
type cppConfig struct {
|
||||
VcpkgAllowGitClone *bool `yaml:"vcpkg-allow-git-clone" json:"vcpkg-allow-git-clone" mapstructure:"vcpkg-allow-git-clone"`
|
||||
}
|
||||
|
||||
func defaultCppConfig() cppConfig {
|
||||
// reference the cataloger default so capability generation can associate this config with the cpp
|
||||
// ecosystem (it discovers ecosystem configs by their cataloger import). the value itself stays nil:
|
||||
// nil defaults to false (no network), and leaving it unset lets --enrich opt in. cloning requires a
|
||||
// network connection, which must be opt-in.
|
||||
_ = cpp.DefaultCatalogerConfig()
|
||||
return cppConfig{
|
||||
VcpkgAllowGitClone: nil,
|
||||
}
|
||||
}
|
||||
|
||||
var _ interface {
|
||||
clio.FieldDescriber
|
||||
} = (*cppConfig)(nil)
|
||||
|
||||
func (o *cppConfig) DescribeFields(descriptions clio.FieldDescriptionSet) {
|
||||
descriptions.Add(&o.VcpkgAllowGitClone, `enables Syft to use clone remote repositories for vcpkg custom git registries.
|
||||
(also useful if the builtin vcpkg registry is not cloned locally)`)
|
||||
}
|
||||
@@ -13,12 +13,10 @@ func DefaultFormatSPDXJSON() FormatSPDXJSON {
|
||||
}
|
||||
|
||||
func (o FormatSPDXJSON) config(v string) spdxjson.EncoderConfig {
|
||||
var pretty bool
|
||||
c := spdxjson.DefaultEncoderConfig()
|
||||
c.Version = v
|
||||
if o.Pretty != nil {
|
||||
pretty = *o.Pretty
|
||||
}
|
||||
return spdxjson.EncoderConfig{
|
||||
Version: v,
|
||||
Pretty: pretty,
|
||||
c.Pretty = *o.Pretty
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package options
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestFormatSPDXJSON_buildConfig(t *testing.T) {
|
||||
@@ -11,5 +13,7 @@ func TestFormatSPDXJSON_buildConfig(t *testing.T) {
|
||||
ft = setAllToNonZero(t, ft).(*FormatSPDXJSON)
|
||||
|
||||
subject := ft.config("Version")
|
||||
assertExpectedValue(t, subject)
|
||||
require.Equal(t, "Version", subject.Version)
|
||||
require.Equal(t, "2.3", subject.DefaultVersion)
|
||||
require.True(t, subject.Pretty)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/clio"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/cataloging"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/golang"
|
||||
)
|
||||
|
||||
@@ -17,10 +19,13 @@ type golangConfig struct {
|
||||
NoProxy string `json:"no-proxy" yaml:"no-proxy" mapstructure:"no-proxy"`
|
||||
MainModuleVersion golangMainModuleVersionConfig `json:"main-module-version" yaml:"main-module-version" mapstructure:"main-module-version"`
|
||||
UsePackagesLib *bool `json:"use-packages-lib" yaml:"use-packages-lib" mapstructure:"use-packages-lib"`
|
||||
CaptureSymbols cataloging.SymbolScope `json:"capture-symbols" yaml:"capture-symbols" mapstructure:"capture-symbols"`
|
||||
CaptureSymbolsModules []string `json:"capture-symbols-modules" yaml:"capture-symbols-modules" mapstructure:"capture-symbols-modules"`
|
||||
}
|
||||
|
||||
var _ interface {
|
||||
clio.FieldDescriber
|
||||
clio.PostLoader
|
||||
} = (*golangConfig)(nil)
|
||||
|
||||
func (o *golangConfig) DescribeFields(descriptions clio.FieldDescriptionSet) {
|
||||
@@ -39,12 +44,44 @@ if unset this defaults to $GONOPROXY`)
|
||||
always show (devel) as the version. Use these options to control heuristics to guess
|
||||
a more accurate version from the binary.`)
|
||||
descriptions.Add(&o.UsePackagesLib, `use the golang.org/x/tools/go/packages library, which executes golang tooling found on the path in addition to potential network access to get the most accurate results`)
|
||||
// note: descriptions must be static string literals; the app config discovery that generates the
|
||||
// capability docs reads them straight out of the AST
|
||||
descriptions.Add(&o.CaptureSymbols, `capture function symbols from the binary symbol table (pclntab). valid values are:
|
||||
"none" (disabled), "stdlib" (only the synthetic stdlib package), "extended-stdlib" (stdlib plus every
|
||||
module under golang.org/x/), and "all" (all module packages plus stdlib)`)
|
||||
descriptions.Add(&o.CaptureSymbolsModules, `glob patterns matched against go module paths (e.g. github.com/klauspost/**) that should have symbols
|
||||
captured in addition to whatever capture-symbols selects. ** crosses path separators, * does not.
|
||||
a trailing major version suffix is ignored when matching, so github.com/foo/* covers github.com/foo/bar/v2;
|
||||
spelling a suffix out in the pattern selects only that major version.
|
||||
this can only widen the selection, never narrow it, and is inert when capture-symbols is none`)
|
||||
descriptions.Add(&o.MainModuleVersion.FromLDFlags, `look for LD flags that appear to be setting a version (e.g. -X main.version=1.0.0)`)
|
||||
descriptions.Add(&o.MainModuleVersion.FromBuildSettings, `use the build settings (e.g. vcs.version & vcs.time) to craft a v0 pseudo version
|
||||
descriptions.Add(&o.MainModuleVersion.FromBuildSettings, `use the build settings (e.g. vcs.version & vcs.time) to craft a v0 pseudo version
|
||||
(e.g. v0.0.0-20220308212642-53e6d0aaf6fb) when a more accurate version cannot be found otherwise`)
|
||||
descriptions.Add(&o.MainModuleVersion.FromContents, `search for semver-like strings in the binary contents`)
|
||||
}
|
||||
|
||||
func (o *golangConfig) PostLoad() error {
|
||||
raw := strings.TrimSpace(string(o.CaptureSymbols))
|
||||
o.CaptureSymbols = o.CaptureSymbols.Parse()
|
||||
|
||||
// an unrecognized value still resolves to "none", but say so rather than silently capturing nothing.
|
||||
// stay quiet for unset and an explicit "none", which is the default and would otherwise warn on nearly
|
||||
// every scan.
|
||||
if o.CaptureSymbols == cataloging.SymbolScopeNone && raw != "" && !strings.EqualFold(raw, string(cataloging.SymbolScopeNone)) {
|
||||
log.Warnf("unknown golang.capture-symbols value %q, defaulting to %q (valid values: none, stdlib, extended-stdlib, all)", raw, cataloging.SymbolScopeNone)
|
||||
}
|
||||
|
||||
// trim only, deliberately not Flatten: viper already splits a comma-separated scalar (env var or a bare
|
||||
// yaml string) into a slice before this point, and there is no CLI flag feeding this key. The one thing
|
||||
// Flatten would add is splitting commas *inside* a list entry, which silently breaks doublestar brace
|
||||
// alternation like github.com/{foo,bar}/**. Viper's split does not trim, so that part is still needed.
|
||||
for i, pattern := range o.CaptureSymbolsModules {
|
||||
o.CaptureSymbolsModules[i] = strings.TrimSpace(pattern)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type golangMainModuleVersionConfig struct {
|
||||
FromLDFlags bool `json:"from-ld-flags" yaml:"from-ld-flags" mapstructure:"from-ld-flags"`
|
||||
FromContents bool `json:"from-contents" yaml:"from-contents" mapstructure:"from-contents"`
|
||||
@@ -66,6 +103,8 @@ func defaultGolangConfig() golangConfig {
|
||||
FromContents: def.MainModuleVersion.FromContents,
|
||||
FromBuildSettings: def.MainModuleVersion.FromBuildSettings,
|
||||
},
|
||||
UsePackagesLib: nil, // this defaults to true, which is the API default
|
||||
UsePackagesLib: nil, // this defaults to true, which is the API default
|
||||
CaptureSymbols: def.CaptureSymbols,
|
||||
CaptureSymbolsModules: def.CaptureSymbolsModules,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
package options
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/anchore/syft/syft/cataloging"
|
||||
)
|
||||
|
||||
func Test_golangConfig_PostLoad(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
cfg golangConfig
|
||||
expected cataloging.SymbolScope
|
||||
expectedModules []string
|
||||
wantErr assert.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "normalize all",
|
||||
cfg: golangConfig{CaptureSymbols: "all"},
|
||||
expected: cataloging.SymbolScopeAll,
|
||||
},
|
||||
{
|
||||
name: "normalize stdlib",
|
||||
cfg: golangConfig{CaptureSymbols: "stdlib"},
|
||||
expected: cataloging.SymbolScopeStdlib,
|
||||
},
|
||||
{
|
||||
name: "normalize extended-stdlib",
|
||||
cfg: golangConfig{CaptureSymbols: " Extended-Stdlib "},
|
||||
expected: cataloging.SymbolScopeExtendedStdlib,
|
||||
},
|
||||
{
|
||||
name: "module patterns keep embedded commas",
|
||||
cfg: golangConfig{
|
||||
CaptureSymbols: "stdlib",
|
||||
// brace alternation contains a comma; splitting on it would corrupt the pattern
|
||||
CaptureSymbolsModules: []string{"github.com/{foo,bar}/**", "golang.org/x/**"},
|
||||
},
|
||||
expected: cataloging.SymbolScopeStdlib,
|
||||
expectedModules: []string{"github.com/{foo,bar}/**", "golang.org/x/**"},
|
||||
},
|
||||
{
|
||||
// viper splits a comma-separated scalar (env var or bare yaml string) but does not trim,
|
||||
// so a leading space would otherwise survive into a pattern that silently matches nothing
|
||||
name: "module patterns are trimmed",
|
||||
cfg: golangConfig{
|
||||
CaptureSymbols: "stdlib",
|
||||
CaptureSymbolsModules: []string{"golang.org/x/**", " github.com/foo/** "},
|
||||
},
|
||||
expected: cataloging.SymbolScopeStdlib,
|
||||
expectedModules: []string{"golang.org/x/**", "github.com/foo/**"},
|
||||
},
|
||||
{
|
||||
name: "empty defaults to none",
|
||||
cfg: golangConfig{CaptureSymbols: ""},
|
||||
expected: cataloging.SymbolScopeNone,
|
||||
},
|
||||
{
|
||||
name: "invalid value defaults to none",
|
||||
cfg: golangConfig{CaptureSymbols: "stdlbi"},
|
||||
expected: cataloging.SymbolScopeNone,
|
||||
},
|
||||
{
|
||||
name: "boolean spellings default to none",
|
||||
cfg: golangConfig{CaptureSymbols: "true"},
|
||||
expected: cataloging.SymbolScopeNone,
|
||||
},
|
||||
{
|
||||
name: "explicit none resolves to none",
|
||||
cfg: golangConfig{CaptureSymbols: "none"},
|
||||
expected: cataloging.SymbolScopeNone,
|
||||
},
|
||||
{
|
||||
name: "explicit none is not case sensitive",
|
||||
cfg: golangConfig{CaptureSymbols: " NONE "},
|
||||
expected: cataloging.SymbolScopeNone,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.wantErr == nil {
|
||||
tt.wantErr = assert.NoError
|
||||
}
|
||||
err := tt.cfg.PostLoad()
|
||||
tt.wantErr(t, err)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
assert.Equal(t, tt.expected, tt.cfg.CaptureSymbols)
|
||||
assert.Equal(t, tt.expectedModules, tt.cfg.CaptureSymbolsModules)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -89,6 +89,8 @@ func TestPkgCoverageImage(t *testing.T) {
|
||||
definedPkgs.Remove(string(pkg.PhpPeclPkg)) // we have coverage for pear instead
|
||||
definedPkgs.Remove(string(pkg.CondaPkg))
|
||||
definedPkgs.Remove(string(pkg.ModelPkg))
|
||||
definedPkgs.Remove(string(pkg.VcpkgPkg))
|
||||
definedPkgs.Remove(string(pkg.AppleAppBundlePkg))
|
||||
|
||||
var cases []testCase
|
||||
cases = append(cases, commonTestCases...)
|
||||
@@ -164,6 +166,8 @@ func TestPkgCoverageDirectory(t *testing.T) {
|
||||
definedPkgs.Remove(string(pkg.CondaPkg))
|
||||
definedPkgs.Remove(string(pkg.PhpPeclPkg)) // this is covered as pear packages
|
||||
definedPkgs.Remove(string(pkg.ModelPkg))
|
||||
definedPkgs.Remove(string(pkg.VcpkgPkg))
|
||||
definedPkgs.Remove(string(pkg.AppleAppBundlePkg))
|
||||
|
||||
// for directory scans we should not expect to see any of the following package types
|
||||
definedPkgs.Remove(string(pkg.KbPkg))
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/stereoscope/pkg/image"
|
||||
"github.com/anchore/stereoscope/pkg/imagetest"
|
||||
"github.com/anchore/syft/syft"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
// multiplatformImage is busybox:1.38.0 pinned by index digest, since docker hub periodically rebuilds and
|
||||
// re-pushes official image tags, which changes the per-platform digests asserted below.
|
||||
const multiplatformImage = "docker.io/library/busybox@sha256:fd7dc98638c8e305f4dc34e979f1c0fdfdcaeb0fbf8fcff77ae834b6da3d7e6e"
|
||||
|
||||
// TestMultiPlatformOCIImageSelection verifies that syft can load a single platform out of a
|
||||
// multi-platform OCI image on disk (both --from oci-dir and --from oci-archive) by selecting the
|
||||
// image that matches the requested platform.
|
||||
func TestMultiPlatformOCIImageSelection(t *testing.T) {
|
||||
// Per-platform image config digests within the multi-platform index, obtained from the OCI layout
|
||||
// (see anchore/stereoscope integration tests: TestPlatformSelectionWithOciLocalSources).
|
||||
expectedDigest := map[string]string{
|
||||
"arm64": "sha256:d2482869a6b838d3b4c9cad0c8085c06277909482771eb525d298fc3a7d07927",
|
||||
"s390x": "sha256:e55a0fa55032cceac2d983f4b4a25b28e42303b46a2f1915fc0fa45051e6ca70",
|
||||
"amd64": "sha256:aaef90e065235eb0b2d9938be85d1b81add2902cf89affe2cdfa19be33476ac0",
|
||||
"ppc64le": "sha256:8158829b015d1c5285d63d54d37e95998ef0af4177e3f416678decbcaff3170c",
|
||||
}
|
||||
|
||||
// syft --from source tag -> stereoscope OCI source used to prepare the local fixture
|
||||
sources := map[string]image.Source{
|
||||
"oci-dir": image.OciDirectorySource,
|
||||
"oci-archive": image.OciTarballSource,
|
||||
}
|
||||
|
||||
for from, imageSource := range sources {
|
||||
t.Run(from, func(t *testing.T) {
|
||||
localPath := imagetest.PrepareMultiplatformFixtureImage(t, imageSource, multiplatformImage)
|
||||
for _, arch := range []string{"amd64", "arm64", "s390x", "ppc64le"} {
|
||||
t.Run(fmt.Sprintf("linux/%s", arch), func(t *testing.T) {
|
||||
platform, err := image.NewPlatform("linux/" + arch)
|
||||
require.NoError(t, err)
|
||||
|
||||
src, err := syft.GetSource(
|
||||
context.Background(),
|
||||
localPath,
|
||||
syft.DefaultGetSourceConfig().WithSources(from).WithPlatform(platform),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
require.NoError(t, src.Close())
|
||||
})
|
||||
|
||||
meta, ok := src.Describe().Metadata.(source.ImageMetadata)
|
||||
require.True(t, ok, "expected image metadata, got %T", src.Describe().Metadata)
|
||||
|
||||
// The raw config of the selected image must match the requested platform...
|
||||
assertConfigPlatform(t, meta.RawConfig, "linux", arch)
|
||||
assert.Equal(t, "linux", meta.OS)
|
||||
assert.Equal(t, arch, meta.Architecture)
|
||||
// ...and it must be the exact per-platform image from the multi-platform index.
|
||||
assert.Equal(t, expectedDigest[arch], meta.ID)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestMultiPlatformOCIImageSelection_UnavailablePlatform verifies that requesting a platform not present
|
||||
// in the multi-platform OCI image results in an error rather than silently selecting the wrong image.
|
||||
func TestMultiPlatformOCIImageSelection_UnavailablePlatform(t *testing.T) {
|
||||
// windows/amd64 is not present in this linux-only multi-platform image
|
||||
platform, err := image.NewPlatform("windows/amd64")
|
||||
require.NoError(t, err)
|
||||
|
||||
sources := map[string]image.Source{
|
||||
"oci-dir": image.OciDirectorySource,
|
||||
"oci-archive": image.OciTarballSource,
|
||||
}
|
||||
|
||||
for from, imageSource := range sources {
|
||||
t.Run(from, func(t *testing.T) {
|
||||
localPath := imagetest.PrepareMultiplatformFixtureImage(t, imageSource, multiplatformImage)
|
||||
|
||||
_, err := syft.GetSource(
|
||||
context.Background(),
|
||||
localPath,
|
||||
syft.DefaultGetSourceConfig().WithSources(from).WithPlatform(platform),
|
||||
)
|
||||
require.ErrorContains(t, err, "windows/amd64")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestMultiPlatformOCIImageSelection_DefaultPlatform verifies that not specifying a platform results
|
||||
// in the current platform being selected.
|
||||
func TestMultiPlatformOCIImageSelection_DefaultPlatform(t *testing.T) {
|
||||
sources := map[string]image.Source{
|
||||
"oci-dir": image.OciDirectorySource,
|
||||
"oci-archive": image.OciTarballSource,
|
||||
}
|
||||
|
||||
for from, imageSource := range sources {
|
||||
t.Run(from, func(t *testing.T) {
|
||||
localPath := imagetest.PrepareMultiplatformFixtureImage(t, imageSource, multiplatformImage)
|
||||
|
||||
src, err := syft.GetSource(
|
||||
context.Background(),
|
||||
localPath,
|
||||
syft.DefaultGetSourceConfig().WithSources(from),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
require.NoError(t, src.Close())
|
||||
})
|
||||
|
||||
meta, ok := src.Describe().Metadata.(source.ImageMetadata)
|
||||
require.True(t, ok, "expected image metadata, got %T", src.Describe().Metadata)
|
||||
|
||||
// The raw config of the selected image must match the current platform
|
||||
assertConfigPlatform(t, meta.RawConfig, "linux", runtime.GOARCH)
|
||||
assert.Equal(t, "linux", meta.OS)
|
||||
assert.Equal(t, runtime.GOARCH, meta.Architecture)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// assertConfigPlatform asserts the os/architecture recorded in a raw OCI image config document.
|
||||
func assertConfigPlatform(t *testing.T, rawConfig []byte, os, architecture string) {
|
||||
t.Helper()
|
||||
require.NotEmpty(t, rawConfig)
|
||||
|
||||
var cfg struct {
|
||||
OS string `json:"os"`
|
||||
Architecture string `json:"architecture"`
|
||||
}
|
||||
require.NoError(t, json.Unmarshal(rawConfig, &cfg))
|
||||
|
||||
assert.Equal(t, os, cfg.OS)
|
||||
assert.Equal(t, architecture, cfg.Architecture)
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/stereoscope/pkg/image"
|
||||
"github.com/anchore/stereoscope/pkg/imagetest"
|
||||
"github.com/anchore/syft/syft"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
// TestSinglePlatformOCIImage covers the single-image (non-index) OCI layouts, where syft used to report
|
||||
// empty os/architecture metadata and silently accept any --platform. Both now come from the image config.
|
||||
func TestSinglePlatformOCIImage(t *testing.T) {
|
||||
// a platform this fixture is never built for, so the mismatch is not host dependent
|
||||
unavailablePlatform, err := image.NewPlatform("linux/ppc64le")
|
||||
require.NoError(t, err)
|
||||
|
||||
sources := map[string]image.Source{
|
||||
"oci-dir": image.OciDirectorySource,
|
||||
"oci-archive": image.OciTarballSource,
|
||||
}
|
||||
|
||||
for from, imageSource := range sources {
|
||||
t.Run(from, func(t *testing.T) {
|
||||
localPath := strings.TrimPrefix(imagetest.PrepareFixtureImage(t, imageSource, "image-distro-id"), string(imageSource)+":")
|
||||
|
||||
t.Run("reports the platform from the image config", func(t *testing.T) {
|
||||
src, err := syft.GetSource(
|
||||
context.Background(),
|
||||
localPath,
|
||||
syft.DefaultGetSourceConfig().WithSources(from),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
require.NoError(t, src.Close())
|
||||
})
|
||||
|
||||
meta, ok := src.Describe().Metadata.(source.ImageMetadata)
|
||||
require.True(t, ok, "expected image metadata, got %T", src.Describe().Metadata)
|
||||
|
||||
// the fixture is built for whatever the local docker daemon runs, so assert against the
|
||||
// config rather than a fixed platform
|
||||
assert.NotEmpty(t, meta.OS)
|
||||
assert.NotEmpty(t, meta.Architecture)
|
||||
assertConfigPlatform(t, meta.RawConfig, meta.OS, meta.Architecture)
|
||||
})
|
||||
|
||||
t.Run("rejects a platform the image does not match", func(t *testing.T) {
|
||||
_, err := syft.GetSource(
|
||||
context.Background(),
|
||||
localPath,
|
||||
syft.DefaultGetSourceConfig().WithSources(from).WithPlatform(unavailablePlatform),
|
||||
)
|
||||
require.ErrorContains(t, err, "linux/ppc64le")
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -7,4 +7,6 @@
|
||||
!image-pkg-coverage/pkgs/java/*.hpi
|
||||
|
||||
**/go.sum
|
||||
!image-go-bin-arch-coverage/go.sum
|
||||
!image-go-bin-arch-coverage/go.sum
|
||||
|
||||
cache/
|
||||
+2
-3
@@ -8,8 +8,6 @@ RUN mkdir tmp
|
||||
|
||||
WORKDIR /usr/share/jenkins/tmp
|
||||
|
||||
RUN apt-get update 2>&1 > /dev/null && apt-get install -y less zip 2>&1 > /dev/null
|
||||
|
||||
RUN unzip ../jenkins.war 2>&1 > /dev/null
|
||||
|
||||
RUN rm -rf ./META-INF/MANIFEST.MF ./WEB-INF ./jsbundles ./scripts ./css
|
||||
@@ -18,7 +16,8 @@ WORKDIR /usr/share/jenkins
|
||||
|
||||
RUN rm -rf jenkins.war
|
||||
|
||||
RUN cd ./tmp && zip -r ../jenkins.war . && cd ..
|
||||
# use jar (from the JDK in the base image) to rezip -- M keeps it manifest-less, which is the point of this fixture
|
||||
RUN cd ./tmp && jar cfM ../jenkins.war . && cd ..
|
||||
|
||||
RUN rm -rf ./tmp
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ RUN unxz /lib/modules/6.0.7-301.fc37.x86_64/kernel/drivers/tty/ttynull.ko.xz
|
||||
# dotnet pkg coverage
|
||||
# https://nuget.info/packages/DocuSign.eSign.dll/6.8.0
|
||||
# https://github.com/docusign/docusign-esign-csharp-client/blob/master/LICENSE
|
||||
RUN curl -LO https://www.nuget.org/api/v2/package/DocuSign.eSign.dll/6.8.0
|
||||
RUN curl -fLO https://www.nuget.org/api/v2/package/DocuSign.eSign.dll/6.8.0
|
||||
RUN unzip 6.8.0
|
||||
RUN chmod 600 lib/net462/DocuSign.eSign.dll
|
||||
RUN rm 6.8.0
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
@@ -8,6 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/wagoodman/go-partybus"
|
||||
|
||||
"github.com/anchore/bubbly"
|
||||
@@ -52,7 +54,7 @@ func New(out io.Writer, quiet bool, handlers ...bubbly.EventHandler) *UI {
|
||||
func (m *UI) Setup(subscription partybus.Unsubscribable) error {
|
||||
// we still want to collect log messages, however, we also the logger shouldn't write to the screen directly
|
||||
if logWrapper, ok := log.Get().(logger.Controller); ok {
|
||||
logWrapper.SetOutput(m.frame.(*frame.Frame).Footer())
|
||||
logWrapper.SetOutput(&logFooter{w: m.frame.(*frame.Frame).Footer()})
|
||||
}
|
||||
|
||||
m.subscription = subscription
|
||||
@@ -100,7 +102,7 @@ func (m *UI) Teardown(force bool) error {
|
||||
})
|
||||
|
||||
// it may be tempting to use Kill() however it has been found that this can cause the terminal to be left in
|
||||
// a bad state (where Ctrl+C and other control characters no longer works for future processes in that terminal).
|
||||
// a bad state (where Ctrl+C and other control characters no longer work for future processes in that terminal).
|
||||
m.program.Quit()
|
||||
|
||||
_ = runWithTimeout(250*time.Millisecond, func() error {
|
||||
@@ -187,6 +189,48 @@ func (m UI) View() string {
|
||||
return m.frame.View()
|
||||
}
|
||||
|
||||
var (
|
||||
logTitleStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("8")) // dark grey
|
||||
// the first log line gets a branch pointing at it from the title, the rest are indented to match
|
||||
logBranch = logTitleStyle.Render("└─▶") + " "
|
||||
logIndent = " "
|
||||
)
|
||||
|
||||
// logFooter decorates log entries drawn below the TUI with a one-time title (written when the first entry arrives)
|
||||
// and a branch from the title to the first log line. Writes are serialized by the logger.
|
||||
type logFooter struct {
|
||||
w io.Writer
|
||||
started bool
|
||||
}
|
||||
|
||||
func (l *logFooter) Write(p []byte) (int, error) {
|
||||
var buf bytes.Buffer
|
||||
first := !l.started
|
||||
if first {
|
||||
buf.WriteString(logTitleStyle.Render("Logs (non-fatal, see -v for more):") + "\n")
|
||||
l.started = true
|
||||
}
|
||||
for i, line := range bytes.SplitAfter(p, []byte("\n")) {
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
if first && i == 0 {
|
||||
buf.WriteString(logBranch)
|
||||
} else {
|
||||
buf.WriteString(logIndent)
|
||||
}
|
||||
buf.Write(line)
|
||||
}
|
||||
if _, err := l.w.Write(buf.Bytes()); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
// IsTerminal marks the footer as terminal-bound (it is drawn by the TUI) so the logger keeps colors. NO_COLOR is
|
||||
// still honored by the formatter.
|
||||
func (*logFooter) IsTerminal() bool { return true }
|
||||
|
||||
func runWithTimeout(timeout time.Duration, fn func() error) (err error) {
|
||||
c := make(chan struct{}, 1)
|
||||
go func() {
|
||||
|
||||
+3
-1
@@ -5,8 +5,10 @@ This directory contains examples of how to use the Syft API.
|
||||
- `create_simple_sbom`: Create a simple SBOM from scratch
|
||||
- `create_custom_sbom`: Create an SBOM using as much custom configuration as possible, including a custom cataloger implementation
|
||||
- `decode_sbom`: Take an existing SBOM file (of arbitrary format) and decode it into a Syft SBOM object
|
||||
- `select_catalogers`: Example demonstrating how to select specific catalogers to run
|
||||
- `source_detection`: Shows how to detect what to catalog automatically from a user string (e.g. container image vs directory)
|
||||
- `source_from_image`: Construct a source from a only a container image
|
||||
- `source_from_image`: Construct a source from only a container image
|
||||
- `source_from_registry`: Construct a source directly from a container registry
|
||||
|
||||
You can run any of these examples from this directory with:
|
||||
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
module github.com/anchore/syft
|
||||
|
||||
go 1.25.8
|
||||
go 1.26.8
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.6.0
|
||||
github.com/CycloneDX/cyclonedx-go v0.10.0
|
||||
github.com/Masterminds/semver/v3 v3.4.0
|
||||
github.com/CycloneDX/cyclonedx-go v0.12.0
|
||||
github.com/Masterminds/semver/v3 v3.5.0
|
||||
github.com/Masterminds/sprig/v3 v3.3.0
|
||||
github.com/OneOfOne/xxhash v1.2.8
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
|
||||
github.com/acobaugh/osrelease v0.1.0
|
||||
github.com/adrg/xdg v0.5.3
|
||||
github.com/anchore/bubbly v0.2.0
|
||||
github.com/anchore/clio v0.1.0
|
||||
github.com/anchore/fangs v0.1.0
|
||||
github.com/anchore/go-collections v0.1.0
|
||||
github.com/anchore/go-homedir v0.1.0
|
||||
github.com/anchore/go-logger v0.1.0
|
||||
github.com/anchore/go-macholibre v0.1.0
|
||||
github.com/anchore/go-rpmdb v0.1.0
|
||||
github.com/anchore/go-sync v0.1.0
|
||||
github.com/anchore/bubbly v0.2.2
|
||||
github.com/anchore/clio v0.1.1
|
||||
github.com/anchore/fangs v0.1.1
|
||||
github.com/anchore/go-collections v0.1.1
|
||||
github.com/anchore/go-homedir v0.1.1
|
||||
github.com/anchore/go-logger v0.2.0
|
||||
github.com/anchore/go-macholibre v0.1.1
|
||||
github.com/anchore/go-rpmdb v0.2.0
|
||||
github.com/anchore/go-sync v0.1.2
|
||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b
|
||||
github.com/anchore/packageurl-go v0.2.0
|
||||
github.com/anchore/stereoscope v0.1.23
|
||||
github.com/anchore/stereoscope v0.3.2
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be
|
||||
github.com/aquasecurity/go-pep440-version v0.0.1
|
||||
github.com/bitnami/go-version v0.0.0-20250131085805-b1f57a8634ef
|
||||
@@ -34,39 +34,40 @@ require (
|
||||
github.com/charmbracelet/lipgloss v1.1.0
|
||||
github.com/dave/jennifer v1.7.1
|
||||
github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da
|
||||
github.com/diskfs/go-diskfs v1.7.0
|
||||
github.com/diskfs/go-diskfs v1.9.4
|
||||
github.com/distribution/reference v0.6.0
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
github.com/dustin/go-humanize v1.1.0
|
||||
github.com/elliotchance/phpserialize v1.4.0
|
||||
github.com/facebookincubator/nvdtools v0.1.5
|
||||
github.com/github/go-spdx/v2 v2.4.0
|
||||
github.com/gkampitakis/go-snaps v0.5.21
|
||||
github.com/go-git/go-billy/v5 v5.9.0
|
||||
github.com/go-git/go-git/v5 v5.19.0
|
||||
github.com/github/go-spdx/v2 v2.7.0
|
||||
github.com/gkampitakis/go-snaps v0.5.23
|
||||
github.com/go-git/go-billy/v5 v5.9.1
|
||||
github.com/go-git/go-git/v5 v5.19.2
|
||||
github.com/go-test/deep v1.1.1
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0
|
||||
github.com/goccy/go-yaml v1.19.2
|
||||
github.com/gohugoio/hashstructure v0.6.0
|
||||
github.com/google/go-cmp v0.7.0
|
||||
github.com/google/go-containerregistry v0.21.5
|
||||
github.com/google/go-containerregistry v0.22.1
|
||||
github.com/google/licensecheck v0.3.1
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gookit/color v1.6.0
|
||||
github.com/gpustack/gguf-parser-go v0.24.0
|
||||
github.com/gookit/color v1.6.1
|
||||
github.com/gpustack/gguf-parser-go v0.26.3
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2
|
||||
github.com/hashicorp/go-getter v1.8.6
|
||||
github.com/hashicorp/go-getter v1.8.9
|
||||
github.com/hashicorp/go-multierror v1.1.1
|
||||
github.com/hashicorp/hcl/v2 v2.24.0
|
||||
github.com/hashicorp/hcl/v2 v2.25.0
|
||||
github.com/iancoleman/strcase v0.3.0
|
||||
github.com/invopop/jsonschema v0.13.0
|
||||
github.com/jedib0t/go-pretty/v6 v6.7.8
|
||||
github.com/invopop/jsonschema v0.14.0
|
||||
github.com/jedib0t/go-pretty/v6 v6.8.3
|
||||
github.com/jinzhu/copier v0.4.0
|
||||
github.com/kastenhq/goversion v0.0.0-20230811215019-93b2f8823953
|
||||
github.com/magiconair/properties v1.8.10
|
||||
github.com/klauspost/compress v1.20.0
|
||||
github.com/magiconair/properties v1.18.12
|
||||
github.com/mholt/archives v0.1.5
|
||||
github.com/moby/sys/mountinfo v0.7.2
|
||||
github.com/nix-community/go-nix v0.0.0-20250101154619-4bdde671e0a1
|
||||
github.com/olekukonko/tablewriter v1.1.4
|
||||
github.com/olekukonko/tablewriter v1.1.5
|
||||
github.com/opencontainers/go-digest v1.0.0
|
||||
github.com/pelletier/go-toml v1.9.5
|
||||
github.com/quasilyte/go-ruleguard/dsl v0.3.23
|
||||
@@ -78,75 +79,78 @@ require (
|
||||
github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e
|
||||
github.com/sergi/go-diff v1.4.0
|
||||
github.com/spdx/gordf v0.0.0-20201111095634-7098f93598fb
|
||||
github.com/spdx/tools-golang v0.5.7
|
||||
github.com/spdx/tools-golang v0.6.0-rc4
|
||||
github.com/spf13/afero v1.15.0
|
||||
github.com/spf13/cobra v1.10.2
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/ulikunitz/xz v0.5.15
|
||||
github.com/stretchr/testify v1.12.1
|
||||
github.com/ulikunitz/xz v0.5.16
|
||||
github.com/vbatts/go-mtree v0.7.0
|
||||
github.com/vifraa/gopom v1.0.0
|
||||
github.com/wagoodman/go-partybus v0.0.0-20230516145632-8ccac152c651
|
||||
github.com/wagoodman/go-progress v0.0.0-20260303201901-10176f79b2c0
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8
|
||||
github.com/xeipuuv/gojsonschema v1.2.0
|
||||
github.com/zyedidia/generic v1.2.2-0.20230320175451-4410d2372cb1
|
||||
go.uber.org/goleak v1.3.0
|
||||
go.yaml.in/yaml/v3 v3.0.4
|
||||
go.yaml.in/yaml/v3 v3.0.5
|
||||
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f
|
||||
golang.org/x/mod v0.35.0
|
||||
golang.org/x/net v0.53.0
|
||||
golang.org/x/time v0.15.0
|
||||
golang.org/x/tools v0.44.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
modernc.org/sqlite v1.46.2
|
||||
golang.org/x/mod v0.41.0
|
||||
golang.org/x/net v0.59.0
|
||||
golang.org/x/time v0.16.0
|
||||
golang.org/x/tools v0.50.0
|
||||
modernc.org/sqlite v1.59.0
|
||||
)
|
||||
|
||||
require (
|
||||
cel.dev/expr v0.25.1 // indirect
|
||||
github.com/pb33f/ordered-map/v2 v2.3.1
|
||||
github.com/tailscale/hujson v0.0.0-20260302212456-ecc657c15afd
|
||||
howett.net/plist v1.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
cel.dev/expr v0.25.2 // indirect
|
||||
cloud.google.com/go v0.123.0 // indirect
|
||||
cloud.google.com/go/auth v0.18.2 // indirect
|
||||
cloud.google.com/go/auth v0.23.2 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.9.0 // indirect
|
||||
cloud.google.com/go/iam v1.5.3 // indirect
|
||||
cloud.google.com/go/monitoring v1.24.3 // indirect
|
||||
cloud.google.com/go/storage v1.61.3 // indirect
|
||||
cyphar.com/go-pathrs v0.2.1 // indirect
|
||||
cloud.google.com/go/iam v1.12.0 // indirect
|
||||
cloud.google.com/go/monitoring v1.30.0 // indirect
|
||||
cloud.google.com/go/storage v1.65.1 // indirect
|
||||
dario.cat/mergo v1.0.2 // indirect
|
||||
github.com/DataDog/zstd v1.5.5 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.33.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.57.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.57.0 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/Microsoft/hcsshim v0.14.0-rc.1 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.4.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.3-0.20251027160822-ad3df93bed29 // indirect
|
||||
github.com/Microsoft/hcsshim v0.15.0-rc.1 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.4.1 // indirect
|
||||
github.com/STARRY-S/zip v0.2.3 // indirect
|
||||
github.com/agext/levenshtein v1.2.1 // indirect
|
||||
github.com/anchore/go-lzo v0.1.0 // indirect
|
||||
github.com/anchore/go-struct-converter v0.1.0 // indirect
|
||||
github.com/anchore/go-lzo v0.1.1 // indirect
|
||||
github.com/anchore/go-struct-converter v0.2.0-rc2 // indirect
|
||||
github.com/andybalholm/brotli v1.2.0 // indirect
|
||||
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
|
||||
github.com/apparentlymart/go-textseg/v17 v17.0.1 // indirect
|
||||
github.com/aquasecurity/go-version v0.0.1 // indirect
|
||||
github.com/atotto/clipboard v0.1.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.12 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.12 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.8 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.13 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.9 // indirect
|
||||
github.com/aws/smithy-go v1.24.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.44.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.20 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.40 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.39 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.40 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.40 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.40 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.41 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.19 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.10.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.40 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.41 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.108.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.6.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.34.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.39.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.46.0 // indirect
|
||||
github.com/aws/smithy-go v1.28.1 // indirect
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||
github.com/becheran/wildmatch-go v1.0.0 // indirect
|
||||
@@ -155,59 +159,57 @@ require (
|
||||
github.com/bodgit/sevenzip v1.6.1 // indirect
|
||||
github.com/bodgit/windows v1.0.1 // indirect
|
||||
github.com/buger/jsonparser v1.1.2 // indirect
|
||||
github.com/charmbracelet/colorprofile v0.4.1 // indirect
|
||||
github.com/charmbracelet/colorprofile v0.4.3 // indirect
|
||||
github.com/charmbracelet/harmonica v0.2.0 // indirect
|
||||
github.com/charmbracelet/x/ansi v0.11.6 // indirect
|
||||
github.com/charmbracelet/x/cellbuf v0.0.15 // indirect
|
||||
github.com/charmbracelet/x/term v0.2.2 // indirect
|
||||
github.com/clipperhouse/displaywidth v0.10.0 // indirect
|
||||
github.com/clipperhouse/uax29/v2 v2.6.0 // indirect
|
||||
github.com/clipperhouse/displaywidth v0.11.0 // indirect
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
|
||||
github.com/cloudflare/circl v1.6.3 // indirect
|
||||
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 // indirect
|
||||
github.com/containerd/cgroups/v3 v3.1.2 // indirect
|
||||
github.com/containerd/containerd/api v1.10.0 // indirect
|
||||
github.com/containerd/containerd/v2 v2.2.2 // indirect
|
||||
github.com/containerd/continuity v0.4.5 // indirect
|
||||
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 // indirect
|
||||
github.com/containerd/cgroups/v3 v3.1.3 // indirect
|
||||
github.com/containerd/containerd/api v1.11.1 // indirect
|
||||
github.com/containerd/containerd/v2 v2.3.6 // indirect
|
||||
github.com/containerd/continuity v0.5.0 // indirect
|
||||
github.com/containerd/errdefs v1.0.0 // indirect
|
||||
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
||||
github.com/containerd/fifo v1.1.0 // indirect
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/containerd/platforms v1.0.0-rc.4 // indirect
|
||||
github.com/containerd/plugin v1.0.0 // indirect
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.18.2 // indirect
|
||||
github.com/containerd/ttrpc v1.2.7 // indirect
|
||||
github.com/containerd/platforms v1.0.0-rc.5 // indirect
|
||||
github.com/containerd/plugin v1.1.0 // indirect
|
||||
github.com/containerd/ttrpc v1.2.8 // indirect
|
||||
github.com/containerd/typeurl/v2 v2.2.3 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/docker/cli v29.4.0+incompatible // indirect
|
||||
github.com/docker/cli v29.8.0+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.9.5 // indirect
|
||||
github.com/docker/go-connections v0.6.0 // indirect
|
||||
github.com/docker/go-connections v0.8.1 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.36.0 // indirect
|
||||
github.com/envoyproxy/protoc-gen-validate v1.3.0 // indirect
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.37.0 // indirect
|
||||
github.com/envoyproxy/protoc-gen-validate v1.3.3 // indirect
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/fatih/color v1.19.0 // indirect
|
||||
github.com/felixge/fgprof v0.9.5 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
|
||||
github.com/gkampitakis/ciinfo v0.3.2 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.15 // indirect
|
||||
github.com/gkampitakis/ciinfo v0.3.4 // indirect
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.4 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/logr v1.4.4 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-restruct/restruct v1.2.0-alpha // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e // indirect
|
||||
github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3 // indirect
|
||||
github.com/google/s2a-go v0.1.9 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.14 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.17.0 // indirect
|
||||
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.72 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.20 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.24.0 // indirect
|
||||
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.74 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-version v1.8.0 // indirect
|
||||
github.com/hashicorp/go-version v1.9.0 // indirect
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||
github.com/henvic/httpretty v0.1.4 // indirect
|
||||
github.com/huandu/xstrings v1.5.0 // indirect
|
||||
@@ -215,18 +217,16 @@ require (
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||
github.com/klauspost/compress v1.18.5 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/klauspost/pgzip v1.2.6 // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/maruel/natural v1.1.1 // indirect
|
||||
github.com/maruel/natural v1.3.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-isatty v0.0.24 // indirect
|
||||
github.com/mattn/go-localereader v0.0.2-0.20220822084749-2491eb6c1c75 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.19 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.21 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||
github.com/mikelolasagasti/xz v1.0.1 // indirect
|
||||
github.com/minio/minlz v1.0.1 // indirect
|
||||
@@ -236,8 +236,8 @@ require (
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/moby/locker v1.0.1 // indirect
|
||||
github.com/moby/moby/api v1.54.1 // indirect
|
||||
github.com/moby/moby/client v0.4.0 // indirect
|
||||
github.com/moby/moby/api v1.56.0 // indirect
|
||||
github.com/moby/moby/client v0.6.0 // indirect
|
||||
github.com/moby/sys/sequential v0.6.0 // indirect
|
||||
github.com/moby/sys/signal v0.7.1 // indirect
|
||||
github.com/moby/sys/user v0.4.0 // indirect
|
||||
@@ -248,22 +248,22 @@ require (
|
||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||
github.com/muesli/termenv v0.16.0 // indirect
|
||||
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||
github.com/nwaples/rardecode/v2 v2.2.0 // indirect
|
||||
github.com/nwaples/rardecode/v2 v2.4.1 // indirect
|
||||
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect
|
||||
github.com/olekukonko/errors v1.2.0 // indirect
|
||||
github.com/olekukonko/ll v0.1.6 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.1 // indirect
|
||||
github.com/opencontainers/runtime-spec v1.3.0 // indirect
|
||||
github.com/opencontainers/selinux v1.13.1 // indirect
|
||||
github.com/pborman/indent v1.2.1 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.22 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.4.3 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.26 // indirect
|
||||
github.com/piprate/json-gold v0.7.0 // indirect
|
||||
github.com/pjbgf/sha1cd v0.6.0 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pkg/profile v1.7.0 // indirect
|
||||
github.com/pkg/xattr v0.4.9 // indirect
|
||||
github.com/pkg/xattr v0.4.12 // indirect
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||
@@ -278,52 +278,52 @@ require (
|
||||
github.com/spf13/cast v1.10.0 // indirect
|
||||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
github.com/spf13/viper v1.21.0 // indirect
|
||||
github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/spiffe/go-spiffe/v2 v2.7.0 // indirect
|
||||
github.com/stretchr/objx v0.5.3 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/sylabs/sif/v2 v2.24.0 // indirect
|
||||
github.com/sylabs/sif/v2 v2.24.1 // indirect
|
||||
github.com/sylabs/squashfs v1.0.6 // indirect
|
||||
github.com/therootcompany/xz v1.0.1 // indirect
|
||||
github.com/tidwall/gjson v1.18.0 // indirect
|
||||
github.com/tidwall/gjson v1.19.0 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/tidwall/sjson v1.2.5 // indirect
|
||||
github.com/vbatts/tar-split v0.12.2 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
github.com/zclconf/go-cty v1.16.3 // indirect
|
||||
github.com/zclconf/go-cty v1.19.0 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.39.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
|
||||
go.opentelemetry.io/otel v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.43.0 // indirect
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.44.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 // indirect
|
||||
go.opentelemetry.io/otel v1.45.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.45.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.45.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.45.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.45.0 // indirect
|
||||
go.yaml.in/yaml/v4 v4.0.0-rc.2 // indirect
|
||||
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
|
||||
golang.org/x/crypto v0.50.0 // indirect
|
||||
golang.org/x/crypto v0.57.0 // indirect
|
||||
golang.org/x/oauth2 v0.36.0 // indirect
|
||||
golang.org/x/sync v0.20.0 // indirect
|
||||
golang.org/x/sys v0.43.0 // indirect
|
||||
golang.org/x/term v0.42.0 // indirect
|
||||
golang.org/x/text v0.36.0 // indirect
|
||||
golang.org/x/sync v0.23.0 // indirect
|
||||
golang.org/x/sys v0.48.0 // indirect
|
||||
golang.org/x/term v0.46.0 // indirect
|
||||
golang.org/x/text v0.42.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
|
||||
gonum.org/v1/gonum v0.16.0 // indirect
|
||||
google.golang.org/api v0.271.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
|
||||
google.golang.org/grpc v1.79.3 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
gonum.org/v1/gonum v0.17.0 // indirect
|
||||
google.golang.org/api v0.294.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20260715232425-e75dac1f907d // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260715232425-e75dac1f907d // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect
|
||||
google.golang.org/grpc v1.83.2 // indirect
|
||||
google.golang.org/protobuf v1.36.12 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
modernc.org/libc v1.70.0 // indirect
|
||||
modernc.org/libc v1.75.7 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.11.0 // indirect
|
||||
modernc.org/memory v1.12.1 // indirect
|
||||
)
|
||||
|
||||
retract (
|
||||
|
||||
+8
-7
@@ -35,7 +35,8 @@ is_command() (
|
||||
)
|
||||
|
||||
echo_stderr() (
|
||||
echo "$@" 1>&2
|
||||
IFS=" "
|
||||
printf "%b\n" "$*" 1>&2
|
||||
)
|
||||
|
||||
_logp=2
|
||||
@@ -75,12 +76,12 @@ init_colors
|
||||
|
||||
log_tag() (
|
||||
case $1 in
|
||||
0) echo "${RED}${BOLD}[error]${RESET}" ;;
|
||||
1) echo "${RED}[warn]${RESET}" ;;
|
||||
2) echo "[info]${RESET}" ;;
|
||||
3) echo "${BLUE}[debug]${RESET}" ;;
|
||||
4) echo "${PURPLE}[trace]${RESET}" ;;
|
||||
*) echo "[$1]" ;;
|
||||
0) printf "%b" "${RED}${BOLD}[error]${RESET}" ;;
|
||||
1) printf "%b" "${RED}[warn]${RESET}" ;;
|
||||
2) printf "%b" "[info]${RESET}" ;;
|
||||
3) printf "%b" "${BLUE}[debug]${RESET}" ;;
|
||||
4) printf "%b" "${PURPLE}[trace]${RESET}" ;;
|
||||
*) printf "[%s]" "$1" ;;
|
||||
esac
|
||||
)
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
# This file is partially auto-generated. Run 'go generate ./internal/capabilities' to regenerate.
|
||||
|
||||
application: # AUTO-GENERATED - application-level config keys
|
||||
- key: cpp.vcpkg-allow-git-clone
|
||||
description: enables Syft to use clone remote repositories for vcpkg custom git registries. (also useful if the builtin vcpkg registry is not cloned locally)
|
||||
- key: dotnet.dep-packages-must-claim-dll
|
||||
description: only keep dep.json packages which have a runtime/resource DLL claimed in the deps.json targets section (but not necessarily found on disk). The package is also included if any child package claims a DLL, even if the package itself does not claim a DLL.
|
||||
- key: dotnet.dep-packages-must-have-dll
|
||||
@@ -12,6 +14,10 @@ application: # AUTO-GENERATED - application-level config keys
|
||||
description: treat DLL claims or on-disk evidence for child packages as DLL claims or on-disk evidence for any parent package
|
||||
- key: dotnet.relax-dll-claims-when-bundling-detected
|
||||
description: show all packages from the deps.json if bundling tooling is present as a dependency (e.g. ILRepack)
|
||||
- key: golang.capture-symbols
|
||||
description: 'capture function symbols from the binary symbol table (pclntab). valid values are: "none" (disabled), "stdlib" (only the synthetic stdlib package), "extended-stdlib" (stdlib plus every module under golang.org/x/), and "all" (all module packages plus stdlib)'
|
||||
- key: golang.capture-symbols-modules
|
||||
description: glob patterns matched against go module paths (e.g. github.com/klauspost/**) that should have symbols captured in addition to whatever capture-symbols selects. ** crosses path separators, * does not. a trailing major version suffix is ignored when matching, so github.com/foo/* covers github.com/foo/bar/v2; spelling a suffix out in the pattern selects only that major version. this can only widen the selection, never narrow it, and is inert when capture-symbols is none
|
||||
- key: golang.local-mod-cache-dir
|
||||
description: specify an explicit go mod cache directory, if unset this defaults to $GOPATH/pkg/mod or $HOME/go/pkg/mod
|
||||
- key: golang.local-vendor-dir
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"sort"
|
||||
|
||||
"github.com/scylladb/go-set/strset"
|
||||
"gopkg.in/yaml.v3"
|
||||
"go.yaml.in/yaml/v3"
|
||||
|
||||
"github.com/anchore/syft/internal/task"
|
||||
)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// this file handles YAML file reading and writing with comment preservation, using gopkg.in/yaml.v3's node tree to maintain all existing comments during regeneration.
|
||||
// this file handles YAML file reading and writing with comment preservation, using go.yaml.in/yaml/v3's node tree to maintain all existing comments during regeneration.
|
||||
package main
|
||||
|
||||
import (
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"os"
|
||||
"regexp"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
"go.yaml.in/yaml/v3"
|
||||
|
||||
"github.com/anchore/syft/internal/capabilities"
|
||||
"github.com/anchore/syft/internal/capabilities/internal"
|
||||
@@ -297,6 +297,11 @@ func addCatalogerFieldComment(keyNode, valueNode *yaml.Node, catalogerName strin
|
||||
if keyNode.LineComment == "" {
|
||||
keyNode.LineComment = autoGeneratedComment
|
||||
}
|
||||
case "purl_types":
|
||||
// cataloger-level purl_types (for custom catalogers) are AUTO-GENERATED
|
||||
if keyNode.LineComment == "" {
|
||||
keyNode.LineComment = autoGeneratedComment
|
||||
}
|
||||
case "json_schema_types":
|
||||
// json_schema_types are AUTO-GENERATED
|
||||
if keyNode.LineComment == "" {
|
||||
@@ -367,7 +372,7 @@ func addParserComments(parsersNode *yaml.Node) {
|
||||
valueNode := parserNode.Content[i+1]
|
||||
|
||||
switch keyNode.Value {
|
||||
case "parser_function", "metadata_types", "package_types", "json_schema_types":
|
||||
case "parser_function", "metadata_types", "package_types", "purl_types", "json_schema_types":
|
||||
// add AUTO-GENERATED comment to these fields
|
||||
if keyNode.LineComment == "" {
|
||||
keyNode.LineComment = autoGeneratedComment
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/yaml.v3"
|
||||
"go.yaml.in/yaml/v3"
|
||||
)
|
||||
|
||||
func TestFindFieldValue(t *testing.T) {
|
||||
|
||||
@@ -3,6 +3,7 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/scylladb/go-set/strset"
|
||||
@@ -10,6 +11,7 @@ import (
|
||||
"github.com/anchore/syft/internal/capabilities"
|
||||
"github.com/anchore/syft/internal/capabilities/internal"
|
||||
"github.com/anchore/syft/internal/packagemetadata"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/binary"
|
||||
)
|
||||
|
||||
@@ -192,6 +194,9 @@ func RegenerateCapabilities(catalogerDir string, repoRoot string) (*Statistics,
|
||||
formatOrphans(orphans))
|
||||
}
|
||||
|
||||
// 5a. Derive PURL types from the discovered package types (purely a function of pkg.Type)
|
||||
derivePURLTypes(updated)
|
||||
|
||||
// 6. Write back to YAML files with comments, preserving existing node trees
|
||||
fmt.Print(" → Writing updated capabilities files...")
|
||||
if err := saveCapabilities(catalogerDir, repoRoot, updated, existingNodes); err != nil {
|
||||
@@ -427,6 +432,39 @@ func (e *EnrichmentData) GetMetadataTypes(catalogerName string) ([]string, bool)
|
||||
return types, ok
|
||||
}
|
||||
|
||||
// derivePURLTypes populates the PURLTypes field on every cataloger and parser by mapping each
|
||||
// observed package type to its canonical PURL type via pkg.Type.PackageURLType. this is a pure
|
||||
// function of the already-discovered package_types, so it needs no additional test observations.
|
||||
func derivePURLTypes(doc *capabilities.Document) {
|
||||
for i := range doc.Catalogers {
|
||||
cat := &doc.Catalogers[i]
|
||||
cat.PURLTypes = purlTypesFromPackageTypes(cat.PackageTypes)
|
||||
for j := range cat.Parsers {
|
||||
cat.Parsers[j].PURLTypes = purlTypesFromPackageTypes(cat.Parsers[j].PackageTypes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// purlTypesFromPackageTypes maps syft package type strings (e.g. "go-module") to their PURL types
|
||||
// (e.g. "golang"), returning a deduplicated, sorted slice. package types with no PURL mapping are skipped.
|
||||
func purlTypesFromPackageTypes(packageTypes []string) []string {
|
||||
if len(packageTypes) == 0 {
|
||||
return nil
|
||||
}
|
||||
set := strset.New()
|
||||
for _, pt := range packageTypes {
|
||||
if purlType := pkg.Type(pt).PackageURLType(); purlType != "" {
|
||||
set.Add(purlType)
|
||||
}
|
||||
}
|
||||
if set.IsEmpty() {
|
||||
return nil
|
||||
}
|
||||
result := set.List()
|
||||
sort.Strings(result)
|
||||
return result
|
||||
}
|
||||
|
||||
// GetPackageTypes returns the package types for the given cataloger name
|
||||
func (e *EnrichmentData) GetPackageTypes(catalogerName string) ([]string, bool) {
|
||||
types, ok := e.packageTypes[catalogerName]
|
||||
@@ -490,11 +528,15 @@ func (e *EnrichmentData) EnrichWithBinaryClassifier(catalogerName string, entry
|
||||
}
|
||||
|
||||
for _, o := range binaryClassifierOverrides[classifier.Class] {
|
||||
cpeStrings := make([]string, len(o.CPEs))
|
||||
for i, c := range o.CPEs {
|
||||
cpeStrings[i] = c.Attributes.BindToFmtString()
|
||||
}
|
||||
packages = append(packages, capabilities.DetectorPackageInfo{
|
||||
Class: o.Class,
|
||||
Name: o.Package,
|
||||
PURL: stripPURLVersion(o.PURL),
|
||||
CPEs: o.CPEs,
|
||||
CPEs: cpeStrings,
|
||||
Type: "BinaryPkg",
|
||||
})
|
||||
}
|
||||
|
||||
@@ -552,3 +552,48 @@ func TestConvertToJSONSchemaTypesFromMetadata(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPURLTypesFromPackageTypes(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
packageTypes []string
|
||||
want []string
|
||||
}{
|
||||
{
|
||||
name: "nil slice returns nil",
|
||||
packageTypes: nil,
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
name: "single package type maps to purl type",
|
||||
packageTypes: []string{"go-module"},
|
||||
want: []string{"golang"},
|
||||
},
|
||||
{
|
||||
name: "multiple package types are sorted",
|
||||
packageTypes: []string{"npm", "go-module"},
|
||||
want: []string{"golang", "npm"},
|
||||
},
|
||||
{
|
||||
// java and jenkins-plugin both map to maven, so the result is deduplicated
|
||||
name: "distinct package types collapsing to one purl type are deduplicated",
|
||||
packageTypes: []string{"java-archive", "jenkins-plugin"},
|
||||
want: []string{"maven"},
|
||||
},
|
||||
{
|
||||
// unknown package types have no purl mapping and are skipped
|
||||
name: "package type without a purl mapping is skipped",
|
||||
packageTypes: []string{"UnknownPackage"},
|
||||
want: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := purlTypesFromPackageTypes(tt.packageTypes)
|
||||
if diff := cmp.Diff(tt.want, got); diff != "" {
|
||||
t.Errorf("purlTypesFromPackageTypes() mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ type binaryClassifierOverride struct {
|
||||
Class string
|
||||
Package string
|
||||
PURL string
|
||||
CPEs []string
|
||||
CPEs []cpe.CPE
|
||||
}
|
||||
|
||||
var binaryClassifierOverrides = map[string][]binaryClassifierOverride{
|
||||
@@ -110,12 +110,47 @@ var binaryClassifierOverrides = map[string][]binaryClassifierOverride{
|
||||
CPEs: singleCPE("cpe:2.3:a:openssl:openssl:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
|
||||
},
|
||||
},
|
||||
"mysqld-binary": {
|
||||
{
|
||||
Class: "mysqld-mysql-cluster-legacy-binary",
|
||||
Package: "mysql-cluster",
|
||||
PURL: mustPURL("pkg:generic/mysql-cluster@version"),
|
||||
CPEs: []cpe.CPE{
|
||||
cpe.Must("cpe:2.3:a:oracle:mysql_cluster:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
|
||||
},
|
||||
},
|
||||
{
|
||||
Class: "mysqld-mysql-cluster-binary",
|
||||
Package: "mysql-cluster",
|
||||
PURL: mustPURL("pkg:generic/mysql-cluster@version"),
|
||||
CPEs: []cpe.CPE{
|
||||
cpe.Must("cpe:2.3:a:oracle:mysql:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
|
||||
cpe.Must("cpe:2.3:a:oracle:mysql_server:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
|
||||
cpe.Must("cpe:2.3:a:oracle:mysql_cluster:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
|
||||
},
|
||||
},
|
||||
{
|
||||
Class: "mysqld-mysql-server-binary",
|
||||
Package: "mysql-server",
|
||||
PURL: mustPURL("pkg:generic/mysql-server@version"),
|
||||
CPEs: []cpe.CPE{
|
||||
cpe.Must("cpe:2.3:a:oracle:mysql:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
|
||||
cpe.Must("cpe:2.3:a:oracle:mysql_server:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func mustPURL(purl string) string {
|
||||
return purl
|
||||
}
|
||||
|
||||
func singleCPE(cpeString string, _ ...any) []string {
|
||||
return []string{cpeString}
|
||||
func singleCPE(cpeString string, source ...cpe.Source) []cpe.CPE {
|
||||
src := cpe.GeneratedSource
|
||||
if len(source) > 0 {
|
||||
src = source[0]
|
||||
}
|
||||
return []cpe.CPE{
|
||||
cpe.Must(cpeString, src),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
"go.yaml.in/yaml/v3"
|
||||
|
||||
"github.com/anchore/syft/internal/capabilities"
|
||||
)
|
||||
|
||||
@@ -82,6 +82,7 @@ type CatalogerEntry struct {
|
||||
Detectors []Detector `yaml:"detectors,omitempty" json:"detectors,omitempty"` // AUTO-GENERATED - detection methods (only for type=custom)
|
||||
MetadataTypes []string `yaml:"metadata_types,omitempty" json:"metadata_types,omitempty"` // AUTO-GENERATED - pkg metadata types emitted (only for type=custom)
|
||||
PackageTypes []string `yaml:"package_types,omitempty" json:"package_types,omitempty"` // AUTO-GENERATED - package types emitted (only for type=custom)
|
||||
PURLTypes []string `yaml:"purl_types,omitempty" json:"purl_types,omitempty"` // AUTO-GENERATED - PURL types derived from package_types (only for type=custom)
|
||||
JSONSchemaTypes []string `yaml:"json_schema_types,omitempty" json:"json_schema_types,omitempty"` // AUTO-GENERATED - JSON schema type names (UpperCamelCase)
|
||||
Capabilities CapabilitySet `yaml:"capabilities,omitempty" json:"capabilities,omitempty"` // MANUAL - config-driven capability definitions (only for type=custom)
|
||||
}
|
||||
@@ -92,6 +93,7 @@ type Parser struct {
|
||||
Detector Detector `yaml:"detector" json:"detector"` // AUTO-GENERATED - how artifacts are detected
|
||||
MetadataTypes []string `yaml:"metadata_types,omitempty" json:"metadata_types,omitempty"` // AUTO-GENERATED - pkg metadata types emitted by this parser
|
||||
PackageTypes []string `yaml:"package_types,omitempty" json:"package_types,omitempty"` // AUTO-GENERATED - package types emitted by this parser
|
||||
PURLTypes []string `yaml:"purl_types,omitempty" json:"purl_types,omitempty"` // AUTO-GENERATED - PURL types derived from package_types
|
||||
JSONSchemaTypes []string `yaml:"json_schema_types,omitempty" json:"json_schema_types,omitempty"` // AUTO-GENERATED - JSON schema type names (UpperCamelCase)
|
||||
Capabilities CapabilitySet `yaml:"capabilities,omitempty" json:"capabilities,omitempty"` // MANUAL - config-driven capability definitions
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/yaml.v3"
|
||||
"go.yaml.in/yaml/v3"
|
||||
)
|
||||
|
||||
func TestConfigSchemaExtensions(t *testing.T) {
|
||||
|
||||
@@ -3,12 +3,18 @@ package internal
|
||||
const (
|
||||
// JSONSchemaVersion is the current schema version output by the JSON encoder
|
||||
// This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment.
|
||||
JSONSchemaVersion = "16.1.3"
|
||||
JSONSchemaVersion = "16.1.10"
|
||||
|
||||
// Changelog
|
||||
// 16.1.0 - reformulated the python pdm fields (added "URL" and removed the unused "path" field).
|
||||
// 16.1.1 - correct elf package osCpe field according to the document of systemd (also add appCpe field)
|
||||
// 16.1.2 - placeholder for 16.1.2 changelog
|
||||
// 16.1.3 - add GGUFFileParts to GGUFFileHeader metadata
|
||||
|
||||
// 16.1.4 - add BunLockEntry metadata type for bun.lock support
|
||||
// 16.1.5 - add DenoLockEntry and DenoRemoteLockEntry metadata types for deno.lock support
|
||||
// 16.1.6 - add Dependencies to ElixirMixLockEntry metadata
|
||||
// 16.1.7 - add AppleAppBundleEntry metadata type for the apple app bundle cataloger
|
||||
// 16.1.8 - add VcpkgManifest metadata type for vcpkg manifest support
|
||||
// 16.1.9 - add Symbols (grouped by owning package import path) to GolangBinaryBuildinfoEntry metadata
|
||||
// 16.1.10 - add SafeTensorsModelInfo metadata type for the safetensors AI model cataloger
|
||||
)
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package file
|
||||
|
||||
import "io"
|
||||
|
||||
// ReaderSize reports the number of bytes behind r. The bool is false when that cannot be determined, and
|
||||
// callers must branch on it rather than treating the zero as a size.
|
||||
//
|
||||
// Binary parsers read sizes out of the files they are parsing and allocate against them. Those sizes are
|
||||
// 32- and 64-bit fields under the control of whoever produced the file, so a truncated or hostile input
|
||||
// can declare gigabytes that are not there. The read would fail afterwards either way; what matters is
|
||||
// refusing before the allocation, and that needs a real byte count to weigh the claim against.
|
||||
//
|
||||
// The bool is the whole point of the signature. An earlier version returned 0 for every failure mode, and
|
||||
// a caller that forgot to special-case it got a bound of "nothing is allowed" or, more often, silently
|
||||
// fell back to an unbounded path. Making the caller name the failure keeps that from happening by
|
||||
// omission.
|
||||
//
|
||||
// Both *bytes.Reader and *io.SectionReader answer directly. A file handle only has Seek, so fall back to
|
||||
// a save-and-restore seek, which leaves the caller's cursor where it found it. That fallback is not
|
||||
// atomic: it moves the cursor and puts it back, so a reader being read concurrently can observe the
|
||||
// intermediate position.
|
||||
//
|
||||
// Neither answer is taken on faith, because a reader can report a length it cannot deliver: an
|
||||
// *io.SectionReader answers with the length it was constructed with, and debug/elf and friends construct
|
||||
// theirs with a nominal one, so io.NewSectionReader(r, 0, 1<<63-1) would otherwise report a bound that
|
||||
// permits everything as if it had been measured. The last byte is read back to confirm the count, which is
|
||||
// one ReadAt whatever the size, and the read is what the caller was going to weigh anyway.
|
||||
//
|
||||
// A wrapper that embeds io.ReaderAt as an interface promotes only ReadAt, so it answers no size here
|
||||
// however sizable the reader underneath it is. A type meant to be bounded needs to forward Size() itself.
|
||||
func ReaderSize(r io.ReaderAt) (int64, bool) {
|
||||
size, ok := reportedSize(r)
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
// ReadAt may return io.EOF alongside a full read, so the count is what says the byte is there
|
||||
var last [1]byte
|
||||
if n, _ := r.ReadAt(last[:], size-1); n != len(last) {
|
||||
return 0, false
|
||||
}
|
||||
return size, true
|
||||
}
|
||||
|
||||
// reportedSize is what the reader says about itself, before ReaderSize checks whether it can back it up.
|
||||
func reportedSize(r io.ReaderAt) (int64, bool) {
|
||||
if sr, ok := r.(interface{ Size() int64 }); ok {
|
||||
size := sr.Size()
|
||||
return size, size > 0
|
||||
}
|
||||
s, ok := r.(io.Seeker)
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
cur, err := s.Seek(0, io.SeekCurrent)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
size, err := s.Seek(0, io.SeekEnd)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
if _, err := s.Seek(cur, io.SeekStart); err != nil {
|
||||
return 0, false
|
||||
}
|
||||
return size, size > 0
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
package file
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestReaderSize(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
r io.ReaderAt
|
||||
want int64
|
||||
wantOK bool
|
||||
}{
|
||||
{
|
||||
name: "bytes.Reader answers directly",
|
||||
r: bytes.NewReader(make([]byte, 1234)),
|
||||
want: 1234,
|
||||
wantOK: true,
|
||||
},
|
||||
{
|
||||
// an empty reader is not a size to bound against, so it reports not-ok along with everything
|
||||
// else that cannot be measured
|
||||
name: "empty bytes.Reader",
|
||||
r: bytes.NewReader(nil),
|
||||
want: 0,
|
||||
},
|
||||
{
|
||||
name: "SectionReader over a real length",
|
||||
r: io.NewSectionReader(bytes.NewReader(make([]byte, 500)), 100, 300),
|
||||
want: 300,
|
||||
wantOK: true,
|
||||
},
|
||||
{
|
||||
name: "seek-only reader falls back to seeking",
|
||||
r: seekOnly{bytes.NewReader(make([]byte, 4096))},
|
||||
want: 4096,
|
||||
wantOK: true,
|
||||
},
|
||||
{
|
||||
name: "reader with neither Size nor Seek cannot be measured",
|
||||
r: readAtOnly{bytes.NewReader(make([]byte, 4096))},
|
||||
want: 0,
|
||||
},
|
||||
{
|
||||
// the shape debug/elf, debug/pe and debug/macho build internally. Taking this at its word hands
|
||||
// back a bound that permits everything, reported as if it had been measured.
|
||||
name: "SectionReader over a nominal length reports what it can deliver",
|
||||
r: io.NewSectionReader(bytes.NewReader(make([]byte, 500)), 0, 1<<63-1),
|
||||
want: 0,
|
||||
},
|
||||
{
|
||||
// a Size() a type simply got wrong is the same failure: the bytes are what decides
|
||||
name: "a size the reader cannot back up is refused",
|
||||
r: overstatedSize{ReaderAt: bytes.NewReader(make([]byte, 10)), size: 1 << 30},
|
||||
want: 0,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
size, ok := ReaderSize(tt.r)
|
||||
assert.Equal(t, tt.want, size)
|
||||
assert.Equal(t, tt.wantOK, ok)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestReaderSize_FileHandle(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "f.bin")
|
||||
require.NoError(t, os.WriteFile(path, make([]byte, 7777), 0o600))
|
||||
f, err := os.Open(path)
|
||||
require.NoError(t, err)
|
||||
defer f.Close()
|
||||
|
||||
// the cursor has to come back where it was found: the caller may be mid-read, and an *os.File is the
|
||||
// one input here that carries a shared position
|
||||
_, err = f.Seek(101, io.SeekStart)
|
||||
require.NoError(t, err)
|
||||
|
||||
size, ok := ReaderSize(f)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, int64(7777), size)
|
||||
|
||||
at, err := f.Seek(0, io.SeekCurrent)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(101), at, "measuring the file must not move the caller's cursor")
|
||||
}
|
||||
|
||||
// TestReaderSize_WrapperMustForwardSize pins the shape that made every bound expressed against this
|
||||
// helper go silently inert. A type that embeds io.ReaderAt as an interface promotes only ReadAt, so it
|
||||
// answers 0 here however sizable the reader underneath is, and a caller that treats 0 as "fall back to
|
||||
// unbounded" then stops bounding anything. Forwarding Size() is what fixes it, and this is the assertion
|
||||
// that a new wrapper has to satisfy.
|
||||
func TestReaderSize_WrapperMustForwardSize(t *testing.T) {
|
||||
inner := bytes.NewReader(make([]byte, 2048))
|
||||
|
||||
size, ok := ReaderSize(embedsReaderAt{inner})
|
||||
assert.False(t, ok, "a wrapper that only embeds the interface cannot be measured")
|
||||
assert.Equal(t, int64(0), size)
|
||||
|
||||
size, ok = ReaderSize(forwardsSize{inner})
|
||||
assert.True(t, ok, "a wrapper meant to be bounded has to forward Size itself")
|
||||
assert.Equal(t, int64(2048), size)
|
||||
}
|
||||
|
||||
type seekOnly struct{ inner *bytes.Reader }
|
||||
|
||||
func (s seekOnly) ReadAt(p []byte, off int64) (int, error) { return s.inner.ReadAt(p, off) }
|
||||
func (s seekOnly) Seek(off int64, whence int) (int64, error) {
|
||||
return s.inner.Seek(off, whence)
|
||||
}
|
||||
|
||||
type readAtOnly struct{ inner *bytes.Reader }
|
||||
|
||||
func (r readAtOnly) ReadAt(p []byte, off int64) (int, error) { return r.inner.ReadAt(p, off) }
|
||||
|
||||
type embedsReaderAt struct{ io.ReaderAt }
|
||||
|
||||
type forwardsSize struct{ io.ReaderAt }
|
||||
|
||||
func (f forwardsSize) Size() int64 {
|
||||
size, _ := ReaderSize(f.ReaderAt)
|
||||
return size
|
||||
}
|
||||
|
||||
// overstatedSize answers with a size larger than the bytes behind it, the way a wrapper that returns a
|
||||
// declared length rather than a measured one would.
|
||||
type overstatedSize struct {
|
||||
io.ReaderAt
|
||||
size int64
|
||||
}
|
||||
|
||||
func (o overstatedSize) Size() int64 { return o.size }
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/diskfs/go-diskfs/filesystem"
|
||||
)
|
||||
@@ -14,8 +15,7 @@ type WalkDiskDirFunc func(fsys filesystem.FileSystem, path string, d os.FileInfo
|
||||
// WalkDiskDir walks the file tree within the go-diskfs filesystem at root, calling fn for each file or directory in the tree, including root.
|
||||
// This is meant to mimic the behavior of fs.WalkDir in the standard library.
|
||||
func WalkDiskDir(fsys filesystem.FileSystem, root string, fn WalkDiskDirFunc) error {
|
||||
infos, err := fsys.ReadDir(root)
|
||||
|
||||
infos, err := readDiskDir(fsys, root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -51,7 +51,7 @@ func walkDiskDir(fsys filesystem.FileSystem, name string, d os.FileInfo, walkDir
|
||||
|
||||
isDir := d != nil && d.IsDir()
|
||||
if d == nil {
|
||||
_, err := fsys.ReadDir(name)
|
||||
_, err := readDiskDir(fsys, name)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -62,7 +62,7 @@ func walkDiskDir(fsys filesystem.FileSystem, name string, d os.FileInfo, walkDir
|
||||
return nil
|
||||
}
|
||||
|
||||
dirs, err := fsys.ReadDir(name)
|
||||
dirs, err := readDiskDir(fsys, name)
|
||||
if err != nil {
|
||||
err = walkDirFn(fsys, name, d, err)
|
||||
if err != nil {
|
||||
@@ -87,3 +87,33 @@ func walkDiskDir(fsys filesystem.FileSystem, name string, d os.FileInfo, walkDir
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// readDiskDir reads the directory entries at the given path from a go-diskfs filesystem.
|
||||
// go-diskfs returns fs.DirEntry values; these are resolved to os.FileInfo so callers have
|
||||
// access to the full file metadata (mode, size, modification time).
|
||||
func readDiskDir(fsys filesystem.FileSystem, p string) ([]os.FileInfo, error) {
|
||||
entries, err := fsys.ReadDir(ToFSPath(p))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
infos := make([]os.FileInfo, 0, len(entries))
|
||||
for _, entry := range entries {
|
||||
info, err := entry.Info()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
infos = append(infos, info)
|
||||
}
|
||||
return infos, nil
|
||||
}
|
||||
|
||||
// ToFSPath converts an absolute ("/"-rooted) path into an io/fs-valid path as required by
|
||||
// go-diskfs, where the root is "." and other paths carry no leading slash (see io/fs.ValidPath).
|
||||
func ToFSPath(p string) string {
|
||||
p = strings.TrimPrefix(p, "/")
|
||||
if p == "" {
|
||||
return "."
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
@@ -43,10 +43,10 @@ func createTestFS(t *testing.T) filesystem.FileSystem {
|
||||
|
||||
for _, tf := range testFiles {
|
||||
if tf.isDir {
|
||||
err := fsys.Mkdir(tf.path)
|
||||
err := fsys.Mkdir(ToFSPath(tf.path))
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
f, err := fsys.OpenFile(tf.path, os.O_CREATE|os.O_RDWR)
|
||||
f, err := fsys.OpenFile(ToFSPath(tf.path), os.O_CREATE|os.O_RDWR)
|
||||
require.NoError(t, err)
|
||||
_, err = f.Write([]byte(tf.content))
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -6,9 +6,9 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/invopop/jsonschema"
|
||||
orderedmap "github.com/pb33f/ordered-map/v2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
orderedmap "github.com/wk8/go-ordered-map/v2"
|
||||
)
|
||||
|
||||
// TestCopyAliasFieldComments verifies that field comments from source types are correctly copied to alias types.
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
)
|
||||
|
||||
// maxScannedLineSize is the longest line NewLineScanner will return before Scan stops with bufio.ErrTooLong.
|
||||
// The buffer only grows to this size when a line needs it.
|
||||
const maxScannedLineSize = 1024 * 1024
|
||||
|
||||
// NewLineScanner returns a bufio.Scanner that accepts lines up to 1MB rather than the 64KB default, since
|
||||
// package metadata files carry long single-line fields (descriptions, license text, dependency lists). Callers
|
||||
// must still check Err() after scanning.
|
||||
func NewLineScanner(r io.Reader) *bufio.Scanner {
|
||||
s := bufio.NewScanner(r)
|
||||
s.Buffer(nil, maxScannedLineSize)
|
||||
return s
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNewLineScanner(t *testing.T) {
|
||||
long := strings.Repeat("a", 100*1024)
|
||||
s := NewLineScanner(strings.NewReader(long + "\nb\n"))
|
||||
require.True(t, s.Scan())
|
||||
require.Equal(t, long, s.Text())
|
||||
require.True(t, s.Scan())
|
||||
require.Equal(t, "b", s.Text())
|
||||
require.False(t, s.Scan())
|
||||
require.NoError(t, s.Err())
|
||||
|
||||
s = NewLineScanner(strings.NewReader(strings.Repeat("a", maxScannedLineSize+1)))
|
||||
require.False(t, s.Scan())
|
||||
require.ErrorIs(t, s.Err(), bufio.ErrTooLong)
|
||||
}
|
||||
@@ -2,14 +2,18 @@ package os
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"slices"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/sbomsync"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
)
|
||||
|
||||
@@ -19,8 +23,11 @@ func DetectFeatures(_ context.Context, resolver file.Resolver, builder sbomsync.
|
||||
return
|
||||
}
|
||||
|
||||
err := findRhelFeatures(resolver, s.Artifacts.LinuxDistribution)
|
||||
if err != nil {
|
||||
if err := findRhelFeatures(resolver, s.Artifacts.LinuxDistribution); err != nil {
|
||||
log.WithFields("error", err, "release", s.Artifacts.LinuxDistribution).Trace("error searching for extended support")
|
||||
}
|
||||
|
||||
if err := findUbuntuFeatures(resolver, s.Artifacts.LinuxDistribution, s.Artifacts.Packages); err != nil {
|
||||
log.WithFields("error", err, "release", s.Artifacts.LinuxDistribution).Trace("error searching for extended support")
|
||||
}
|
||||
})
|
||||
@@ -28,6 +35,9 @@ func DetectFeatures(_ context.Context, resolver file.Resolver, builder sbomsync.
|
||||
return nil
|
||||
}
|
||||
|
||||
// rhelEUSPatterns match a RHEL content manifest referencing an extended-update-support (EUS) repo.
|
||||
var rhelEUSPatterns = []*regexp.Regexp{regexp.MustCompile(`baseos-eus`), regexp.MustCompile(`baseos-eus|appstream-eus`)}
|
||||
|
||||
func findRhelFeatures(resolver file.Resolver, release *linux.Release) error {
|
||||
if release == nil || release.ID != "rhel" {
|
||||
return nil
|
||||
@@ -37,9 +47,9 @@ func findRhelFeatures(resolver file.Resolver, release *linux.Release) error {
|
||||
return fmt.Errorf("unable to find content manifests: %w", err)
|
||||
}
|
||||
for _, contentManifestFile := range contentManifestFiles {
|
||||
found, err := hasRhelExtendedSupportInContentManifest(resolver, contentManifestFile)
|
||||
found, err := fileMatchesAny(resolver, contentManifestFile, rhelEUSPatterns...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to read content manifest from %s: %w", contentManifestFile.String(), err)
|
||||
return fmt.Errorf("unable to read content manifest from %s: %w", contentManifestFile.RealPath, err)
|
||||
}
|
||||
if found {
|
||||
release.ExtendedSupport = true
|
||||
@@ -49,13 +59,149 @@ func findRhelFeatures(resolver file.Resolver, release *linux.Release) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func hasRhelExtendedSupportInContentManifest(resolver file.Resolver, contentManifestFile file.Location) (bool, error) {
|
||||
contents, err := resolver.FileContentsByLocation(contentManifestFile)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("unable to read content manifest from %s: %w", contentManifestFile.String(), err)
|
||||
}
|
||||
defer internal.CloseAndLogError(contents, "content-manifest")
|
||||
// Ubuntu Pro (formerly Ubuntu Advantage) is Canonical's subscription that unlocks Expanded Security
|
||||
// Maintenance (ESM): security fixes past a package's standard support window. we detect the two base ESM
|
||||
// streams, esm-infra (the `main` repo) and esm-apps (`universe`), and collapse them into one ExtendedSupport
|
||||
// signal; the infra-vs-apps split is disambiguated by the downstream consumer, not by this boolean.
|
||||
//
|
||||
// the other Pro streams on the same host (esm.ubuntu.com/fips, /fips-updates, /realtime) are deliberately
|
||||
// excluded: they are separate compliance products with their own downstream channels, and a host can run
|
||||
// them with base ESM disabled, so folding them in would be unsound.
|
||||
//
|
||||
// detection is deliberately forgiving: apt/status evidence disappears when ESM is disabled, but an installed
|
||||
// +esmN package is durable proof ESM content is on disk. so ExtendedSupport means "ESM was or is in effect
|
||||
// for this host or its content", not "currently entitled" (mere eligibility, without evidence, is not enough).
|
||||
|
||||
patterns := []*regexp.Regexp{regexp.MustCompile(`baseos-eus`), regexp.MustCompile(`baseos-eus|appstream-eus`)}
|
||||
// esmAptSourcePattern matches an uncommented apt source (classic or DEB822) or auth entry pointing at the
|
||||
// plain ESM streams esm.ubuntu.com/infra or /apps. it deliberately scopes to /(infra|apps) so the other
|
||||
// Pro streams served from the same host (/fips, /fips-updates, /realtime) are never folded into the base
|
||||
// esm channel. the [^#\n]* before the host ensures a leading `#` (comment) can never satisfy the match.
|
||||
var esmAptSourcePattern = regexp.MustCompile(`(?m)^[^#\n]*esm\.ubuntu\.com/(infra|apps)`)
|
||||
|
||||
// esmVersionPattern matches a dpkg version carrying a numbered ESM pocket suffix, e.g. `...+esm1` or
|
||||
// `...~esm2`. the trailing \d scopes this to the ESM pocket and excludes other Pro pockets (e.g. +fips).
|
||||
var esmVersionPattern = regexp.MustCompile(`[~+]esm\d`)
|
||||
|
||||
func findUbuntuFeatures(resolver file.Resolver, release *linux.Release, packages *pkg.Collection) error {
|
||||
if release == nil || release.ID != "ubuntu" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// an uncommented esm.ubuntu.com apt source or an active pro esm service both prove Pro/ESM was enabled.
|
||||
// these are the durable, file-based signals present on attached hosts. a read error in one signal must
|
||||
// not suppress the others, so errors are collected and detection continues to the next signal.
|
||||
var errs error
|
||||
for _, check := range []func() (bool, error){
|
||||
func() (bool, error) { return hasUbuntuESMAptEvidence(resolver) },
|
||||
func() (bool, error) { return hasActiveUbuntuESMService(resolver) },
|
||||
} {
|
||||
found, err := check()
|
||||
if err != nil {
|
||||
errs = errors.Join(errs, err)
|
||||
continue
|
||||
}
|
||||
if found {
|
||||
release.ExtendedSupport = true
|
||||
return errs
|
||||
}
|
||||
}
|
||||
|
||||
// fallback for images (typically not pro-attached): an installed package whose version carries an
|
||||
// +esm/~esm suffix was pulled from an ESM pocket, so ESM was in effect when it was installed.
|
||||
if hasInstalledESMPackage(packages) {
|
||||
release.ExtendedSupport = true
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
// hasUbuntuESMAptEvidence reports whether any apt source (classic .list or DEB822 .sources) or auth entry
|
||||
// references esm.ubuntu.com via an uncommented line.
|
||||
func hasUbuntuESMAptEvidence(resolver file.Resolver) (bool, error) {
|
||||
// apt source files live in a directory (classic .list or DEB822 .sources), the credentials file is a fixed path.
|
||||
sourceLocations, err := resolver.FilesByGlob("/etc/apt/sources.list.d/*")
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("unable to find apt esm sources: %w", err)
|
||||
}
|
||||
authLocations, err := resolver.FilesByPath("/etc/apt/auth.conf.d/90ubuntu-advantage")
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("unable to find apt esm auth: %w", err)
|
||||
}
|
||||
|
||||
// ponytail: a DEB822 source with `Enabled: no` would still match; the pro client removes/comments the
|
||||
// file on disable rather than toggling that key, so this is an accepted false-positive ceiling. the
|
||||
// auth.conf.d entry is deleted by the pro client on disable, so it is a durable positive-only signal.
|
||||
for _, location := range append(sourceLocations, authLocations...) {
|
||||
match, err := fileMatchesAny(resolver, location, esmAptSourcePattern)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if match {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// hasActiveUbuntuESMService reports whether the ubuntu-advantage/pro status cache shows an enabled esm service.
|
||||
func hasActiveUbuntuESMService(resolver file.Resolver) (bool, error) {
|
||||
locations, err := resolver.FilesByPath("/var/lib/ubuntu-advantage/status.json")
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("unable to find ubuntu-advantage status: %w", err)
|
||||
}
|
||||
for _, location := range locations {
|
||||
enabled, err := hasEnabledESMServiceInStatus(resolver, location)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if enabled {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func hasEnabledESMServiceInStatus(resolver file.Resolver, location file.Location) (bool, error) {
|
||||
contents, err := resolver.FileContentsByLocation(location)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("unable to read ubuntu-advantage status from %s: %w", location.RealPath, err)
|
||||
}
|
||||
defer internal.CloseAndLogError(contents, location.RealPath)
|
||||
|
||||
var status struct {
|
||||
Services []struct {
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
} `json:"services"`
|
||||
}
|
||||
if err := json.NewDecoder(contents).Decode(&status); err != nil {
|
||||
return false, fmt.Errorf("unable to parse ubuntu-advantage status from %s: %w", location.RealPath, err)
|
||||
}
|
||||
|
||||
// pro status values are enabled/disabled/n-a/warning/—; only "enabled" proves the stream is active.
|
||||
// warning/expired states are intentionally excluded here and left to the apt-source and installed-package signals.
|
||||
for _, svc := range status.Services {
|
||||
if (svc.Name == "esm-infra" || svc.Name == "esm-apps") && svc.Status == "enabled" {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// hasInstalledESMPackage reports whether any installed dpkg package version carries an ESM pocket suffix.
|
||||
func hasInstalledESMPackage(packages *pkg.Collection) bool {
|
||||
if packages == nil {
|
||||
return false
|
||||
}
|
||||
return slices.ContainsFunc(packages.Sorted(pkg.DebPkg), func(p pkg.Package) bool {
|
||||
return esmVersionPattern.MatchString(p.Version)
|
||||
})
|
||||
}
|
||||
|
||||
func fileMatchesAny(resolver file.Resolver, location file.Location, patterns ...*regexp.Regexp) (bool, error) {
|
||||
contents, err := resolver.FileContentsByLocation(location)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("unable to read %s: %w", location.RealPath, err)
|
||||
}
|
||||
defer internal.CloseAndLogError(contents, location.RealPath)
|
||||
return internal.MatchAnyFromReader(contents, patterns...)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,13 @@ package os_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
stdos "os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
@@ -11,7 +17,9 @@ import (
|
||||
"github.com/anchore/syft/internal/sbomsync"
|
||||
"github.com/anchore/syft/internal/task"
|
||||
"github.com/anchore/syft/syft"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
@@ -99,3 +107,237 @@ func Test_EnvironmentTask(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_findUbuntuFeatures(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
dir string
|
||||
releaseID string
|
||||
packages []pkg.Package
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "esm apt source present",
|
||||
dir: "testdata/ubuntu_esm_apt",
|
||||
releaseID: "ubuntu",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "esm apt source in DEB822 .sources format",
|
||||
dir: "testdata/ubuntu_esm_deb822",
|
||||
releaseID: "ubuntu",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "esm evidence in apt auth.conf.d",
|
||||
dir: "testdata/ubuntu_esm_authconf",
|
||||
releaseID: "ubuntu",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "active esm service in ubuntu-advantage status",
|
||||
dir: "testdata/ubuntu_esm_status",
|
||||
releaseID: "ubuntu",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "only esm-infra enabled (esm-apps disabled)",
|
||||
dir: "testdata/ubuntu_esm_infra_only",
|
||||
releaseID: "ubuntu",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "malformed status.json does not panic or match",
|
||||
dir: "testdata/ubuntu_esm_malformed",
|
||||
releaseID: "ubuntu",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "installed +esm package version",
|
||||
dir: "testdata/ubuntu_plain",
|
||||
releaseID: "ubuntu",
|
||||
packages: []pkg.Package{
|
||||
{Name: "openssl", Version: "1.1.1f-1ubuntu2.19+esm1", Type: pkg.DebPkg},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "installed ~esm package version",
|
||||
dir: "testdata/ubuntu_plain",
|
||||
releaseID: "ubuntu",
|
||||
packages: []pkg.Package{
|
||||
{Name: "libcap2", Version: "1:2.32-1ubuntu0.1~esm1", Type: pkg.DebPkg},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "plain ubuntu with no esm evidence",
|
||||
dir: "testdata/ubuntu_plain",
|
||||
releaseID: "ubuntu",
|
||||
packages: []pkg.Package{
|
||||
{Name: "openssl", Version: "1.1.1f-1ubuntu2.19", Type: pkg.DebPkg},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "commented esm source and disabled esm service",
|
||||
dir: "testdata/ubuntu_esm_disabled",
|
||||
releaseID: "ubuntu",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "esm disabled but +esm package remains installed",
|
||||
dir: "testdata/ubuntu_esm_disabled",
|
||||
releaseID: "ubuntu",
|
||||
packages: []pkg.Package{
|
||||
{Name: "openssl", Version: "1.1.1f-1ubuntu2.19+esm1", Type: pkg.DebPkg},
|
||||
},
|
||||
expected: true, // durable proof: ESM content is on disk even though the channel is now off
|
||||
},
|
||||
{
|
||||
name: "fips-only pro host is not plain esm",
|
||||
dir: "testdata/ubuntu_fips_only",
|
||||
releaseID: "ubuntu",
|
||||
expected: false, // esm.ubuntu.com/fips must not be folded into the base esm channel
|
||||
},
|
||||
{
|
||||
name: "non-ubuntu is unaffected by esm evidence",
|
||||
dir: "testdata/ubuntu_esm_apt",
|
||||
releaseID: "debian",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "non-ubuntu is unaffected by esm package version",
|
||||
dir: "testdata/ubuntu_plain",
|
||||
releaseID: "debian",
|
||||
packages: []pkg.Package{
|
||||
{Name: "openssl", Version: "1.1.1f-1ubuntu2.19+esm1", Type: pkg.DebPkg},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
resolver := fixtureResolverForDir(t, test.dir)
|
||||
|
||||
s := sbom.SBOM{}
|
||||
s.Artifacts.LinuxDistribution = &linux.Release{ID: test.releaseID}
|
||||
s.Artifacts.Packages = pkg.NewCollection(test.packages...)
|
||||
|
||||
err := os.DetectFeatures(context.Background(), resolver, sbomsync.NewBuilder(&s))
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, test.expected, s.Artifacts.LinuxDistribution.ExtendedSupport)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// returning early from Collection.Enumerate leaks its goroutine and read lock, so any later write blocks forever.
|
||||
func Test_findUbuntuFeatures_releasesPackageCollectionLock(t *testing.T) {
|
||||
resolver := fixtureResolverForDir(t, "testdata/ubuntu_plain")
|
||||
|
||||
s := sbom.SBOM{}
|
||||
s.Artifacts.LinuxDistribution = &linux.Release{ID: "ubuntu"}
|
||||
s.Artifacts.Packages = pkg.NewCollection(
|
||||
pkg.Package{Name: "openssl", Version: "1.1.1f-1ubuntu2.19+esm1", Type: pkg.DebPkg},
|
||||
pkg.Package{Name: "libcap2", Version: "1:2.32-1ubuntu0.1~esm1", Type: pkg.DebPkg},
|
||||
)
|
||||
|
||||
require.NoError(t, os.DetectFeatures(context.Background(), resolver, sbomsync.NewBuilder(&s)))
|
||||
require.True(t, s.Artifacts.LinuxDistribution.ExtendedSupport)
|
||||
|
||||
added := make(chan struct{})
|
||||
go func() {
|
||||
s.Artifacts.Packages.Add(pkg.Package{Name: "zlib1g", Version: "1:1.2.11.dfsg-2ubuntu1", Type: pkg.DebPkg})
|
||||
close(added)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-added:
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("package collection is still read-locked after feature detection")
|
||||
}
|
||||
}
|
||||
|
||||
// a read error in the apt-source signal must not suppress the ubuntu-advantage status signal; the three
|
||||
// signals are meant to be independent fallbacks.
|
||||
func Test_findUbuntuFeatures_signalErrorDoesNotSuppressOthers(t *testing.T) {
|
||||
resolver := errOnGlobResolver{
|
||||
fixtureResolver: fixtureResolverForDir(t, "testdata/ubuntu_esm_status").(fixtureResolver),
|
||||
errOnGlob: "/etc/apt/sources.list.d/*",
|
||||
}
|
||||
|
||||
s := sbom.SBOM{}
|
||||
s.Artifacts.LinuxDistribution = &linux.Release{ID: "ubuntu"}
|
||||
s.Artifacts.Packages = pkg.NewCollection()
|
||||
|
||||
require.NoError(t, os.DetectFeatures(context.Background(), resolver, sbomsync.NewBuilder(&s)))
|
||||
|
||||
// the apt glob errors, but the status.json signal (esm-infra enabled) should still be consulted
|
||||
require.True(t, s.Artifacts.LinuxDistribution.ExtendedSupport)
|
||||
}
|
||||
|
||||
// errOnGlobResolver injects a failure for a specific glob to simulate an unreadable apt directory.
|
||||
type errOnGlobResolver struct {
|
||||
fixtureResolver
|
||||
errOnGlob string
|
||||
}
|
||||
|
||||
func (r errOnGlobResolver) FilesByGlob(patterns ...string) ([]file.Location, error) {
|
||||
if slices.Contains(patterns, r.errOnGlob) {
|
||||
return nil, stdos.ErrPermission
|
||||
}
|
||||
return r.fixtureResolver.FilesByGlob(patterns...)
|
||||
}
|
||||
|
||||
// fixtureResolver maps in-image logical paths to on-disk fixture files so DetectFeatures can be driven
|
||||
// without building a container image.
|
||||
type fixtureResolver struct {
|
||||
file.Resolver // unused methods; nil is fine since only the three below are called
|
||||
files map[string]string
|
||||
}
|
||||
|
||||
func (r fixtureResolver) FilesByGlob(patterns ...string) ([]file.Location, error) {
|
||||
var out []file.Location
|
||||
for logical := range r.files {
|
||||
for _, p := range patterns {
|
||||
if ok, _ := path.Match(p, logical); ok {
|
||||
out = append(out, file.NewLocation(logical))
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (r fixtureResolver) FilesByPath(paths ...string) ([]file.Location, error) {
|
||||
var out []file.Location
|
||||
for _, p := range paths {
|
||||
if _, ok := r.files[p]; ok {
|
||||
out = append(out, file.NewLocation(p))
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (r fixtureResolver) FileContentsByLocation(l file.Location) (io.ReadCloser, error) {
|
||||
return stdos.Open(r.files[l.RealPath])
|
||||
}
|
||||
|
||||
func fixtureResolverForDir(t *testing.T, dir string) file.Resolver {
|
||||
t.Helper()
|
||||
files := map[string]string{}
|
||||
require.NoError(t, filepath.WalkDir(dir, func(p string, d stdos.DirEntry, err error) error {
|
||||
if err != nil || d.IsDir() {
|
||||
return err
|
||||
}
|
||||
rel, err := filepath.Rel(dir, p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
files["/"+filepath.ToSlash(rel)] = p
|
||||
return nil
|
||||
}))
|
||||
return fixtureResolver{files: files}
|
||||
}
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
# Written by ubuntu-advantage-tools
|
||||
deb https://esm.ubuntu.com/infra/ubuntu focal-infra-security main
|
||||
# deb-src https://esm.ubuntu.com/infra/ubuntu focal-infra-security main
|
||||
|
||||
deb https://esm.ubuntu.com/infra/ubuntu focal-infra-updates main
|
||||
# deb-src https://esm.ubuntu.com/infra/ubuntu focal-infra-updates main
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
machine esm.ubuntu.com/infra/ubuntu/ login bearer password DUMMYTOKENREDACTED # ubuntu-pro-client
|
||||
machine esm.ubuntu.com/apps/ubuntu/ login bearer password DUMMYTOKENREDACTED # ubuntu-pro-client
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
Types: deb
|
||||
URIs: https://esm.ubuntu.com/infra/ubuntu
|
||||
Suites: noble-infra-security noble-infra-updates
|
||||
Components: main
|
||||
Signed-By: /usr/share/keyrings/ubuntu-pro-esm-infra-keyring.gpg
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
# Written by ubuntu-advantage-tools
|
||||
# deb https://esm.ubuntu.com/infra/ubuntu focal-infra-security main
|
||||
# deb-src https://esm.ubuntu.com/infra/ubuntu focal-infra-security main
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"_schema_version": "0.1",
|
||||
"attached": true,
|
||||
"services": [
|
||||
{
|
||||
"name": "esm-apps",
|
||||
"available": "yes",
|
||||
"entitled": "yes",
|
||||
"status": "disabled"
|
||||
},
|
||||
{
|
||||
"name": "esm-infra",
|
||||
"available": "yes",
|
||||
"entitled": "yes",
|
||||
"status": "disabled"
|
||||
}
|
||||
]
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"_schema_version": "0.1",
|
||||
"attached": true,
|
||||
"execution_status": "success",
|
||||
"services": [
|
||||
{
|
||||
"name": "esm-apps",
|
||||
"description": "Expanded Security Maintenance for Applications",
|
||||
"available": "yes",
|
||||
"entitled": "yes",
|
||||
"status": "disabled"
|
||||
},
|
||||
{
|
||||
"name": "esm-infra",
|
||||
"description": "Expanded Security Maintenance for Infrastructure",
|
||||
"available": "yes",
|
||||
"entitled": "yes",
|
||||
"status": "enabled"
|
||||
}
|
||||
]
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{ this is not valid json
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"_doc": "Content provided in json response is currently considered Experimental and may change",
|
||||
"_schema_version": "0.1",
|
||||
"attached": true,
|
||||
"services": [
|
||||
{
|
||||
"name": "esm-apps",
|
||||
"description": "Expanded Security Maintenance for Applications",
|
||||
"available": "yes",
|
||||
"entitled": "yes",
|
||||
"status": "enabled"
|
||||
},
|
||||
{
|
||||
"name": "esm-infra",
|
||||
"description": "Expanded Security Maintenance for Infrastructure",
|
||||
"available": "yes",
|
||||
"entitled": "yes",
|
||||
"status": "enabled"
|
||||
},
|
||||
{
|
||||
"name": "fips",
|
||||
"description": "NIST-certified FIPS crypto packages",
|
||||
"available": "no",
|
||||
"entitled": "no",
|
||||
"status": "n/a"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# Written by ubuntu-pro-client
|
||||
deb https://esm.ubuntu.com/fips/ubuntu focal main
|
||||
# deb-src https://esm.ubuntu.com/fips/ubuntu focal main
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"_schema_version": "0.1",
|
||||
"attached": true,
|
||||
"execution_status": "success",
|
||||
"services": [
|
||||
{
|
||||
"name": "esm-apps",
|
||||
"description": "Expanded Security Maintenance for Applications",
|
||||
"available": "yes",
|
||||
"entitled": "yes",
|
||||
"status": "disabled"
|
||||
},
|
||||
{
|
||||
"name": "esm-infra",
|
||||
"description": "Expanded Security Maintenance for Infrastructure",
|
||||
"available": "yes",
|
||||
"entitled": "yes",
|
||||
"status": "disabled"
|
||||
},
|
||||
{
|
||||
"name": "fips",
|
||||
"description": "NIST-certified FIPS crypto packages",
|
||||
"available": "yes",
|
||||
"entitled": "yes",
|
||||
"status": "enabled"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
Types: deb
|
||||
URIs: http://archive.ubuntu.com/ubuntu/
|
||||
Suites: noble noble-updates noble-backports
|
||||
Components: main restricted universe multiverse
|
||||
Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
|
||||
@@ -27,6 +27,7 @@ var knownNonMetadataTypeNames = strset.New(
|
||||
var knownMetadataTypeNames = strset.New(
|
||||
"DotnetPortableExecutableEntry",
|
||||
"GGUFFileHeader",
|
||||
"SafeTensorsModelInfo",
|
||||
)
|
||||
|
||||
func DiscoverTypeNames() ([]string, error) {
|
||||
|
||||
@@ -9,8 +9,10 @@ func AllTypes() []any {
|
||||
return []any{
|
||||
pkg.AlpmDBEntry{},
|
||||
pkg.ApkDBEntry{},
|
||||
pkg.AppleAppBundleEntry{},
|
||||
pkg.BinarySignature{},
|
||||
pkg.BitnamiSBOMEntry{},
|
||||
pkg.BunLockEntry{},
|
||||
pkg.CocoaPodfileLockEntry{},
|
||||
pkg.ConanV1LockEntry{},
|
||||
pkg.ConanV2LockEntry{},
|
||||
@@ -19,6 +21,8 @@ func AllTypes() []any {
|
||||
pkg.CondaMetaPackage{},
|
||||
pkg.DartPubspec{},
|
||||
pkg.DartPubspecLockEntry{},
|
||||
pkg.DenoLockEntry{},
|
||||
pkg.DenoRemoteLockEntry{},
|
||||
pkg.DotnetDepsEntry{},
|
||||
pkg.DotnetPackagesLockEntry{},
|
||||
pkg.DotnetPortableExecutableEntry{},
|
||||
@@ -64,10 +68,12 @@ func AllTypes() []any {
|
||||
pkg.RubyGemspec{},
|
||||
pkg.RustBinaryAuditEntry{},
|
||||
pkg.RustCargoLockEntry{},
|
||||
pkg.SafeTensorsModelInfo{},
|
||||
pkg.SnapEntry{},
|
||||
pkg.SwiftPackageManagerResolvedEntry{},
|
||||
pkg.SwiplPackEntry{},
|
||||
pkg.TerraformLockProviderEntry{},
|
||||
pkg.VcpkgManifest{},
|
||||
pkg.WordpressPluginEntry{},
|
||||
pkg.YarnLockEntry{},
|
||||
}
|
||||
|
||||
@@ -73,6 +73,8 @@ var jsonTypes = makeJSONTypes(
|
||||
jsonNames(pkg.ConaninfoEntry{}, "c-conan-info-entry"),
|
||||
jsonNames(pkg.DartPubspecLockEntry{}, "dart-pubspec-lock-entry", "DartPubMetadata"),
|
||||
jsonNames(pkg.DartPubspec{}, "dart-pubspec"),
|
||||
jsonNames(pkg.DenoLockEntry{}, "deno-lock-entry"),
|
||||
jsonNames(pkg.DenoRemoteLockEntry{}, "deno-remote-lock-entry"),
|
||||
jsonNames(pkg.DotnetDepsEntry{}, "dotnet-deps-entry", "DotnetDepsMetadata"),
|
||||
jsonNames(pkg.DotnetPortableExecutableEntry{}, "dotnet-portable-executable-entry"),
|
||||
jsonNames(pkg.DpkgArchiveEntry{}, "dpkg-archive-entry"),
|
||||
@@ -96,6 +98,7 @@ var jsonTypes = makeJSONTypes(
|
||||
jsonNames(pkg.NpmPackageLockEntry{}, "javascript-npm-package-lock-entry", "NpmPackageLockJsonMetadata"),
|
||||
jsonNames(pkg.YarnLockEntry{}, "javascript-yarn-lock-entry", "YarnLockJsonMetadata"),
|
||||
jsonNames(pkg.PnpmLockEntry{}, "javascript-pnpm-lock-entry"),
|
||||
jsonNames(pkg.BunLockEntry{}, "javascript-bun-lock-entry"),
|
||||
jsonNames(pkg.PEBinary{}, "pe-binary"),
|
||||
jsonNames(pkg.PhpComposerLockEntry{}, "php-composer-lock-entry", "PhpComposerJsonMetadata"),
|
||||
jsonNamesWithoutLookup(pkg.PhpComposerInstalledEntry{}, "php-composer-installed-entry", "PhpComposerJsonMetadata"), // the legacy value is split into two types, where the other is preferred
|
||||
@@ -121,11 +124,14 @@ var jsonTypes = makeJSONTypes(
|
||||
jsonNames(pkg.SnapEntry{}, "snap-entry"),
|
||||
jsonNames(pkg.WordpressPluginEntry{}, "wordpress-plugin-entry", "WordpressMetadata"),
|
||||
jsonNames(pkg.HomebrewFormula{}, "homebrew-formula"),
|
||||
jsonNames(pkg.AppleAppBundleEntry{}, "apple-app-bundle-entry"),
|
||||
jsonNames(pkg.LuaRocksPackage{}, "luarocks-package"),
|
||||
jsonNames(pkg.TerraformLockProviderEntry{}, "terraform-lock-provider-entry"),
|
||||
jsonNames(pkg.DotnetPackagesLockEntry{}, "dotnet-packages-lock-entry"),
|
||||
jsonNames(pkg.CondaMetaPackage{}, "conda-metadata-entry", "CondaPackageMetadata"),
|
||||
jsonNames(pkg.GGUFFileHeader{}, "gguf-file-header"),
|
||||
jsonNames(pkg.SafeTensorsModelInfo{}, "safetensors-model-info"),
|
||||
jsonNames(pkg.VcpkgManifest{}, "vcpkg-manifest"),
|
||||
)
|
||||
|
||||
func expandLegacyNameVariants(names ...string) []string {
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
package packagemetadata
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// untypedFieldAllowlist holds package metadata fields that are allowed to carry untyped (interface) values.
|
||||
// Values in these fields come straight from whatever a parser decoded, so they are not shaped by syft
|
||||
// and can hold anything an encoder or hasher chokes on. Every entry is part of existing SBOM output and
|
||||
// cannot be retyped without breaking it; new entries need a reason and a review.
|
||||
var untypedFieldAllowlist = map[string]string{
|
||||
"GGUFFileHeader.RemainingKeyValues": "raw GGUF header key-values; retyping changes the header output and metadataHash",
|
||||
}
|
||||
|
||||
// TestNoUntypedMetadataFields fails when a package metadata type gains a field typed any, []any,
|
||||
// map[string]any (or any other interface) that is not on the allowlist.
|
||||
func TestNoUntypedMetadataFields(t *testing.T) {
|
||||
var hits []string
|
||||
for _, ty := range AllTypes() {
|
||||
rt := reflect.TypeOf(ty)
|
||||
collectUntyped(rt, rt.Name(), map[reflect.Type]bool{}, &hits)
|
||||
}
|
||||
|
||||
seen := map[string]bool{}
|
||||
for _, h := range hits {
|
||||
seen[h] = true
|
||||
if _, ok := untypedFieldAllowlist[h]; !ok {
|
||||
t.Errorf("untyped field %s: use a concrete type, or add it to untypedFieldAllowlist with a reason", h)
|
||||
}
|
||||
}
|
||||
for k := range untypedFieldAllowlist {
|
||||
if !seen[k] {
|
||||
t.Errorf("stale untypedFieldAllowlist entry %s: field no longer exists or is now typed", k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// collectUntyped records "Struct.Field" for every field whose type reaches an interface, descending
|
||||
// through pointers, slices, arrays, maps and nested syft-owned structs.
|
||||
func collectUntyped(rt reflect.Type, path string, visited map[reflect.Type]bool, hits *[]string) {
|
||||
switch rt.Kind() {
|
||||
case reflect.Interface:
|
||||
*hits = append(*hits, path)
|
||||
case reflect.Pointer, reflect.Slice, reflect.Array:
|
||||
collectUntyped(rt.Elem(), path, visited, hits)
|
||||
case reflect.Map:
|
||||
collectUntyped(rt.Key(), path, visited, hits)
|
||||
collectUntyped(rt.Elem(), path, visited, hits)
|
||||
case reflect.Struct:
|
||||
if visited[rt] {
|
||||
return
|
||||
}
|
||||
if rt.Name() != "" && !strings.HasPrefix(rt.PkgPath(), "github.com/anchore/syft/") {
|
||||
// a third-party type in the model carries whatever shape the library gives it (the same leak as an
|
||||
// interface field), so it counts as untyped. The standard library is left alone.
|
||||
if strings.Contains(strings.Split(rt.PkgPath(), "/")[0], ".") {
|
||||
*hits = append(*hits, path)
|
||||
}
|
||||
return
|
||||
}
|
||||
visited[rt] = true
|
||||
for i := 0; i < rt.NumField(); i++ {
|
||||
f := rt.Field(i)
|
||||
// unexported embedded structs still promote their exported fields to the encoders and the hasher. A
|
||||
// field is only out of reach when it is neither encoded nor hashed by SetID.
|
||||
if (!f.IsExported() && !f.Anonymous) || (f.Tag.Get("json") == "-" && f.Tag.Get("hash") == "ignore") {
|
||||
continue
|
||||
}
|
||||
collectUntyped(f.Type, rt.Name()+"."+f.Name, visited, hits)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,6 +65,30 @@ func (i *Index) Add(relationships ...artifact.Relationship) {
|
||||
}
|
||||
|
||||
func (i *Index) Remove(id artifact.ID) {
|
||||
// scrub this node's edges from the adjacent nodes' maps before dropping the node itself. without this, the
|
||||
// edges live on as dangling pointers inside the other endpoints' mappedRelationships, so query methods
|
||||
// (From/To/References/Coordinates/Contains) would keep returning relationships that have been removed.
|
||||
if mapped := i.fromID[id]; mapped != nil {
|
||||
for _, sr := range mapped.allRelated {
|
||||
if sr.to == id {
|
||||
continue // self-edge: this node's own maps are deleted below
|
||||
}
|
||||
if other := i.toID[sr.to]; other != nil {
|
||||
other.remove(id, sr)
|
||||
}
|
||||
}
|
||||
}
|
||||
if mapped := i.toID[id]; mapped != nil {
|
||||
for _, sr := range mapped.allRelated {
|
||||
if sr.from == id {
|
||||
continue // self-edge: this node's own maps are deleted below
|
||||
}
|
||||
if other := i.fromID[sr.from]; other != nil {
|
||||
other.remove(id, sr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delete(i.fromID, id)
|
||||
delete(i.toID, id)
|
||||
|
||||
@@ -78,6 +102,12 @@ func (i *Index) Remove(id artifact.ID) {
|
||||
}
|
||||
|
||||
func (i *Index) Replace(ogID artifact.ID, replacement artifact.Identifiable) {
|
||||
if replacement == nil || replacement.ID() == ogID {
|
||||
// replacing a node with one that has the same ID would re-add the (deduped) edges and then the trailing
|
||||
// Remove(ogID) would delete them, silently wiping all of the node's relationships. treat it as a no-op.
|
||||
return
|
||||
}
|
||||
|
||||
for _, mapped := range fromMappedByID(i.fromID, ogID) {
|
||||
// the stale relationship(i.e. if there's an elder ID in either side) should be discarded
|
||||
if len(fromMappedByID(i.toID, mapped.relationship.To.ID())) == 0 {
|
||||
@@ -91,8 +121,10 @@ func (i *Index) Replace(ogID artifact.ID, replacement artifact.Identifiable) {
|
||||
}
|
||||
|
||||
for _, mapped := range fromMappedByID(i.toID, ogID) {
|
||||
// same as the above
|
||||
if len(fromMappedByID(i.fromID, mapped.relationship.To.ID())) == 0 {
|
||||
// same as the above, but check the surviving other endpoint (the From side, since these are edges TO ogID).
|
||||
// note: this must reference From (not To, which is ogID itself) so that nodes appearing only on the To side
|
||||
// of relationships (e.g. a go main module that nothing depends on) keep their edges after an ID change.
|
||||
if len(fromMappedByID(i.fromID, mapped.relationship.From.ID())) == 0 {
|
||||
continue
|
||||
}
|
||||
i.Add(artifact.Relationship{
|
||||
@@ -206,6 +238,22 @@ func (m *mappedRelationships) add(id artifact.ID, newRelationship *sortableRelat
|
||||
typeMap[id] = newRelationship
|
||||
}
|
||||
|
||||
// remove deletes a single relationship (the other endpoint keyed by id) from this node's maps. it is the inverse
|
||||
// of add and is used to keep adjacent nodes consistent when a node is removed from the index.
|
||||
func (m *mappedRelationships) remove(id artifact.ID, target *sortableRelationship) {
|
||||
filtered := m.allRelated[:0]
|
||||
for _, r := range m.allRelated {
|
||||
if r != target {
|
||||
filtered = append(filtered, r)
|
||||
}
|
||||
}
|
||||
m.allRelated = filtered
|
||||
|
||||
if typeMap := m.typeMap[target.relationship.Type]; typeMap != nil && typeMap[id] == target {
|
||||
delete(typeMap, id)
|
||||
}
|
||||
}
|
||||
|
||||
type sortableRelationship struct {
|
||||
from artifact.ID
|
||||
to artifact.ID
|
||||
|
||||
@@ -380,6 +380,41 @@ func TestReplace(t *testing.T) {
|
||||
compareRelationships(t, expectedRels, index.All())
|
||||
}
|
||||
|
||||
func TestReplace_NodeOnlyOnToSide(t *testing.T) {
|
||||
// regression: a node that only appears on the To side of relationships (nothing depends on it, e.g. a go main
|
||||
// module that other modules are a dependency-of) must keep its incoming edges when its ID changes (such as when a
|
||||
// compliance rule stubs a missing version and recomputes the package ID). previously the To-side remap guard
|
||||
// checked ogID itself instead of the surviving From endpoint, so all such edges were dropped.
|
||||
main := pkg.Package{Name: "main-module"} // empty version, like a go main module
|
||||
dep1 := pkg.Package{Name: "dep-1", Version: "1.0.0"}
|
||||
dep2 := pkg.Package{Name: "dep-2", Version: "2.0.0"}
|
||||
|
||||
for _, p := range []*pkg.Package{&main, &dep1, &dep2} {
|
||||
p.SetID()
|
||||
}
|
||||
|
||||
// both deps are a dependency-of main; main has no outgoing edges
|
||||
r1 := artifact.Relationship{From: dep1, To: main, Type: artifact.DependencyOfRelationship}
|
||||
r2 := artifact.Relationship{From: dep2, To: main, Type: artifact.DependencyOfRelationship}
|
||||
|
||||
index := NewIndex(r1, r2)
|
||||
|
||||
// simulate the version stub: main gets a new ID
|
||||
stubbedMain := main
|
||||
stubbedMain.Version = "UNKNOWN"
|
||||
stubbedMain.SetID()
|
||||
require.NotEqual(t, main.ID(), stubbedMain.ID())
|
||||
|
||||
index.Replace(main.ID(), &stubbedMain)
|
||||
|
||||
expectedRels := []artifact.Relationship{
|
||||
{From: dep1, To: stubbedMain, Type: artifact.DependencyOfRelationship},
|
||||
{From: dep2, To: stubbedMain, Type: artifact.DependencyOfRelationship},
|
||||
}
|
||||
|
||||
compareRelationships(t, expectedRels, index.All())
|
||||
}
|
||||
|
||||
func compareRelationships(t testing.TB, expected, actual []artifact.Relationship) {
|
||||
assert.Equal(t, len(expected), len(actual), "number of relationships should match")
|
||||
for _, e := range expected {
|
||||
@@ -408,3 +443,94 @@ func TestReplace_NoExistingRelations(t *testing.T) {
|
||||
allRels := index.All()
|
||||
assert.Len(t, allRels, 0)
|
||||
}
|
||||
|
||||
// Remove must scrub the removed node's edges from adjacent nodes too, otherwise query methods keep returning
|
||||
// relationships that no longer exist. the original Remove only deleted the removed node's own maps, leaving
|
||||
// dangling pointers in the other endpoints.
|
||||
func TestRemove_NoStaleEdgesInAdjacentNodes(t *testing.T) {
|
||||
p1 := pkg.Package{Name: "pkg-1", Version: "1"}
|
||||
p2 := pkg.Package{Name: "pkg-2", Version: "2"}
|
||||
p3 := pkg.Package{Name: "pkg-3", Version: "3"}
|
||||
for _, p := range []*pkg.Package{&p1, &p2, &p3} {
|
||||
p.SetID()
|
||||
}
|
||||
|
||||
r12 := artifact.Relationship{From: p1, To: p2, Type: artifact.DependencyOfRelationship}
|
||||
r32 := artifact.Relationship{From: p3, To: p2, Type: artifact.DependencyOfRelationship}
|
||||
index := NewIndex(r12, r32)
|
||||
|
||||
index.Remove(p1.ID())
|
||||
|
||||
// p2 was on the To side of the removed edge; its incoming view must no longer include the stale p1 edge,
|
||||
// but must still include the live p3 edge.
|
||||
compareRelationships(t, []artifact.Relationship{r32}, index.To(p2, artifact.DependencyOfRelationship))
|
||||
assert.False(t, index.Contains(r12), "Contains must not report a removed edge")
|
||||
assert.Len(t, index.References(p2), 1, "p2 should only reference the surviving p3 edge")
|
||||
assert.True(t, index.Contains(r32), "surviving edge should remain")
|
||||
compareRelationships(t, []artifact.Relationship{r32}, index.All())
|
||||
}
|
||||
|
||||
// Replace must leave no dangling references to the old ID in adjacent nodes' query views.
|
||||
func TestReplace_NoStaleEdgesAfterReplace(t *testing.T) {
|
||||
main := pkg.Package{Name: "main-module"} // empty version
|
||||
dep := pkg.Package{Name: "dep", Version: "1.0.0"}
|
||||
main.SetID()
|
||||
dep.SetID()
|
||||
|
||||
oldEdge := artifact.Relationship{From: dep, To: main, Type: artifact.DependencyOfRelationship}
|
||||
index := NewIndex(oldEdge)
|
||||
|
||||
stubbed := main
|
||||
stubbed.Version = "UNKNOWN"
|
||||
stubbed.SetID()
|
||||
require.NotEqual(t, main.ID(), stubbed.ID())
|
||||
|
||||
index.Replace(main.ID(), &stubbed)
|
||||
|
||||
newEdge := artifact.Relationship{From: dep, To: stubbed, Type: artifact.DependencyOfRelationship}
|
||||
// the dep's outgoing view must point at the stubbed node, not the old one
|
||||
from := index.From(dep, artifact.DependencyOfRelationship)
|
||||
require.Len(t, from, 1)
|
||||
assert.Equal(t, stubbed.ID(), from[0].To.ID())
|
||||
assert.True(t, index.Contains(newEdge))
|
||||
assert.False(t, index.Contains(oldEdge), "stale edge to old ID must be gone")
|
||||
assert.Empty(t, index.To(main), "old node should have no incoming edges")
|
||||
compareRelationships(t, []artifact.Relationship{newEdge}, index.All())
|
||||
}
|
||||
|
||||
// replacing a node with one that has the same ID must be a no-op, not silently wipe its edges.
|
||||
func TestReplace_SameIDIsNoOp(t *testing.T) {
|
||||
a := pkg.Package{Name: "a", Version: "1"}
|
||||
b := pkg.Package{Name: "b", Version: "2"}
|
||||
a.SetID()
|
||||
b.SetID()
|
||||
edge := artifact.Relationship{From: a, To: b, Type: artifact.DependencyOfRelationship}
|
||||
index := NewIndex(edge)
|
||||
|
||||
// same identity object -> same ID
|
||||
index.Replace(b.ID(), b)
|
||||
|
||||
compareRelationships(t, []artifact.Relationship{edge}, index.All())
|
||||
assert.True(t, index.Contains(edge))
|
||||
}
|
||||
|
||||
// a self-edge on the replaced node should be remapped to replacement->replacement, not dropped.
|
||||
func TestReplace_SelfEdge(t *testing.T) {
|
||||
n := pkg.Package{Name: "n"} // empty version
|
||||
n.SetID()
|
||||
index := NewIndex(artifact.Relationship{From: n, To: n, Type: artifact.DependencyOfRelationship})
|
||||
|
||||
sn := n
|
||||
sn.Version = "UNKNOWN"
|
||||
sn.SetID()
|
||||
require.NotEqual(t, n.ID(), sn.ID())
|
||||
|
||||
index.Replace(n.ID(), &sn)
|
||||
|
||||
all := index.All()
|
||||
require.Len(t, all, 1)
|
||||
assert.Equal(t, sn.ID(), all[0].From.ID())
|
||||
assert.Equal(t, sn.ID(), all[0].To.ID())
|
||||
assert.Empty(t, index.From(n), "no edges should reference the old ID")
|
||||
assert.Empty(t, index.To(n), "no edges should reference the old ID")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package internal
|
||||
|
||||
// MaxSafeTensorsHeaderSize caps a safetensors header's JSON (excluding its
|
||||
// 8-byte length prefix). Shared by the ai cataloger and the OCI model source so
|
||||
// dir and OCI scans accept the same headers. Real-world shard headers are well
|
||||
// under 1 MB.
|
||||
const MaxSafeTensorsHeaderSize = 8 * 1024 * 1024
|
||||
@@ -44,6 +44,7 @@ type LicenseInfo struct {
|
||||
// LicenseByURL returns the license ID and name for a given URL from the SPDX license list.
|
||||
// The URL should match one of the URLs in the seeAlso field of an SPDX license.
|
||||
// The scheme (http:// or https://) is stripped before lookup, so both schemes match.
|
||||
// opensource.org URLs additionally match regardless of the form used (see normalizeOpenSourceOrgURL).
|
||||
func LicenseByURL(url string) (LicenseInfo, bool) {
|
||||
url = strings.TrimSpace(url)
|
||||
url = stripScheme(url)
|
||||
@@ -52,9 +53,51 @@ func LicenseByURL(url string) (LicenseInfo, bool) {
|
||||
ID: id,
|
||||
}, true
|
||||
}
|
||||
if normalized, ok := normalizeOpenSourceOrgURL(url); ok {
|
||||
if id, exists := openSourceOrgURLToLicense[normalized]; exists {
|
||||
return LicenseInfo{
|
||||
ID: id,
|
||||
}, true
|
||||
}
|
||||
}
|
||||
return LicenseInfo{}, false
|
||||
}
|
||||
|
||||
// openSourceOrgURLToLicense indexes the opensource.org entries of urlToLicense by normalized URL.
|
||||
var openSourceOrgURLToLicense = buildOpenSourceOrgURLToLicense(urlToLicense)
|
||||
|
||||
func buildOpenSourceOrgURLToLicense(urls map[string]string) map[string]string {
|
||||
index := make(map[string]string)
|
||||
ambiguous := make(map[string]bool)
|
||||
for url, id := range urls {
|
||||
normalized, ok := normalizeOpenSourceOrgURL(url)
|
||||
if !ok || ambiguous[normalized] {
|
||||
continue
|
||||
}
|
||||
if existing, exists := index[normalized]; exists && existing != id {
|
||||
// don't guess between licenses that only differ by URL form
|
||||
delete(index, normalized)
|
||||
ambiguous[normalized] = true
|
||||
continue
|
||||
}
|
||||
index[normalized] = id
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
||||
// normalizeOpenSourceOrgURL reduces a scheme-less opensource.org URL to a canonical form.
|
||||
// SPDX has changed these URLs over time (e.g. 3.29.0 replaced opensource.org/licenses/MIT and
|
||||
// opensource.org/license/mit/ with opensource.org/license/MIT), while package metadata in the wild
|
||||
// still uses the older forms. The www. prefix, /licenses/ vs /license/, case, and trailing slash are ignored.
|
||||
func normalizeOpenSourceOrgURL(url string) (string, bool) {
|
||||
url = strings.TrimPrefix(strings.ToLower(url), "www.")
|
||||
if !strings.HasPrefix(url, "opensource.org/") {
|
||||
return "", false
|
||||
}
|
||||
url = strings.Replace(url, "opensource.org/licenses/", "opensource.org/license/", 1)
|
||||
return strings.TrimSuffix(url, "/"), true
|
||||
}
|
||||
|
||||
// stripScheme removes http:// or https:// prefix from a URL.
|
||||
// This allows a single map entry to match both schemes.
|
||||
func stripScheme(url string) string {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// This file was generated using data from https://spdx.org/licenses/licenses.json
|
||||
package spdxlicense
|
||||
|
||||
const Version = "3.28.0"
|
||||
const Version = "3.29.0"
|
||||
|
||||
var licenseIDs = map[string]string{
|
||||
"0bsd": "0BSD",
|
||||
@@ -116,6 +116,7 @@ var licenseIDs = map[string]string{
|
||||
"aswfdigitalassets1.0.0": "ASWF-Digital-Assets-1.0",
|
||||
"aswfdigitalassets1.1": "ASWF-Digital-Assets-1.1",
|
||||
"aswfdigitalassets1.1.0": "ASWF-Digital-Assets-1.1",
|
||||
"atcgame": "atc-game",
|
||||
"baekmuk": "Baekmuk",
|
||||
"bahyph": "Bahyph",
|
||||
"barr": "Barr",
|
||||
@@ -142,8 +143,11 @@ var licenseIDs = map[string]string{
|
||||
"briangladman2.0clause": "Brian-Gladman-2-Clause",
|
||||
"briangladman2clause": "Brian-Gladman-2-Clause",
|
||||
"briangladman3.0.0clause": "Brian-Gladman-3-Clause",
|
||||
"briangladman3.0.0clausenoconversion": "Brian-Gladman-3-Clause-no-conversion",
|
||||
"briangladman3.0clause": "Brian-Gladman-3-Clause",
|
||||
"briangladman3.0clausenoconversion": "Brian-Gladman-3-Clause-no-conversion",
|
||||
"briangladman3clause": "Brian-Gladman-3-Clause",
|
||||
"briangladman3clausenoconversion": "Brian-Gladman-3-Clause-no-conversion",
|
||||
"bsd1.0.0clause": "BSD-1-Clause",
|
||||
"bsd1.0clause": "BSD-1-Clause",
|
||||
"bsd1clause": "BSD-1-Clause",
|
||||
@@ -154,6 +158,7 @@ var licenseIDs = map[string]string{
|
||||
"bsd2.0.0clausenetbsd": "BSD-2-Clause",
|
||||
"bsd2.0.0clausepatent": "BSD-2-Clause-Patent",
|
||||
"bsd2.0.0clausepkgconfdisclaimer": "BSD-2-Clause-pkgconf-disclaimer",
|
||||
"bsd2.0.0clauseposunchanged": "BSD-2-Clause-pos-unchanged",
|
||||
"bsd2.0.0clauseviews": "BSD-2-Clause-Views",
|
||||
"bsd2.0clause": "BSD-2-Clause",
|
||||
"bsd2.0clausedarwin": "BSD-2-Clause-Darwin",
|
||||
@@ -162,6 +167,7 @@ var licenseIDs = map[string]string{
|
||||
"bsd2.0clausenetbsd": "BSD-2-Clause",
|
||||
"bsd2.0clausepatent": "BSD-2-Clause-Patent",
|
||||
"bsd2.0clausepkgconfdisclaimer": "BSD-2-Clause-pkgconf-disclaimer",
|
||||
"bsd2.0clauseposunchanged": "BSD-2-Clause-pos-unchanged",
|
||||
"bsd2.0clauseviews": "BSD-2-Clause-Views",
|
||||
"bsd2clause": "BSD-2-Clause",
|
||||
"bsd2clausedarwin": "BSD-2-Clause-Darwin",
|
||||
@@ -170,6 +176,7 @@ var licenseIDs = map[string]string{
|
||||
"bsd2clausenetbsd": "BSD-2-Clause",
|
||||
"bsd2clausepatent": "BSD-2-Clause-Patent",
|
||||
"bsd2clausepkgconfdisclaimer": "BSD-2-Clause-pkgconf-disclaimer",
|
||||
"bsd2clauseposunchanged": "BSD-2-Clause-pos-unchanged",
|
||||
"bsd2clauseviews": "BSD-2-Clause-Views",
|
||||
"bsd3.0.0clause": "BSD-3-Clause",
|
||||
"bsd3.0.0clauseacpica": "BSD-3-Clause-acpica",
|
||||
@@ -184,6 +191,7 @@ var licenseIDs = map[string]string{
|
||||
"bsd3.0.0clausenonuclearlicense2014": "BSD-3-Clause-No-Nuclear-License-2014",
|
||||
"bsd3.0.0clausenonuclearwarranty": "BSD-3-Clause-No-Nuclear-Warranty",
|
||||
"bsd3.0.0clauseopenmpi": "BSD-3-Clause-Open-MPI",
|
||||
"bsd3.0.0clauseopenwebui": "BSD-3-Clause-OpenWebUI",
|
||||
"bsd3.0.0clausesun": "BSD-3-Clause-Sun",
|
||||
"bsd3.0.0clausetso": "BSD-3-Clause-Tso",
|
||||
"bsd3.0clause": "BSD-3-Clause",
|
||||
@@ -199,6 +207,7 @@ var licenseIDs = map[string]string{
|
||||
"bsd3.0clausenonuclearlicense2014": "BSD-3-Clause-No-Nuclear-License-2014",
|
||||
"bsd3.0clausenonuclearwarranty": "BSD-3-Clause-No-Nuclear-Warranty",
|
||||
"bsd3.0clauseopenmpi": "BSD-3-Clause-Open-MPI",
|
||||
"bsd3.0clauseopenwebui": "BSD-3-Clause-OpenWebUI",
|
||||
"bsd3.0clausesun": "BSD-3-Clause-Sun",
|
||||
"bsd3.0clausetso": "BSD-3-Clause-Tso",
|
||||
"bsd3clause": "BSD-3-Clause",
|
||||
@@ -214,6 +223,7 @@ var licenseIDs = map[string]string{
|
||||
"bsd3clausenonuclearlicense2014": "BSD-3-Clause-No-Nuclear-License-2014",
|
||||
"bsd3clausenonuclearwarranty": "BSD-3-Clause-No-Nuclear-Warranty",
|
||||
"bsd3clauseopenmpi": "BSD-3-Clause-Open-MPI",
|
||||
"bsd3clauseopenwebui": "BSD-3-Clause-OpenWebUI",
|
||||
"bsd3clausesun": "BSD-3-Clause-Sun",
|
||||
"bsd3clausetso": "BSD-3-Clause-Tso",
|
||||
"bsd4.0.0clause": "BSD-4-Clause",
|
||||
@@ -232,12 +242,15 @@ var licenseIDs = map[string]string{
|
||||
"bsd4reno": "BSD-4.3RENO",
|
||||
"bsd4tahoe": "BSD-4.3TAHOE",
|
||||
"bsdadvertisingacknowledgement": "BSD-Advertising-Acknowledgement",
|
||||
"bsdasktoendorse": "BSD-ask-to-endorse",
|
||||
"bsdattributionhpnddisclaimer": "BSD-Attribution-HPND-disclaimer",
|
||||
"bsdinfernonettverk": "BSD-Inferno-Nettverk",
|
||||
"bsdmarkmodifications": "BSD-Mark-Modifications",
|
||||
"bsdprotection": "BSD-Protection",
|
||||
"bsdsourcealtgpl": "BSD-Source-alt-GPL",
|
||||
"bsdsourcebeginningfile": "BSD-Source-beginning-file",
|
||||
"bsdsourcecode": "BSD-Source-Code",
|
||||
"bsdsourcecodenodisclaimer": "BSD-Source-Code-no-disclaimer",
|
||||
"bsdsystemics": "BSD-Systemics",
|
||||
"bsdsystemicsw3.0.0works": "BSD-Systemics-W3Works",
|
||||
"bsdsystemicsw3.0works": "BSD-Systemics-W3Works",
|
||||
@@ -246,6 +259,7 @@ var licenseIDs = map[string]string{
|
||||
"bsl1.0": "BSL-1.0",
|
||||
"bsl1.0.0": "BSL-1.0",
|
||||
"buddy": "Buddy",
|
||||
"bugroff": "Bugroff",
|
||||
"busl1": "BUSL-1.1",
|
||||
"busl1.1": "BUSL-1.1",
|
||||
"busl1.1.0": "BUSL-1.1",
|
||||
@@ -316,8 +330,11 @@ var licenseIDs = map[string]string{
|
||||
"ccbync3.0": "CC-BY-NC-3.0",
|
||||
"ccbync3.0.0": "CC-BY-NC-3.0",
|
||||
"ccbync3.0.0de": "CC-BY-NC-3.0-DE",
|
||||
"ccbync3.0.0igo": "CC-BY-NC-3.0-IGO",
|
||||
"ccbync3.0de": "CC-BY-NC-3.0-DE",
|
||||
"ccbync3.0igo": "CC-BY-NC-3.0-IGO",
|
||||
"ccbync3de": "CC-BY-NC-3.0-DE",
|
||||
"ccbync3igo": "CC-BY-NC-3.0-IGO",
|
||||
"ccbync4": "CC-BY-NC-4.0",
|
||||
"ccbync4.0": "CC-BY-NC-4.0",
|
||||
"ccbync4.0.0": "CC-BY-NC-4.0",
|
||||
@@ -592,6 +609,7 @@ var licenseIDs = map[string]string{
|
||||
"fair": "Fair",
|
||||
"fbm": "FBM",
|
||||
"fdkaac": "FDK-AAC",
|
||||
"fdkmpegh": "FDK-MPEG-H",
|
||||
"fergusontwofish": "Ferguson-Twofish",
|
||||
"frameworx1": "Frameworx-1.0",
|
||||
"frameworx1.0": "Frameworx-1.0",
|
||||
@@ -767,6 +785,9 @@ var licenseIDs = map[string]string{
|
||||
"hippocratic2": "Hippocratic-2.1",
|
||||
"hippocratic2.1": "Hippocratic-2.1",
|
||||
"hippocratic2.1.0": "Hippocratic-2.1",
|
||||
"hippocratic3.0.0core": "Hippocratic-3.0-core",
|
||||
"hippocratic3.0core": "Hippocratic-3.0-core",
|
||||
"hippocratic3core": "Hippocratic-3.0-core",
|
||||
"hp1986": "HP-1986",
|
||||
"hp1986.0": "HP-1986",
|
||||
"hp1986.0.0": "HP-1986",
|
||||
@@ -813,6 +834,7 @@ var licenseIDs = map[string]string{
|
||||
"imlib2": "Imlib2",
|
||||
"imlib2.0": "Imlib2",
|
||||
"imlib2.0.0": "Imlib2",
|
||||
"informatica": "Informatica",
|
||||
"infozip": "Info-ZIP",
|
||||
"innernet2": "Inner-Net-2.0",
|
||||
"innernet2.0": "Inner-Net-2.0",
|
||||
@@ -1011,6 +1033,9 @@ var licenseIDs = map[string]string{
|
||||
"mulanpsl2.0.0": "MulanPSL-2.0",
|
||||
"multics": "Multics",
|
||||
"mup": "Mup",
|
||||
"mvt1": "MVT-1.1",
|
||||
"mvt1.1": "MVT-1.1",
|
||||
"mvt1.1.0": "MVT-1.1",
|
||||
"naist2003": "NAIST-2003",
|
||||
"naist2003.0": "NAIST-2003",
|
||||
"naist2003.0.0": "NAIST-2003",
|
||||
@@ -1489,6 +1514,8 @@ var urlToLicense = map[string]string{
|
||||
"cdla.io/permissive-1-0": "CDLA-Permissive-1.0",
|
||||
"cdla.io/sharing-1-0": "CDLA-Sharing-1.0",
|
||||
"celestrak.org/publications/AIAA/2006-6753/faq.php": "SGP4",
|
||||
"cgit.freebsd.org/src/plain/sys/dev/aic7xxx/aic7xxx_osm.c": "BSD-Source-alt-GPL",
|
||||
"cgit.freebsd.org/src/tree/usr.bin/sockstat/sockstat.c": "BSD-2-Clause-pos-unchanged",
|
||||
"cgit.freedesktop.org/libbsd/tree/man/setproctitle.3bsd": "libutil-David-Nugent",
|
||||
"coil.apotheon.org/plaintext/01.0.txt": "COIL-1.0",
|
||||
"core.tcl-lang.org/tk/file?name=compat/unistd.h": "HPND-UC",
|
||||
@@ -1512,6 +1539,7 @@ var urlToLicense = map[string]string{
|
||||
"creativecommons.org/licenses/by-nc/2.0/legalcode": "CC-BY-NC-2.0",
|
||||
"creativecommons.org/licenses/by-nc/2.5/legalcode": "CC-BY-NC-2.5",
|
||||
"creativecommons.org/licenses/by-nc/3.0/de/legalcode": "CC-BY-NC-3.0-DE",
|
||||
"creativecommons.org/licenses/by-nc/3.0/igo/legalcode": "CC-BY-NC-3.0-IGO",
|
||||
"creativecommons.org/licenses/by-nc/3.0/legalcode": "CC-BY-NC-3.0",
|
||||
"creativecommons.org/licenses/by-nc/4.0/legalcode": "CC-BY-NC-4.0",
|
||||
"creativecommons.org/licenses/by-nd-nc/1.0/legalcode": "CC-BY-NC-ND-1.0",
|
||||
@@ -1564,6 +1592,7 @@ var urlToLicense = map[string]string{
|
||||
"directory.fsf.org/wiki/License:Fdk": "FDK-AAC",
|
||||
"doc.qt.io/archives/3.3/license.html": "QPL-1.0",
|
||||
"doc.qt.nokia.com/3.3/license.html": "QPL-1.0",
|
||||
"docs.mvt.re/en/latest/license/": "MVT-1.1",
|
||||
"docs.omniverse.nvidia.com/install-guide/latest/common/licenses.html": "AML-glslang",
|
||||
"docs.oracle.com/cd/E81115_01/html/E81116/licenses.html": "man2html",
|
||||
"docs.python.org/3/license.html": "Python-2.0.1",
|
||||
@@ -1669,6 +1698,7 @@ var urlToLicense = map[string]string{
|
||||
"fedoraproject.org/wiki/Licensing:Mplus?rd=Licensing/mplus": "mplus",
|
||||
"fedoraproject.org/wiki/Licensing:ThorPublicLicense": "TPL-1.0",
|
||||
"firstdonoharm.dev/version/2/1/license.html": "Hippocratic-2.1",
|
||||
"firstdonoharm.dev/version/3/0/core.txt": "Hippocratic-3.0-core",
|
||||
"forge.gouv.qc.ca/licence/fr/liliq-v1-1/": "LiLiQ-P-1.1",
|
||||
"forge.gouv.qc.ca/licence/liliq-p": "LiLiQ-R-1.1",
|
||||
"forge.gouv.qc.ca/licence/liliq-p/": "LiLiQ-P-1.1",
|
||||
@@ -1686,68 +1716,70 @@ var urlToLicense = map[string]string{
|
||||
"ftp://ftp.tin.org/pub/news/utils/newsx/newsx-1.6.tar.gz": "Zeeff",
|
||||
"gianluca.dellavedova.org/2011/01/03/clarified-artistic-license/": "ClArtistic",
|
||||
"git.denx.de/?p=u-boot.git;a=blob;f=arch/powerpc/cpu/ppc4xx/miiphy.c;h=297155fdafa064b955e53e9832de93bfb0cfb85b;hb=9fab4bf4cc077c21e43941866f3f2c196f28670d": "IBM-pibs",
|
||||
"git.enlightenment.org/legacy/imlib2.git/tree/COPYING": "Imlib2",
|
||||
"git.kernel.org/pub/scm/docs/man-pages/man-pages.git/tree/man2/getcpu.2#n4": "Linux-man-pages-1-para",
|
||||
"git.kernel.org/pub/scm/docs/man-pages/man-pages.git/tree/man2/migrate_pages.2#n8": "Linux-man-pages-copyleft-2-para",
|
||||
"git.kernel.org/pub/scm/docs/man-pages/man-pages.git/tree/man2/move_pages.2#n5": "Linux-man-pages-copyleft-2-para",
|
||||
"git.kernel.org/pub/scm/docs/man-pages/man-pages.git/tree/man2/set_mempolicy.2#n5": "Linux-man-pages-copyleft-var",
|
||||
"git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/infiniband/core/sa.h": "Linux-OpenIB",
|
||||
"git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/sunrpc/auth_gss/gss_generic_token.c?h=v4.19": "HPND-sell-variant",
|
||||
"git.openldap.org/openldap/openldap/-/blob/master/COPYRIGHT#L55-63": "BSD-4.3RENO",
|
||||
"git.openldap.org/openldap/openldap/-/blob/master/COPYRIGHT?ref_type=heads#L39-51": "Furuseth",
|
||||
"git.openldap.org/openldap/openldap/-/blob/master/libraries/libldap/os-local.c?ref_type=heads#L19-23": "PADL",
|
||||
"git.savannah.gnu.org/cgit/freetype/freetype2.git/tree/docs/FTL.TXT": "FTL",
|
||||
"git.savannah.gnu.org/cgit/gcl.git/tree/gcl/lsp/gcl_loop.lsp?h=Version_2_6_13pre": "LOOP",
|
||||
"git.savannah.gnu.org/cgit/gnulib.git/tree/modules/COPYING?id=7b08932179d0d6b017f7df01a2ddf6e096b038e3": "FSFULLRSD",
|
||||
"git.savannah.gnu.org/cgit/indent.git/tree/doc/indent.texi?id=a74c6b4ee49397cf330b333da1042bffa60ed14f#n1788": "BSD-4.3TAHOE",
|
||||
"git.savannah.gnu.org/cgit/indent.git/tree/doc/indent.texi?id=a74c6b4ee49397cf330b333da1042bffa60ed14f#n74": "Latex2e-translated-notice",
|
||||
"git.savannah.gnu.org/cgit/kazlib.git/tree/except.c?id=0062df360c2d17d57f6af19b0e444c51feb99036": "Kazlib",
|
||||
"git.savannah.gnu.org/cgit/wget.git/tree/util/trunc.c?h=v1.21.3&id=40747a11e44ced5a8ac628a41f879ced3e2ebce9#n6": "FSFAP-no-warranty-disclaimer",
|
||||
"git.sr.ht/~nabijaczleweli/ossp-var": "OSSP",
|
||||
"github.com/389ds/389-ds-base/blob/main/ldap/include/sysexits-compat.h#L15": "BSD-4.3TAHOE",
|
||||
"github.com/AcademySoftwareFoundation/foundation/blob/main/digital_assets/aswf_digital_assets_license_v1.0.txt": "ASWF-Digital-Assets-1.0",
|
||||
"github.com/AcademySoftwareFoundation/foundation/blob/main/digital_assets/aswf_digital_assets_license_v1.1.txt": "ASWF-Digital-Assets-1.1",
|
||||
"github.com/AdaCore/gnatcoll-core/blob/master/docs/index.rst": "AdaCore-doc",
|
||||
"github.com/AdaCore/gnatcoll-db/blob/master/docs/index.rst": "AdaCore-doc",
|
||||
"github.com/AdaCore/xmlada/blob/master/docs/index.rst": "AdaCore-doc",
|
||||
"github.com/BuildCraft/BuildCraft/blob/623d323b1868712f29f4a8b0979a02e8d1835131/LICENSE": "MMPL-1.0.1",
|
||||
"github.com/CommunitySpecification/1.0/blob/master/1._Community_Specification_License-v1.md": "Community-Spec-1.0",
|
||||
"github.com/CorsixTH/deps/blob/fd339a9f526d1d9c9f01ccf39e438a015da50035/licences/libgsm.txt": "TU-Berlin-2.0",
|
||||
"github.com/Cyan4973/xxHash/blob/dev/doc/xxhash_spec.md": "LPD-document",
|
||||
"github.com/DISIC/politique-de-contribution-open-source/blob/master/LICENSE.pdf": "etalab-2.0",
|
||||
"github.com/Distrotech/hdparm/blob/4517550db29a91420fb2b020349523b1b4512df2/LICENSE.TXT": "hdparm",
|
||||
"github.com/Distrotech/transfig/blob/master/transfig/transfig.c": "Xfig",
|
||||
"github.com/DrItanium/maya/blob/master/LICENSE.CLIPS": "Clips",
|
||||
"github.com/Dual-Life/mime-base64/blob/master/Base64.xs#L12": "metamail",
|
||||
"github.com/EthicalSource/hippocratic-license/blob/58c0e646d64ff6fbee275bfe2b9492f914e3ab2a/LICENSE.txt": "Hippocratic-2.1",
|
||||
"github.com/FreeRADIUS/freeradius-client/blob/master/COPYRIGHT#L32": "HPND-Fenneberg-Livingston",
|
||||
"github.com/GNOME/gcr/blob/master/docs/COPYING": "GCR-docs",
|
||||
"github.com/GNOME/libxml2/blob/master/dict.c": "ISC-Veillard",
|
||||
"github.com/HDFGroup/hdf5/?tab=License-1-ov-file#readme": "HDF5",
|
||||
"github.com/KhronosGroup/SPIRV-Cross/blob/main/LICENSES/LicenseRef-KhronosFreeUse.txt": "MIT-Khronos-old",
|
||||
"github.com/KhronosGroup/glslang/blob/main/LICENSE.txt#L949": "AML-glslang",
|
||||
"github.com/MariaDB/server/blob/11.6/libmysqld/lib_sql.cc": "Boehm-GC-without-fee",
|
||||
"github.com/Neo23x0/sigma/blob/master/LICENSE.Detection.Rules.md": "DRL-1.0",
|
||||
"github.com/OGRECave/ogre/blob/master/OgreMain/include/OgreSingleton.h#L28C3-L35C46": "Game-Programming-Gems",
|
||||
"github.com/Perl/perl5/blob/blead/util.c#L6136": "Martin-Birgmeier",
|
||||
"github.com/PixarAnimationStudios/OpenSubdiv/blob/v3_5_0/opensubdiv/version.cpp#L2-L22": "Pixar",
|
||||
"github.com/PixarAnimationStudios/OpenSubdiv/raw/v3_5_0/LICENSE.txt": "Pixar",
|
||||
"github.com/RTimothyEdwards/magic/blob/master/LICENSE": "HPND-UC-export-US",
|
||||
"github.com/SELinuxProject/selinux/blob/master/libselinux/LICENSE": "libselinux-1.0",
|
||||
"github.com/SWI-Prolog/packages-clib/blob/master/sha1/brg_endian.h": "Brian-Gladman-3-Clause",
|
||||
"github.com/SWI-Prolog/packages-xpce/blob/161a40cd82004f731ba48024f9d30af388a7edf5/src/img/gifwrite.c#L21-L26": "FBM",
|
||||
"github.com/SWI-Prolog/swipl-devel/blob/master/src/os/dtoa.c": "dtoa",
|
||||
"github.com/SigmaHQ/Detection-Rule-License/blob/6ec7fbde6101d101b5b5d1fcb8f9b69fbc76c04a/LICENSE.Detection.Rules.md": "DRL-1.1",
|
||||
"github.com/Slicer/Slicer/blob/main/License.txt": "3D-Slicer-1.0",
|
||||
"github.com/Unidata/UDUNITS-2/blob/master/COPYRIGHT": "UCAR",
|
||||
"github.com/acpica/acpica/blob/master/source/common/acfileio.c#L119": "BSD-3-Clause-acpica",
|
||||
"github.com/adaptivecomputing/torque/blob/master/PBS_License.txt": "OpenPBS-2.3",
|
||||
"github.com/adversary-org/wtfnmf/raw/3f2cd8235a64350a57a51b9739715edaea63ec1a/COPYING.WTFNMFPL-utf8": "WTFNMFPL",
|
||||
"github.com/adversary-org/wtfnmf/raw/refs/tags/1.0/COPYING.WTFNMFPL": "WTFNMFPL",
|
||||
"github.com/ap/Text-Tabs/blob/master/lib.modern/Text/Tabs.pm#L148": "TTWL",
|
||||
"github.com/apache/apr/blob/trunk/LICENSE#L298C6-L298C29": "Caldera-no-preamble",
|
||||
"github.com/audacious-media-player/audacious/blob/master/COPYING": "BSD-2-Clause-pkgconf-disclaimer",
|
||||
"github.com/audacious-media-player/audacious/blob/master/src/audacious/main.cc": "BSD-2-Clause-pkgconf-disclaimer",
|
||||
"git.enlightenment.org/legacy/imlib2.git/tree/COPYING": "Imlib2",
|
||||
"git.kernel.org/pub/scm/docs/man-pages/man-pages.git/tree/man2/getcpu.2#n4": "Linux-man-pages-1-para",
|
||||
"git.kernel.org/pub/scm/docs/man-pages/man-pages.git/tree/man2/migrate_pages.2#n8": "Linux-man-pages-copyleft-2-para",
|
||||
"git.kernel.org/pub/scm/docs/man-pages/man-pages.git/tree/man2/move_pages.2#n5": "Linux-man-pages-copyleft-2-para",
|
||||
"git.kernel.org/pub/scm/docs/man-pages/man-pages.git/tree/man2/set_mempolicy.2#n5": "Linux-man-pages-copyleft-var",
|
||||
"git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/infiniband/core/sa.h": "Linux-OpenIB",
|
||||
"git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/sunrpc/auth_gss/gss_generic_token.c?h=v4.19": "HPND-sell-variant",
|
||||
"git.openldap.org/openldap/openldap/-/blob/master/COPYRIGHT#L55-63": "BSD-4.3RENO",
|
||||
"git.openldap.org/openldap/openldap/-/blob/master/COPYRIGHT?ref_type=heads#L39-51": "Furuseth",
|
||||
"git.openldap.org/openldap/openldap/-/blob/master/libraries/libldap/os-local.c?ref_type=heads#L19-23": "PADL",
|
||||
"git.savannah.gnu.org/cgit/freetype/freetype2.git/tree/docs/FTL.TXT": "FTL",
|
||||
"git.savannah.gnu.org/cgit/gcl.git/tree/gcl/lsp/gcl_loop.lsp?h=Version_2_6_13pre": "LOOP",
|
||||
"git.savannah.gnu.org/cgit/gnulib.git/tree/modules/COPYING?id=7b08932179d0d6b017f7df01a2ddf6e096b038e3": "FSFULLRSD",
|
||||
"git.savannah.gnu.org/cgit/indent.git/tree/doc/indent.texi?id=a74c6b4ee49397cf330b333da1042bffa60ed14f#n1788": "BSD-4.3TAHOE",
|
||||
"git.savannah.gnu.org/cgit/indent.git/tree/doc/indent.texi?id=a74c6b4ee49397cf330b333da1042bffa60ed14f#n74": "Latex2e-translated-notice",
|
||||
"git.savannah.gnu.org/cgit/kazlib.git/tree/except.c?id=0062df360c2d17d57f6af19b0e444c51feb99036": "Kazlib",
|
||||
"git.savannah.gnu.org/cgit/wget.git/tree/util/trunc.c?h=v1.21.3&id=40747a11e44ced5a8ac628a41f879ced3e2ebce9#n6": "FSFAP-no-warranty-disclaimer",
|
||||
"git.sr.ht/~nabijaczleweli/ossp-var": "OSSP",
|
||||
"github.com/389ds/389-ds-base/blob/main/ldap/include/sysexits-compat.h#L15": "BSD-4.3TAHOE",
|
||||
"github.com/AcademySoftwareFoundation/foundation/blob/main/digital_assets/aswf_digital_assets_license_v1.0.txt": "ASWF-Digital-Assets-1.0",
|
||||
"github.com/AcademySoftwareFoundation/foundation/blob/main/digital_assets/aswf_digital_assets_license_v1.1.txt": "ASWF-Digital-Assets-1.1",
|
||||
"github.com/AdaCore/gnatcoll-core/blob/master/docs/index.rst": "AdaCore-doc",
|
||||
"github.com/AdaCore/gnatcoll-db/blob/master/docs/index.rst": "AdaCore-doc",
|
||||
"github.com/AdaCore/xmlada/blob/master/docs/index.rst": "AdaCore-doc",
|
||||
"github.com/BuildCraft/BuildCraft/blob/623d323b1868712f29f4a8b0979a02e8d1835131/LICENSE": "MMPL-1.0.1",
|
||||
"github.com/CommunitySpecification/1.0/blob/master/1._Community_Specification_License-v1.md": "Community-Spec-1.0",
|
||||
"github.com/CorsixTH/deps/blob/fd339a9f526d1d9c9f01ccf39e438a015da50035/licences/libgsm.txt": "TU-Berlin-2.0",
|
||||
"github.com/Cyan4973/xxHash/blob/dev/doc/xxhash_spec.md": "LPD-document",
|
||||
"github.com/DISIC/politique-de-contribution-open-source/blob/master/LICENSE.pdf": "etalab-2.0",
|
||||
"github.com/Distrotech/hdparm/blob/4517550db29a91420fb2b020349523b1b4512df2/LICENSE.TXT": "hdparm",
|
||||
"github.com/Distrotech/transfig/blob/master/transfig/transfig.c": "Xfig",
|
||||
"github.com/DrItanium/maya/blob/master/LICENSE.CLIPS": "Clips",
|
||||
"github.com/Dual-Life/mime-base64/blob/master/Base64.xs#L12": "metamail",
|
||||
"github.com/EthicalSource/hippocratic-license/blob/58c0e646d64ff6fbee275bfe2b9492f914e3ab2a/LICENSE.txt": "Hippocratic-2.1",
|
||||
"github.com/Fraunhofer-IIS/mpeghdec/blob/main/LICENSE.txt": "FDK-MPEG-H",
|
||||
"github.com/FreeRADIUS/freeradius-client/blob/master/COPYRIGHT#L32": "HPND-Fenneberg-Livingston",
|
||||
"github.com/GNOME/gcr/blob/master/docs/COPYING": "GCR-docs",
|
||||
"github.com/GNOME/libxml2/blob/master/dict.c": "ISC-Veillard",
|
||||
"github.com/HDFGroup/hdf5/?tab=License-1-ov-file#readme": "HDF5",
|
||||
"github.com/KhronosGroup/SPIRV-Cross/blob/main/LICENSES/LicenseRef-KhronosFreeUse.txt": "MIT-Khronos-old",
|
||||
"github.com/KhronosGroup/glslang/blob/main/LICENSE.txt#L949": "AML-glslang",
|
||||
"github.com/MariaDB/server/blob/11.6/libmysqld/lib_sql.cc": "Boehm-GC-without-fee",
|
||||
"github.com/Neo23x0/sigma/blob/master/LICENSE.Detection.Rules.md": "DRL-1.0",
|
||||
"github.com/OGRECave/ogre/blob/master/OgreMain/include/OgreSingleton.h#L28C3-L35C46": "Game-Programming-Gems",
|
||||
"github.com/Perl/perl5/blob/blead/util.c#L6136": "Martin-Birgmeier",
|
||||
"github.com/PicoEFI/PicoEFI/blob/2263ac1a2bc98e5534d64a4896d3cfbbae00745a/LICENSES/LicenseRef-scancode-bsd-no-disclaimer-unmodified.txt": "BSD-Source-Code-no-disclaimer",
|
||||
"github.com/PixarAnimationStudios/OpenSubdiv/blob/v3_5_0/opensubdiv/version.cpp#L2-L22": "Pixar",
|
||||
"github.com/PixarAnimationStudios/OpenSubdiv/raw/v3_5_0/LICENSE.txt": "Pixar",
|
||||
"github.com/RTimothyEdwards/magic/blob/master/LICENSE": "HPND-UC-export-US",
|
||||
"github.com/SELinuxProject/selinux/blob/master/libselinux/LICENSE": "libselinux-1.0",
|
||||
"github.com/SWI-Prolog/packages-clib/blob/master/sha1/brg_endian.h": "Brian-Gladman-3-Clause",
|
||||
"github.com/SWI-Prolog/packages-xpce/blob/161a40cd82004f731ba48024f9d30af388a7edf5/src/img/gifwrite.c#L21-L26": "FBM",
|
||||
"github.com/SWI-Prolog/swipl-devel/blob/master/src/os/dtoa.c": "dtoa",
|
||||
"github.com/SigmaHQ/Detection-Rule-License/blob/6ec7fbde6101d101b5b5d1fcb8f9b69fbc76c04a/LICENSE.Detection.Rules.md": "DRL-1.1",
|
||||
"github.com/Slicer/Slicer/blob/main/License.txt": "3D-Slicer-1.0",
|
||||
"github.com/Unidata/UDUNITS-2/blob/master/COPYRIGHT": "UCAR",
|
||||
"github.com/acpica/acpica/blob/master/source/common/acfileio.c#L119": "BSD-3-Clause-acpica",
|
||||
"github.com/adaptivecomputing/torque/blob/master/PBS_License.txt": "OpenPBS-2.3",
|
||||
"github.com/adversary-org/wtfnmf/raw/3f2cd8235a64350a57a51b9739715edaea63ec1a/COPYING.WTFNMFPL-utf8": "WTFNMFPL",
|
||||
"github.com/adversary-org/wtfnmf/raw/refs/tags/1.0/COPYING.WTFNMFPL": "WTFNMFPL",
|
||||
"github.com/ap/Text-Tabs/blob/master/lib.modern/Text/Tabs.pm#L148": "TTWL",
|
||||
"github.com/apache/apr/blob/trunk/LICENSE#L298C6-L298C29": "Caldera-no-preamble",
|
||||
"github.com/audacious-media-player/audacious/blob/master/COPYING": "BSD-2-Clause-pkgconf-disclaimer",
|
||||
"github.com/audacious-media-player/audacious/blob/master/src/audacious/main.cc": "BSD-2-Clause-pkgconf-disclaimer",
|
||||
"github.com/aws/mit-0": "MIT-0",
|
||||
"github.com/awsdocs/aws-cloud9-user-guide/blob/master/LICENSE-SAMPLECODE": "MIT-0",
|
||||
"github.com/bagder/curl/blob/master/COPYING": "curl",
|
||||
@@ -1772,6 +1804,7 @@ var urlToLicense = map[string]string{
|
||||
"github.com/file/file/blob/master/COPYING": "BSD-2-Clause-Darwin",
|
||||
"github.com/freebsd/freebsd-src/blob/main/lib/msun/src/e_acosh.c": "SunPro",
|
||||
"github.com/freebsd/freebsd-src/blob/main/lib/msun/src/e_lgammal.c": "SunPro",
|
||||
"github.com/freebsd/pkg/blob/main/src/add.c": "BSD-2-Clause-pos-unchanged",
|
||||
"github.com/gabegundy/atlserver/blob/master/License.txt": "MS-LPL",
|
||||
"github.com/greymass/swift-eosio/blob/master/LICENSE": "BSD-3-Clause-No-Military-License",
|
||||
"github.com/hamano/man2html/blob/master/man2html.c": "man2html",
|
||||
@@ -1784,6 +1817,7 @@ var urlToLicense = map[string]string{
|
||||
"github.com/jsommers/pytricia/blob/master/patricia.c#L33-L67": "BSD-4-Clause",
|
||||
"github.com/kfish/xsel/blob/master/COPYING": "HPND-sell-variant",
|
||||
"github.com/kohler/t1utils/blob/master/LICENSE": "MIT-Click",
|
||||
"github.com/krb5/krb5/blob/kfw-4.0.1-beta1/src/lib/crypto/builtin/aes/aesopt.h": "Brian-Gladman-3-Clause-no-conversion",
|
||||
"github.com/krb5/krb5/blob/krb5-1.21.2-final/NOTICE#L111-L133": "HPND-export2-US",
|
||||
"github.com/krb5/krb5/blob/krb5-1.21.2-final/NOTICE#L1157-L1182": "HPND-export-US-modify",
|
||||
"github.com/krb5/krb5/blob/krb5-1.21.2-final/NOTICE#L140-L156": "Brian-Gladman-2-Clause",
|
||||
@@ -1793,6 +1827,7 @@ var urlToLicense = map[string]string{
|
||||
"github.com/krb5/krb5/blob/krb5-1.21.2-final/NOTICE#L831-L852": "HPND-export-US-acknowledgement",
|
||||
"github.com/krzyzanowskim/CryptoSwift/blob/main/LICENSE": "CryptoSwift",
|
||||
"github.com/lattera/freebsd/blob/master/sys/cam/cam.c#L4": "BSD-Source-beginning-file",
|
||||
"github.com/libarchive/libarchive/blob/master/libarchive_fe/line_reader.c": "BSD-2-Clause-pos-unchanged",
|
||||
"github.com/libcheck/check/blob/master/checkmk/checkmk.in": "checkmk",
|
||||
"github.com/libjpeg-turbo/libjpeg-turbo/blob/main/README.ijg#L117-L161": "IJG",
|
||||
"github.com/lsof-org/lsof/blob/master/COPYING": "lsof",
|
||||
@@ -1805,6 +1840,7 @@ var urlToLicense = map[string]string{
|
||||
"github.com/mm2/Little-CMS/blob/master/src/cmssm.c#L207": "softSurfer",
|
||||
"github.com/mruby/mruby/blob/83d12f8d52522cdb7c8cc46fad34821359f453e6/mrbgems/mruby-dir/src/Win/dirent.c#L127-L140": "HPND-Kevlin-Henney",
|
||||
"github.com/mtoyoda/sl/blob/master/LICENSE": "SL",
|
||||
"github.com/mvt-project/mvt/blob/main/LICENSE": "MVT-1.1",
|
||||
"github.com/n8n-io/n8n/blob/master/LICENSE.md": "SUL-1.0",
|
||||
"github.com/ncbi/datasets/blob/0ea4cd16b61e5b799d9cc55aecfa016d6c9bd2bf/LICENSE.md": "NCBI-PD",
|
||||
"github.com/ncbi/datasets/blob/master/LICENSE.md": "NCBI-PD",
|
||||
@@ -1815,6 +1851,7 @@ var urlToLicense = map[string]string{
|
||||
"github.com/novnc/noVNC/blob/master/core/crypto/des.js#L24": "Widget-Workshop",
|
||||
"github.com/oetiker/rrdtool-1.x/blob/master/src/plbasename.c#L8-L11": "gtkbook",
|
||||
"github.com/open-quantum-safe/liboqs/blob/40b01fdbb270f8614fde30e65d30e9da18c02393/src/common/rand/rand_nist.c#L1-L15": "NIST-Software",
|
||||
"github.com/open-webui/open-webui/blob/main/LICENSE": "BSD-3-Clause-OpenWebUI",
|
||||
"github.com/openjdk/jdk/blob/6162e2c5213c5dd7c1127fd9616b543efa898962/LICENSE": "GPL-2.0-or-later",
|
||||
"github.com/openssh/openssh-portable/blob/1b11ea7c58cd5c59838b5fa574cd456d6047b2d4/LICENCE#L10": "SSH-OpenSSH",
|
||||
"github.com/openssh/openssh-portable/blob/1b11ea7c58cd5c59838b5fa574cd456d6047b2d4/pathnames.h": "SSH-short",
|
||||
@@ -1846,6 +1883,7 @@ var urlToLicense = map[string]string{
|
||||
"github.com/sigmavirus24/x11-ssh-askpass/blob/master/dynlist.c": "HPND-sell-variant-MIT-disclaimer-rev",
|
||||
"github.com/signal11/hidapi/blob/master/LICENSE-orig.txt": "HIDAPI",
|
||||
"github.com/slogan621/gtkbook": "gtkbook",
|
||||
"github.com/sudo-project/sudo/blob/c1307ea9ff340ce0538779f8e456501461fc44b7/plugins/sudoers/redblack.c#L24-L43": "BSD-ask-to-endorse",
|
||||
"github.com/swh/ladspa/blob/7bf6f3799fdba70fda297c2d8fd9f526803d9680/gsm/COPYRIGHT": "TU-Berlin-1.0",
|
||||
"github.com/tats/w3m/blob/master/COPYING": "w3m",
|
||||
"github.com/tcheneau/Routing/blob/f09f46fcfe636107f22f2c98348188a65a135d98/README.md": "NIST-PD",
|
||||
@@ -1856,6 +1894,7 @@ var urlToLicense = map[string]string{
|
||||
"github.com/uim/libgcroots/blob/master/COPYING": "Boehm-GC",
|
||||
"github.com/usnistgov/fipy/blob/86aaa5c2ba2c6f1be19593c5986071cf6568cc34/LICENSE.rst": "NIST-PD-fallback",
|
||||
"github.com/usnistgov/jsip/blob/59700e6926cbe96c5cdae897d9a7d2656b42abe3/LICENSE": "NIST-PD-fallback",
|
||||
"github.com/vattam/BSDGames/blob/master/atc/extern.c#L36": "atc-game",
|
||||
"github.com/vixie/cron/tree/545b3f5246824a9cda5905eeb7cf019c95e66995": "Vixie-Cron",
|
||||
"github.com/vstroebel/jpeg-encoder/blob/main/src/fdct.rs#L1-L72": "IJG",
|
||||
"github.com/wernerd/ZRTPCPP/blob/6b3cd8e6783642292bad0c21e3e5e5ce45ff3e03/cryptcommon/twofish.c#L113C3-L127": "Ferguson-Twofish",
|
||||
@@ -1869,6 +1908,7 @@ var urlToLicense = map[string]string{
|
||||
"gitlab.com/embeddable-common-lisp/ecl/-/blob/develop/src/lsp/loop.lsp": "LOOP",
|
||||
"gitlab.com/libeigen/eigen/-/blob/master/COPYING.MINPACK": "Minpack",
|
||||
"gitlab.common-lisp.net/cmucl/cmucl/-/blob/master/src/code/loop.lisp": "LOOP",
|
||||
"gitlab.freedesktop.org/dbus/dbus/-/blob/789d97ad53044f94351a958e785c2923643b346a/COPYING#L11-207": "AFL-2.1",
|
||||
"gitlab.freedesktop.org/freetype/freetype/-/raw/master/docs/FTL.TXT": "FTL",
|
||||
"gitlab.freedesktop.org/libopenraw/libopenraw/blob/master/lib/ljpegdecompressor.cpp#L32": "Cornell-Lossless-JPEG",
|
||||
"gitlab.freedesktop.org/mesa/glw/-/blob/master/README?ref_type=heads": "SGI-OpenGL",
|
||||
@@ -1892,6 +1932,7 @@ var urlToLicense = map[string]string{
|
||||
"gitlab.freedesktop.org/xorg/lib/libxinerama/-/blob/cc22c2f60c3862482562955116d5455263b443dc/COPYING#L44-66": "X11-no-permit-persons",
|
||||
"gitlab.freedesktop.org/xorg/lib/libxtst/-/blob/master/COPYING?ref_type=heads#L108-117": "HPND-doc-sell",
|
||||
"gitlab.freedesktop.org/xorg/lib/libxtst/-/blob/master/COPYING?ref_type=heads#L70-77": "HPND-doc",
|
||||
"gitlab.freedesktop.org/xorg/xserver/-/blob/dd5c2595a42d3ff0c4f18d9b53d1f6c3fd934fd4/COPYING#L365-389": "MIT",
|
||||
"gitlab.freedesktop.org/xorg/xserver/-/blob/master/COPYING?ref_type=heads#L1781": "HPND-sell-MIT-disclaimer-xserver",
|
||||
"gitlab.freedesktop.org/xorg/xserver/-/blob/master/COPYING?ref_type=heads#L239": "DEC-3-Clause",
|
||||
"gitlab.freedesktop.org/xorg/xserver/-/blob/master/COPYING?ref_type=heads#L752": "Adobe-Display-PostScript",
|
||||
@@ -1906,6 +1947,7 @@ var urlToLicense = map[string]string{
|
||||
"heasarc.gsfc.nasa.gov/docs/software/ftools/fv/doc/license.html": "CFITSIO",
|
||||
"helixcommunity.org/content/rpsl": "RPSL-1.0",
|
||||
"hstechdocs.helpsystems.com/manuals/globalscape/archive/cuteftp6/open_ssl_license_agreement.htm": "OpenSSL-standalone",
|
||||
"interoperable-europe.ec.europa.eu/sites/default/files/inline-files/EUPL%20v1_2%20EN(1).txt": "EUPL-1.2",
|
||||
"java.net/projects/javaeetutorial/pages/BerkeleyLicense": "BSD-3-Clause-No-Nuclear-License-2014",
|
||||
"javaee.github.io/glassfish/LICENSE": "CDDL-1.1",
|
||||
"jogamp.org/git/?p=gluegen.git;a=blob_plain;f=LICENSE.txt": "BSD-3-Clause-No-Nuclear-Warranty",
|
||||
@@ -1924,6 +1966,7 @@ var urlToLicense = map[string]string{
|
||||
"library.netapp.com/ecm/ecm_download_file/ECMP1196395": "OpenSSL-standalone",
|
||||
"license.coscl.org.cn/MulanPSL/": "MulanPSL-1.0",
|
||||
"license.coscl.org.cn/MulanPSL2": "MulanPSL-2.0",
|
||||
"license.mvt.re/1.1/": "MVT-1.1",
|
||||
"lists.gnu.org/archive/html/autoconf/2012-04/msg00061.html": "FSFULLRWD",
|
||||
"lists.opensource.org/pipermail/license-discuss_lists.opensource.org/2002-November/006304.html": "HPND",
|
||||
"mariadb.com/bsl11/": "BUSL-1.1",
|
||||
@@ -1965,99 +2008,105 @@ var urlToLicense = map[string]string{
|
||||
"opensource.linux-mirror.org/licenses/afl-1.1.txt": "AFL-1.1",
|
||||
"opensource.linux-mirror.org/licenses/afl-1.2.txt": "AFL-1.2",
|
||||
"opensource.linux-mirror.org/licenses/afl-2.1.txt": "AFL-2.1",
|
||||
"opensource.org/license/apache-2-0": "Apache-2.0",
|
||||
"opensource.org/license/mit/": "MIT",
|
||||
"opensource.org/license/olfl-1-3/": "OLFL-1.3",
|
||||
"opensource.org/license/osc-license-1-0": "OSC-1.0",
|
||||
"opensource.org/licenses/0BSD": "0BSD",
|
||||
"opensource.org/licenses/AGPL-3.0": "AGPL-3.0-or-later",
|
||||
"opensource.org/licenses/APL-1.0": "APL-1.0",
|
||||
"opensource.org/licenses/Apache-1.1": "Apache-1.1",
|
||||
"opensource.org/licenses/Apache-2.0": "Apache-2.0",
|
||||
"opensource.org/licenses/Artistic-1.0": "Artistic-1.0-cl8",
|
||||
"opensource.org/licenses/BSD-2-Clause": "BSD-2-Clause",
|
||||
"opensource.org/licenses/BSD-3-Clause": "BSD-3-Clause",
|
||||
"opensource.org/licenses/BSDplusPatent": "BSD-2-Clause-Patent",
|
||||
"opensource.org/licenses/BSL-1.0": "BSL-1.0",
|
||||
"opensource.org/licenses/CAL-1.0": "CAL-1.0-Combined-Work-Exception",
|
||||
"opensource.org/licenses/CATOSL-1.1": "CATOSL-1.1",
|
||||
"opensource.org/licenses/CNRI-Python": "CNRI-Python",
|
||||
"opensource.org/licenses/CPAL-1.0": "CPAL-1.0",
|
||||
"opensource.org/licenses/CPL-1.0": "CPL-1.0",
|
||||
"opensource.org/licenses/CUA-OPL-1.0": "CUA-OPL-1.0",
|
||||
"opensource.org/licenses/ECL-1.0": "ECL-1.0",
|
||||
"opensource.org/licenses/ECL-2.0": "ECL-2.0",
|
||||
"opensource.org/licenses/EFL-1.0": "EFL-1.0",
|
||||
"opensource.org/licenses/EFL-2.0": "EFL-2.0",
|
||||
"opensource.org/licenses/EPL-1.0": "EPL-1.0",
|
||||
"opensource.org/licenses/EUDatagrid": "EUDatagrid",
|
||||
"opensource.org/licenses/EUPL-1.1": "EUPL-1.1",
|
||||
"opensource.org/licenses/EUPL-1.2": "EUPL-1.2",
|
||||
"opensource.org/licenses/Entessa": "Entessa",
|
||||
"opensource.org/licenses/Fair": "Fair",
|
||||
"opensource.org/licenses/Frameworx-1.0": "Frameworx-1.0",
|
||||
"opensource.org/licenses/GPL-2.0": "GPL-2.0-or-later",
|
||||
"opensource.org/licenses/GPL-3.0": "GPL-3.0-or-later",
|
||||
"opensource.org/licenses/HPND": "HPND",
|
||||
"opensource.org/licenses/IPA": "IPA",
|
||||
"opensource.org/licenses/IPL-1.0": "IPL-1.0",
|
||||
"opensource.org/licenses/ISC": "ISC",
|
||||
"opensource.org/licenses/Intel": "Intel",
|
||||
"opensource.org/licenses/LGPL-2.1": "LGPL-2.1-or-later",
|
||||
"opensource.org/licenses/LGPL-3.0": "LGPL-3.0-or-later",
|
||||
"opensource.org/licenses/LPL-1.0": "LPL-1.0",
|
||||
"opensource.org/licenses/LPL-1.02": "LPL-1.02",
|
||||
"opensource.org/licenses/LPPL-1.3c": "LPPL-1.3c",
|
||||
"opensource.org/licenses/LiLiQ-P-1.1": "LiLiQ-P-1.1",
|
||||
"opensource.org/licenses/LiLiQ-R-1.1": "LiLiQ-R-1.1",
|
||||
"opensource.org/licenses/LiLiQ-Rplus-1.1": "LiLiQ-Rplus-1.1",
|
||||
"opensource.org/licenses/MIT": "MIT",
|
||||
"opensource.org/licenses/MPL-1.0": "MPL-1.0",
|
||||
"opensource.org/licenses/MPL-1.1": "MPL-1.1",
|
||||
"opensource.org/licenses/MPL-2.0": "MPL-2.0-no-copyleft-exception",
|
||||
"opensource.org/licenses/MS-PL": "MS-PL",
|
||||
"opensource.org/licenses/MS-RL": "MS-RL",
|
||||
"opensource.org/licenses/MirOS": "MirOS",
|
||||
"opensource.org/licenses/Motosoto": "Motosoto",
|
||||
"opensource.org/licenses/Multics": "Multics",
|
||||
"opensource.org/licenses/NASA-1.3": "NASA-1.3",
|
||||
"opensource.org/licenses/NCSA": "NCSA",
|
||||
"opensource.org/licenses/NGPL": "NGPL",
|
||||
"opensource.org/licenses/NOSL3.0": "NPOSL-3.0",
|
||||
"opensource.org/licenses/NTP": "NTP",
|
||||
"opensource.org/licenses/Naumen": "Naumen",
|
||||
"opensource.org/licenses/OCLC-2.0": "OCLC-2.0",
|
||||
"opensource.org/licenses/OFL-1.1": "OFL-1.1-no-RFN",
|
||||
"opensource.org/licenses/OGTSL": "OGTSL",
|
||||
"opensource.org/licenses/OPL-2.1": "OSET-PL-2.1",
|
||||
"opensource.org/licenses/OSL-1.0": "OSL-1.0",
|
||||
"opensource.org/licenses/OSL-2.1": "OSL-2.1",
|
||||
"opensource.org/licenses/OSL-3.0": "OSL-3.0",
|
||||
"opensource.org/licenses/PHP-3.0": "PHP-3.0",
|
||||
"opensource.org/licenses/PostgreSQL": "PostgreSQL",
|
||||
"opensource.org/licenses/Python-2.0": "Python-2.0",
|
||||
"opensource.org/licenses/QPL-1.0": "QPL-1.0",
|
||||
"opensource.org/licenses/RPL-1.1": "RPL-1.1",
|
||||
"opensource.org/licenses/RPL-1.5": "RPL-1.5",
|
||||
"opensource.org/licenses/RPSL-1.0": "RPSL-1.0",
|
||||
"opensource.org/licenses/RSCPL": "RSCPL",
|
||||
"opensource.org/licenses/SISSL": "SISSL",
|
||||
"opensource.org/licenses/SPL-1.0": "SPL-1.0",
|
||||
"opensource.org/licenses/SimPL-2.0": "SimPL-2.0",
|
||||
"opensource.org/licenses/Sleepycat": "Sleepycat",
|
||||
"opensource.org/licenses/UCL-1.0": "UCL-1.0",
|
||||
"opensource.org/licenses/UPL": "UPL-1.0",
|
||||
"opensource.org/licenses/VSL-1.0": "VSL-1.0",
|
||||
"opensource.org/licenses/W3C": "W3C",
|
||||
"opensource.org/licenses/Watcom-1.0": "Watcom-1.0",
|
||||
"opensource.org/licenses/Xnet": "Xnet",
|
||||
"opensource.org/licenses/ZPL-2.0": "ZPL-2.0",
|
||||
"opensource.org/licenses/Zlib": "Zlib",
|
||||
"opensource.org/licenses/afl-3.0": "AFL-3.0",
|
||||
"opensource.org/licenses/artistic-license-2.0": "Artistic-2.0",
|
||||
"opensource.org/licenses/attribution": "AAL",
|
||||
"opensource.org/licenses/cddl1": "CDDL-1.0",
|
||||
"opensource.org/licenses/nokia": "Nokia",
|
||||
"opensource.org/license/0BSD": "0BSD",
|
||||
"opensource.org/license/AAL": "AAL",
|
||||
"opensource.org/license/AFL-3.0": "AFL-3.0",
|
||||
"opensource.org/license/AGPL-3.0": "AGPL-3.0-or-later",
|
||||
"opensource.org/license/AGPL-3.0-only": "AGPL-3.0-only",
|
||||
"opensource.org/license/APL-1.0": "APL-1.0",
|
||||
"opensource.org/license/Artistic-1.0": "Artistic-1.0-cl8",
|
||||
"opensource.org/license/Artistic-2.0": "Artistic-2.0",
|
||||
"opensource.org/license/BSD-2-Clause": "BSD-2-Clause",
|
||||
"opensource.org/license/BSD-2-Clause-Patent": "BSD-2-Clause-Patent",
|
||||
"opensource.org/license/BSD-3-Clause": "BSD-3-Clause",
|
||||
"opensource.org/license/BSL-1.0": "BSL-1.0",
|
||||
"opensource.org/license/CAL-1.0": "CAL-1.0-Combined-Work-Exception",
|
||||
"opensource.org/license/CATOSL-1.1": "CATOSL-1.1",
|
||||
"opensource.org/license/CDDL-1.0": "CDDL-1.0",
|
||||
"opensource.org/license/CNRI-Python": "CNRI-Python",
|
||||
"opensource.org/license/CNRI-Python-GPL-Compatible": "CNRI-Python-GPL-Compatible",
|
||||
"opensource.org/license/CPAL-1.0": "CPAL-1.0",
|
||||
"opensource.org/license/CPL-1.0": "CPL-1.0",
|
||||
"opensource.org/license/CUA-OPL-1.0": "CUA-OPL-1.0",
|
||||
"opensource.org/license/ECL-1.0": "ECL-1.0",
|
||||
"opensource.org/license/ECL-2.0": "ECL-2.0",
|
||||
"opensource.org/license/EFL-1.0": "EFL-1.0",
|
||||
"opensource.org/license/EFL-2.0": "EFL-2.0",
|
||||
"opensource.org/license/EPL-1.0": "EPL-1.0",
|
||||
"opensource.org/license/EUDatagrid": "EUDatagrid",
|
||||
"opensource.org/license/EUPL-1.1": "EUPL-1.1",
|
||||
"opensource.org/license/EUPL-1.2": "EUPL-1.2",
|
||||
"opensource.org/license/Entessa": "Entessa",
|
||||
"opensource.org/license/Fair": "Fair",
|
||||
"opensource.org/license/Frameworx-1.0": "Frameworx-1.0",
|
||||
"opensource.org/license/GPL-2.0": "GPL-2.0-or-later",
|
||||
"opensource.org/license/GPL-2.0-only": "GPL-2.0-only",
|
||||
"opensource.org/license/GPL-3.0": "GPL-3.0-or-later",
|
||||
"opensource.org/license/GPL-3.0-only": "GPL-3.0-only",
|
||||
"opensource.org/license/HPND": "HPND",
|
||||
"opensource.org/license/IPA": "IPA",
|
||||
"opensource.org/license/IPL-1.0": "IPL-1.0",
|
||||
"opensource.org/license/ISC": "ISC",
|
||||
"opensource.org/license/Intel": "Intel",
|
||||
"opensource.org/license/LGPL-2.1": "LGPL-2.1-or-later",
|
||||
"opensource.org/license/LGPL-2.1-only": "LGPL-2.1-only",
|
||||
"opensource.org/license/LGPL-3.0": "LGPL-3.0-or-later",
|
||||
"opensource.org/license/LPL-1.0": "LPL-1.0",
|
||||
"opensource.org/license/LPL-1.02": "LPL-1.02",
|
||||
"opensource.org/license/LPPL-1.3c": "LPPL-1.3c",
|
||||
"opensource.org/license/LiLiQ-P-1.1": "LiLiQ-P-1.1",
|
||||
"opensource.org/license/LiLiQ-R-1.1": "LiLiQ-R-1.1",
|
||||
"opensource.org/license/LiLiQ-Rplus-1.1": "LiLiQ-Rplus-1.1",
|
||||
"opensource.org/license/MIT": "MIT",
|
||||
"opensource.org/license/MPL-1.0": "MPL-1.0",
|
||||
"opensource.org/license/MPL-1.1": "MPL-1.1",
|
||||
"opensource.org/license/MPL-2.0": "MPL-2.0-no-copyleft-exception",
|
||||
"opensource.org/license/MS-PL": "MS-PL",
|
||||
"opensource.org/license/MS-RL": "MS-RL",
|
||||
"opensource.org/license/MirOS": "MirOS",
|
||||
"opensource.org/license/Motosoto": "Motosoto",
|
||||
"opensource.org/license/Multics": "Multics",
|
||||
"opensource.org/license/NASA-1.3": "NASA-1.3",
|
||||
"opensource.org/license/NCSA": "NCSA",
|
||||
"opensource.org/license/NGPL": "NGPL",
|
||||
"opensource.org/license/NPOSL-3.0": "NPOSL-3.0",
|
||||
"opensource.org/license/NTP": "NTP",
|
||||
"opensource.org/license/Naumen": "Naumen",
|
||||
"opensource.org/license/Nokia": "Nokia",
|
||||
"opensource.org/license/OCLC-2.0": "OCLC-2.0",
|
||||
"opensource.org/license/OFL-1.1": "OFL-1.1-no-RFN",
|
||||
"opensource.org/license/OGTSL": "OGTSL",
|
||||
"opensource.org/license/OLFL-1.3": "OLFL-1.3",
|
||||
"opensource.org/license/OSC-1.0": "OSC-1.0",
|
||||
"opensource.org/license/OSET-PL-2.1": "OSET-PL-2.1",
|
||||
"opensource.org/license/OSL-1.0": "OSL-1.0",
|
||||
"opensource.org/license/OSL-2.1": "OSL-2.1",
|
||||
"opensource.org/license/OSL-3.0": "OSL-3.0",
|
||||
"opensource.org/license/PHP-3.0": "PHP-3.0",
|
||||
"opensource.org/license/PSF-2-0": "PSF-2.0",
|
||||
"opensource.org/license/PostgreSQL": "PostgreSQL",
|
||||
"opensource.org/license/Python-2.0": "Python-2.0",
|
||||
"opensource.org/license/Python-2.0.1": "Python-2.0.1",
|
||||
"opensource.org/license/QPL-1.0": "QPL-1.0",
|
||||
"opensource.org/license/RPL-1.1": "RPL-1.1",
|
||||
"opensource.org/license/RPL-1.5": "RPL-1.5",
|
||||
"opensource.org/license/RPSL-1.0": "RPSL-1.0",
|
||||
"opensource.org/license/RSCPL": "RSCPL",
|
||||
"opensource.org/license/SISSL": "SISSL",
|
||||
"opensource.org/license/SPL-1.0": "SPL-1.0",
|
||||
"opensource.org/license/SiMPL-2.0": "SimPL-2.0",
|
||||
"opensource.org/license/Sleepycat": "Sleepycat",
|
||||
"opensource.org/license/UCL-1.0": "UCL-1.0",
|
||||
"opensource.org/license/UPL-1.0": "UPL-1.0",
|
||||
"opensource.org/license/VSL-1.0": "VSL-1.0",
|
||||
"opensource.org/license/W3C": "W3C",
|
||||
"opensource.org/license/Watcom-1.0": "Watcom-1.0",
|
||||
"opensource.org/license/Xnet": "Xnet",
|
||||
"opensource.org/license/ZPL-2.0": "ZPL-2.0",
|
||||
"opensource.org/license/Zlib": "Zlib",
|
||||
"opensource.org/license/apache-1.1": "Apache-1.1",
|
||||
"opensource.org/license/apache-2.0": "Apache-2.0",
|
||||
"opensource.org/license/curl": "curl",
|
||||
"oss.sgi.com/projects/FreeB/": "SGI-B-1.1",
|
||||
"oss.sgi.com/projects/FreeB/SGIFreeSWLicB.1.0.html": "SGI-B-1.0",
|
||||
"oss.sgi.com/projects/FreeB/SGIFreeSWLicB.2.0.pdf": "SGI-B-2.0",
|
||||
@@ -2082,37 +2131,40 @@ var urlToLicense = map[string]string{
|
||||
"raw.githubusercontent.com/NTIA/scos-sensor/refs/heads/master/LICENSE.md": "NTIA-PD",
|
||||
"raw.githubusercontent.com/OpenMDW/OpenMDW/refs/heads/main/1.0/LICENSE.openmdw": "OpenMDW-1.0",
|
||||
"raw.githubusercontent.com/blackhole89/notekit/master/data/fonts/Charter%20license.txt": "Bitstream-Charter",
|
||||
"rc.quest.com/topics/openssh/license.php#tcpwrappers": "TCP-wrappers",
|
||||
"research.cs.wisc.edu/condor/license.html#condor": "Condor-1.1",
|
||||
"research.scea.com/scea_shared_source_license.html": "SCEA",
|
||||
"romanrm.net/mit-zero": "MIT-0",
|
||||
"sam.zoy.org/wtfpl/COPYING": "WTFPL",
|
||||
"scripts.sil.org/cms/scripts/page.php?item_id=OFL10_web": "OFL-1.0-no-RFN",
|
||||
"scripts.sil.org/cms/scripts/page.php?item_id=OFL_web": "OFL-1.1-no-RFN",
|
||||
"slicer.org/LICENSE": "3D-Slicer-1.0",
|
||||
"solderpad.org/licenses/SHL-0.5/": "SHL-0.5",
|
||||
"solderpad.org/licenses/SHL-0.51/": "SHL-0.51",
|
||||
"source.icu-project.org/repos/icu/icu/trunk/license.html": "ICU",
|
||||
"sourceforge.net/p/buddy/gitcode/ci/master/tree/README": "Buddy",
|
||||
"sourceforge.net/p/ctrio/git/ci/master/tree/README": "ISC-Veillard",
|
||||
"sourceforge.net/p/mcj/xfig/ci/master/tree/src/Makefile.am": "Xfig",
|
||||
"sourceforge.net/p/netpbm/code/HEAD/tree/super_stable/converter/other/pnmtorle.c": "URT-RLE",
|
||||
"sourceforge.net/p/netpbm/code/HEAD/tree/super_stable/converter/other/rletopnm.c": "URT-RLE",
|
||||
"sourceforge.net/p/netpbm/code/HEAD/tree/super_stable/converter/ppm/ppmtompeg/jrevdct.c#l1189": "MPEG-SSG",
|
||||
"sourceforge.net/p/netpbm/code/HEAD/tree/super_stable/editor/pnmstitch.c#l2": "pnmstitch",
|
||||
"sourceforge.net/p/netpbm/code/HEAD/tree/super_stable/netpbm.c#l8": "HPND-Pbmplus",
|
||||
"sourceforge.net/p/sbcl/sbcl/ci/master/tree/src/code/loop.lisp": "LOOP",
|
||||
"sourceforge.net/p/trustedqsl/tqsl/ci/master/tree/LICENSE.txt": "TrustedQSL",
|
||||
"sourceforge.net/p/xmedcon/code/ci/master/tree/libs/dicom/README": "OFFIS",
|
||||
"sourceforge.net/p/xmedcon/code/ci/master/tree/libs/ljpg/": "IJG-short",
|
||||
"sourceforge.net/projects/sevenzip/files/LZMA%20SDK/": "LZMA-SDK-9.22",
|
||||
"sources.debian.org/copyright/license/debianutils/4.11.2/": "SMAIL-GPL",
|
||||
"sources.debian.org/src/openmpi/4.1.0-10/ompi/debuggers/msgq_interface.h/?hl=19#L19": "mpi-permissive",
|
||||
"sources.gentoo.org/cgi-bin/viewvc.cgi/gentoo-x86/licenses/BitTorrent?r1=1.1&r2=1.1.1.1&diff_format=s": "BitTorrent-1.0",
|
||||
"sourceware.org/bzip2/1.0.5/bzip2-manual-1.0.5.html": "bzip2-1.0.6",
|
||||
"sourceware.org/cgit/binutils-gdb/tree/include/coff/sym.h#n11": "MIPS",
|
||||
"sourceware.org/cgit/binutils-gdb/tree/include/coff/symconst.h#n11": "ThirdEye",
|
||||
"sourceware.org/cgit/valgrind/tree/mpi/libmpiwrap.c": "bzip2-1.0.6",
|
||||
"raw.githubusercontent.com/landtuna/open-mtools/6c04c2882eca7c4313ec267766ea53ac9bfe129b/mdump.c": "Informatica",
|
||||
"raw.githubusercontent.com/landtuna/open-mtools/6c04c2882eca7c4313ec267766ea53ac9bfe129b/msend.c": "Informatica",
|
||||
"rc.quest.com/topics/openssh/license.php#tcpwrappers": "TCP-wrappers",
|
||||
"research.cs.wisc.edu/condor/license.html#condor": "Condor-1.1",
|
||||
"research.scea.com/scea_shared_source_license.html": "SCEA",
|
||||
"romanrm.net/mit-zero": "MIT-0",
|
||||
"sam.zoy.org/wtfpl/COPYING": "WTFPL",
|
||||
"scancode-licensedb.aboutcode.org/bsd-unchanged": "BSD-2-Clause-pos-unchanged",
|
||||
"scripts.sil.org/cms/scripts/page.php?item_id=OFL10_web": "OFL-1.0-no-RFN",
|
||||
"scripts.sil.org/cms/scripts/page.php?item_id=OFL_web": "OFL-1.1-no-RFN",
|
||||
"slicer.org/LICENSE": "3D-Slicer-1.0",
|
||||
"solderpad.org/licenses/SHL-0.5/": "SHL-0.5",
|
||||
"solderpad.org/licenses/SHL-0.51/": "SHL-0.51",
|
||||
"source.icu-project.org/repos/icu/icu/trunk/license.html": "ICU",
|
||||
"sourceforge.net/p/buddy/gitcode/ci/master/tree/README": "Buddy",
|
||||
"sourceforge.net/p/ctrio/git/ci/master/tree/README": "ISC-Veillard",
|
||||
"sourceforge.net/p/mcj/xfig/ci/master/tree/src/Makefile.am": "Xfig",
|
||||
"sourceforge.net/p/netpbm/code/HEAD/tree/super_stable/converter/other/pnmtorle.c": "URT-RLE",
|
||||
"sourceforge.net/p/netpbm/code/HEAD/tree/super_stable/converter/other/rletopnm.c": "URT-RLE",
|
||||
"sourceforge.net/p/netpbm/code/HEAD/tree/super_stable/converter/ppm/ppmtompeg/jrevdct.c#l1189": "MPEG-SSG",
|
||||
"sourceforge.net/p/netpbm/code/HEAD/tree/super_stable/editor/pnmstitch.c#l2": "pnmstitch",
|
||||
"sourceforge.net/p/netpbm/code/HEAD/tree/super_stable/netpbm.c#l8": "HPND-Pbmplus",
|
||||
"sourceforge.net/p/sbcl/sbcl/ci/master/tree/src/code/loop.lisp": "LOOP",
|
||||
"sourceforge.net/p/trustedqsl/tqsl/ci/master/tree/LICENSE.txt": "TrustedQSL",
|
||||
"sourceforge.net/p/xmedcon/code/ci/master/tree/libs/dicom/README": "OFFIS",
|
||||
"sourceforge.net/p/xmedcon/code/ci/master/tree/libs/ljpg/": "IJG-short",
|
||||
"sourceforge.net/projects/sevenzip/files/LZMA%20SDK/": "LZMA-SDK-9.22",
|
||||
"sources.debian.org/copyright/license/debianutils/4.11.2/": "SMAIL-GPL",
|
||||
"sources.debian.org/src/openmpi/4.1.0-10/ompi/debuggers/msgq_interface.h/?hl=19#L19": "mpi-permissive",
|
||||
"sources.gentoo.org/cgi-bin/viewvc.cgi/gentoo-x86/licenses/BitTorrent?r1=1.1&r2=1.1.1.1&diff_format=s": "BitTorrent-1.0",
|
||||
"sourceware.org/bzip2/1.0.5/bzip2-manual-1.0.5.html": "bzip2-1.0.6",
|
||||
"sourceware.org/cgit/binutils-gdb/tree/include/coff/sym.h#n11": "MIPS",
|
||||
"sourceware.org/cgit/binutils-gdb/tree/include/coff/symconst.h#n11": "ThirdEye",
|
||||
"sourceware.org/cgit/valgrind/tree/mpi/libmpiwrap.c": "bzip2-1.0.6",
|
||||
"sourceware.org/git/?p=binutils-gdb.git;a=blob;f=libiberty/strcasecmp.c;h=131d81c2ce7881fa48c363dc5bf5fb302c61ce0b;hb=HEAD": "BSD-4.3RENO",
|
||||
"sourceware.org/git/?p=binutils-gdb.git;a=blob;f=readline/readline/support/wcwidth.c;h=0f5ec995796f4813abbcf4972aec0378ab74722a;hb=HEAD#l55": "HPND-Markus-Kuhn",
|
||||
"sourceware.org/git/?p=bzip2.git;a=blob;f=LICENSE;hb=bzip2-1.0.6": "bzip2-1.0.6",
|
||||
@@ -2129,6 +2181,7 @@ var urlToLicense = map[string]string{
|
||||
"tahoe-lafs.org/trac/tahoe-lafs/browser/trunk/COPYING.TGPPL.rst": "TGPPL-1.0",
|
||||
"ti.arc.nasa.gov/opensource/nosa/": "NASA-1.3",
|
||||
"trac.enlightenment.org/e/browser/trunk/imlib2/COPYING": "Imlib2",
|
||||
"tunes.org/legalese/bugroff.html": "Bugroff",
|
||||
"ubuntu.com/legal/font-licence": "Ubuntu-font-1.0",
|
||||
"unlicense.org/": "Unlicense",
|
||||
"vimdoc.sourceforge.net/htmldoc/uganda.html": "Vim",
|
||||
@@ -2139,6 +2192,7 @@ var urlToLicense = map[string]string{
|
||||
"web.archive.org/web/20041020171434/http://www.rosenlaw.com/osl2.0.html": "OSL-2.0",
|
||||
"web.archive.org/web/20050212003940/http://www.rosenlaw.com/osl21.htm": "OSL-2.1",
|
||||
"web.archive.org/web/20060319014854/http://info.borland.com/devsupport/interbase/opensource/IPL.html": "Interbase-1.0",
|
||||
"web.archive.org/web/20061013013632/http://opensource.linux-mirror.org/licenses/afl-2.1.txt": "AFL-2.1",
|
||||
"web.archive.org/web/20080207013128/http://www.gnome.org/fonts/": "Bitstream-Vera",
|
||||
"web.archive.org/web/20100302225219/http://www.zimbra.com/license/zimbra-public-license-1-3.html": "Zimbra-1.3",
|
||||
"web.archive.org/web/20111123062036/http://research.cs.wisc.edu/condor/license.html#condor": "Condor-1.1",
|
||||
@@ -2243,11 +2297,11 @@ var urlToLicense = map[string]string{
|
||||
"www.jython.org/license.html": "CNRI-Jython",
|
||||
"www.kermitproject.org/ck90.html#source": "HPND-export-US",
|
||||
"www.kernel.org/doc/man-pages/licenses.html": "Linux-man-pages-copyleft",
|
||||
"www.latex-project.org/lppl/LPPL-1.3c.txt": "LPPL-1.3c",
|
||||
"www.latex-project.org/lppl/lppl-1-0.txt": "LPPL-1.0",
|
||||
"www.latex-project.org/lppl/lppl-1-1.txt": "LPPL-1.1",
|
||||
"www.latex-project.org/lppl/lppl-1-2.txt": "LPPL-1.2",
|
||||
"www.latex-project.org/lppl/lppl-1-3a.txt": "LPPL-1.3a",
|
||||
"www.latex-project.org/lppl/lppl-1-3c.txt": "LPPL-1.3c",
|
||||
"www.lemis.com/grog/UNIX/ancient-source-all.pdf": "Caldera",
|
||||
"www.libpng.org/pub/png/src/libpng-LICENSE.txt": "libpng-2.0",
|
||||
"www.mail-archive.com/debian-bugs-rc@lists.debian.org/msg11494.html": "Symlinks",
|
||||
@@ -2269,16 +2323,17 @@ var urlToLicense = map[string]string{
|
||||
"www.netlib.org/lapack/LICENSE.txt": "BSD-3-Clause-Open-MPI",
|
||||
"www.netlib.org/minpack/disclaimer": "Minpack",
|
||||
"www.networkcultures.org/_uploads/%233notebook_telekommunist.pdf": "PPL",
|
||||
"www.oclc.org/research/activities/software/license/v2final.htm": "OCLC-2.0",
|
||||
"www.ogc.org/ogc/software/1.0": "OGC-1.0",
|
||||
"www.ohwr.org/project/cernohl/wikis/Documents/CERN-OHL-version-2": "CERN-OHL-W-2.0",
|
||||
"www.ohwr.org/project/licenses/wikis/cern-ohl-v1.1": "CERN-OHL-1.1",
|
||||
"www.ohwr.org/project/licenses/wikis/cern-ohl-v1.2": "CERN-OHL-1.2",
|
||||
"www.open-mpi.org/community/license.php": "BSD-3-Clause-Open-MPI",
|
||||
"www.opencascade.com/content/occt-public-license": "OCCT-PL",
|
||||
"www.opendatacommons.org/licenses/odbl/1.0/": "ODbL-1.0",
|
||||
"www.opengroup.org/testing/downloads/The_Open_Group_TSL.txt": "OGTSL",
|
||||
"www.openhub.net/licenses/mslpl": "MS-LPL",
|
||||
"www.nist.gov/open/copyright-fair-use-and-licensing-statements-srd-data-software-and-technical-series-publications": "NIST-Software",
|
||||
"www.oclc.org/research/activities/software/license/v2final.htm": "OCLC-2.0",
|
||||
"www.ogc.org/ogc/software/1.0": "OGC-1.0",
|
||||
"www.ohwr.org/project/cernohl/wikis/Documents/CERN-OHL-version-2": "CERN-OHL-W-2.0",
|
||||
"www.ohwr.org/project/licenses/wikis/cern-ohl-v1.1": "CERN-OHL-1.1",
|
||||
"www.ohwr.org/project/licenses/wikis/cern-ohl-v1.2": "CERN-OHL-1.2",
|
||||
"www.open-mpi.org/community/license.php": "BSD-3-Clause-Open-MPI",
|
||||
"www.opencascade.com/content/occt-public-license": "OCCT-PL",
|
||||
"www.opendatacommons.org/licenses/odbl/1.0/": "ODbL-1.0",
|
||||
"www.opengroup.org/testing/downloads/The_Open_Group_TSL.txt": "OGTSL",
|
||||
"www.openhub.net/licenses/mslpl": "MS-LPL",
|
||||
"www.openldap.org/devel/gitweb.cgi?p=openldap.git;a=blob;f=LICENSE;hb=1cae062821881f41b73012ba816434897abf4205": "OLDAP-2.6",
|
||||
"www.openldap.org/devel/gitweb.cgi?p=openldap.git;a=blob;f=LICENSE;hb=37b4b3f6cc4bf34e1d3dec61e69914b9819d8894": "NBPL-1.0",
|
||||
"www.openldap.org/devel/gitweb.cgi?p=openldap.git;a=blob;f=LICENSE;hb=42b0383c50c299977b5893ee695cf4e486fb0dc7": "OLDAP-1.2",
|
||||
@@ -2301,7 +2356,7 @@ var urlToLicense = map[string]string{
|
||||
"www.opensource.apple.com/license/apsl/": "APSL-2.0",
|
||||
"www.opensource.apple.com/source/IOSerialFamily/IOSerialFamily-7/APPLE_LICENSE": "APSL-1.1",
|
||||
"www.opensource.apple.com/source/tcl/tcl-5/tcl/generic/regfronts.c": "Spencer-99",
|
||||
"www.opensource.org/licenses/EPL-2.0": "EPL-2.0",
|
||||
"www.opensource.org/license/EPL-2.0": "EPL-2.0",
|
||||
"www.openssl.org/source/license.html": "OpenSSL",
|
||||
"www.osetfoundation.org/public-license": "OSET-PL-2.1",
|
||||
"www.parliament.uk/site-information/copyright-parliament/open-parliament-licence/": "OPL-UK-3.0",
|
||||
|
||||
@@ -123,6 +123,96 @@ func TestLicenseByURL_AlternateScheme(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestLicenseByURL_OpenSourceOrgVariants(t *testing.T) {
|
||||
// SPDX has changed the form of opensource.org URLs across list versions (e.g. 3.29.0 moved
|
||||
// opensource.org/licenses/MIT to opensource.org/license/MIT); every form should keep resolving.
|
||||
tests := []struct {
|
||||
name string
|
||||
url string
|
||||
wantID string
|
||||
}{
|
||||
{name: "old plural path", url: "http://opensource.org/licenses/MIT", wantID: "MIT"},
|
||||
{name: "new singular path", url: "https://opensource.org/license/MIT", wantID: "MIT"},
|
||||
{name: "lowercase with trailing slash", url: "https://opensource.org/license/mit/", wantID: "MIT"},
|
||||
{name: "plural path with trailing slash", url: "https://opensource.org/licenses/MIT/", wantID: "MIT"},
|
||||
{name: "www prefix", url: "https://www.opensource.org/licenses/MIT", wantID: "MIT"},
|
||||
{name: "uppercase host", url: "https://WWW.OpenSource.org/licenses/MIT", wantID: "MIT"},
|
||||
{name: "old plural path BSD-3-Clause", url: "https://opensource.org/licenses/BSD-3-Clause", wantID: "BSD-3-Clause"},
|
||||
{name: "new singular path BSD-3-Clause", url: "https://opensource.org/license/BSD-3-Clause", wantID: "BSD-3-Clause"},
|
||||
{name: "lowercase BSD-3-Clause", url: "https://opensource.org/licenses/bsd-3-clause", wantID: "BSD-3-Clause"},
|
||||
{name: "old plural path Apache-2.0", url: "http://opensource.org/licenses/Apache-2.0", wantID: "Apache-2.0"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
info, found := LicenseByURL(tt.url)
|
||||
if !found {
|
||||
t.Fatalf("LicenseByURL(%q) found = false, want true", tt.url)
|
||||
}
|
||||
if info.ID != tt.wantID {
|
||||
t.Errorf("LicenseByURL(%q) ID = %v, want %v", tt.url, info.ID, tt.wantID)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLicenseByURL_NonOpenSourceOrgStaysExact(t *testing.T) {
|
||||
// the relaxed matching is scoped to opensource.org; other hosts may have case-sensitive paths
|
||||
if _, found := LicenseByURL("https://www.apache.org/licenses/license-2.0"); found {
|
||||
t.Errorf("LicenseByURL() found = true for case-altered non-opensource.org URL, want false")
|
||||
}
|
||||
if _, found := LicenseByURL("https://opensource.org/licenses/not-a-real-license"); found {
|
||||
t.Errorf("LicenseByURL() found = true for unknown opensource.org URL, want false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeOpenSourceOrgURL(t *testing.T) {
|
||||
tests := []struct {
|
||||
url string
|
||||
want string
|
||||
wantOK bool
|
||||
}{
|
||||
{url: "opensource.org/licenses/MIT", want: "opensource.org/license/mit", wantOK: true},
|
||||
{url: "opensource.org/license/mit/", want: "opensource.org/license/mit", wantOK: true},
|
||||
{url: "www.opensource.org/licenses/EPL-2.0", want: "opensource.org/license/epl-2.0", wantOK: true},
|
||||
{url: "lists.opensource.org/pipermail/license-discuss", wantOK: false},
|
||||
{url: "www.apache.org/licenses/LICENSE-2.0", wantOK: false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.url, func(t *testing.T) {
|
||||
got, ok := normalizeOpenSourceOrgURL(tt.url)
|
||||
if ok != tt.wantOK {
|
||||
t.Fatalf("normalizeOpenSourceOrgURL(%q) ok = %v, want %v", tt.url, ok, tt.wantOK)
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("normalizeOpenSourceOrgURL(%q) = %q, want %q", tt.url, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildOpenSourceOrgURLToLicense(t *testing.T) {
|
||||
index := buildOpenSourceOrgURLToLicense(map[string]string{
|
||||
"opensource.org/licenses/MIT": "MIT",
|
||||
"opensource.org/license/mit/": "MIT",
|
||||
"opensource.org/licenses/Foo": "Foo-1.0",
|
||||
"opensource.org/license/foo": "Foo-2.0",
|
||||
"opensource.org/license/FOO/": "Foo-1.0",
|
||||
"www.apache.org/licenses/Bar": "Bar",
|
||||
})
|
||||
|
||||
if got := index["opensource.org/license/mit"]; got != "MIT" {
|
||||
t.Errorf("index[mit] = %q, want MIT", got)
|
||||
}
|
||||
if got, exists := index["opensource.org/license/foo"]; exists {
|
||||
t.Errorf("index[foo] = %q, want no entry for URL forms mapping to different licenses", got)
|
||||
}
|
||||
if len(index) != 1 {
|
||||
t.Errorf("len(index) = %d, want 1 (non-opensource.org URLs excluded): %v", len(index), index)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStripScheme(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
@@ -0,0 +1,330 @@
|
||||
// Package spillbuf provides a sparse, offset-addressed byte buffer that keeps a bounded amount in memory
|
||||
// and spills the rest to a temp file.
|
||||
//
|
||||
// It exists for the shape where output arrives out of order, at offsets the input itself declares: a
|
||||
// decompressor placing extents, an archive rebuilding a file from chunks. A plain []byte cannot do that
|
||||
// without either pre-sizing to a length the input claims or growing to the furthest offset it names, and
|
||||
// both hand a hostile input an allocation knob.
|
||||
//
|
||||
// The buffer reports only what it actually stored. Size is the contiguous run written from offset zero,
|
||||
// and a read past it is io.EOF rather than the zeros an unwritten region would otherwise hand back. That
|
||||
// distinction is the whole point: sparse storage serves holes as zeros for free, so a reader that reported
|
||||
// a length covering them would let a write offset stand in for real output. Anything sizing an allocation
|
||||
// against this reader (debug/elf's saferio does exactly that) would then allocate bytes nothing produced.
|
||||
//
|
||||
// Allocation is bounded by the memory limit rather than by how much is written: filling 1MB and filling
|
||||
// 64MB cost about the same, because everything past the limit goes to the file. Only memory is bounded
|
||||
// here: how much ends up on disk is whatever the caller writes, so bounding that stays the caller's job.
|
||||
package spillbuf
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
"slices"
|
||||
|
||||
"github.com/anchore/syft/internal/tmpdir"
|
||||
)
|
||||
|
||||
// DefaultMemLimit is how much a buffer keeps in memory before it spills, when the caller does not say.
|
||||
// Small on purpose: the memory tier saves a temp file for the common small case, it is not a place to
|
||||
// hold output. A limit large enough to matter is a limit large enough to be an amplification path.
|
||||
const DefaultMemLimit int64 = 1 << 20 // 1MB
|
||||
|
||||
// ErrNoTempDir is returned when a buffer needs to spill and has nowhere to spill to.
|
||||
var ErrNoTempDir = errors.New("no temp dir available to spill to")
|
||||
|
||||
// Buffer is a sparse, offset-addressed buffer. The zero value is not usable; call New.
|
||||
//
|
||||
// Storage is two tiers with a fixed boundary: bytes below memLimit live in mem, bytes at or past it live
|
||||
// in file. Nothing ever moves between them, so an offset belongs to exactly one tier for the life of the
|
||||
// buffer and the only case needing care is a range that straddles the boundary.
|
||||
//
|
||||
// Not safe for concurrent use. Every consumer writes from a single goroutine, and guarding it would cost
|
||||
// each write an uncontended lock for no reader.
|
||||
type Buffer struct {
|
||||
td *tmpdir.TempDir
|
||||
memLimit int64
|
||||
|
||||
// mem holds [0, memLimit). Grown geometrically to fit what has been written, never past the limit.
|
||||
mem []byte
|
||||
|
||||
// file holds [memLimit, ...), created on the first write that reaches there, so a buffer that stays
|
||||
// small never touches disk. remove deletes it on Close.
|
||||
file *os.File
|
||||
remove func()
|
||||
|
||||
// written is the set of ranges stored, sorted by start and merged, so runs of adjacent writes collapse
|
||||
// to one entry rather than growing per call. Everything the buffer reports comes from it.
|
||||
written []extent
|
||||
|
||||
closed bool
|
||||
}
|
||||
|
||||
// extent is a half-open byte range that has been written.
|
||||
//
|
||||
// Unexported on purpose. What a caller needs to know about the contents is answered by Size and FirstGap;
|
||||
// handing out the bookkeeping invites re-deriving those at the call site and getting them subtly wrong,
|
||||
// which is the bug this package exists to prevent.
|
||||
type extent struct {
|
||||
start, end int64
|
||||
}
|
||||
|
||||
// Option configures a Buffer.
|
||||
type Option func(*Buffer)
|
||||
|
||||
// WithMemLimit sets how many bytes are held in memory before the buffer spills to disk. Zero sends every
|
||||
// write to the temp file. Negative is treated as zero.
|
||||
//
|
||||
// Peak allocation is a small multiple of this, since growing the tier holds the old copy alongside the
|
||||
// new one and the new one may be rounded up past what was asked for.
|
||||
func WithMemLimit(n int64) Option {
|
||||
return func(b *Buffer) {
|
||||
b.memLimit = max(n, 0)
|
||||
}
|
||||
}
|
||||
|
||||
// New returns a buffer that spills into td.
|
||||
//
|
||||
// td may be nil only when every write is known to stay inside the memory limit; a write past it then
|
||||
// fails with ErrNoTempDir rather than allocating. Nothing is created on disk until a write needs it.
|
||||
func New(td *tmpdir.TempDir, opts ...Option) *Buffer {
|
||||
b := &Buffer{td: td, memLimit: DefaultMemLimit}
|
||||
for _, opt := range opts {
|
||||
opt(b)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// split divides a range of n bytes starting at off across the two tiers, reporting how many bytes fall in
|
||||
// each. The file portion starts at off+inMem.
|
||||
func (b *Buffer) split(off, n int64) (inMem, inFile int64) {
|
||||
if off >= b.memLimit {
|
||||
return 0, n
|
||||
}
|
||||
inMem = min(n, b.memLimit-off)
|
||||
return inMem, n - inMem
|
||||
}
|
||||
|
||||
// fileOffset translates a buffer offset at or past the limit into an offset within the spill file.
|
||||
func (b *Buffer) fileOffset(off int64) int64 { return off - b.memLimit }
|
||||
|
||||
// WriteAt stores p at off. Writes may land anywhere, in any order, and may overlap. A write that fails
|
||||
// leaves the buffer as it was.
|
||||
func (b *Buffer) WriteAt(p []byte, off int64) (int, error) {
|
||||
if b.closed {
|
||||
return 0, fmt.Errorf("spillbuf: write to a closed buffer: %w", os.ErrClosed)
|
||||
}
|
||||
if off < 0 {
|
||||
return 0, fmt.Errorf("spillbuf: negative offset %d", off)
|
||||
}
|
||||
if len(p) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
// subtraction form so off+len(p) cannot wrap into a small in-range end
|
||||
if off > math.MaxInt64-int64(len(p)) {
|
||||
return 0, fmt.Errorf("spillbuf: write of %d bytes at %d overflows", len(p), off)
|
||||
}
|
||||
|
||||
inMem, inFile := b.split(off, int64(len(p)))
|
||||
// the fallible half goes first: once the memory tier is stamped the old bytes are gone, so a file
|
||||
// write that fails afterward would report failure over a buffer it had already changed
|
||||
if inFile > 0 {
|
||||
if err := b.writeFile(p[inMem:], b.fileOffset(off+inMem)); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
if inMem > 0 {
|
||||
b.writeMem(p[:inMem], off)
|
||||
}
|
||||
|
||||
b.record(extent{start: off, end: off + int64(len(p))})
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (b *Buffer) writeMem(p []byte, off int64) {
|
||||
if need := off + int64(len(p)); int64(len(b.mem)) < need {
|
||||
// geometric, capped at the limit. Sizing to exactly what each write needs looks tidier and is
|
||||
// quadratic: a caller streaming a block in 32KB chunks reallocates and copies the whole tier every
|
||||
// chunk, so filling 1MB costs ~16MB of allocation.
|
||||
grown := min(b.memLimit, max(need, 2*int64(len(b.mem))))
|
||||
// sized to exactly the geometric target rather than grown through append: append rounds the new
|
||||
// capacity up past what was asked for, and the tier is already capped, so the slack is pure waste
|
||||
mem := make([]byte, grown)
|
||||
copy(mem, b.mem)
|
||||
b.mem = mem
|
||||
}
|
||||
copy(b.mem[off:], p)
|
||||
}
|
||||
|
||||
func (b *Buffer) writeFile(p []byte, off int64) error {
|
||||
if b.file == nil {
|
||||
if b.td == nil {
|
||||
return ErrNoTempDir
|
||||
}
|
||||
f, remove, err := b.td.NewFile("syft-spill-*.bin") //nolint:gocritic // removal happens in Close
|
||||
if err != nil {
|
||||
return fmt.Errorf("spillbuf: unable to create spill file: %w", err)
|
||||
}
|
||||
b.file, b.remove = f, remove
|
||||
}
|
||||
if _, err := b.file.WriteAt(p, off); err != nil {
|
||||
return fmt.Errorf("spillbuf: unable to write to spill file: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadAt returns bytes this buffer actually stored. A read starting at or past Size is io.EOF, and one
|
||||
// running past it is short, because everything beyond is a hole the buffer never held.
|
||||
func (b *Buffer) ReadAt(p []byte, off int64) (int, error) {
|
||||
if b.closed {
|
||||
return 0, fmt.Errorf("spillbuf: read from a closed buffer: %w", os.ErrClosed)
|
||||
}
|
||||
if off < 0 {
|
||||
return 0, fmt.Errorf("spillbuf: negative offset %d", off)
|
||||
}
|
||||
if len(p) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
size := b.Size()
|
||||
if off >= size {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
// bounded by Size, so every byte in [off, off+want) was written and both tiers really hold theirs
|
||||
want := min(int64(len(p)), size-off)
|
||||
inMem, inFile := b.split(off, want)
|
||||
|
||||
// guarded rather than relying on a zero-length copy: when off is past the limit the slice expression
|
||||
// itself is out of range, since mem is only as long as what was written into it
|
||||
if inMem > 0 {
|
||||
copy(p[:inMem], b.mem[off:off+inMem])
|
||||
}
|
||||
if inFile > 0 {
|
||||
if err := readFullAt(b.file, p[inMem:want], b.fileOffset(off+inMem)); err != nil {
|
||||
return int(inMem), fmt.Errorf("spillbuf: unable to read spill file: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if want < int64(len(p)) {
|
||||
return int(want), io.EOF
|
||||
}
|
||||
return int(want), nil
|
||||
}
|
||||
|
||||
// readFullAt fills p from r. A short read is an error rather than a short return: the caller has already
|
||||
// bounded the range by what was written, so the file failing to deliver means it is not what we left.
|
||||
func readFullAt(r io.ReaderAt, p []byte, off int64) error {
|
||||
for read := 0; read < len(p); {
|
||||
n, err := r.ReadAt(p[read:], off+int64(read))
|
||||
read += n
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Size reports the contiguous run written from offset zero, which is the length this buffer is willing to
|
||||
// stand behind. Deliberately not the furthest offset written: see the package doc for why the two are not
|
||||
// interchangeable.
|
||||
//
|
||||
// record keeps the set merged and sorted, so the run from zero is at most the first two entries and the
|
||||
// loop stops there: the cost this adds to every ReadAt is effectively constant, not a scan of the set.
|
||||
func (b *Buffer) Size() int64 {
|
||||
var covered int64
|
||||
for _, e := range b.written {
|
||||
if e.start > covered {
|
||||
break
|
||||
}
|
||||
covered = max(covered, e.end)
|
||||
}
|
||||
return covered
|
||||
}
|
||||
|
||||
// FirstGap returns the offset of the earliest unwritten run of at least size bytes lying entirely within
|
||||
// [0, within), and whether there is one. Callers that place output at offsets of their own choosing use it
|
||||
// to fill what they left behind, without needing the buffer's bookkeeping.
|
||||
//
|
||||
// The arithmetic is in subtraction form throughout: within comes from caller input and the result is used
|
||||
// as a write offset, so a wrap here would be a write nobody bounded.
|
||||
func (b *Buffer) FirstGap(size, within int64) (int64, bool) {
|
||||
if b.closed || size < 0 || within < 0 {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
var at int64
|
||||
for _, e := range b.written {
|
||||
// once the cursor is at the bound there is nothing left to offer, and it only moves forward
|
||||
if at >= within {
|
||||
return 0, false
|
||||
}
|
||||
// the gap ahead of this extent ends at whichever comes first, the extent or the bound
|
||||
end := min(e.start, within)
|
||||
if end > at && end-at >= size {
|
||||
return at, true
|
||||
}
|
||||
at = max(at, e.end)
|
||||
}
|
||||
if at <= within && within-at >= size {
|
||||
return at, true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// record merges e into the written set, keeping it sorted by start with no overlapping or adjacent pairs.
|
||||
func (b *Buffer) record(e extent) {
|
||||
if e.end <= e.start {
|
||||
return
|
||||
}
|
||||
|
||||
// inserted in place rather than appended and re-sorted: the set is already ordered, so a run of
|
||||
// ascending writes (the common case) stays linear
|
||||
i, _ := slices.BinarySearchFunc(b.written, e, func(x, y extent) int {
|
||||
return cmpInt64(x.start, y.start)
|
||||
})
|
||||
b.written = slices.Insert(b.written, i, e)
|
||||
|
||||
merged := b.written[:1]
|
||||
for _, cur := range b.written[1:] {
|
||||
last := &merged[len(merged)-1]
|
||||
if cur.start <= last.end {
|
||||
last.end = max(last.end, cur.end)
|
||||
continue
|
||||
}
|
||||
merged = append(merged, cur)
|
||||
}
|
||||
b.written = merged
|
||||
}
|
||||
|
||||
func cmpInt64(x, y int64) int {
|
||||
switch {
|
||||
case x < y:
|
||||
return -1
|
||||
case x > y:
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Close releases the spill file, if one was ever created, and drops the memory tier. Safe on a nil
|
||||
// receiver and safe to call more than once. Reads and writes after it fail with os.ErrClosed.
|
||||
func (b *Buffer) Close() error {
|
||||
if b == nil {
|
||||
return nil
|
||||
}
|
||||
b.mem = nil
|
||||
b.written = nil
|
||||
b.closed = true
|
||||
if b.file == nil {
|
||||
return nil
|
||||
}
|
||||
err := b.file.Close()
|
||||
if b.remove != nil {
|
||||
b.remove()
|
||||
}
|
||||
b.file, b.remove = nil, nil
|
||||
return err
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user