Compare commits

..
Author SHA1 Message Date
Laurent Goderre 873f6e4037 Added classifier for wordpress cli binary
Signed-off-by: Laurent Goderre <laurent.goderre@docker.com>
2024-01-15 17:40:26 -05:00
1707 changed files with 22152 additions and 245482 deletions
+20 -44
View File
@@ -1,53 +1,39 @@
tools: tools:
# we want to use a pinned version of binny to manage the toolchain (so binny manages itself!)
- name: binny - name: binny
version: version:
want: v0.9.0 want: v0.6.3
method: github-release method: github-release
with: with:
repo: anchore/binny repo: anchore/binny
# used to produce SBOMs during release
- name: syft
version:
want: latest
method: github-release
with:
repo: anchore/syft
# used to sign mac binaries at release
- name: quill - name: quill
version: version:
want: v0.5.1 want: v0.4.1
method: github-release method: github-release
with: with:
repo: anchore/quill repo: anchore/quill
# used for linting
- name: golangci-lint - name: golangci-lint
version: version:
want: v2.4.0 want: v1.55.2
method: github-release method: github-release
with: with:
repo: golangci/golangci-lint repo: golangci/golangci-lint
# used for showing the changelog at release
- name: glow - name: glow
version: version:
want: v2.1.1 want: v1.5.1
method: github-release method: github-release
with: with:
repo: charmbracelet/glow repo: charmbracelet/glow
# used for signing the checksums file at release
- name: cosign - name: cosign
version: version:
want: v2.5.3 want: v2.2.2
method: github-release method: github-release
with: with:
repo: sigstore/cosign repo: sigstore/cosign
# used in integration tests to verify JSON schemas
- name: yajsv - name: yajsv
version: version:
want: v1.4.1 want: v1.4.1
@@ -55,15 +41,13 @@ tools:
with: with:
repo: neilpa/yajsv repo: neilpa/yajsv
# used to release all artifacts
- name: goreleaser - name: goreleaser
version: version:
want: v2.11.2 want: v1.23.0
method: github-release method: github-release
with: with:
repo: goreleaser/goreleaser repo: goreleaser/goreleaser
# used for organizing imports during static analysis
- name: gosimports - name: gosimports
version: version:
want: v0.3.8 want: v0.3.8
@@ -71,7 +55,6 @@ tools:
with: with:
repo: rinchsan/gosimports repo: rinchsan/gosimports
# used at release to generate the changelog
- name: chronicle - name: chronicle
version: version:
want: v0.8.0 want: v0.8.0
@@ -79,7 +62,6 @@ tools:
with: with:
repo: anchore/chronicle repo: anchore/chronicle
# used during static analysis for license compliance
- name: bouncer - name: bouncer
version: version:
want: v0.4.0 want: v0.4.0
@@ -87,34 +69,28 @@ tools:
with: with:
repo: wagoodman/go-bouncer repo: wagoodman/go-bouncer
# used for running all local and CI tasks - name: benchstat
version:
want: latest
method: go-proxy
with:
module: golang.org/x/perf
allow-unresolved-version: true
method: go-install
with:
entrypoint: cmd/benchstat
module: golang.org/x/perf
- name: task - name: task
version: version:
want: v3.44.1 want: v3.33.1
method: github-release method: github-release
with: with:
repo: go-task/task repo: go-task/task
# used for triggering a release
- name: gh - name: gh
version: version:
want: v2.78.0 want: v2.42.0
method: github-release method: github-release
with: with:
repo: cli/cli repo: cli/cli
# used to upload test fixture cache
- name: oras
version:
want: v1.2.3
method: github-release
with:
repo: oras-project/oras
# used to upload test fixture cache
- name: yq
version:
want: v4.47.1
method: github-release
with:
repo: mikefarah/yq
-1
View File
@@ -6,7 +6,6 @@ permit:
- MPL.* - MPL.*
- ISC - ISC
- WTFPL - WTFPL
- Unlicense
ignore-packages: ignore-packages:
# packageurl-go is released under the MIT license located in the root of the repo at /mit.LICENSE # packageurl-go is released under the MIT license located in the root of the repo at /mit.LICENSE
+1 -1
View File
@@ -1,4 +1,4 @@
enforce-v0: false enforce-v0: true # don't make breaking-change label bump major version before 1.0.
title: "" title: ""
github: github:
+3 -3
View File
@@ -1,6 +1,6 @@
contact_links: contact_links:
- name: Join our Discourse community 💬 - name: Join the Slack community 💬
# link to our community Discourse site # link to our community Slack registration page
url: https://anchore.com/discourse url: https://anchore.com/slack
about: 'Come chat with us! Ask for help, join our software development efforts, or just give us feedback!' about: 'Come chat with us! Ask for help, join our software development efforts, or just give us feedback!'
+12 -25
View File
@@ -5,7 +5,7 @@ inputs:
go-version: go-version:
description: "Go version to install" description: "Go version to install"
required: true required: true
default: ">= 1.24" default: "1.21.x"
go-dependencies: go-dependencies:
description: "Download go dependencies" description: "Download go dependencies"
required: true required: true
@@ -13,38 +13,34 @@ inputs:
cache-key-prefix: cache-key-prefix:
description: "Prefix all cache keys with this value" description: "Prefix all cache keys with this value"
required: true required: true
default: "181053ac82" default: "1ac8281053"
download-test-fixture-cache: compute-fingerprints:
description: "Download test fixture cache from OCI and github actions" description: "Compute test fixture fingerprints"
required: true required: true
default: "false"
tools:
description: "whether to install tools"
default: "true" default: "true"
bootstrap-apt-packages: bootstrap-apt-packages:
description: "Space delimited list of tools to install via apt" description: "Space delimited list of tools to install via apt"
default: "libxml2-utils" default: "libxml2-utils"
runs: runs:
using: "composite" using: "composite"
steps: steps:
# note: go mod and build is automatically cached on default with v4+ # note: go mod and build is automatically cached on default with v4+
- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 - uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe #v4.1.0
if: inputs.go-version != '' if: inputs.go-version != ''
with: with:
go-version: ${{ inputs.go-version }} go-version: ${{ inputs.go-version }}
- name: Restore tool cache - name: Restore tool cache
if: inputs.tools == 'true'
id: tool-cache id: tool-cache
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 #v3.3.2
with: with:
path: ${{ github.workspace }}/.tool path: ${{ github.workspace }}/.tool
key: ${{ inputs.cache-key-prefix }}-${{ runner.os }}-tool-${{ hashFiles('.binny.yaml') }} key: ${{ inputs.cache-key-prefix }}-${{ runner.os }}-tool-${{ hashFiles('.binny.yaml') }}
- name: Install project tools - name: Install project tools
shell: bash shell: bash
if: inputs.tools == 'true'
run: make tools run: make tools
- name: Install go dependencies - name: Install go dependencies
@@ -55,20 +51,11 @@ runs:
- name: Install apt packages - name: Install apt packages
if: inputs.bootstrap-apt-packages != '' if: inputs.bootstrap-apt-packages != ''
shell: bash shell: bash
env:
APT_PACKAGES: ${{ inputs.bootstrap-apt-packages }}
run: | run: |
IFS=' ' read -ra packages <<< "$APT_PACKAGES" DEBIAN_FRONTEND=noninteractive sudo apt update && sudo -E apt install -y ${{ inputs.bootstrap-apt-packages }}
DEBIAN_FRONTEND=noninteractive sudo apt update && sudo -E apt install -y "${packages[@]}"
- name: Restore ORAS cache from github actions - name: Create all cache fingerprints
if: inputs.download-test-fixture-cache == 'true' if: inputs.compute-fingerprints == 'true'
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
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 shell: bash
run: make download-test-fixture-cache run: make fingerprints
+1 -16
View File
@@ -1,27 +1,12 @@
version: 2 version: 2
updates: updates:
- package-ecosystem: gomod - package-ecosystem: gomod
directory: "/" directory: "/"
schedule: schedule:
interval: "daily" interval: "daily"
open-pull-requests-limit: 10 open-pull-requests-limit: 10
labels:
- "dependencies"
- package-ecosystem: "github-actions" - package-ecosystem: "github-actions"
directory: "/" directory: "/"
schedule: schedule:
interval: "daily" interval: "daily"
open-pull-requests-limit: 10 open-pull-requests-limit: 10
labels:
- "dependencies"
- package-ecosystem: "github-actions"
directory: "/.github/actions/bootstrap"
schedule:
interval: "daily"
open-pull-requests-limit: 10
labels:
- "dependencies"
-25
View File
@@ -1,25 +0,0 @@
# Description
Please include a summary of the changes along with any relevant motivation and context,
or link to an issue where this is explained.
<!-- If this completes an issue, please include: -->
- Fixes
## Type of change
<!-- Delete any that are not relevant -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (please discuss with the team first; Syft is 1.0 software and we won't accept breaking changes without going to 2.0)
- [ ] Documentation (updates the documentation)
- [ ] Chore (improve the developer experience, fix a test flake, etc, without changing the visible behavior of Syft)
- [ ] Performance (make Syft run faster or use less memory, without changing visible behavior much)
# Checklist:
- [ ] I have added unit tests that cover changed behavior
- [ ] I have tested my code in common scenarios and confirmed there are no regressions
- [ ] I have added comments to my code, particularly in hard-to-understand sections
-137
View File
@@ -1,137 +0,0 @@
#!/usr/bin/env python3
from __future__ import annotations
import os
import glob
import sys
import json
import hashlib
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 = {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 = []
valid_paths = set()
fingerprint_contents = []
for fingerprint in all_fingerprints:
path = fingerprint.replace('.fingerprint', '')
if not os.path.exists(path):
missing_content.append(path)
continue
if not os.path.isdir(path):
valid_paths.add(path)
continue
if os.listdir(path):
valid_paths.add(path)
else:
missing_content.append(path)
with open(fingerprint, 'r') as f:
content = f.read().strip()
fingerprint_contents.append((fingerprint, content))
return sorted(valid_paths), missing_content, fingerprint_contents
def parse_fingerprint_contents(fingerprint_content):
input_map = {}
for line in fingerprint_content.splitlines():
digest, path = line.split()
input_map[path] = digest
return input_map
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)
sha256_hash = hashlib.sha256(concatenated_contents.encode()).hexdigest()
return sha256_hash
def calculate_file_sha256(file_path):
sha256_hash = hashlib.sha256()
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()
def show(*s: str):
print(*s, file=sys.stderr)
def main(file_path: str | None):
base_dir = '.'
valid_paths, missing_content, 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):
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)
sha256_hash = calculate_sha256(fingerprint_contents)
paths_with_digests = []
for path in sorted(valid_paths):
fingerprint_file = f"{path}.fingerprint"
try:
if os.path.exists(fingerprint_file):
file_digest = calculate_file_sha256(fingerprint_file)
# Parse the fingerprint file to get the digest/path tuples
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
})
except Exception as e:
show(f"Error processing {fingerprint_file}: {e}")
raise e
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:
f.write(content)
print(content)
if __name__ == "__main__":
file_path = None
if len(sys.argv) > 1:
file_path = sys.argv[1]
main(file_path)
@@ -1,70 +0,0 @@
#!/usr/bin/env python3
import os
import subprocess
import hashlib
BOLD = '\033[1m'
YELLOW = '\033[0;33m'
RESET = '\033[0m'
def print_message(message):
print(f"{YELLOW}{message}{RESET}")
def sha256sum(filepath):
h = hashlib.sha256()
with open(filepath, 'rb') as f:
for chunk in iter(lambda: f.read(4096), b""):
h.update(chunk)
return h.hexdigest()
def is_git_tracked_or_untracked(directory):
"""Returns a sorted list of files in the directory that are tracked or not ignored by Git."""
result = subprocess.run(
["git", "ls-files", "--cached", "--others", "--exclude-standard"],
cwd=directory,
stdout=subprocess.PIPE,
text=True
)
return sorted(result.stdout.strip().splitlines())
def find_test_fixture_dirs_with_images(base_dir):
"""Find directories that contain 'test-fixtures' and at least one 'image-*' directory."""
for root, dirs, files in os.walk(base_dir):
if 'test-fixtures' in root:
image_dirs = [d for d in dirs if d.startswith('image-')]
if image_dirs:
yield os.path.realpath(root)
def generate_fingerprints():
print_message("creating fingerprint files for docker fixtures...")
for test_fixture_dir in find_test_fixture_dirs_with_images('.'):
cache_fingerprint_path = os.path.join(test_fixture_dir, 'cache.fingerprint')
with open(cache_fingerprint_path, 'w') as fingerprint_file:
for image_dir in find_image_dirs(test_fixture_dir):
for file in is_git_tracked_or_untracked(image_dir):
file_path = os.path.join(image_dir, file)
checksum = sha256sum(file_path)
path_from_fixture_dir = os.path.relpath(file_path, test_fixture_dir)
fingerprint_file.write(f"{checksum} {path_from_fixture_dir}\n")
def find_image_dirs(test_fixture_dir):
"""Find all 'image-*' directories inside a given test-fixture directory."""
result = []
for root, dirs, files in os.walk(test_fixture_dir):
for dir_name in dirs:
if dir_name.startswith('image-'):
result.append(os.path.join(root, dir_name))
return sorted(result)
if __name__ == "__main__":
generate_fingerprints()
Executable → Regular
+6 -24
View File
@@ -1,5 +1,3 @@
#!/usr/bin/env python3
from __future__ import annotations from __future__ import annotations
import sys import sys
@@ -58,8 +56,6 @@ def main(changed_files: str | None = None, merge_base_schema_files: str | None =
pr_json_schema_files = filter_to_schema_files(pr_changed_files) pr_json_schema_files = filter_to_schema_files(pr_changed_files)
pr_labels = get_pr_labels(pr_number)
# print("schema files in pr: ", summarize_schema_files(pr_json_schema_files)) # print("schema files in pr: ", summarize_schema_files(pr_json_schema_files))
# print("og schema files: ", summarize_schema_files(og_json_schema_files)) # print("og schema files: ", summarize_schema_files(og_json_schema_files))
@@ -80,8 +76,7 @@ def main(changed_files: str | None = None, merge_base_schema_files: str | None =
add_label(pr_number, JSON_SCHEMA_LABEL) add_label(pr_number, JSON_SCHEMA_LABEL)
else: else:
if JSON_SCHEMA_LABEL in pr_labels: remove_label(pr_number, JSON_SCHEMA_LABEL)
remove_label(pr_number, JSON_SCHEMA_LABEL)
# new schema files should be scrutinized, comparing the latest and added versions to see if it's a breaking # new schema files should be scrutinized, comparing the latest and added versions to see if it's a breaking
# change (major version bump). Warn about it on the PR via adding a breaking-change label... # change (major version bump). Warn about it on the PR via adding a breaking-change label...
@@ -89,8 +84,7 @@ def main(changed_files: str | None = None, merge_base_schema_files: str | None =
print("\nBreaking change detected...") print("\nBreaking change detected...")
add_label(pr_number, BREAKING_CHANGE_LABEL) add_label(pr_number, BREAKING_CHANGE_LABEL)
else: else:
if BREAKING_CHANGE_LABEL in pr_labels: remove_label(pr_number, BREAKING_CHANGE_LABEL)
remove_label(pr_number, BREAKING_CHANGE_LABEL)
# modifying an existing schema could be a breaking change, we should warn about it on the PR via a comment... # modifying an existing schema could be a breaking change, we should warn about it on the PR via a comment...
# removing schema files should never be allowed, we should warn about it on the PR via a comment... # removing schema files should never be allowed, we should warn about it on the PR via a comment...
@@ -125,7 +119,7 @@ def add_label(pr_number: str, label: str):
# run "gh pr edit --add-label <label>" # run "gh pr edit --add-label <label>"
result = run(f"gh pr edit {pr_number} --add-label {label}", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) result = run(f"gh pr edit {pr_number} --add-label {label}", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if result.returncode != 0: if result.returncode != 0:
print(f"Unable to add '{label!r}' label to PR, error:") print(f"Unable to add {label!r} label to PR with")
print(str(result.stderr)) print(str(result.stderr))
sys.exit(1) sys.exit(1)
@@ -134,7 +128,7 @@ def remove_label(pr_number: str, label: str):
# run "gh pr edit --remove-label <label>" # run "gh pr edit --remove-label <label>"
result = run(f"gh pr edit {pr_number} --remove-label {label}", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) result = run(f"gh pr edit {pr_number} --remove-label {label}", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if result.returncode != 0: if result.returncode != 0:
print(f"Unable to remove '{label!r}' label from PR, error:") print(f"Unable to label PR with {label!r}")
print(str(result.stderr)) print(str(result.stderr))
sys.exit(1) sys.exit(1)
@@ -171,17 +165,6 @@ def get_pr_changed_files(pr_number: str) -> list[str]:
return list_of_files return list_of_files
def get_pr_labels(pr_number: str) -> list[str]:
result = run(f"gh pr view {pr_number} --json labels --jq '.labels[].name'", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
if result.returncode != 0:
print("Unable to get list of labels on PR")
print(str(result.stderr))
sys.exit(1)
list_of_labels = result.stdout.splitlines()
return list_of_labels
def filter_to_schema_files(list_of_files: list[str]) -> list[str]: def filter_to_schema_files(list_of_files: list[str]) -> list[str]:
# get files matching "schema/json/schema-*.json" # get files matching "schema/json/schema-*.json"
files = [] files = []
@@ -193,8 +176,7 @@ def filter_to_schema_files(list_of_files: list[str]) -> list[str]:
def list_json_schema_files() -> list[str]: def list_json_schema_files() -> list[str]:
# list files in "schema/json" directory matching the pattern of "schema-*.json" # list files in "schema/json" directory matching the pattern of "schema-*.json"
# special case: always ignore the "latest" schema file return sort_json_schema_files(list(glob.glob("schema/json/schema-*.json")))
return sort_json_schema_files([f for f in glob.glob("schema/json/schema-*.json") if "latest" not in f])
def run(command: str, **kwargs) -> subprocess.CompletedProcess: def run(command: str, **kwargs) -> subprocess.CompletedProcess:
@@ -215,7 +197,7 @@ def sort_json_schema_files(files: list[str]) -> list[str]:
# so that "schema/json/schema-1.2.1.json" comes before "schema/json/schema-1.12.1.json". # so that "schema/json/schema-1.2.1.json" comes before "schema/json/schema-1.12.1.json".
versions = [get_semver(file) for file in files if file] versions = [get_semver(file) for file in files if file]
versions = sorted(versions, key=lambda s: [int(u) for u in s.split('.') if "." in s]) versions = sorted(versions, key=lambda s: [int(u) for u in s.split('.')])
return [f"schema/json/schema-{version}.json" for version in versions] return [f"schema/json/schema-{version}.json" for version in versions]
Executable → Regular
-7
View File
@@ -1,5 +1,3 @@
#!/usr/bin/env python3
import unittest import unittest
from unittest.mock import patch from unittest.mock import patch
import subprocess import subprocess
@@ -62,11 +60,6 @@ class Labeler(unittest.TestCase):
expected_sorted_files = ["schema/json/schema-1.2.1.json", "schema/json/schema-1.12.1.json"] expected_sorted_files = ["schema/json/schema-1.2.1.json", "schema/json/schema-1.12.1.json"]
self.assertEqual(labeler.sort_json_schema_files(files), expected_sorted_files) self.assertEqual(labeler.sort_json_schema_files(files), expected_sorted_files)
# ensure that "latest" doesn't cause a problem and is ultimately ignored
files = ["schema/json/schema-1.12.1.json", "schema/json/schema-_bogus.json"]
expected_sorted_files = ["schema/json/schema-_bogus.json", "schema/json/schema-1.12.1.json"]
self.assertEqual(labeler.sort_json_schema_files(files), expected_sorted_files)
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()
+7 -14
View File
@@ -4,19 +4,12 @@ set -eu
bold=$(tput bold) bold=$(tput bold)
normal=$(tput sgr0) normal=$(tput sgr0)
GH_CLI=.tool/gh if ! [ -x "$(command -v gh)" ]; then
echo "The GitHub CLI could not be found. To continue follow the instructions at https://github.com/cli/cli#installation"
if ! [ -x "$(command -v $GH_CLI)" ]; then
echo "The GitHub CLI could not be found. run: make bootstrap"
exit 1 exit 1
fi fi
$GH_CLI auth status gh 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 # 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. # the release pipeline it is possible to not have all of the tags from previous releases.
@@ -44,7 +37,7 @@ done
echo "${bold}Kicking off release for ${NEXT_VERSION}${normal}..." echo "${bold}Kicking off release for ${NEXT_VERSION}${normal}..."
echo echo
$GH_CLI workflow run release.yaml -f version=${NEXT_VERSION} gh workflow run release.yaml -f version=${NEXT_VERSION}
echo echo
echo "${bold}Waiting for release to start...${normal}" echo "${bold}Waiting for release to start...${normal}"
@@ -52,6 +45,6 @@ sleep 10
set +e 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')" echo "${bold}Head to the release workflow to monitor the release:${normal} $(gh run list --workflow=release.yaml --limit=1 --json url --jq '.[].url')"
id=$($GH_CLI run list --workflow=release.yaml --limit=1 --json databaseId --jq '.[].databaseId') id=$(gh 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) gh run watch $id --exit-status || (echo ; echo "${bold}Logs of failed step:${normal}" && GH_PAGER="" gh run view $id --log-failed)
+23
View File
@@ -0,0 +1,23 @@
#!/usr/bin/env bash
set -ue
BIN="syft"
DISTDIR=$1
VERSION=$2
# the source of truth as to whether we want to notify users of an update is if the release just created is NOT
# flagged as a pre-release on github
if [[ "$(curl -SsL https://api.github.com/repos/anchore/${BIN}/releases/tags/${VERSION} | jq .prerelease)" == "true" ]] ; then
echo "skipping publishing a version file (this is a pre-release: ${VERSION})"
exit 0
fi
echo "creating and publishing version file"
# create a version file for version-update checks
VERSION_FILE="${DISTDIR}/VERSION"
echo "${VERSION}" | tee "${VERSION_FILE}"
# upload the version file that supports the application version update check
export AWS_DEFAULT_REGION=us-west-2
aws s3 cp "${VERSION_FILE}" s3://toolbox-data.anchore.io/${BIN}/releases/latest/VERSION
+62
View File
@@ -0,0 +1,62 @@
name: "Benchmark testing"
on:
workflow_dispatch:
pull_request:
permissions:
contents: read
jobs:
Benchmark-Test:
name: "Benchmark tests"
runs-on: ubuntu-20.04
# note: we want benchmarks to run on pull_request events in order to publish results to a sticky comment, and
# we also want to run on push such that merges to main are recorded to the cache. For this reason we don't filter
# the job by event.
steps:
- name: Checkout code
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Bootstrap environment
uses: ./.github/actions/bootstrap
- name: Restore base benchmark result
uses: actions/cache@e12d46a63a90f2fae62d114769bbf2a179198b5c #v3.3.3
with:
path: test/results/benchmark-main.txt
# use base sha for PR or new commit hash for main push in benchmark result key
key: ${{ runner.os }}-bench-${{ (github.event.pull_request.base.sha != github.event.after) && github.event.pull_request.base.sha || github.event.after }}
- name: Run benchmark tests
id: benchmark
run: |
REF_NAME=${GITHUB_REF##*/} make benchmark
OUTPUT=$(make show-benchstat)
OUTPUT="${OUTPUT//'%'/'%25'}" # URL encode all '%' characters
OUTPUT="${OUTPUT//$'\n'/'%0A'}" # URL encode all '\n' characters
OUTPUT="${OUTPUT//$'\r'/'%0D'}" # URL encode all '\r' characters
echo "result=$OUTPUT" >> $GITHUB_OUTPUT
- uses: actions/upload-artifact@1eb3cb2b3e0f29609092a73eb033bb759a334595 # v4.1.0
with:
name: benchmark-test-results
path: test/results/**/*
- name: Update PR benchmark results comment
uses: marocchino/sticky-pull-request-comment@efaaab3fd41a9c3de579aba759d2552635e590fd #v2.8.0
continue-on-error: true
with:
header: benchmark
message: |
### Benchmark Test Results
<details>
<summary>Benchmark results from the latest changes vs base branch</summary>
```
${{ steps.benchmark.outputs.result }}
```
</details>
+7 -9
View File
@@ -20,8 +20,8 @@ permissions:
jobs: jobs:
analyze: analyze:
name: Analyze name: Analyze
runs-on: ubuntu-22.04-4core-16gb runs-on: ubuntu-latest
if: github.repository == 'anchore/syft' # only run for main repo
permissions: permissions:
security-events: write security-events: write
@@ -36,18 +36,16 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0 uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
with:
persist-credentials: false
- name: Install Go - name: Install Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 #v5.5.0 uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 #v5.0.0
with: with:
go-version-file: go.mod go-version-file: go.mod
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@3c3833e0f8c1c83d449a7478aa59c036a9165498 #v3.29.5 uses: github/codeql-action/init@e5f05b81d5b6ff8cfa111c80c22c5fd02a384118 #v3.23.0
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file. # If you wish to specify custom queries, you can do so here or in a config file.
@@ -58,7 +56,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below) # If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@3c3833e0f8c1c83d449a7478aa59c036a9165498 #v3.29.5 uses: github/codeql-action/autobuild@e5f05b81d5b6ff8cfa111c80c22c5fd02a384118 #v3.23.0
# ℹ️ Command-line programs to run using the OS shell. # ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl # 📚 https://git.io/JvXDl
@@ -72,4 +70,4 @@ jobs:
# make release # make release
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@3c3833e0f8c1c83d449a7478aa59c036a9165498 #v3.29.5 uses: github/codeql-action/analyze@e5f05b81d5b6ff8cfa111c80c22c5fd02a384118 #v3.23.0
@@ -1,10 +0,0 @@
name: Dependabot Automation
on:
pull_request:
permissions:
pull-requests: write
jobs:
run:
uses: anchore/workflows/.github/workflows/dependabot-automation.yaml@main
+3 -9
View File
@@ -28,15 +28,9 @@ jobs:
label: label:
name: "Label changes" name: "Label changes"
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
permissions:
contents: read
pull-requests: write
issues: write
steps: steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0 - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
with:
persist-credentials: false
- run: python .github/scripts/labeler.py - run: python .github/scripts/labeler.py
env: env:
@@ -46,7 +40,7 @@ jobs:
- name: Delete existing comment - name: Delete existing comment
if: ${{ hashFiles( env.CI_COMMENT_FILE ) == '' }} if: ${{ hashFiles( env.CI_COMMENT_FILE ) == '' }}
uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 #v2.9.4 uses: marocchino/sticky-pull-request-comment@efaaab3fd41a9c3de579aba759d2552635e590fd #v2.8.0
with: with:
header: ${{ env.COMMENT_HEADER }} header: ${{ env.COMMENT_HEADER }}
hide: true hide: true
@@ -54,7 +48,7 @@ jobs:
- name: Add comment - name: Add comment
if: ${{ hashFiles( env.CI_COMMENT_FILE ) != '' }} if: ${{ hashFiles( env.CI_COMMENT_FILE ) != '' }}
uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 #v2.9.4 uses: marocchino/sticky-pull-request-comment@efaaab3fd41a9c3de579aba759d2552635e590fd #v2.8.0
with: with:
header: ${{ env.COMMENT_HEADER }} header: ${{ env.COMMENT_HEADER }}
path: ${{ env.CI_COMMENT_FILE }} path: ${{ env.CI_COMMENT_FILE }}
+48
View File
@@ -0,0 +1,48 @@
name: "Release Homebrew"
on:
workflow_dispatch:
release:
types:
- released
jobs:
homebrew:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
with:
# we need to be able to get the commit for a tag
fetch-depth: 0
- name: Get release info
id: release_info
run: |
echo -n "tag_name=$(gh release view --json tagName --jq '.tagName')" | tee -a $GITHUB_OUTPUT
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Get commit info
id: git_info
run: |
git checkout ${{ steps.release_info.outputs.tag_name }}
echo -n "commit=$(git rev-parse HEAD)" | tee -a $GITHUB_OUTPUT
- name: Update Homebrew formula
uses: dawidd6/action-homebrew-bump-formula@75ed025ff3ad1d617862838b342b06d613a0ddf3 # v3.10.1
with:
token: ${{ secrets.HOMEBREW_TOKEN }}
org: anchore
formula: syft
tag: ${{ steps.release_info.outputs.tag_name }}
revision: ${{ steps.git_info.outputs.commit }}
- uses: 8398a7/action-slack@fbd6aa58ba854a740e11a35d0df80cb5d12101d8 #v3.15.1
if: ${{ failure() }}
with:
status: ${{ job.status }}
fields: repo,workflow,action,eventName
text: "Post-release homebrew publish workflow has failed: https://github.com/anchore/syft/actions/workflows/release-homebrew.yaml"
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_TOOLBOX_WEBHOOK_URL }}
+29 -65
View File
@@ -13,30 +13,18 @@ on:
jobs: jobs:
quality-gate: quality-gate:
environment: release environment: release
runs-on: ubuntu-24.04 runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0 - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
with:
persist-credentials: false
- name: Check if running on main
if: github.ref != 'refs/heads/main'
# we are using the following flag when running `cosign blob-verify` for checksum signature verification:
# --certificate-identity-regexp "https://github.com/anchore/.github/workflows/release.yaml@refs/heads/main"
# if we are not on the main branch, the signature will not be verifiable since the suffix requires the main branch
# at the time of when the OIDC token was issued on the Github Actions runner.
run: echo "This can only be run on the main branch otherwise releases produced will not be verifiable with cosign" && exit 1
- name: Check if tag already exists - name: Check if tag already exists
# note: this will fail if the tag already exists # note: this will fail if the tag already exists
run: | run: |
[[ "$VERSION" == v* ]] || (echo "version '$VERSION' does not have a 'v' prefix" && exit 1) [[ "${{ github.event.inputs.version }}" == v* ]] || (echo "version '${{ github.event.inputs.version }}' does not have a 'v' prefix" && exit 1)
git tag "$VERSION" git tag ${{ github.event.inputs.version }}
env:
VERSION: ${{ github.event.inputs.version }}
- name: Check static analysis results - name: Check static analysis results
uses: fountainhead/action-wait-for-check@5a908a24814494009c4bb27c242ea38c93c593be # v1.2.0 uses: fountainhead/action-wait-for-check@297be350cf8393728ea4d4b39435c7d7ae167c93 # v1.1.0
id: static-analysis id: static-analysis
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
@@ -45,7 +33,7 @@ jobs:
ref: ${{ github.event.pull_request.head.sha || github.sha }} ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Check unit test results - name: Check unit test results
uses: fountainhead/action-wait-for-check@5a908a24814494009c4bb27c242ea38c93c593be # v1.2.0 uses: fountainhead/action-wait-for-check@297be350cf8393728ea4d4b39435c7d7ae167c93 # v1.1.0
id: unit id: unit
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
@@ -54,7 +42,7 @@ jobs:
ref: ${{ github.event.pull_request.head.sha || github.sha }} ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Check integration test results - name: Check integration test results
uses: fountainhead/action-wait-for-check@5a908a24814494009c4bb27c242ea38c93c593be # v1.2.0 uses: fountainhead/action-wait-for-check@297be350cf8393728ea4d4b39435c7d7ae167c93 # v1.1.0
id: integration id: integration
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
@@ -63,7 +51,7 @@ jobs:
ref: ${{ github.event.pull_request.head.sha || github.sha }} ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Check acceptance test results (linux) - name: Check acceptance test results (linux)
uses: fountainhead/action-wait-for-check@5a908a24814494009c4bb27c242ea38c93c593be # v1.2.0 uses: fountainhead/action-wait-for-check@297be350cf8393728ea4d4b39435c7d7ae167c93 # v1.1.0
id: acceptance-linux id: acceptance-linux
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
@@ -72,7 +60,7 @@ jobs:
ref: ${{ github.event.pull_request.head.sha || github.sha }} ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Check acceptance test results (mac) - name: Check acceptance test results (mac)
uses: fountainhead/action-wait-for-check@5a908a24814494009c4bb27c242ea38c93c593be # v1.2.0 uses: fountainhead/action-wait-for-check@297be350cf8393728ea4d4b39435c7d7ae167c93 # v1.1.0
id: acceptance-mac id: acceptance-mac
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
@@ -81,7 +69,7 @@ jobs:
ref: ${{ github.event.pull_request.head.sha || github.sha }} ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Check cli test results (linux) - name: Check cli test results (linux)
uses: fountainhead/action-wait-for-check@5a908a24814494009c4bb27c242ea38c93c593be # v1.2.0 uses: fountainhead/action-wait-for-check@297be350cf8393728ea4d4b39435c7d7ae167c93 # v1.1.0
id: cli-linux id: cli-linux
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
@@ -91,47 +79,37 @@ jobs:
- name: Quality gate - name: Quality gate
if: steps.static-analysis.outputs.conclusion != 'success' || steps.unit.outputs.conclusion != 'success' || steps.integration.outputs.conclusion != 'success' || steps.cli-linux.outputs.conclusion != 'success' || steps.acceptance-linux.outputs.conclusion != 'success' || steps.acceptance-mac.outputs.conclusion != 'success' if: steps.static-analysis.outputs.conclusion != 'success' || steps.unit.outputs.conclusion != 'success' || steps.integration.outputs.conclusion != 'success' || steps.cli-linux.outputs.conclusion != 'success' || steps.acceptance-linux.outputs.conclusion != 'success' || steps.acceptance-mac.outputs.conclusion != 'success'
env:
STATIC_ANALYSIS_STATUS: ${{ steps.static-analysis.conclusion }}
UNIT_TEST_STATUS: ${{ steps.unit.outputs.conclusion }}
INTEGRATION_TEST_STATUS: ${{ steps.integration.outputs.conclusion }}
ACCEPTANCE_LINUX_STATUS: ${{ steps.acceptance-linux.outputs.conclusion }}
ACCEPTANCE_MAC_STATUS: ${{ steps.acceptance-mac.outputs.conclusion }}
CLI_LINUX_STATUS: ${{ steps.cli-linux.outputs.conclusion }}
run: | run: |
echo "Static Analysis Status: $STATIC_ANALYSIS_STATUS" echo "Static Analysis Status: ${{ steps.static-analysis.conclusion }}"
echo "Unit Test Status: $UNIT_TEST_STATUS" echo "Unit Test Status: ${{ steps.unit.outputs.conclusion }}"
echo "Integration Test Status: $INTEGRATION_TEST_STATUS" echo "Integration Test Status: ${{ steps.integration.outputs.conclusion }}"
echo "Acceptance Test (Linux) Status: $ACCEPTANCE_LINUX_STATUS" echo "Acceptance Test (Linux) Status: ${{ steps.acceptance-linux.outputs.conclusion }}"
echo "Acceptance Test (Mac) Status: $ACCEPTANCE_MAC_STATUS" echo "Acceptance Test (Mac) Status: ${{ steps.acceptance-mac.outputs.conclusion }}"
echo "CLI Test (Linux) Status: $CLI_LINUX_STATUS" echo "CLI Test (Linux) Status: ${{ steps.cli-linux.outputs.conclusion }}"
false false
release: release:
needs: [quality-gate] needs: [quality-gate]
runs-on: ubuntu-24.04 runs-on: ubuntu-20.04
permissions: permissions:
contents: write contents: write
packages: write packages: write
# required for goreleaser signs section with cosign
id-token: write
steps: steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0 - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
with: with:
fetch-depth: 0 fetch-depth: 0
persist-credentials: true
- name: Bootstrap environment - name: Bootstrap environment
uses: ./.github/actions/bootstrap uses: ./.github/actions/bootstrap
- name: Login to Docker Hub - name: Login to Docker Hub
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 #v3.5.0 uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d #v3.0.0
with: with:
username: ${{ secrets.ANCHOREOSSWRITE_DH_USERNAME }} username: ${{ secrets.TOOLBOX_DOCKER_USER }}
password: ${{ secrets.ANCHOREOSSWRITE_DH_PAT }} password: ${{ secrets.TOOLBOX_DOCKER_PASS }}
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 #v3.5.0 uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d #v3.0.0
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
@@ -141,11 +119,10 @@ jobs:
run: | run: |
git config --global user.name "anchoreci" git config --global user.name "anchoreci"
git config --global user.email "anchoreci@users.noreply.github.com" git config --global user.email "anchoreci@users.noreply.github.com"
git tag -a "$VERSION" -m "Release $VERSION" git tag -a ${{ github.event.inputs.version }} -m "Release ${{ github.event.inputs.version }}"
git push origin --tags git push origin --tags
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ github.event.inputs.version }}
- name: Build & publish release artifacts - name: Build & publish release artifacts
run: make ci-release run: make ci-release
@@ -159,15 +136,17 @@ jobs:
# for creating the release (requires write access to packages and content) # for creating the release (requires write access to packages and content)
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# for updating brew formula in anchore/homebrew-syft # for updating brew formula in anchore/homebrew-syft
GITHUB_BREW_TOKEN: ${{ secrets.ANCHOREOPS_GITHUB_OSS_WRITE_TOKEN }} GITHUB_BREW_TOKEN: ${{ secrets.ANCHORE_GIT_READ_TOKEN }}
# for updating the VERSION file in S3...
AWS_ACCESS_KEY_ID: ${{ secrets.TOOLBOX_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.TOOLBOX_AWS_SECRET_ACCESS_KEY }}
- uses: anchore/sbom-action@da167eac915b4e86f08b264dbdbc867b61be6f0c #v0.20.5 - uses: anchore/sbom-action@c7f031d9249a826a082ea14c79d3b686a51d485a #v0.15.3
continue-on-error: true continue-on-error: true
with: with:
file: go.mod
artifact-name: sbom.spdx.json artifact-name: sbom.spdx.json
- uses: 8398a7/action-slack@1750b5085f3ec60384090fb7c52965ef822e869e #v3.18.0 - uses: 8398a7/action-slack@fbd6aa58ba854a740e11a35d0df80cb5d12101d8 #v3.15.1
continue-on-error: true continue-on-error: true
with: with:
status: ${{ job.status }} status: ${{ job.status }}
@@ -176,18 +155,3 @@ jobs:
env: env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_TOOLBOX_WEBHOOK_URL }} SLACK_WEBHOOK_URL: ${{ secrets.SLACK_TOOLBOX_WEBHOOK_URL }}
if: ${{ success() }} if: ${{ success() }}
release-install-script:
needs: [release]
if: ${{ needs.release.result == 'success' }}
uses: "anchore/workflows/.github/workflows/release-install-script.yaml@main"
with:
tag: ${{ github.event.inputs.version }}
secrets:
# needed for r2...
R2_INSTALL_ACCESS_KEY_ID: ${{ secrets.OSS_R2_INSTALL_ACCESS_KEY_ID }}
R2_INSTALL_SECRET_ACCESS_KEY: ${{ secrets.OSS_R2_INSTALL_SECRET_ACCESS_KEY }}
R2_ENDPOINT: ${{ secrets.TOOLBOX_CLOUDFLARE_R2_ENDPOINT }}
# needed for s3...
S3_INSTALL_AWS_ACCESS_KEY_ID: ${{ secrets.TOOLBOX_AWS_ACCESS_KEY_ID }}
S3_INSTALL_AWS_SECRET_ACCESS_KEY: ${{ secrets.TOOLBOX_AWS_SECRET_ACCESS_KEY }}
@@ -1,15 +0,0 @@
name: "Manage Awaiting Response Label"
on:
issue_comment:
types: [created]
jobs:
run:
permissions:
contents: read
issues: write
pull-requests: write
uses: "anchore/workflows/.github/workflows/remove-awaiting-response-label.yaml@main"
secrets:
token: ${{ secrets.OSS_PROJECT_GH_TOKEN }}
@@ -1,44 +0,0 @@
name: "Test fixture cache: publish"
on:
workflow_dispatch:
schedule:
# run nightly at 4AM UTC
- cron: "0 4 * * *"
permissions:
contents: read
jobs:
Publish:
name: "Publish test fixture image cache"
# we use this runner to get enough storage space for docker images and fixture cache
runs-on: ubuntu-22.04-4core-16gb
if: github.repository == 'anchore/syft' # only run for main repo
permissions:
packages: write
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0
with:
persist-credentials: false
- name: Bootstrap environment
uses: ./.github/actions/bootstrap
with:
# we want to rebuild the cache with no previous state
download-test-fixture-cache: false
- name: Run all tests
run: make test
env:
# we want to rebuild the cache with no previous state
DOWNLOAD_TEST_FIXTURE_CACHE: "false"
- name: Login to GitHub Container Registry (ORAS)
run: echo "${{ secrets.GITHUB_TOKEN }}" | .tool/oras login ghcr.io -u "$ACTOR" --password-stdin
env:
ACTOR: ${{ github.actor }}
- name: Publish test fixture cache
run: make upload-test-fixture-cache
@@ -1,51 +0,0 @@
name: PR to update Anchore dependencies
on:
workflow_dispatch:
inputs:
repos:
description: "List of dependencies to update"
required: true
type: string
permissions:
contents: read
jobs:
update:
runs-on: ubuntu-latest
if: github.repository_owner == 'anchore' # only run for main repo (not forks)
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0
with:
persist-credentials: false
- name: Bootstrap environment
uses: ./.github/actions/bootstrap
with:
tools: false
bootstrap-apt-packages: ""
- name: Update dependencies
id: update
uses: anchore/workflows/.github/actions/update-go-dependencies@main
with:
repos: ${{ github.event.inputs.repos }}
- uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a #v2.1.0
id: generate-token
with:
app_id: ${{ secrets.TOKEN_APP_ID }}
private_key: ${{ secrets.TOKEN_APP_PRIVATE_KEY }}
- uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e #v7.0.8
with:
signoff: true
delete-branch: true
draft: ${{ steps.update.outputs.draft }}
# do not change this branch, as other workflows depend on it
branch: auto/integration
labels: dependencies,pre-release
commit-message: "chore(deps): update anchore dependencies"
title: "chore(deps): update anchore dependencies"
body: ${{ steps.update.outputs.summary }}
token: ${{ steps.generate-token.outputs.token }}
+3 -4
View File
@@ -13,14 +13,13 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.repository == 'anchore/syft' # only run for main repo if: github.repository == 'anchore/syft' # only run for main repo
steps: steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0 - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
with:
persist-credentials: false
- name: Bootstrap environment - name: Bootstrap environment
uses: ./.github/actions/bootstrap uses: ./.github/actions/bootstrap
with: with:
bootstrap-apt-packages: "" bootstrap-apt-packages: ""
compute-fingerprints: "false"
go-dependencies: false go-dependencies: false
- name: "Update tool versions" - name: "Update tool versions"
@@ -51,7 +50,7 @@ jobs:
app_id: ${{ secrets.TOKEN_APP_ID }} app_id: ${{ secrets.TOKEN_APP_ID }}
private_key: ${{ secrets.TOKEN_APP_PRIVATE_KEY }} private_key: ${{ secrets.TOKEN_APP_PRIVATE_KEY }}
- uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e #v7.0.8 - uses: peter-evans/create-pull-request@153407881ec5c347639a548ade7d8ad1d6740e38 #v5.0.2
with: with:
signoff: true signoff: true
delete-branch: true delete-branch: true
@@ -16,9 +16,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.repository == 'anchore/syft' # only run for main repo if: github.repository == 'anchore/syft' # only run for main repo
steps: steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0 - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
with:
persist-credentials: false
- name: Bootstrap environment - name: Bootstrap environment
uses: ./.github/actions/bootstrap uses: ./.github/actions/bootstrap
@@ -35,7 +33,7 @@ jobs:
app_id: ${{ secrets.TOKEN_APP_ID }} app_id: ${{ secrets.TOKEN_APP_ID }}
private_key: ${{ secrets.TOKEN_APP_PRIVATE_KEY }} private_key: ${{ secrets.TOKEN_APP_PRIVATE_KEY }}
- uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e #v7.0.8 - uses: peter-evans/create-pull-request@153407881ec5c347639a548ade7d8ad1d6740e38 #v5.0.2
with: with:
signoff: true signoff: true
delete-branch: true delete-branch: true
@@ -47,7 +45,7 @@ jobs:
Update CPE dictionary index based on the latest available CPE dictionary Update CPE dictionary index based on the latest available CPE dictionary
token: ${{ steps.generate-token.outputs.token }} token: ${{ steps.generate-token.outputs.token }}
- uses: 8398a7/action-slack@1750b5085f3ec60384090fb7c52965ef822e869e #v3.18.0 - uses: 8398a7/action-slack@fbd6aa58ba854a740e11a35d0df80cb5d12101d8 #v3.15.1
with: with:
status: ${{ job.status }} status: ${{ job.status }}
fields: workflow,eventName,job fields: workflow,eventName,job
@@ -0,0 +1,57 @@
name: PR for latest Stereoscope release
on:
schedule:
- cron: "0 8 * * *" # 3 AM EST
workflow_dispatch:
env:
GO_VERSION: "1.21.x"
GO_STABLE_VERSION: true
permissions:
contents: read
jobs:
upgrade-stereoscope:
runs-on: ubuntu-latest
if: github.repository == 'anchore/syft' # only run for main repo
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
- uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 #v5.0.0
with:
go-version: ${{ env.GO_VERSION }}
stable: ${{ env.GO_STABLE_VERSION }}
- name: Bootstrap environment
uses: ./.github/actions/bootstrap
- run: |
LATEST_VERSION=$(git ls-remote https://github.com/anchore/stereoscope main | head -n1 | awk '{print $1;}')
# update go.mod
go get github.com/anchore/stereoscope@$LATEST_VERSION
go mod tidy
# export the version for use with create-pull-request
echo "LATEST_VERSION=$LATEST_VERSION" >> $GITHUB_OUTPUT
id: latest-version
- uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a #v2.1.0
id: generate-token
with:
app_id: ${{ secrets.TOKEN_APP_ID }}
private_key: ${{ secrets.TOKEN_APP_PRIVATE_KEY }}
- uses: peter-evans/create-pull-request@153407881ec5c347639a548ade7d8ad1d6740e38 #v5.0.2
with:
signoff: true
delete-branch: true
branch: auto/latest-stereoscope
labels: dependencies
commit-message: "chore(deps): update stereoscope to ${{ steps.latest-version.outputs.LATEST_VERSION }}"
title: "chore(deps): update stereoscope to ${{ steps.latest-version.outputs.LATEST_VERSION }}"
body: |
Update Stereoscope to ${{ steps.latest-version.outputs.LATEST_VERSION }}
token: ${{ steps.generate-token.outputs.token }}
@@ -1,35 +0,0 @@
name: "Validate GitHub Actions"
on:
pull_request:
paths:
- '.github/workflows/**'
- '.github/actions/**'
push:
branches:
- main
paths:
- '.github/workflows/**'
- '.github/actions/**'
permissions:
contents: read
jobs:
zizmor:
name: "Lint"
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write # for uploading SARIF results
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- name: "Run zizmor"
uses: zizmorcore/zizmor-action@5ca5fc7a4779c5263a3ffa0e1f693009994446d1 # v0.1.2
with:
config-file: .github/zizmor.yml
sarif-upload: true
inputs: .github
+73 -124
View File
@@ -15,11 +15,9 @@ jobs:
Static-Analysis: Static-Analysis:
# Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline # Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline
name: "Static analysis" name: "Static analysis"
runs-on: ubuntu-24.04 runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0 - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
with:
persist-credentials: false
- name: Bootstrap environment - name: Bootstrap environment
uses: ./.github/actions/bootstrap uses: ./.github/actions/bootstrap
@@ -31,17 +29,42 @@ jobs:
Unit-Test: Unit-Test:
# Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline # Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline
name: "Unit tests" name: "Unit tests"
# we need more storage than what's on the default runner runs-on: ubuntu-20.04
runs-on: ubuntu-22.04-4core-16gb
steps: steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0 - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
with:
persist-credentials: false
- name: Bootstrap environment - name: Bootstrap environment
uses: ./.github/actions/bootstrap uses: ./.github/actions/bootstrap
- name: Restore Java test-fixture cache
uses: actions/cache@e12d46a63a90f2fae62d114769bbf2a179198b5c #v3.3.3
with: with:
download-test-fixture-cache: true path: syft/pkg/cataloger/java/test-fixtures/java-builds/packages
key: ${{ runner.os }}-unit-java-cache-${{ hashFiles( 'syft/pkg/cataloger/java/test-fixtures/java-builds/cache.fingerprint' ) }}
- name: Restore RPM test-fixture cache
uses: actions/cache@e12d46a63a90f2fae62d114769bbf2a179198b5c #v3.3.3
with:
path: syft/pkg/cataloger/redhat/test-fixtures/rpms
key: ${{ runner.os }}-unit-rpm-cache-${{ hashFiles( 'syft/pkg/cataloger/redhat/test-fixtures/rpms.fingerprint' ) }}
- name: Restore go binary test-fixture cache
uses: actions/cache@e12d46a63a90f2fae62d114769bbf2a179198b5c #v3.3.3
with:
path: syft/pkg/cataloger/golang/test-fixtures/archs/binaries
key: ${{ runner.os }}-unit-go-binaries-cache-${{ hashFiles( 'syft/pkg/cataloger/golang/test-fixtures/archs/binaries.fingerprint' ) }}
- name: Restore binary cataloger test-fixture cache
uses: actions/cache@e12d46a63a90f2fae62d114769bbf2a179198b5c #v3.3.3
with:
path: syft/pkg/cataloger/binary/test-fixtures/classifiers/bin
key: ${{ runner.os }}-unit-binary-cataloger-cache-${{ hashFiles( 'syft/pkg/cataloger/binary/test-fixtures/cache.fingerprint' ) }}
- name: Restore Kernel test-fixture cache
uses: actions/cache@e12d46a63a90f2fae62d114769bbf2a179198b5c #v3.3.3
with:
path: syft/pkg/cataloger/kernel/test-fixtures/cache
key: ${{ runner.os }}-unit-kernel-cache-${{ hashFiles( 'syft/pkg/cataloger/kernel/test-fixtures/cache.fingerprint' ) }}
- name: Run unit tests - name: Run unit tests
run: make unit run: make unit
@@ -50,31 +73,31 @@ jobs:
Integration-Test: Integration-Test:
# Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline # Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline
name: "Integration tests" name: "Integration tests"
runs-on: ubuntu-24.04 runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0 - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
with:
persist-credentials: false
- name: Bootstrap environment - name: Bootstrap environment
uses: ./.github/actions/bootstrap uses: ./.github/actions/bootstrap
with:
download-test-fixture-cache: true
- name: Validate syft output against the CycloneDX schema - name: Validate syft output against the CycloneDX schema
run: make validate-cyclonedx-schema run: make validate-cyclonedx-schema
- name: Restore integration test cache
uses: actions/cache@e12d46a63a90f2fae62d114769bbf2a179198b5c #v3.3.3
with:
path: ${{ github.workspace }}/test/integration/test-fixtures/cache
key: ${{ runner.os }}-integration-test-cache-${{ hashFiles('test/integration/test-fixtures/cache.fingerprint') }}
- name: Run integration tests - name: Run integration tests
run: make integration run: make integration
Build-Snapshot-Artifacts: Build-Snapshot-Artifacts:
name: "Build snapshot artifacts" name: "Build snapshot artifacts"
runs-on: ubuntu-24.04 runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0 - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
with:
persist-credentials: false
- name: Bootstrap environment - name: Bootstrap environment
uses: ./.github/actions/bootstrap uses: ./.github/actions/bootstrap
@@ -84,13 +107,10 @@ jobs:
- name: Build snapshot artifacts - name: Build snapshot artifacts
run: make snapshot run: make snapshot
- name: Smoke test snapshot build
run: make snapshot-smoke-test
# why not use actions/upload-artifact? It is very slow (3 minutes to upload ~600MB of data, vs 10 seconds with this approach). # why not use actions/upload-artifact? It is very slow (3 minutes to upload ~600MB of data, vs 10 seconds with this approach).
# see https://github.com/actions/upload-artifact/issues/199 for more info # see https://github.com/actions/upload-artifact/issues/199 for more info
- name: Upload snapshot artifacts - name: Upload snapshot artifacts
uses: actions/cache/save@0400d5f644dc74513175e3cd8d07132dd4860809 #v4.2.4 uses: actions/cache/save@e12d46a63a90f2fae62d114769bbf2a179198b5c #v3.3.3
with: with:
# we need to preserve the snapshot data itself as well as the task data that confirms if the # we need to preserve the snapshot data itself as well as the task data that confirms if the
# snapshot build is stale or not. Otherwise the downstream jobs will attempt to rebuild the snapshot # snapshot build is stale or not. Otherwise the downstream jobs will attempt to rebuild the snapshot
@@ -101,81 +121,20 @@ jobs:
key: snapshot-build-${{ github.run_id }} key: snapshot-build-${{ github.run_id }}
Upload-Snapshot-Artifacts:
# Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline
name: "Upload snapshot artifacts"
needs: [Build-Snapshot-Artifacts]
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0
with:
persist-credentials: false
- name: Bootstrap environment
uses: ./.github/actions/bootstrap
with:
download-test-fixture-cache: true
- name: Download snapshot build
id: snapshot-cache
uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 #v4.2.4
with:
path: |
snapshot
.task
fail-on-cache-miss: true
key: snapshot-build-${{ github.run_id }}
# workaround for https://github.com/actions/cache/issues/1265
- name: (cache-miss) Snapshot build missing
if: steps.snapshot-cache.outputs.cache-hit != 'true'
run: echo "unable to download snapshots from previous job" && false
- run: npm install @actions/artifact@2.2.2
- uses: actions/github-script@v7
with:
script: |
const { readdirSync } = require('fs')
const { DefaultArtifactClient } = require('@actions/artifact')
const artifact = new DefaultArtifactClient()
const ls = d => readdirSync(d, { withFileTypes: true })
const baseDir = "./snapshot"
const dirs = ls(baseDir).filter(f => f.isDirectory()).map(f => f.name)
const uploads = []
for (const dir of dirs) {
// uploadArtifact returns Promise<{id, size}>
uploads.push(artifact.uploadArtifact(
// name of the archive:
`${dir}`,
// array of all files to include:
ls(`${baseDir}/${dir}`).map(f => `${baseDir}/${dir}/${f.name}`),
// base directory to trim from entries:
`${baseDir}/${dir}`,
{ retentionDays: 30 }
))
}
// wait for all uploads to finish
Promise.all(uploads)
Acceptance-Linux: Acceptance-Linux:
# Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline # Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline
name: "Acceptance tests (Linux)" name: "Acceptance tests (Linux)"
needs: [Build-Snapshot-Artifacts] needs: [Build-Snapshot-Artifacts]
runs-on: ubuntu-24.04 runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0 - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
with:
persist-credentials: false
- name: Bootstrap environment - name: Bootstrap environment
uses: ./.github/actions/bootstrap uses: ./.github/actions/bootstrap
with:
download-test-fixture-cache: true
- name: Download snapshot build - name: Download snapshot build
id: snapshot-cache id: snapshot-cache
uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 #v4.2.4 uses: actions/cache/restore@e12d46a63a90f2fae62d114769bbf2a179198b5c #v3.3.3
with: with:
path: | path: |
snapshot snapshot
@@ -191,6 +150,13 @@ jobs:
- name: Run comparison tests (Linux) - name: Run comparison tests (Linux)
run: make compare-linux run: make compare-linux
- name: Restore install.sh test image cache
id: install-test-image-cache
uses: actions/cache@e12d46a63a90f2fae62d114769bbf2a179198b5c #v3.3.3
with:
path: ${{ github.workspace }}/test/install/cache
key: ${{ runner.os }}-install-test-image-cache-${{ hashFiles('test/install/cache.fingerprint') }}
- name: Load test image cache - name: Load test image cache
if: steps.install-test-image-cache.outputs.cache-hit == 'true' if: steps.install-test-image-cache.outputs.cache-hit == 'true'
run: make install-test-cache-load run: make install-test-cache-load
@@ -209,23 +175,19 @@ jobs:
needs: [Build-Snapshot-Artifacts] needs: [Build-Snapshot-Artifacts]
runs-on: macos-latest runs-on: macos-latest
steps: steps:
- name: Install Cosign - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
uses: sigstore/cosign-installer@d58896d6a1865668819e1d91763c7751a165e159 # v3.9.2
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0
with:
persist-credentials: false
- name: Bootstrap environment - name: Bootstrap environment
uses: ./.github/actions/bootstrap uses: ./.github/actions/bootstrap
with: with:
bootstrap-apt-packages: "" bootstrap-apt-packages: ""
compute-fingerprints: "false"
go-dependencies: false go-dependencies: false
download-test-fixture-cache: true go-version: ""
- name: Download snapshot build - name: Download snapshot build
id: snapshot-cache id: snapshot-cache
uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 #v4.2.4 uses: actions/cache/restore@e12d46a63a90f2fae62d114769bbf2a179198b5c #v3.3.3
with: with:
path: | path: |
snapshot snapshot
@@ -238,6 +200,13 @@ jobs:
if: steps.snapshot-cache.outputs.cache-hit != 'true' if: steps.snapshot-cache.outputs.cache-hit != 'true'
run: echo "unable to download snapshots from previous job" && false run: echo "unable to download snapshots from previous job" && false
- name: Restore docker image cache for compare testing
id: mac-compare-testing-cache
uses: actions/cache@e12d46a63a90f2fae62d114769bbf2a179198b5c #v3.3.3
with:
path: image.tar
key: ${{ runner.os }}-${{ hashFiles('test/compare/mac.sh') }}
- name: Run comparison tests (Mac) - name: Run comparison tests (Mac)
run: make compare-mac run: make compare-mac
@@ -249,20 +218,22 @@ jobs:
# Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline # Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline
name: "CLI tests (Linux)" name: "CLI tests (Linux)"
needs: [Build-Snapshot-Artifacts] needs: [Build-Snapshot-Artifacts]
runs-on: ubuntu-24.04 runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0 - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
with:
persist-credentials: false
- name: Bootstrap environment - name: Bootstrap environment
uses: ./.github/actions/bootstrap uses: ./.github/actions/bootstrap
- name: Restore CLI test-fixture cache
uses: actions/cache@e12d46a63a90f2fae62d114769bbf2a179198b5c #v3.3.3
with: with:
download-test-fixture-cache: true path: ${{ github.workspace }}/test/cli/test-fixtures/cache
key: ${{ runner.os }}-cli-test-cache-${{ hashFiles('test/cli/test-fixtures/cache.fingerprint') }}
- name: Download snapshot build - name: Download snapshot build
id: snapshot-cache id: snapshot-cache
uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 #v4.2.4 uses: actions/cache/restore@e12d46a63a90f2fae62d114769bbf2a179198b5c #v3.3.3
with: with:
path: | path: |
snapshot snapshot
@@ -277,25 +248,3 @@ jobs:
- name: Run CLI Tests (Linux) - name: Run CLI Tests (Linux)
run: make cli run: make cli
Cleanup-Cache:
name: "Cleanup snapshot cache"
if: github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-24.04
permissions:
actions: write
needs:
- Acceptance-Linux
- Acceptance-Mac
- Cli-Linux
- Upload-Snapshot-Artifacts
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0
with:
persist-credentials: false
- name: Delete snapshot cache
run: gh cache delete "snapshot-build-${{ github.run_id }}"
env:
GH_TOKEN: ${{ github.token }}
-11
View File
@@ -1,11 +0,0 @@
rules:
unpinned-uses:
ignore:
# Allow unpinned uses of trusted internal anchore/workflows actions
- update-anchore-dependencies.yml
dangerous-triggers:
ignore:
# Safe use of pull_request_target - only runs trusted scripts from base repo,
# never checks out PR code, needs secrets for labeling PRs from forks
- detect-schema-changes.yaml
+1
View File
@@ -32,6 +32,7 @@ VERSION
/test/results /test/results
coverage.txt coverage.txt
*.log *.log
test/integration/test-fixtures/**/go.sum
# probable archives # probable archives
.images .images
+32 -51
View File
@@ -1,58 +1,58 @@
version: "2" issues:
run: max-same-issues: 25
tests: false
# TODO: enable this when we have coverage on docstring comments
# # The list of ids of default excludes to include or disable.
# include:
# - EXC0002 # disable excluding of issues about comments from golint
linters: linters:
default: none # inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint
disable-all: true
enable: enable:
- asciicheck - asciicheck
- bodyclose - bodyclose
- copyloopvar
- dogsled - dogsled
- dupl - dupl
- errcheck - errcheck
- exportloopref
- funlen - funlen
- gocognit - gocognit
- goconst - goconst
- gocritic - gocritic
- gocyclo - gocyclo
- gofmt
- goimports
- goprintffuncname - goprintffuncname
- gosec - gosec
- gosimple
- govet - govet
- ineffassign - ineffassign
- misspell - misspell
- nakedret - nakedret
- nolintlint
- revive - revive
- staticcheck - staticcheck
- stylecheck
- typecheck
- unconvert - unconvert
- unparam - unparam
- unused - unused
- whitespace - whitespace
settings:
funlen: linters-settings:
lines: 70 funlen:
statements: 50 # Checks the number of lines in a function.
gocritic: # If lower than 0, disable the check.
enabled-checks: # Default: 60
- deferInLoop lines: 70
- ruleguard # Checks the number of statements in a function.
settings: # If lower than 0, disable the check.
ruleguard: # Default: 40
rules: test/rules/rules.go statements: 50
gosec: output:
excludes: uniq-by-line: false
- G115 run:
exclusions: timeout: 10m
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
paths:
- third_party$
- builtin$
- examples$
# do not enable... # do not enable...
# - deadcode # The owner seems to have abandoned the linter. Replaced by "unused". # - deadcode # The owner seems to have abandoned the linter. Replaced by "unused".
@@ -72,6 +72,7 @@ linters:
# - lll # without a way to specify per-line exception cases, this is not usable # - lll # without a way to specify per-line exception cases, this is not usable
# - maligned # this is an excellent linter, but tricky to optimize and we are not sensitive to memory layout optimizations # - maligned # this is an excellent linter, but tricky to optimize and we are not sensitive to memory layout optimizations
# - nestif # - nestif
# - nolintlint # as of go1.19 this conflicts with the behavior of gofmt, which is a deal-breaker (lint-fix will still fail when running lint)
# - prealloc # following this rule isn't consistently a good idea, as it sometimes forces unnecessary allocations that result in less idiomatic code # - prealloc # following this rule isn't consistently a good idea, as it sometimes forces unnecessary allocations that result in less idiomatic code
# - rowserrcheck # not in a repo with sql, so this is not useful # - rowserrcheck # not in a repo with sql, so this is not useful
# - scopelint # deprecated # - scopelint # deprecated
@@ -79,23 +80,3 @@ linters:
# - testpackage # - testpackage
# - varcheck # The owner seems to have abandoned the linter. Replaced by "unused". # - varcheck # The owner seems to have abandoned the linter. Replaced by "unused".
# - wsl # this doens't have an auto-fixer yet and is pretty noisy (https://github.com/bombsimon/wsl/issues/90) # - wsl # this doens't have an auto-fixer yet and is pretty noisy (https://github.com/bombsimon/wsl/issues/90)
issues:
max-same-issues: 25
uniq-by-line: false
# TODO: enable this when we have coverage on docstring comments
# # The list of ids of default excludes to include or disable.
# include:
# - EXC0002 # disable excluding of issues about comments from golint
formatters:
enable:
- gofmt
- goimports
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$
+120 -216
View File
@@ -1,11 +1,9 @@
version: 2
project_name: syft
release: release:
prerelease: auto prerelease: auto
draft: false draft: false
env: env:
# required to support multi architecture docker builds
- DOCKER_CLI_EXPERIMENTAL=enabled - DOCKER_CLI_EXPERIMENTAL=enabled
- CGO_ENABLED=0 - CGO_ENABLED=0
@@ -13,8 +11,14 @@ builds:
- id: linux-build - id: linux-build
dir: ./cmd/syft dir: ./cmd/syft
binary: syft binary: syft
goos: [linux] goos:
goarch: [amd64, arm64, ppc64le, s390x] - linux
goarch:
- amd64
- arm64
- ppc64le
- s390x
# set the modified timestamp on the output binary to the git timestamp to ensure a reproducible build
mod_timestamp: &build-timestamp '{{ .CommitTimestamp }}' mod_timestamp: &build-timestamp '{{ .CommitTimestamp }}'
ldflags: &build-ldflags | ldflags: &build-ldflags |
-w -w
@@ -28,8 +32,11 @@ builds:
- id: darwin-build - id: darwin-build
dir: ./cmd/syft dir: ./cmd/syft
binary: syft binary: syft
goos: [darwin] goos:
goarch: [amd64, arm64] - darwin
goarch:
- amd64
- arm64
mod_timestamp: *build-timestamp mod_timestamp: *build-timestamp
ldflags: *build-ldflags ldflags: *build-ldflags
hooks: hooks:
@@ -41,43 +48,115 @@ builds:
- id: windows-build - id: windows-build
dir: ./cmd/syft dir: ./cmd/syft
binary: syft binary: syft
goos: [windows] goos:
goarch: [amd64] - windows
goarch:
- amd64
mod_timestamp: *build-timestamp mod_timestamp: *build-timestamp
ldflags: *build-ldflags ldflags: *build-ldflags
archives: archives:
- id: linux-archives - id: linux-archives
ids: [linux-build] builds:
- linux-build
# note: the signing process is depending on tar.gz archives. If this format changes then .github/scripts/apple-signing/*.sh will need to be adjusted
- id: darwin-archives - id: darwin-archives
ids: [darwin-build] builds:
- darwin-build
- id: windows-archives - id: windows-archives
formats: format: zip
- zip builds:
ids: [windows-build] - windows-build
nfpms: nfpms:
- license: "Apache 2.0" - license: "Apache 2.0"
maintainer: "Anchore, Inc" maintainer: "Anchore, Inc"
homepage: &website "https://github.com/anchore/syft" homepage: &website "https://github.com/anchore/syft"
description: &description "A tool that generates a Software Bill Of Materials (SBOM) from container images and filesystems" description: &description "A tool that generates a Software Bill Of Materials (SBOM) from container images and filesystems"
formats: [rpm, deb] formats:
- rpm
- deb
brews: brews:
- repository: - tap:
owner: anchore owner: anchore
name: homebrew-syft name: homebrew-syft
token: "{{.Env.GITHUB_BREW_TOKEN}}" token: "{{.Env.GITHUB_BREW_TOKEN}}"
ids: [darwin-archives, linux-archives] ids:
- darwin-archives
- linux-archives
homepage: *website homepage: *website
description: *description description: *description
license: "Apache License 2.0" license: "Apache License 2.0"
dockers: dockers:
# production images...
- image_templates: - image_templates:
- anchore/syft:{{.Tag}}-amd64 - anchore/syft:debug
- ghcr.io/anchore/syft:{{.Tag}}-amd64 - anchore/syft:{{.Tag}}-debug
- ghcr.io/anchore/syft:debug
- ghcr.io/anchore/syft:{{.Tag}}-debug
goarch: amd64
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}}"
- image_templates:
- anchore/syft:debug-arm64v8
- anchore/syft:{{.Tag}}-debug-arm64v8
- ghcr.io/anchore/syft: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:debug-ppc64le
- anchore/syft:{{.Tag}}-debug-ppc64le
- ghcr.io/anchore/syft: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:debug-s390x
- anchore/syft:{{.Tag}}-debug-s390x
- ghcr.io/anchore/syft: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}}"
- image_templates:
- anchore/syft:latest
- anchore/syft:{{.Tag}}
- ghcr.io/anchore/syft:latest
- ghcr.io/anchore/syft:{{.Tag}}
goarch: amd64 goarch: amd64
dockerfile: Dockerfile dockerfile: Dockerfile
use: buildx use: buildx
@@ -127,219 +206,44 @@ dockers:
- "--build-arg=VCS_REF={{.FullCommit}}" - "--build-arg=VCS_REF={{.FullCommit}}"
- "--build-arg=VCS_URL={{.GitURL}}" - "--build-arg=VCS_URL={{.GitURL}}"
# nonroot images...
- image_templates:
- anchore/syft:{{.Tag}}-nonroot-amd64
- ghcr.io/anchore/syft:{{.Tag}}-nonroot-amd64
goarch: amd64
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}}"
- 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-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
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}}"
- 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-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: docker_manifests:
- name_template: anchore/syft:latest - name_template: anchore/syft:latest
image_templates: image_templates:
- anchore/syft:{{.Tag}}-amd64 - anchore/syft:{{.Tag}}
- anchore/syft:{{.Tag}}-arm64v8
- anchore/syft:{{.Tag}}-ppc64le
- anchore/syft:{{.Tag}}-s390x
- name_template: anchore/syft:debug
- anchore/syft:{{.Tag}}-debug
- anchore/syft:{{.Tag}}-debug-arm64v8
- anchore/syft:{{.Tag}}-debug-ppc64le
- anchore/syft:{{.Tag}}-debug-s390x
- name_template: anchore/syft:{{.Tag}}
image_templates:
- anchore/syft:{{.Tag}}
- anchore/syft:{{.Tag}}-arm64v8 - anchore/syft:{{.Tag}}-arm64v8
- anchore/syft:{{.Tag}}-ppc64le - anchore/syft:{{.Tag}}-ppc64le
- anchore/syft:{{.Tag}}-s390x - anchore/syft:{{.Tag}}-s390x
- name_template: ghcr.io/anchore/syft:latest - name_template: ghcr.io/anchore/syft:latest
image_templates: image_templates:
- ghcr.io/anchore/syft:{{.Tag}}-amd64 - ghcr.io/anchore/syft:{{.Tag}}
- ghcr.io/anchore/syft:{{.Tag}}-arm64v8 - ghcr.io/anchore/syft:{{.Tag}}-arm64v8
- ghcr.io/anchore/syft:{{.Tag}}-ppc64le - ghcr.io/anchore/syft:{{.Tag}}-ppc64le
- ghcr.io/anchore/syft:{{.Tag}}-s390x - 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}}-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}}-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-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-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-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-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-s390x
- name_template: ghcr.io/anchore/syft:debug - name_template: ghcr.io/anchore/syft:debug
image_templates: image_templates:
- ghcr.io/anchore/syft:{{.Tag}}-debug-amd64 - ghcr.io/anchore/syft:{{.Tag}}-debug
- ghcr.io/anchore/syft:{{.Tag}}-debug-arm64v8 - ghcr.io/anchore/syft:{{.Tag}}-debug-arm64v8
- ghcr.io/anchore/syft:{{.Tag}}-debug-ppc64le - ghcr.io/anchore/syft:{{.Tag}}-debug-ppc64le
- ghcr.io/anchore/syft:{{.Tag}}-debug-s390x - ghcr.io/anchore/syft:{{.Tag}}-debug-s390x
- name_template: anchore/syft:{{.Tag}}-debug - name_template: ghcr.io/anchore/syft:{{.Tag}}
image_templates: image_templates:
- anchore/syft:{{.Tag}}-debug-amd64 - ghcr.io/anchore/syft:{{.Tag}}
- anchore/syft:{{.Tag}}-debug-arm64v8 - ghcr.io/anchore/syft:{{.Tag}}-arm64v8
- anchore/syft:{{.Tag}}-debug-ppc64le - ghcr.io/anchore/syft:{{.Tag}}-ppc64le
- anchore/syft:{{.Tag}}-debug-s390x - ghcr.io/anchore/syft:{{.Tag}}-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-s390x
sboms:
- artifacts: archive
cmd: ../.tool/syft
documents:
- "{{ .Binary }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}.sbom"
args:
- "scan"
- "$artifact"
- "--output"
- "json=$document"
signs:
- cmd: .tool/cosign
signature: "${artifact}.sig"
certificate: "${artifact}.pem"
args:
- "sign-blob"
- "--oidc-issuer=https://token.actions.githubusercontent.com"
- "--output-certificate=${certificate}"
- "--output-signature=${signature}"
- "${artifact}"
- "--yes"
artifacts: checksum
-128
View File
@@ -1,128 +0,0 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
[opensource@anchore.com](mailto:opensource@anchore.com).
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.
+1 -5
View File
@@ -8,8 +8,4 @@ A cataloger contributed by Oracle Corporation that extracts packages given withi
## Swift Package Manager ## Swift Package Manager
A cataloger contributed by Axis Communications that catalogs packages resolved by Swift Package Manager. A cataloger contributed by Axis Communications that catalogs packages resolved by Swift Package Manager.
## Bitnami Packages
A cataloger contributed by Bitnami developer that catalogs packages described in Bitnami SBOMs.
+6 -16
View File
@@ -83,7 +83,7 @@ Syft's core library is implemented in the `syft` package and subpackages, where
- the `syft` package contains a single function that can take a `source.Source` object and catalog it, producing an `sbom.SBOM` object - the `syft` package contains a single function that can take a `source.Source` object and catalog it, producing an `sbom.SBOM` object
- the `syft/format` package contains the ability to encode and decode SBOMs to and from different SBOM formats (such as SPDX and CycloneDX) - the `syft/format` package contains the ability to encode and decode SBOMs to and from different SBOM formats (such as SPDX and CycloneDX)
The `cmd` package at the highest level execution flow wires up [`spf13/cobra`](https://github.com/spf13/cobra) commands for execution in the main application: The `cmd` pacakge at the highest level execution flow wires up [`spf13/cobra`](https://github.com/spf13/cobra) commands for execution in the main application:
```mermaid ```mermaid
sequenceDiagram sequenceDiagram
participant main as cmd/syft/main participant main as cmd/syft/main
@@ -212,7 +212,7 @@ Finally, here is an example of where the package construction is done within the
- [The APK package constructor itself](https://github.com/anchore/syft/tree/v0.70.0/syft/pkg/cataloger/apkdb/package.go#L12-L27) - [The APK package constructor itself](https://github.com/anchore/syft/tree/v0.70.0/syft/pkg/cataloger/apkdb/package.go#L12-L27)
Interested in building a new cataloger? Checkout the [list of issues with the `new-cataloger` label](https://github.com/anchore/syft/issues?q=is%3Aopen+is%3Aissue+label%3Anew-cataloger+no%3Aassignee)! Interested in building a new cataloger? Checkout the [list of issues with the `new-cataloger` label](https://github.com/anchore/syft/issues?q=is%3Aopen+is%3Aissue+label%3Anew-cataloger+no%3Aassignee)!
If you have questions about implementing a cataloger feel free to file an issue or reach out to us [on discourse](https://anchore.com/discourse)! If you have questions about implementing a cataloger feel free to file an issue or reach out to us [on slack](https://anchore.com/slack)!
#### Searching for files #### Searching for files
@@ -227,16 +227,6 @@ rough outline how that works:
## Testing ## Testing
### Testing commands
* `make help` shows a list of available commands
* `make unit`, `make integration`, `make cli`, and `make acceptance` run those test suites (see below)
* `make test` runs all those tests (and is therefore pretty slow)
* `make fixtures` clears and re-fetches all test fixtures.
* `go test ./syft/pkg/` for example can test particular packages, assuming fixtures are already made
* `make clean-cache` cleans all test cache. Note that subsequent test runs will be slower after this
### Levels of testing ### Levels of testing
- `unit`: The default level of test which is distributed throughout the repo are unit tests. Any `_test.go` file that - `unit`: The default level of test which is distributed throughout the repo are unit tests. Any `_test.go` file that
@@ -244,7 +234,7 @@ rough outline how that works:
the `/test` directory. These tests should focus on correctness of functionality in depth. % test coverage metrics the `/test` directory. These tests should focus on correctness of functionality in depth. % test coverage metrics
only considers unit tests and no other forms of testing. only considers unit tests and no other forms of testing.
- `integration`: located within `cmd/syft/internal/test/integration`, these tests focus on the behavior surfaced by the common library - `integration`: located within `test/integration`, these tests focus on the behavior surfaced by the common library
entrypoints from the `syft` package and make light assertions about the results surfaced. Additionally, these tests entrypoints from the `syft` package and make light assertions about the results surfaced. Additionally, these tests
tend to make diversity assertions for enum-like objects, ensuring that as enum values are added to a definition tend to make diversity assertions for enum-like objects, ensuring that as enum values are added to a definition
that integration tests will automatically fail if no test attempts to use that enum value. For more details see that integration tests will automatically fail if no test attempts to use that enum value. For more details see
@@ -377,11 +367,11 @@ package under test and should always be updated by invoking `go test` on the spe
update flag provided. update flag provided.
Many of the `Format` tests make use of this approach, where the raw SBOM report is saved in the repo and the test Many of the `Format` tests make use of this approach, where the raw SBOM report is saved in the repo and the test
compares that SBOM with what is generated from the latest presenter code. The following command can be used to compares that SBOM with what is generated from the latest presenter code. For instance, at the time of this writing
update the golden files for the various snapshot tests: the CycloneDX presenter snapshots can be updated by running:
```bash ```bash
make update-format-golden-files go test ./internal/formats -update-cyclonedx
``` ```
These flags are defined at the top of the test files that have tests that use the snapshot files. These flags are defined at the top of the test files that have tests that use the snapshot files.
+1 -1
View File
@@ -1,4 +1,4 @@
FROM gcr.io/distroless/static-debian12:latest AS build FROM gcr.io/distroless/static-debian11:debug@sha256:a0a404776dec98be120089ae42bbdfbe48c177921d856937d124d48eb8c0b951 AS build
FROM scratch FROM scratch
# needed for version check HTTPS request # needed for version check HTTPS request
+1 -3
View File
@@ -1,12 +1,10 @@
FROM gcr.io/distroless/static-debian12:debug-nonroot FROM gcr.io/distroless/static-debian11:debug@sha256:a0a404776dec98be120089ae42bbdfbe48c177921d856937d124d48eb8c0b951
# create the /tmp dir, which is needed for image content cache # create the /tmp dir, which is needed for image content cache
WORKDIR /tmp WORKDIR /tmp
COPY syft / COPY syft /
USER nonroot
ARG BUILD_DATE ARG BUILD_DATE
ARG BUILD_VERSION ARG BUILD_VERSION
ARG VCS_REF ARG VCS_REF
-27
View File
@@ -1,27 +0,0 @@
FROM gcr.io/distroless/static-debian12:nonroot
# 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"
ENTRYPOINT ["/syft"]
+3 -3
View File
@@ -12,7 +12,7 @@ TASK = $(TOOL_DIR)/task
# note: we need to assume that binny and task have not already been installed # note: we need to assume that binny and task have not already been installed
$(BINNY): $(BINNY):
@mkdir -p $(TOOL_DIR) @mkdir -p $(TOOL_DIR)
@curl -sSfL https://get.anchore.io/binny | sh -s -- -b $(TOOL_DIR) @curl -sSfL https://raw.githubusercontent.com/$(OWNER)/binny/main/install.sh | sh -s -- -b $(TOOL_DIR)
# note: we need to assume that binny and task have not already been installed # note: we need to assume that binny and task have not already been installed
.PHONY: task .PHONY: task
@@ -25,8 +25,8 @@ ci-bootstrap-go:
# this is a bootstrapping catch-all, where if the target doesn't exist, we'll ensure the tools are installed and then try again # this is a bootstrapping catch-all, where if the target doesn't exist, we'll ensure the tools are installed and then try again
%: %:
@make --silent $(TASK) make $(TASK)
@$(TASK) $@ $(TASK) $@
## Shim targets ################################# ## Shim targets #################################
+767 -97
View File
@@ -2,28 +2,25 @@
<img src="https://user-images.githubusercontent.com/5199289/136844524-1527b09f-c5cb-4aa9-be54-5aa92a6086c1.png" width="271" alt="Cute pink owl syft logo"> <img src="https://user-images.githubusercontent.com/5199289/136844524-1527b09f-c5cb-4aa9-be54-5aa92a6086c1.png" width="271" alt="Cute pink owl syft logo">
</p> </p>
# Syft [![Validations](https://github.com/anchore/syft/actions/workflows/validations.yaml/badge.svg)](https://github.com/anchore/syft/actions/workflows/validations.yaml)
[![Go Report Card](https://goreportcard.com/badge/github.com/anchore/syft)](https://goreportcard.com/report/github.com/anchore/syft)
[![GitHub release](https://img.shields.io/github/release/anchore/syft.svg)](https://github.com/anchore/syft/releases/latest)
[![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/anchore/syft.svg)](https://github.com/anchore/syft)
[![License: Apache-2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/anchore/syft/blob/main/LICENSE)
[![Slack Invite](https://img.shields.io/badge/Slack-Join-blue?logo=slack)](https://anchore.com/slack)
**A CLI tool and Go library for generating a Software Bill of Materials (SBOM) from container images and filesystems. Exceptional for vulnerability detection when used with a scanner like [Grype](https://github.com/anchore/grype).** A CLI tool and Go library for generating a Software Bill of Materials (SBOM) from container images and filesystems. Exceptional for vulnerability detection when used with a scanner like [Grype](https://github.com/anchore/grype).
<p align="center"> ### Join our community meetings!
&nbsp;<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>&nbsp;
&nbsp;<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>&nbsp; - Calendar: https://calendar.google.com/calendar/u/0/r?cid=Y182OTM4dGt0MjRtajI0NnNzOThiaGtnM29qNEBncm91cC5jYWxlbmRhci5nb29nbGUuY29t
&nbsp;<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>&nbsp; - Agenda: https://docs.google.com/document/d/1ZtSAa6fj2a6KRWviTn3WoJm09edvrNUp4Iz_dOjjyY8/edit?usp=sharing (join [this group](https://groups.google.com/g/anchore-oss-community) for write access)
&nbsp;<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>&nbsp; - All are welcome!
&nbsp;<a href="" target="_blank"><img alt="License: Apache-2.0" src="https://img.shields.io/badge/License-Apache%202.0-blue.svg"></a>&nbsp;
&nbsp;<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>&nbsp; For commercial support options with Syft or Grype, please [contact Anchore](https://get.anchore.com/contact/)
&nbsp;<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>&nbsp;
</p>
![syft-demo](https://user-images.githubusercontent.com/590471/90277200-2a253000-de33-11ea-893f-32c219eea11a.gif) ![syft-demo](https://user-images.githubusercontent.com/590471/90277200-2a253000-de33-11ea-893f-32c219eea11a.gif)
## Introduction
Syft is a powerful and easy-to-use open-source tool for generating Software Bill of Materials (SBOMs) for container images and filesystems. It provides detailed visibility into the packages and dependencies in your software, helping you manage vulnerabilities, license compliance, and software supply chain security.
Syft development is sponsored by [Anchore](https://anchore.com/), and is released under the [Apache-2.0 License](https://github.com/anchore/syft?tab=Apache-2.0-1-ov-file). For commercial support options with Syft or Grype, please [contact Anchore](https://get.anchore.com/contact/).
## Features ## Features
- Generates SBOMs for container images, filesystems, archives, and more to discover packages and libraries - Generates SBOMs for container images, filesystems, archives, and more to discover packages and libraries
- Supports OCI, Docker and [Singularity](https://github.com/sylabs/singularity) image formats - Supports OCI, Docker and [Singularity](https://github.com/sylabs/singularity) image formats
@@ -32,23 +29,53 @@ Syft development is sponsored by [Anchore](https://anchore.com/), and is release
- Able to create signed SBOM attestations using the [in-toto specification](https://github.com/in-toto/attestation/blob/main/spec/README.md) - Able to create signed SBOM attestations using the [in-toto specification](https://github.com/in-toto/attestation/blob/main/spec/README.md)
- Convert between SBOM formats, such as CycloneDX, SPDX, and Syft's own format. - Convert between SBOM formats, such as CycloneDX, SPDX, and Syft's own format.
### Supported Ecosystems
- Alpine (apk)
- C (conan)
- C++ (conan)
- Dart (pubs)
- Debian (dpkg)
- Dotnet (deps.json)
- Objective-C (cocoapods)
- Elixir (mix)
- Erlang (rebar3)
- Go (go.mod, Go binaries)
- Haskell (cabal, stack)
- Java (jar, ear, war, par, sar, nar, native-image)
- JavaScript (npm, yarn)
- Jenkins Plugins (jpi, hpi)
- Linux kernel archives (vmlinz)
- Linux kernel modules (ko)
- Nix (outputs in /nix/store)
- PHP (composer)
- Python (wheel, egg, poetry, requirements.txt)
- Red Hat (rpm)
- Ruby (gem)
- Rust (cargo.lock)
- Swift (cocoapods, swift-package-manager)
## Installation ## Installation
Syft binaries are provided for Linux, macOS and Windows. **Note**: Currently, Syft is built only for Linux, macOS and Windows.
### Recommended ### Recommended
> ```bash
> curl -sSfL https://get.anchore.io/syft | sudo sh -s -- -b /usr/local/bin
> ```
Install script options:
- `-b`: Specify a custom installation directory (defaults to `./bin`)
- `-d`: More verbose logging levels (`-d` for debug, `-dd` for trace)
- `-v`: Verify the signature of the downloaded artifact before installation (requires [`cosign`](https://github.com/sigstore/cosign) to be installed)
### Homebrew
```bash ```bash
brew install syft curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
```
... or, you can specify a release version and destination directory for the installation:
```
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b <DESTINATION_DIR> <RELEASE_VERSION>
```
### Chocolatey
The chocolatey distribution of syft is community maintained and not distributed by the anchore team
```powershell
choco install syft -y
``` ```
### Scoop ### Scoop
@@ -57,17 +84,14 @@ brew install syft
scoop install syft scoop install syft
``` ```
### Chocolatey ### Homebrew
```bash
The chocolatey distribution of Syft is community-maintained and not distributed by the Anchore team brew install syft
```powershell
choco install syft -y
``` ```
### Nix ### Nix
**Note**: Nix packaging of Syft is [community maintained](https://github.com/NixOS/nixpkgs/blob/master/pkgs/by-name/sy/syft/package.nix). Syft is available in the [stable channel](https://wiki.nixos.org/wiki/Nix_channels#The_official_channels) since NixOS `22.05`. **Note**: Nix packaging of Syft is [community maintained](https://github.com/NixOS/nixpkgs/blob/master/pkgs/tools/admin/syft/default.nix). Syft is available in the [stable channel](https://nixos.wiki/wiki/Nix_channels#The_official_channels) since NixOS `22.05`.
```bash ```bash
nix-env -i syft nix-env -i syft
@@ -95,9 +119,172 @@ The above output includes only software that is visible in the container (i.e.,
syft <image> --scope all-layers syft <image> --scope all-layers
``` ```
### Supported sources
Syft can generate an SBOM from a variety of sources:
```bash
# catalog a container image archive (from the result of `docker image save ...`, `podman save ...`, or `skopeo copy` commands)
syft path/to/image.tar
# catalog a Singularity Image Format (SIF) container
syft path/to/image.sif
# catalog a directory
syft path/to/dir
```
Sources can be explicitly provided with a scheme:
```
docker:yourrepo/yourimage:tag use images from the Docker daemon
podman:yourrepo/yourimage:tag use images from the Podman daemon
containerd:yourrepo/yourimage:tag use images from the Containerd daemon
docker-archive:path/to/yourimage.tar use a tarball from disk for archives created from "docker save"
oci-archive:path/to/yourimage.tar use a tarball from disk for OCI archives (from Skopeo or otherwise)
oci-dir:path/to/yourimage read directly from a path on disk for OCI layout directories (from Skopeo or otherwise)
singularity:path/to/yourimage.sif read directly from a Singularity Image Format (SIF) container on disk
dir:path/to/yourproject read directly from a path on disk (any directory)
file:path/to/yourproject/file read directly from a path on disk (any single file)
registry:yourrepo/yourimage:tag pull image directly from a registry (no container runtime required)
```
If an image source is not provided and cannot be detected from the given reference it is assumed the image should be pulled from the Docker daemon.
If docker is not present, then the Podman daemon is attempted next, followed by reaching out directly to the image registry last.
This default behavior can be overridden with the `default-image-pull-source` configuration option (See [Configuration](https://github.com/anchore/syft#configuration) for more details).
### File selection
By default, Syft will catalog file details and digests for files that are owned by discovered packages. You can change this behavior by using the `SYFT_FILE_METADATA_SELECTION` environment variable or the `file.metadata.selection` configuration option. The options are:
- `all`: capture all files from the search space
- `owned-by-package`: capture only files owned by packages (default)
- `none`: disable capturing any file information
### Package cataloger selection
#### Concepts
> [!IMPORTANT]
> Syft uses a different set of catalogers by default when scanning files directly than it does when scanning images
The catalogers for an image scan assumes that package installation steps have already been completed. For example, Syft will identify Python packages that have egg or wheel metadata files under a `site-packages` directory, since this is how the canonical tooling `pip` installs python packages.
The catalogers for a directory scan will look for installed software as well as declared dependencies that are not necessarily installed. For example, dependencies listed in a Python `requirements.txt`.
This default set of catalogers being dynamic is critical as this allows Syft to be used in a variety of contexts while still generating accurate SBOMs.
Overriding the set of default catalogers is not recommended for most purposes, however, is possible if needed.
Catalogers can be referenced in two different ways:
- *by name*: the exact cataloger name (e.g. `java-pom-cataloger` or `java-archive-cataloger`)
- *by tag*: a tag that is associated with a cataloger (e.g. `java`)
Syft can take lists of references on the CLI or in the application configuration to define which catalogers to use.
You can **set** the list of catalogers explicitly to use with the `--override-default-catalogers` CLI flag, accepting a comma-separated list of cataloger names or tags.
You can also **add** to, **remove** from, or **sub-select** catalogers to use within the default set of catalogers by using the `--select-catalogers` CLI flag.
- To **sub-select** catalogers simply provide a tag (e.g. `--select-catalogers TAG`). Catalogers will always be selected from the default set of catalogers (e.g. `--select-catalogers java,go` will select all the `java` catalogers in the default set and all the `go` catalogers in the default set).
- To **add** a cataloger prefix the cataloger name with `+` (e.g. `--select-catalogers +NAME`). Added catalogers will _always be added_ regardless of removals, filtering, or other defaults.
- To **remove** a cataloger prefix the cataloger name or tag with `-` (e.g. `--select-catalogers -NAME_OR_TAG`). Catalogers are removed from the set of default catalogers after processing any sub-selections.
These rules and the dynamic default cataloger sets approximates to the following logic:
```
image_catalogers = all_catalogers AND catalogers_tagged("image")
directory_catalogers = all_catalogers AND catalogers_tagged("directory")
default_catalogers = image_catalogers OR directory_catalogers
sub_selected_catalogers = default_catalogers INTERSECT catalogers_tagged(TAG) [ UNION sub_selected_catalogers ... ]
base_catalogers = default_catalogers OR sub_selected_catalogers
final_set = (base_catalogers SUBTRACT removed_catalogers) UNION added_catalogers
```
#### Examples
Only scan for python related packages with catalogers appropriate for the source type (image or directory):
```bash
syft <some container image> --select-catalogers "python"
# results in the following catalogers being used:
# - python-installed-package-cataloger
```
Same command, but the set of catalogers changes based on what is being analyzed (in this case a directory):
```bash
syft <a directory> --select-catalogers "python"
# results in the following catalogers being used:
# - python-installed-package-cataloger
# - python-package-cataloger
```
Use the default set of catalogers and add a cataloger to the set:
```bash
syft ... --catalogers "+sbom-cataloger"
```
Use the default set of catalogers but remove any catalogers that deal with RPMs:
```bash
syft ... --catalogers "-rpm"
```
Only scan with catalogers that:
- are tagged with "go"
- always use the sbom-cataloger
- are appropriate for the source type (image or directory)
```bash
syft <some container image> --select-catalogers "go,+sbom-cataloger"
# results in the following catalogers being used:
# - go-module-binary-cataloger
# - sbom-cataloger
```
Scan with all catalogers that deal with binary analysis, regardless of the source type:
```bash
syft ... --override-default-catalogers "binary"
# results in the following catalogers being used:
# - binary-cataloger
# - cargo-auditable-binary-cataloger
# - dotnet-portable-executable-cataloger
# - go-module-binary-cataloger
```
Only scan with the specific `go-module-binary-cataloger` and `go-module-file-cataloger` catalogers:
```bash
syft ... --override-default-catalogers "go-module-binary-cataloger,go-module-file-cataloger"
```
### Excluding file paths
Syft can exclude files and paths from being scanned within a source by using glob expressions
with one or more `--exclude` parameters:
```
syft <source> --exclude './out/**/*.json' --exclude /etc
```
**Note:** in the case of _image scanning_, since the entire filesystem is scanned it is
possible to use absolute paths like `/etc` or `/usr/**/*.txt` whereas _directory scans_
exclude files _relative to the specified directory_. For example: scanning `/usr/foo` with
`--exclude ./package.json` would exclude `/usr/foo/package.json` and `--exclude '**/package.json'`
would exclude all `package.json` files under `/usr/foo`. For _directory scans_,
it is required to begin path expressions with `./`, `*/`, or `**/`, all of which
will be resolved _relative to the specified scan directory_. Keep in mind, your shell
may attempt to expand wildcards, so put those parameters in single quotes, like:
`'**/*.json'`.
### Output formats ### Output formats
The output format for Syft is configurable as well using the `-o` (or `--output`) option: The output format for Syft is configurable as well using the
`-o` (or `--output`) option:
``` ```
syft <image> -o <format> syft <image> -o <format>
@@ -106,10 +293,8 @@ syft <image> -o <format>
Where the `formats` available are: Where the `formats` available are:
- `syft-json`: Use this to get as much information out of Syft as possible! - `syft-json`: Use this to get as much information out of Syft as possible!
- `syft-text`: A row-oriented, human-and-machine-friendly output. - `syft-text`: A row-oriented, human-and-machine-friendly output.
- `cyclonedx-xml`: A XML report conforming to the [CycloneDX 1.6 specification](https://cyclonedx.org/specification/overview/). - `cyclonedx-xml`: A XML report conforming to the [CycloneDX 1.4 specification](https://cyclonedx.org/specification/overview/).
- `cyclonedx-xml@1.5`: A XML report conforming to the [CycloneDX 1.5 specification](https://cyclonedx.org/specification/overview/). - `cyclonedx-json`: A JSON report conforming to the [CycloneDX 1.4 specification](https://cyclonedx.org/specification/overview/).
- `cyclonedx-json`: A JSON report conforming to the [CycloneDX 1.6 specification](https://cyclonedx.org/specification/overview/).
- `cyclonedx-json@1.5`: A JSON report conforming to the [CycloneDX 1.5 specification](https://cyclonedx.org/specification/overview/).
- `spdx-tag-value`: A tag-value formatted report conforming to the [SPDX 2.3 specification](https://spdx.github.io/spdx-spec/v2.3/). - `spdx-tag-value`: A tag-value formatted report conforming to the [SPDX 2.3 specification](https://spdx.github.io/spdx-spec/v2.3/).
- `spdx-tag-value@2.2`: A tag-value formatted report conforming to the [SPDX 2.2 specification](https://spdx.github.io/spdx-spec/v2.2.2/). - `spdx-tag-value@2.2`: A tag-value formatted report conforming to the [SPDX 2.2 specification](https://spdx.github.io/spdx-spec/v2.2.2/).
- `spdx-json`: A JSON report conforming to the [SPDX 2.3 JSON Schema](https://github.com/spdx/spdx-spec/blob/v2.3/schemas/spdx-schema.json). - `spdx-json`: A JSON report conforming to the [SPDX 2.3 JSON Schema](https://github.com/spdx/spdx-spec/blob/v2.3/schemas/spdx-schema.json).
@@ -118,70 +303,555 @@ Where the `formats` available are:
- `syft-table`: A columnar summary (default). - `syft-table`: A columnar summary (default).
- `template`: Lets the user specify the output format. See ["Using templates"](#using-templates) below. - `template`: Lets the user specify the output format. See ["Using templates"](#using-templates) below.
Note that flags using the @<version> can be used for earlier versions of each specification as well. ## Using templates
### Supported Ecosystems Syft lets you define custom output formats, using [Go templates](https://pkg.go.dev/text/template). Here's how it works:
- Alpine (apk) - Define your format as a Go template, and save this template as a file.
- Bitnami packages
- C (conan)
- C++ (conan)
- Dart (pubs)
- Debian (dpkg)
- Dotnet (deps.json)
- Objective-C (cocoapods)
- Elixir (mix)
- Erlang (rebar3)
- Go (go.mod, Go binaries)
- Haskell (cabal, stack)
- Java (jar, ear, war, par, sar, nar, native-image)
- JavaScript (npm, yarn)
- Jenkins Plugins (jpi, hpi)
- Linux kernel archives (vmlinz)
- Linux kernel modules (ko)
- Nix (outputs in /nix/store)
- PHP (composer, PECL, Pear)
- Python (wheel, egg, poetry, requirements.txt, uv)
- Red Hat (rpm)
- Ruby (gem)
- Rust (cargo.lock, auditable binary)
- Swift (cocoapods, swift-package-manager)
- Wordpress plugins
- Terraform providers (.terraform.lock.hcl)
## Documentation - Set the output format to "template" (`-o template`).
Our [wiki](https://github.com/anchore/syft/wiki) contains further details on the following topics: - Specify the path to the template file (`-t ./path/to/custom.template`).
* [Supported Sources](https://github.com/anchore/syft/wiki/supported-sources) - Syft's template processing uses the same data models as the `syft-json` output format — so if you're wondering what data is available as you author a template, you can use the output from `syft <image> -o syft-json` as a reference.
* [File Selection](https://github.com/anchore/syft/wiki/file-selection)
* [Excluding file paths](https://github.com/anchore/syft/wiki/excluding-file-paths)
* [Output formats](https://github.com/anchore/syft/wiki/output-formats)
* [Package Cataloger Selection](https://github.com/anchore/syft/wiki/package-cataloger-selection)
* [Concepts](https://github.com/anchore/syft/wiki/package-cataloger-selection#concepts)
* [Examples](https://github.com/anchore/syft/wiki/package-cataloger-selection#examples)
* [Using templates](https://github.com/anchore/syft/wiki/using-templates)
* [Multiple outputs](https://github.com/anchore/syft/wiki/multiple-outputs)
* [Private Registry Authentication](https://github.com/anchore/syft/wiki/private-registry-authentication)
* [Local Docker Credentials](https://github.com/anchore/syft/wiki/private-registry-authentication#local-docker)
* [Docker Credentials in Kubernetes](https://github.com/anchore/syft/wiki/private-registry-authentication#docker-credentials-in-kubernetes)
* [Attestation (experimental)](https://github.com/anchore/syft/wiki/attestation)
* [Keyless Support](https://github.com/anchore/syft/wiki/attestation#keyless-support)
* [Local private key support](https://github.com/anchore/syft/wiki/attestation#local-private-key-support)
* [Adding an SBOM to an image as an attestation using Syft](https://github.com/anchore/syft/wiki/attestation#adding-an-sbom-to-an-image-as-an-attestation-using-syft)
* [Configuration](https://github.com/anchore/syft/wiki/configuration)
## Contributing **Example:** You could make Syft output data in CSV format by writing a Go template that renders CSV data and then running `syft <image> -o template -t ~/path/to/csv.tmpl`.
Check out our [contributing](/CONTRIBUTING.md) guide and [developer](/DEVELOPING.md) docs. Here's what the `csv.tmpl` file might look like:
```gotemplate
"Package","Version Installed","Found by"
{{- range .Artifacts}}
"{{.Name}}","{{.Version}}","{{.FoundBy}}"
{{- end}}
```
## Syft Team Meetings Which would produce output like:
```text
"Package","Version Installed","Found by"
"alpine-baselayout","3.2.0-r20","apkdb-cataloger"
"alpine-baselayout-data","3.2.0-r20","apkdb-cataloger"
"alpine-keys","2.4-r1","apkdb-cataloger"
...
```
The Syft Team hold regular community meetings online. All are welcome to join to bring topics for discussion. Syft also includes a vast array of utility templating functions from [sprig](http://masterminds.github.io/sprig/) apart from the default Golang [text/template](https://pkg.go.dev/text/template#hdr-Functions) to allow users to customize the output format.
- Check the [calendar](https://calendar.google.com/calendar/u/0/r?cid=Y182OTM4dGt0MjRtajI0NnNzOThiaGtnM29qNEBncm91cC5jYWxlbmRhci5nb29nbGUuY29t) for the next meeting date.
- Add items to the [agenda](https://docs.google.com/document/d/1ZtSAa6fj2a6KRWviTn3WoJm09edvrNUp4Iz_dOjjyY8/edit?usp=sharing) (join [this group](https://groups.google.com/g/anchore-oss-community) for write access to the [agenda](https://docs.google.com/document/d/1ZtSAa6fj2a6KRWviTn3WoJm09edvrNUp4Iz_dOjjyY8/edit?usp=sharing))
- See you there!
## Syft Logo Lastly, Syft has custom templating functions defined in `./syft/format/template/encoder.go` to help parse the passed-in JSON structs.
<p xmlns:cc="http://creativecommons.org/ns#" xmlns:dct="http://purl.org/dc/terms/"><a property="dct:title" rel="cc:attributionURL" href="https://anchore.com/wp-content/uploads/2024/11/syft-logo.svg">Syft Logo</a> by <a rel="cc:attributionURL dct:creator" property="cc:attributionName" href="https://anchore.com/">Anchore</a> is licensed under <a href="https://creativecommons.org/licenses/by/4.0/" target="_blank" rel="license noopener noreferrer" style="display:inline-block;">CC BY 4.0<img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" src="https://mirrors.creativecommons.org/presskit/icons/cc.svg" alt=""><img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" src="https://mirrors.creativecommons.org/presskit/icons/by.svg" alt=""></a></p> ## Multiple outputs
Syft can also output _multiple_ files in differing formats by appending
`=<file>` to the option, for example to output Syft JSON and SPDX JSON:
```shell
syft <image> -o syft-json=sbom.syft.json -o spdx-json=sbom.spdx.json
```
## Private Registry Authentication
### Local Docker Credentials
When a container runtime is not present, Syft can still utilize credentials configured in common credential sources (such as `~/.docker/config.json`). It will pull images from private registries using these credentials. The config file is where your credentials are stored when authenticating with private registries via some command like `docker login`. For more information see the `go-containerregistry` [documentation](https://github.com/google/go-containerregistry/tree/main/pkg/authn).
An example `config.json` looks something like this:
```json
{
"auths": {
"registry.example.com": {
"username": "AzureDiamond",
"password": "hunter2"
}
}
}
```
You can run the following command as an example. It details the mount/environment configuration a container needs to access a private registry:
```
docker run -v ./config.json:/config/config.json -e "DOCKER_CONFIG=/config" anchore/syft:latest <private_image>
```
### Docker Credentials in Kubernetes
Here's a simple workflow to mount this config file as a secret into a container on Kubernetes.
1. Create a secret. The value of `config.json` is important. It refers to the specification detailed [here](https://github.com/google/go-containerregistry/tree/main/pkg/authn#the-config-file). Below this section is the `secret.yaml` file that the pod configuration will consume as a volume. The key `config.json` is important. It will end up being the name of the file when mounted into the pod.
```yaml
# secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: registry-config
namespace: syft
data:
config.json: <base64 encoded config.json>
```
`kubectl apply -f secret.yaml`
2. Create your pod running syft. The env `DOCKER_CONFIG` is important because it advertises where to look for the credential file. In the below example, setting `DOCKER_CONFIG=/config` informs syft that credentials can be found at `/config/config.json`. This is why we used `config.json` as the key for our secret. When mounted into containers the secrets' key is used as the filename. The `volumeMounts` section mounts our secret to `/config`. The `volumes` section names our volume and leverages the secret we created in step one.
```yaml
# pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: syft-k8s-usage
spec:
containers:
- image: anchore/syft:latest
name: syft-private-registry-demo
env:
- name: DOCKER_CONFIG
value: /config
volumeMounts:
- mountPath: /config
name: registry-config
readOnly: true
args:
- <private_image>
volumes:
- name: registry-config
secret:
secretName: registry-config
```
`kubectl apply -f pod.yaml`
3. The user can now run `kubectl logs syft-private-registry-demo`. The logs should show the Syft analysis for the `<private_image>` provided in the pod configuration.
Using the above information, users should be able to configure private registry access without having to do so in the `grype` or `syft` configuration files. They will also not be dependent on a Docker daemon, (or some other runtime software) for registry configuration and access.
## Format conversion (experimental)
The ability to convert existing SBOMs means you can create SBOMs in different formats quickly, without the need to regenerate the SBOM from scratch, which may take significantly more time.
```
syft convert <ORIGINAL-SBOM-FILE> -o <NEW-SBOM-FORMAT>[=<NEW-SBOM-FILE>]
```
This feature is experimental and data might be lost when converting formats. Packages are the main SBOM component easily transferable across formats, whereas files and relationships, as well as other information Syft doesn't support, are more likely to be lost.
We support formats with wide community usage AND good encode/decode support by Syft. The supported formats are:
- Syft JSON (```-o syft-json```)
- SPDX 2.2 JSON (```-o spdx-json```)
- SPDX 2.2 tag-value (```-o spdx-tag-value```)
- CycloneDX 1.4 JSON (```-o cyclonedx-json```)
- CycloneDX 1.4 XML (```-o cyclonedx-xml```)
Conversion example:
```sh
syft alpine:latest -o syft-json=sbom.syft.json # generate a syft SBOM
syft convert sbom.syft.json -o cyclonedx-json=sbom.cdx.json # convert it to CycloneDX
```
## Attestation (experimental)
### Keyless support
Syft supports generating attestations using cosign's [keyless](https://github.com/sigstore/cosign/blob/main/KEYLESS.md) signatures.
Note: users need to have >= v1.12.0 of cosign installed for this command to function
To use this feature with a format like CycloneDX json simply run:
```
syft attest --output cyclonedx-json <IMAGE WITH OCI WRITE ACCESS>
```
This command will open a web browser and allow the user to authenticate their OIDC identity as the root of trust for the attestation (Github, Google, Microsoft).
After authenticating, Syft will upload the attestation to the OCI registry specified by the image that the user has write access to.
You will need to make sure your credentials are configured for the OCI registry you are uploading to so that the attestation can write successfully.
Users can then verify the attestation(or any image with attestations) by running:
```
COSIGN_EXPERIMENTAL=1 cosign verify-attestation <IMAGE_WITH_ATTESTATIONS>
```
Users should see that the uploaded attestation claims are validated, the claims exist within the transparency log, and certificates on the attestations were verified against [fulcio](https://github.com/SigStore/fulcio).
There will also be a printout of the certificates subject `<user identity>` and the certificate issuer URL: `<provider of user identity (Github, Google, Microsoft)>`:
```
Certificate subject: test.email@testdomain.com
Certificate issuer URL: https://accounts.google.com
```
### Local private key support
To generate an SBOM attestation for a container image using a local private key:
```
syft attest --output [FORMAT] --key [KEY] [SOURCE] [flags]
```
The above output is in the form of the [DSSE envelope](https://github.com/secure-systems-lab/dsse/blob/master/envelope.md#dsse-envelope).
The payload is a base64 encoded `in-toto` statement with the generated SBOM as the predicate. For details on workflows using this command see [here](#adding-an-sbom-to-an-image-as-an-attestation-using-syft).
## Configuration
Configuration search paths:
- `.syft.yaml`
- `.syft/config.yaml`
- `~/.syft.yaml`
- `<XDG_CONFIG_HOME>/syft/config.yaml`
Configuration options (example values are the default):
```yaml
# the output format(s) of the SBOM report (options: syft-table, syft-text, syft-json, spdx-json, ...)
# same as -o, --output, and SYFT_OUTPUT env var
# to specify multiple output files in differing formats, use a list:
# output:
# - "syft-json=<syft-json-output-file>"
# - "spdx-json=<spdx-json-output-file>"
output: "syft-table"
# suppress all output (except for the SBOM report)
# same as -q ; SYFT_QUIET env var
quiet: false
# enable/disable checking for application updates on startup
# same as SYFT_CHECK_FOR_APP_UPDATE env var
check-for-app-update: true
# maximum number of workers used to process the list of package catalogers in parallel
parallelism: 1
# a list of globs to exclude from scanning, for example:
# exclude:
# - "/etc/**"
# - "./out/**/*.json"
# same as --exclude ; SYFT_EXCLUDE env var
exclude: []
# allows users to exclude synthetic binary packages from the sbom
# these packages are removed if an overlap with a non-synthetic package is found
exclude-binary-overlap-by-ownership: true
# os and/or architecture to use when referencing container images (e.g. "windows/armv6" or "arm64")
# same as --platform; SYFT_PLATFORM env var
platform: ""
# the search space to look for file and package data (options: all-layers, squashed)
# SYFT_SCOPE env var
scope: "squashed"
# set the list of package catalogers to use when generating the SBOM
# default = empty (cataloger set determined automatically by the source type [image or file/directory])
# catalogers:
# - alpm-db-cataloger
# - apkdb-cataloger
# - binary-cataloger
# - cargo-auditable-binary-cataloger
# - cocoapods-cataloger
# - conan-cataloger
# - dartlang-lock-cataloger
# - dotnet-deps-cataloger
# - dotnet-portable-executable-cataloger
# - dpkg-db-cataloger
# - elixir-mix-lock-cataloger
# - erlang-rebar-lock-cataloger
# - go-module-file-cataloger
# - go-module-binary-cataloger
# - graalvm-native-image-cataloger
# - haskell-cataloger
# - java-cataloger
# - java-gradle-lockfile-cataloger
# - java-pom-cataloger
# - javascript-lock-cataloger
# - javascript-package-cataloger
# - linux-kernel-cataloger
# - nix-store-cataloger
# - php-composer-installed-cataloger
# - php-composer-lock-cataloger
# - portage-cataloger
# - python-package-cataloger
# - python-installed-package-cataloger
# - rpm-db-cataloger
# - rpm-archive-cataloger
# - ruby-gemfile-cataloger
# - ruby-installed-gemspec-cataloger
# - rust-cargo-lock-cataloger
# - sbom-cataloger
# - spm-cataloger
catalogers:
# all format configuration
format:
# default value for all formats that support the "pretty" option (default is unset)
pretty:
# all syft-json format options
json:
# include space indention and newlines (inherits default value from 'format.pretty' or 'false' if parent is unset)
# note: inherits default value from 'format.pretty' or 'false' if parent is unset
pretty: false
# transform any syft-json output to conform to an approximation of the v11.0.1 schema. This includes:
# - using the package metadata type names from before v12 of the JSON schema (changed in https://github.com/anchore/syft/pull/1983)
#
# Note: this will still include package types and fields that were added at or after json schema v12. This means
# that output might not strictly be json schema v11 compliant, however, for consumers that require time to port
# over to the final syft 1.0 json output this option can be used to ease the transition.
#
# Note: long term support for this option is not guaranteed.
legacy: false
# all template format options
template:
# path to the template file to use when rendering the output with the `template` output format.
# Note that all template paths are based on the current syft-json schema.
# same as -t ; SYFT_TEMPLATE_PATH env var
path: ""
# all spdx-json format options
spdx-json:
# include space indention and newlines
# note: inherits default value from 'format.pretty' or 'false' if parent is unset
pretty: false
# all cyclonedx-json format options
cyclonedx-json:
# include space indention and newlines
# note: inherits default value from 'format.pretty' or 'false' if parent is unset
pretty: false
# all cyclonedx-xml format options
cyclonedx-xml:
# include space indention
# note: inherits default value from 'format.pretty' or 'false' if parent is unset
pretty: false
file:
metadata:
# select which files should be captured by the file-metadata cataloger and included in the SBOM.
# Options include:
# - "all-files": capture all files from the search space
# - "owned-files": capture only files owned by packages
# - "unowned-files": capture only files not owned by packages
# - "no-files", "": do not capture any files
# SYFT_FILE_METADATA_SELECTION env var
selection: "owned-files"
# the file digest algorithms to use when cataloging files (options: "md5", "sha1", "sha224", "sha256", "sha384", "sha512")
# SYFT_FILE_METADATA_DIGESTS env var
digests: ["sha256"]
# capture the contents of select files in the SBOM
content:
# skip searching a file entirely if it is above the given size (default = 1MB; unit = bytes)
# SYFT_FILE_CONTENT_SKIP_FILES_ABOVE_SIZE env var
skip-files-above-size: 1048576
# file globs for the cataloger to match on
# SYFT_FILE_CONTENT_GLOBS env var
globs: []
# cataloging packages is exposed through the packages and power-user subcommands
package:
# search within archives that do contain a file index to search against (zip)
# note: for now this only applies to the java package cataloger
# SYFT_PACKAGE_SEARCH_INDEXED_ARCHIVES env var
search-indexed-archives: true
# search within archives that do not contain a file index to search against (tar, tar.gz, tar.bz2, etc)
# note: enabling this may result in a performance impact since all discovered compressed tars will be decompressed
# note: for now this only applies to the java package cataloger
# SYFT_PACKAGE_SEARCH_UNINDEXED_ARCHIVES env var
search-unindexed-archives: false
# allows users to exclude synthetic binary packages from the sbom
# these packages are removed if an overlap with a non-synthetic package is found
exclude-binary-overlap-by-ownership: true
golang:
# search for go package licences in the GOPATH of the system running Syft, note that this is outside the
# container filesystem and potentially outside the root of a local directory scan
# SYFT_GOLANG_SEARCH_LOCAL_MOD_CACHE_LICENSES env var
search-local-mod-cache-licenses: false
# specify an explicit go mod cache directory, if unset this defaults to $GOPATH/pkg/mod or $HOME/go/pkg/mod
# SYFT_GOLANG_LOCAL_MOD_CACHE_DIR env var
local-mod-cache-dir: ""
# search for go package licences by retrieving the package from a network proxy
# SYFT_GOLANG_SEARCH_REMOTE_LICENSES env var
search-remote-licenses: false
# remote proxy to use when retrieving go packages from the network,
# if unset this defaults to $GOPROXY followed by https://proxy.golang.org
# SYFT_GOLANG_PROXY env var
proxy: ""
# specifies packages which should not be fetched by proxy
# if unset this defaults to $GONOPROXY
# SYFT_GOLANG_NOPROXY env var
no-proxy: ""
java:
maven-url: "https://repo1.maven.org/maven2"
max-parent-recursive-depth: 5
# enables Syft to use the network to fill in more detailed information about artifacts
# currently this enables searching maven-url for license data
# when running across pom.xml files that could have more information, syft will
# explicitly search maven for license information by querying the online pom when this is true
# this option is helpful for when the parent pom has more data,
# that is not accessible from within the final built artifact
use-network: false
linux-kernel:
# whether to catalog linux kernel modules found within lib/modules/** directories
# SYFT_LINUX_KERNEL_CATALOG_MODULES env var
catalog-modules: true
python:
# when running across entries in requirements.txt that do not specify a specific version
# (e.g. "sqlalchemy >= 1.0.0, <= 2.0.0, != 3.0.0, <= 3.0.0"), attempt to guess what the version could
# be based on the version requirements specified (e.g. "1.0.0"). When enabled the lowest expressible version
# when given an arbitrary constraint will be used (even if that version may not be available/published).
guess-unpinned-requirements: false
javascript:
search-remote-licenses: false
npm-base-url: "https://registry.npmjs.org"
# configuration for the source that the SBOM is generated from (e.g. a file, directory, or container image)
source:
# alias name for the source
# SYFT_SOURCE_NAME env var; --source-name flag
name: ""
# alias version for the source
# SYFT_SOURCE_VERSION env var; --source-version flag
version: ""
# options affecting the file source type
file:
# the file digest algorithms to use on the scanned file (options: "md5", "sha1", "sha224", "sha256", "sha384", "sha512")
digests: ["sha256"]
image:
# allows users to specify which image source should be used to generate the sbom
# valid values are: registry, docker, podman
# SYFT_SOURCE_IMAGE_DEFAULT_PULL_SOURCE env var
default-pull-source: ""
# options when pulling directly from a registry via the "registry:" or "containerd:" scheme
registry:
# skip TLS verification when communicating with the registry
# SYFT_REGISTRY_INSECURE_SKIP_TLS_VERIFY env var
insecure-skip-tls-verify: false
# use http instead of https when connecting to the registry
# SYFT_REGISTRY_INSECURE_USE_HTTP env var
insecure-use-http: false
# filepath to a CA certificate (or directory containing *.crt, *.cert, *.pem) used to generate the client certificate
# SYFT_REGISTRY_CA_CERT env var
ca-cert: ""
# credentials for specific registries
auth:
# the URL to the registry (e.g. "docker.io", "localhost:5000", etc.)
# SYFT_REGISTRY_AUTH_AUTHORITY env var
- authority: ""
# SYFT_REGISTRY_AUTH_USERNAME env var
username: ""
# SYFT_REGISTRY_AUTH_PASSWORD env var
password: ""
# note: token and username/password are mutually exclusive
# SYFT_REGISTRY_AUTH_TOKEN env var
token: ""
# filepath to the client certificate used for TLS authentication to the registry
# SYFT_REGISTRY_AUTH_TLS_CERT env var
tls-cert: ""
# filepath to the client key used for TLS authentication to the registry
# SYFT_REGISTRY_AUTH_TLS_KEY env var
tls-key: ""
# - ... # note, more credentials can be provided via config file only (not env vars)
# generate an attested SBOM
attest:
# path to the private key file to use for attestation
# SYFT_ATTEST_KEY env var
key: "cosign.key"
# password to decrypt to given private key
# SYFT_ATTEST_PASSWORD env var, additionally responds to COSIGN_PASSWORD
password: ""
log:
# use structured logging
# same as SYFT_LOG_STRUCTURED env var
structured: false
# the log level; note: detailed logging suppress the ETUI
# same as SYFT_LOG_LEVEL env var
level: "error"
# location to write the log file (default is not to have a log file)
# same as SYFT_LOG_FILE env var
file: ""
```
### Adding an SBOM to an image as an attestation using Syft
`syft attest --output [FORMAT] --key [KEY] [SOURCE] [flags]`
SBOMs themselves can serve as input to different analysis tools. [Grype](https://github.com/anchore/grype), a vulnerability scanner CLI tool from Anchore, is one such tool. Publishers of container images can use attestations to enable their consumers to trust Syft-generated SBOM descriptions of those container images. To create and provide these attestations, image publishers can run `syft attest` in conjunction with the [cosign](https://github.com/sigstore/cosign) tool to attach SBOM attestations to their images.
#### Example attestation
Note for the following example replace `docker.io/image:latest` with an image you own. You should also have push access to
its remote reference. Replace `$MY_PRIVATE_KEY` with a private key you own or have generated with cosign.
```bash
syft attest --key $MY_PRIVATE_KEY -o spdx-json docker.io/image:latest > image_latest_sbom_attestation.json
cosign attach attestation --attestation image_latest_sbom_attestation.json docker.io/image:latest
```
Verify the new attestation exists on your image.
```bash
cosign verify-attestation --key $MY_PUBLIC_KEY --type spdxjson docker.io/image:latest | jq '.payload | @base64d | .payload | fromjson | .predicate'
```
You should see this output along with the attached SBOM:
```
Verification for docker.io/image:latest --
The following checks were performed on each of these signatures:
- The cosign claims were validated
- The signatures were verified against the specified public key
- Any certificates were verified against the Fulcio roots.
```
Consumers of your image can now trust that the SBOM associated with your image is correct and from a trusted source.
The SBOM can be piped to Grype:
```bash
cosign verify-attestation --key $MY_PUBLIC_KEY --type spdxjson docker.io/image:latest | jq '.payload | @base64d | .payload | fromjson | .predicate' | grype
```
+86 -13
View File
@@ -1,16 +1,5 @@
# Release # Release
A release of syft comprises:
- a new semver git tag from the current tip of the main branch
- a new [github release](https://github.com/anchore/syft/releases) with a changelog and archived binary assets
- docker images published to `ghcr.io` and `dockerhub`, including multi architecture images + manifest
- [`anchore/homebrew-syft`](https://github.com/anchore/homebrew-syft) tap updated to point to assets in the latest github release
Ideally releasing should be done often with small increments when possible. Unless a
breaking change is blocking the release, or no fixes/features have been merged, a good
target release cadence is between every 1 or 2 weeks.
## Creating a release ## Creating a release
This release process itself should be as automated as possible, and has only a few steps: This release process itself should be as automated as possible, and has only a few steps:
@@ -20,10 +9,16 @@ This release process itself should be as automated as possible, and has only a f
you can abort and adjust the labels on the PRs and issues to be included in the release and you can abort and adjust the labels on the PRs and issues to be included in the release and
re-run the release trigger command. re-run the release trigger command.
1. A release admin must approve the release on the GitHub Actions [release pipeline](https://github.com/anchore/syft/actions/workflows/release.yaml) run page. 1. A release admin must approve the release on the GitHub Actions release pipeline run page.
Once approved, the release pipeline will generate all assets and publish a GitHub Release. Once approved, the release pipeline will generate all assets and publish a GitHub Release.
1. If there is a release Milestone, close it.
Ideally releasing should be done often with small increments when possible. Unless a
breaking change is blocking the release, or no fixes/features have been merged, a good
target release cadence is between every 1 or 2 weeks.
## Retracting a release ## Retracting a release
If a release is found to be problematic, it can be retracted with the following steps: If a release is found to be problematic, it can be retracted with the following steps:
@@ -37,3 +32,81 @@ If a release is found to be problematic, it can be retracted with the following
in the go proxy, which will cause confusion when trying to reuse the tag later (the H1 hash will not match and there in the go proxy, which will cause confusion when trying to reuse the tag later (the H1 hash will not match and there
will be a warning when users try to pull the new release). will be a warning when users try to pull the new release).
## Background
A good release process has the following qualities:
1. There is a way to plan what should be in a release
1. There is a way to see what is actually in a release
1. Allow for different kinds of releases (major breaking vs backwards compatible enhancements vs patch updates)
1. Specify a repeatable way to build and publish software artifacts
### Planning a release
To indicate a set of features to be released together add each issue to an in-repository
Milestone named with major-minor version to be released (e.g. `v0.1`). It is OK for other
features to be in the release that were not originally planned, and these issues and PRs
do not need to be added to the Milestone in question. Only the set of features that, when
completed, would allow the release to be considered complete. A Milestone is only used to:
- Plan what is desired to be in a release
- Track progress to indicate when we may be ready to cut a new release
Not all releases need to be planned. For instance, patch releases for fixes should be
released when they are ready and when releasing would not interfere with another current
release (where some partial or breaking features have already been merged).
Unless necessary, feature releases should be small and frequent, which may obviate the
need for regular release planning under a Milestone.
### What is in a release
Milestones are specifically for planning a release, not necessarily tracking all changes
that a release may bring (and more importantly, not all releases are necessarily planned
either).
This is one of the (many) reasons for a Changelog. A good Changelog lists changes grouped
by the type of change (new, enhancement, deprecation, breaking, bug fix, security fix), in
chronological order (within groups), linking the PR where the change was made in the
Changelog line. Furthermore, there should be a place to see all released versions, the
release date for each release, the semantic version of the release, and the set of changes
for each release.
**This project auto-generates the Changelog contents for each current release and posts the
generated contents to the GitHub Release page**. Leveraging the GitHub Releases feature
allows GitHub to manage the Changelog on each release outside of the git source tree while
still being hosted with the released assets.
The Changelog is generated from the metadata from in-repository issues and PRs, using
labels to guide what kind of change each item is (e.g. breaking, new feature, bug fix,
etx). Only issues/PRs with select labels are included in the Changelog, and only if the
issue/PR was created after the last release. Additional labels are used to exclude items
from the Changelog.
The above suggestions imply that we should:
- Ensure there is a sufficient title for each PR and issue title to be included in the
Changelog
- The appropriate label is applied to PRs and/or issues to drive specific change type
sections (deprecated, breaking, security, bug, etc)
**With this approach as we cultivate good organization of PRs and issues we automatically
get an equally good Changelog.**
### Major, minor, and patch releases
The latest version of the tool is the only supported version, which implies that multiple
parallel release branches will not be a regular process (if ever). Multiple releases can
be planned in parallel, however, only one can be actively developed at a time. That is, if
PRs attached to a release Milestone have been merged into the main branch, that release is
now the "next" release. **This implies that the source of truth for release lies with the
git log and Changelog, not with the release Milestones** (which are purely for planning and
tracking).
Semantic versioning should be used to indicate breaking changes, new features, and fixes.
The exception to this is `< 1.0`, where the major version is not bumped for breaking changes,
instead the minor version indicates both new features and breaking changes.
-32
View File
@@ -1,32 +0,0 @@
# Security Policy
## Supported Versions
<!-- Use this section to tell people about which versions of your project are
currently being supported with security updates.
| Version | Supported |
| ------- | ------------------ |
| 5.1.x | :white_check_mark: |
| 5.0.x | :x: |
| 4.0.x | :white_check_mark: |
| < 4.0 | :x: |
-->
Security updates are applied only to the most recent release, try to always be up to date.
## Reporting a Vulnerability
<!-- Use this section to tell people how to report a vulnerability.
Tell them where to go, how often they can expect to get an update on a
reported vulnerability, what to expect if the vulnerability is accepted or
declined, etc. -->
To report a security issue, please email
[security@anchore.com](mailto:security@anchore.com)
with a description of the issue, the steps you took to create the issue,
affected versions, and, if known, mitigations for the issue.
All support will be made on the best effort base, so please indicate the "urgency level" of the vulnerability as Critical, High, Medium or Low.
+81 -245
View File
@@ -4,19 +4,9 @@ vars:
OWNER: anchore OWNER: anchore
PROJECT: syft PROJECT: syft
CACHE_IMAGE: ghcr.io/{{ .OWNER }}/{{ .PROJECT }}/test-fixture-cache:latest
# static file dirs # static file dirs
TOOL_DIR: .tool TOOL_DIR: .tool
TMP_DIR: .tmp TMP_DIR: .tmp
ORAS_CACHE: "{{ .TMP_DIR }}/oras-cache"
CACHE_PATHS_FILE: "{{ .TMP_DIR }}/cache_paths.json"
LAST_CACHE_PULL_FILE: "{{ .TMP_DIR }}/last_cache_paths.json"
# TOOLS
ORAS: "{{ .TOOL_DIR }}/oras"
YQ: "{{ .TOOL_DIR }}/yq"
TASK: "{{ .TOOL_DIR }}/task"
# used for changelog generation # used for changelog generation
CHANGELOG: CHANGELOG.md CHANGELOG: CHANGELOG.md
@@ -27,12 +17,10 @@ vars:
sh: uname -s | tr '[:upper:]' '[:lower:]' sh: uname -s | tr '[:upper:]' '[:lower:]'
ARCH: ARCH:
sh: | sh: |
[ "$(uname -m)" = "x86_64" ] && echo "amd64_v1" || { [ "$(uname -m)" = "aarch64" ] && echo "arm64_v8.0" || [ "$(uname -m)" = "arm64" ] && echo "arm64_v8.0" || echo $(uname -m); } [ "$(uname -m)" = "x86_64" ] && echo "amd64_v1" || echo $(uname -m)
PROJECT_ROOT: PROJECT_ROOT:
sh: echo $PWD sh: echo $PWD
# note: the snapshot dir must be a relative path starting with ./ # 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_DIR: ./snapshot
SNAPSHOT_BIN: "{{ .PROJECT_ROOT }}/{{ .SNAPSHOT_DIR }}/{{ .OS }}-build_{{ .OS }}_{{ .ARCH }}/{{ .PROJECT }}" 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" SNAPSHOT_CMD: "{{ .TOOL_DIR }}/goreleaser release --config {{ .TMP_DIR }}/goreleaser.yaml --clean --snapshot --skip=publish --skip=sign"
@@ -45,9 +33,6 @@ vars:
COMPARE_DIR: ./test/compare COMPARE_DIR: ./test/compare
COMPARE_TEST_IMAGE: centos:8.2.2004 COMPARE_TEST_IMAGE: centos:8.2.2004
env:
GNUMAKEFLAGS: '--no-print-directory'
tasks: tasks:
## High-level tasks ################################# ## High-level tasks #################################
@@ -77,10 +62,9 @@ tasks:
- task: unit - task: unit
- task: integration - task: integration
- task: validate-cyclonedx-schema - task: validate-cyclonedx-schema
- task: benchmark
- task: test-utils - task: test-utils
- task: snapshot
- task: cli - task: cli
- task: check-docker-cache
## Bootstrap tasks ################################# ## Bootstrap tasks #################################
@@ -91,7 +75,7 @@ tasks:
- "{{ .TOOL_DIR }}/binny" - "{{ .TOOL_DIR }}/binny"
status: status:
- "test -f {{ .TOOL_DIR }}/binny" - "test -f {{ .TOOL_DIR }}/binny"
cmd: "curl -sSfL https://get.anchore.io/binny | sh -s -- -b .tool" cmd: "curl -sSfL https://raw.githubusercontent.com/anchore/binny/main/install.sh | sh -s -- -b .tool"
silent: true silent: true
tools: tools:
@@ -193,14 +177,6 @@ tasks:
## Testing tasks ################################# ## Testing tasks #################################
update-format-golden-files:
desc: "Update golden (i.e. snapshot) files used by unit tests"
cmds:
- go test ./syft/format/spdxjson -update-spdx-json
- go test ./syft/format/spdxtagvalue -update-spdx-tv
- go test ./syft/format/cyclonedxxml -update-cyclonedx-xml
- go test ./syft/format/cyclonedxjson -update-cyclonedx-json
- go test ./syft/format/syftjson -update-json
unit: unit:
desc: Run unit tests desc: Run unit tests
@@ -209,7 +185,7 @@ tasks:
- fixtures - fixtures
vars: vars:
TEST_PKGS: TEST_PKGS:
sh: "go list ./... | grep -v {{ .OWNER }}/{{ .PROJECT }}/test | grep -v {{ .OWNER }}/{{ .PROJECT }}/cmd/syft/internal/test | tr '\n' ' '" sh: "go list ./... | grep -v {{ .OWNER }}/{{ .PROJECT }}/test | tr '\n' ' '"
# unit test coverage threshold (in % coverage) # unit test coverage threshold (in % coverage)
COVERAGE_THRESHOLD: 62 COVERAGE_THRESHOLD: 62
@@ -221,23 +197,30 @@ tasks:
integration: integration:
desc: Run integration tests desc: Run integration tests
cmds: cmds:
- "go test -v ./cmd/syft/internal/test/integration" - "go test -v ./test/integration"
# exercise most of the CLI with the data race detector # 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 alpine:latest"
- "go run -race cmd/syft/main.go anchore/test_images:grype-quality-dotnet-69f15d2"
validate-cyclonedx-schema: validate-cyclonedx-schema:
desc: Validate that Syft produces valid CycloneDX documents desc: Run integration tests
cmds: cmds:
- "cd schema/cyclonedx && make" - "cd schema/cyclonedx && make"
cli: cli:
desc: Run CLI tests desc: Run CLI tests
deps: [tools] # note: we don't want to regenerate the snapshot unless we have to. In CI it's probable
# that the cache being restored with the correct binary will be rebuilt since the timestamps
# and local checksums will not line up.
deps: [tools, snapshot]
sources:
- "{{ .SNAPSHOT_BIN }}"
- ./test/cli/**
- ./**/*.go
cmds: cmds:
- cmd: "echo 'testing binary: {{ .SNAPSHOT_BIN }}'" - cmd: "echo 'testing binary: {{ .SNAPSHOT_BIN }}'"
silent: true silent: true
- cmd: "test -f {{ .SNAPSHOT_BIN }} || (find {{ .SNAPSHOT_DIR }} && echo '\nno snapshot found for {{ .SNAPSHOT_BIN }}' && false)"
- cmd: "test -f {{ .SNAPSHOT_BIN }} || (find {{ .SNAPSHOT_DIR }} && echo '\nno snapshot found' && false)"
silent: true silent: true
- "go test -count=1 -timeout=15m -v ./test/cli" - "go test -count=1 -timeout=15m -v ./test/cli"
@@ -246,195 +229,83 @@ tasks:
test-utils: test-utils:
desc: Run tests for pipeline utils desc: Run tests for pipeline utils
sources:
- .github/scripts/labeler*.py
cmds: cmds:
- cmd: .github/scripts/labeler_test.py - cmd: python .github/scripts/labeler_test.py
## Benchmark test targets #################################
benchmark:
deps: [tmpdir]
sources:
- ./**/*.go
generates:
- "{{ .TMP_DIR }}/benchmark-main.txt"
cmds:
- "go test -p 1 -run=^Benchmark -bench=. -count=7 -benchmem ./... | tee {{ .TMP_DIR }}/benchmark-{{ .VERSION }}.txt"
- |
bash -c "(test -s {{ .TMP_DIR }}/benchmark-main.txt && \
{{ .TOOL_DIR }}/benchstat {{ .TMP_DIR }}/benchmark-main.txt {{ .TMP_DIR }}/benchmark-{{ .VERSION }}.txt || \
{{ .TOOL_DIR }}/benchstat {{ .TMP_DIR }}/benchmark-{{ .VERSION }}.txt) \
| tee {{ .TMP_DIR }}/benchstat.txt"
show-benchstat:
deps: [benchmark, tmpdir]
sources:
- "{{ .TMP_DIR }}/benchstat.txt"
cmds:
- cmd: "cat {{ .TMP_DIR }}/benchstat.txt"
silent: true
## Test-fixture-related targets ################################# ## Test-fixture-related targets #################################
fingerprints: fingerprints:
desc: Generate fingerprints for all non-docker test fixture desc: Generate test fixture fingerprints
silent: true
# this will look for `test-fixtures/Makefile` and invoke the `fingerprint` target to calculate all cache input fingerprint files
generates: generates:
- '**/test-fixtures/**/*.fingerprint' - test/integration/test-fixtures/cache.fingerprint
- syft/pkg/cataloger/binary/test-fixtures/cache.fingerprint
- syft/pkg/cataloger/java/test-fixtures/java-builds/cache.fingerprint
- syft/pkg/cataloger/golang/test-fixtures/archs/binaries.fingerprint
- syft/pkg/cataloger/redhat/test-fixtures/rpms.fingerprint
- syft/pkg/cataloger/kernel/test-fixtures/cache.fingerprint
- test/install/cache.fingerprint - test/install/cache.fingerprint
- test/cli/test-fixtures/cache.fingerprint
cmds: cmds:
- | # for IMAGE integration test fixtures
BOLD='\033[1m' - "cd test/integration/test-fixtures && make cache.fingerprint"
YELLOW='\033[0;33m' # for BINARY test fixtures
RESET='\033[0m' - "cd syft/pkg/cataloger/binary/test-fixtures && make cache.fingerprint"
# for JAVA BUILD test fixtures
- "cd syft/pkg/cataloger/java/test-fixtures/java-builds && make cache.fingerprint"
# for GO BINARY test fixtures
- "cd syft/pkg/cataloger/golang/test-fixtures/archs && make binaries.fingerprint"
# for RPM test fixtures
- "cd syft/pkg/cataloger/redhat/test-fixtures && make rpms.fingerprint"
# for Kernel test fixtures
- "cd syft/pkg/cataloger/kernel/test-fixtures && make cache.fingerprint"
# for INSTALL integration test fixtures
- "cd test/install && make cache.fingerprint"
# for CLI test fixtures
- "cd test/cli/test-fixtures && make cache.fingerprint"
echo -e "${YELLOW}creating fingerprint files for non-docker fixtures...${RESET}" fixtures:
for dir in $(find . -type d -name 'test-fixtures'); do desc: Generate test fixtures
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
if [ "$DOWNLOAD_TEST_FIXTURE_CACHE" = "false" ]; then
exit 0
fi
.github/scripts/find_cache_paths.py {{ .CACHE_PATHS_FILE }} > /dev/null
refresh-fixtures:
desc: Clear and fetch all test fixture cache
aliases:
- fixtures
silent: true
deps:
- tools
cmds: cmds:
- | - "cd syft/pkg/cataloger/java/test-fixtures/java-builds && make"
BOLD='\033[1m' - "cd syft/pkg/cataloger/redhat/test-fixtures && make"
PURPLE='\033[0;35m' - "cd syft/pkg/cataloger/binary/test-fixtures && make"
RESET='\033[0m'
# if DOWNLOAD_TEST_FIXTURE_CACHE is set to 'false', then skip the cache download and always build
if [ "$DOWNLOAD_TEST_FIXTURE_CACHE" = "false" ]; then
echo -e "${BOLD}${PURPLE}skipping cache download, rebuilding cache...${RESET}"
{{ .TASK }} build-fixtures
exit 0
fi
LATEST_FINGERPRINT=$(docker manifest inspect {{ .CACHE_IMAGE }} | {{ .YQ }} -r '.annotations.fingerprint')
echo "latest cache: $LATEST_FINGERPRINT"
if [ -f {{ .LAST_CACHE_PULL_FILE }} ]; then
LAST_PULL_FINGERPRINT=$(cat {{ .LAST_CACHE_PULL_FILE }} | {{ .YQ }} -r '.digest')
else
echo -e "${BOLD}${PURPLE}empty cache, downloading cache...${RESET}"
{{ .TASK }} download-test-fixture-cache
exit 0
fi
{{ .TASK }} fingerprints
WANT_FINGERPRINT=$(cat {{ .CACHE_PATHS_FILE }} | {{ .YQ }} -r '.digest')
echo "desired cache: $WANT_FINGERPRINT"
echo "last pulled cache: $LAST_PULL_FINGERPRINT"
# if we already have the latest cache, skip the refresh
if [ "$LAST_PULL_FINGERPRINT" = "$WANT_FINGERPRINT" ]; then
echo -e "${BOLD}${PURPLE}already have the latest cache (skipping cache download)${RESET}"
exit 0
fi
# at this point we only refresh the cache if we want the same cache that is currently available.
# we don't by default refresh the cache if the cache if it is simply different from what we have,
# because we may be working on a code change that doesn't require a cache refresh (but could trigger one,
# which would be annoying to deal with in a development workflow).
if [ "$LATEST_FINGERPRINT" = "$WANT_FINGERPRINT" ]; then
echo -e "${BOLD}${PURPLE}found newer cache! downloading cache...${RESET}"
{{ .TASK }} download-test-fixture-cache
else
echo -e "${BOLD}${PURPLE}found different cache, but isn't clear if it's newer (skipping cache download and manually building)${RESET}"
{{ .YQ }} eval '.paths[] | "\(.digest) \(.path)"' {{ .LAST_CACHE_PULL_FILE }} > .tmp/last_cache_lines
{{ .YQ }} eval '.paths[] | "\(.digest) \(.path)"' {{ .CACHE_PATHS_FILE }} > .tmp/cache_lines
diff .tmp/last_cache_lines .tmp/cache_lines || true
echo -e "${BOLD}${PURPLE}diff with more context...${RESET}"
diff -U10000 {{ .LAST_CACHE_PULL_FILE }} {{ .CACHE_PATHS_FILE }} || true
echo -e "${BOLD}${PURPLE}detected changes to input material, manually building fixtures...${RESET}"
{{ .TASK }} build-fixtures
fi
build-fixtures:
desc: Generate all non-docker test fixtures
silent: true
# this will look for `test-fixtures/Makefile` and invoke the `fixtures` target to generate any and all test fixtures
cmds:
- |
BOLD='\033[1m'
YELLOW='\033[0;33m'
RESET='\033[0m'
# Use a for loop with command substitution to avoid subshell issues
for dir in $(find . -type d -name 'test-fixtures'); do
if [ -f "$dir/Makefile" ]; then
echo -e "${YELLOW}${BOLD}generating fixtures in $dir${RESET}"
(make -C "$dir" fixtures)
fi
done
echo -e "${BOLD}generated all fixtures${RESET}"
download-test-fixture-cache:
desc: Download test fixture cache from ghcr.io
deps: [tools, clean-cache]
vars:
CACHE_DIGEST:
sh: docker manifest inspect {{ .CACHE_IMAGE }} | {{ .YQ }} -r '.annotations.fingerprint'
cmds:
- silent: true
cmd: |
# if oras cache is > 4 GB, delete it
if [ -d {{ .ORAS_CACHE }} ]; then
total_size=$(du -c {{ .ORAS_CACHE }} | grep total | awk '{print $1}')
if [ "$total_size" -gt 4194304 ]; then
echo 'deleting oras cache'
rm -rf {{ .ORAS_CACHE }}
fi
fi
- "ORAS_CACHE={{ .ORAS_CACHE }} {{ .ORAS }} pull {{ .CACHE_IMAGE }}"
- "cp {{ .CACHE_PATHS_FILE }} {{ .LAST_CACHE_PULL_FILE }}"
upload-test-fixture-cache:
desc: Upload the test fixture cache to ghcr.io
deps: [tools, fingerprints]
silent: true
cmd: |
set -eu
oras_command="{{ .ORAS }} push {{ .CACHE_IMAGE }}"
paths=$(cat {{ .CACHE_PATHS_FILE }} | {{ .YQ }} -r '.paths[].path')
for path in $paths; do
oras_command+=" $path"
done
oras_command+=" {{ .CACHE_PATHS_FILE }}"
oras_command+=" --annotation org.opencontainers.image.source=https://github.com/{{ .OWNER }}/{{ .PROJECT }}"
oras_command+=" --annotation fingerprint=$(cat {{ .CACHE_PATHS_FILE }} | {{ .YQ }} -r '.digest')"
echo "Executing: $oras_command"
eval $oras_command
show-test-image-cache: show-test-image-cache:
silent: true silent: true
cmds: cmds:
- "echo 'Docker daemon cache:'" - "echo '\nDocker daemon cache:'"
- "docker images --format '{{`{{.ID}}`}} {{`{{.Repository}}`}}:{{`{{.Tag}}`}}' | grep stereoscope-fixture- | sort" - "docker images --format '{{`{{.ID}}`}} {{`{{.Repository}}`}}:{{`{{.Tag}}`}}' | grep stereoscope-fixture- | sort"
- "echo '\nTar cache:'" - "echo '\nTar cache:'"
- 'find . -type f -wholename "**/test-fixtures/cache/stereoscope-fixture-*.tar" | sort' - 'find . -type f -wholename "**/test-fixtures/snapshot/*" | sort'
check-docker-cache:
desc: Ensure docker caches aren't using too much disk space
silent: true
cmd: |
total_size=$(find . | grep cache | grep tar | xargs du -c | grep total | awk '{print $1}')
find . | grep cache | grep tar | xargs du
echo "total $total_size KB"
if [ "$total_size" -gt 5242880 ]; then
echo 'docker cache is larger than 5GB'
exit 1
fi
## install.sh testing targets ################################# ## install.sh testing targets #################################
@@ -512,7 +383,7 @@ tasks:
generate-cpe-dictionary-index: generate-cpe-dictionary-index:
desc: Generate the CPE index based off of the latest available CPE dictionary desc: Generate the CPE index based off of the latest available CPE dictionary
dir: "syft/pkg/cataloger/internal/cpegenerate/dictionary" dir: "syft/pkg/cataloger/common/cpe/dictionary"
cmds: cmds:
- "go generate" - "go generate"
@@ -552,18 +423,6 @@ tasks:
- "{{ .SNAPSHOT_CMD }}" - "{{ .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: changelog:
desc: Generate a changelog desc: Generate a changelog
deps: [tools] deps: [tools]
@@ -572,7 +431,7 @@ tasks:
- "{{ .NEXT_VERSION }}" - "{{ .NEXT_VERSION }}"
cmds: cmds:
- "{{ .TOOL_DIR }}/chronicle -vv -n --version-file {{ .NEXT_VERSION }} > {{ .CHANGELOG }}" - "{{ .TOOL_DIR }}/chronicle -vv -n --version-file {{ .NEXT_VERSION }} > {{ .CHANGELOG }}"
- "{{ .TOOL_DIR }}/glow -w 0 {{ .CHANGELOG }}" - "{{ .TOOL_DIR }}/glow {{ .CHANGELOG }}"
## Release targets ################################# ## Release targets #################################
@@ -613,31 +472,8 @@ tasks:
- "rm -rf {{ .SNAPSHOT_DIR }}" - "rm -rf {{ .SNAPSHOT_DIR }}"
- "rm -rf {{ .TMP_DIR }}/goreleaser.yaml" - "rm -rf {{ .TMP_DIR }}/goreleaser.yaml"
clean-docker-cache:
desc: Remove all docker cache tars and images from the daemon
cmds:
- find . -type d -wholename "**/test-fixtures/cache" | xargs rm -rf
- docker images --format '{{`{{.ID}}`}} {{`{{.Repository}}`}}' | grep stereoscope-fixture- | awk '{print $1}' | uniq | xargs -r docker rmi --force
clean-oras-cache:
desc: Remove all cache for oras commands
cmd: rm -rf {{ .ORAS_CACHE }}
clean-cache: clean-cache:
desc: Remove all image docker tar cache, images from the docker daemon, and ephemeral test fixtures desc: Remove all docker cache and local image tar cache
cmds: cmds:
- task: clean-docker-cache - 'find . -type f -wholename "**/test-fixtures/cache/stereoscope-fixture-*.tar" -delete'
- | - "docker images --format '{{`{{.ID}}`}} {{`{{.Repository}}`}}' | grep stereoscope-fixture- | awk '{print $$1}' | uniq | xargs -r docker rmi --force"
BOLD='\033[1m'
YELLOW='\033[0;33m'
RESET='\033[0m'
# Use a for loop with command substitution to avoid subshell issues
for dir in $(find . -type d -name 'test-fixtures'); do
if [ -f "$dir/Makefile" ]; then
echo -e "${YELLOW}${BOLD}deleting ephemeral test fixtures in $dir${RESET}"
(make -C "$dir" clean)
fi
done
echo -e "${BOLD}Deleted all ephemeral test fixtures${RESET}"
- rm -f {{ .LAST_CACHE_PULL_FILE }} {{ .CACHE_PATHS_FILE }}
+46 -11
View File
@@ -8,9 +8,13 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/anchore/clio" "github.com/anchore/clio"
"github.com/anchore/syft/cmd/syft/internal" "github.com/anchore/stereoscope"
"github.com/anchore/syft/cmd/syft/internal/commands" "github.com/anchore/syft/cmd/syft/cli/commands"
constants "github.com/anchore/syft/internal" handler "github.com/anchore/syft/cmd/syft/cli/ui"
"github.com/anchore/syft/cmd/syft/internal/ui"
"github.com/anchore/syft/internal/bus"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/redact"
) )
// Application constructs the `syft packages` command and aliases the root command to `syft packages`. // Application constructs the `syft packages` command and aliases the root command to `syft packages`.
@@ -30,7 +34,44 @@ func Command(id clio.Identification) *cobra.Command {
} }
func create(id clio.Identification, out io.Writer) (clio.Application, *cobra.Command) { func create(id clio.Identification, out io.Writer) (clio.Application, *cobra.Command) {
clioCfg := internal.AppClioSetupConfig(id, out) clioCfg := clio.NewSetupConfig(id).
WithGlobalConfigFlag(). // add persistent -c <path> for reading an application config from
WithGlobalLoggingFlags(). // add persistent -v and -q flags tied to the logging config
WithConfigInRootHelp(). // --help on the root command renders the full application config in the help text
WithUIConstructor(
// select a UI based on the logging configuration and state of stdin (if stdin is a tty)
func(cfg clio.Config) ([]clio.UI, error) {
noUI := ui.None(out, cfg.Log.Quiet)
if !cfg.Log.AllowUI(os.Stdin) || cfg.Log.Quiet {
return []clio.UI{noUI}, nil
}
return []clio.UI{
ui.New(out, cfg.Log.Quiet,
handler.New(handler.DefaultHandlerConfig()),
),
noUI,
}, nil
},
).
WithInitializers(
func(state *clio.State) error {
// clio is setting up and providing the bus, redact store, and logger to the application. Once loaded,
// we can hoist them into the internal packages for global use.
stereoscope.SetBus(state.Bus)
bus.Set(state.Bus)
redact.Set(state.RedactStore)
log.Set(state.Logger)
stereoscope.SetLogger(state.Logger)
return nil
},
).
WithPostRuns(func(state *clio.State, err error) {
stereoscope.Cleanup()
})
app := clio.New(*clioCfg) app := clio.New(*clioCfg)
@@ -45,11 +86,9 @@ func create(id clio.Identification, out io.Writer) (clio.Application, *cobra.Com
rootCmd.AddCommand( rootCmd.AddCommand(
scanCmd, scanCmd,
commands.Packages(app, scanCmd), // this is currently an alias for the scan command commands.Packages(app, scanCmd), // this is currently an alias for the scan command
commands.Cataloger(app),
commands.Attest(app), commands.Attest(app),
commands.Convert(app), commands.Convert(app),
clio.VersionCommand(id, schemaVersion), clio.VersionCommand(id),
clio.ConfigCommand(app, nil),
cranecmd.NewCmdAuthLogin(id.Name), // syft login uses the same command as crane cranecmd.NewCmdAuthLogin(id.Name), // syft login uses the same command as crane
) )
@@ -60,7 +99,3 @@ func create(id clio.Identification, out io.Writer) (clio.Application, *cobra.Com
return app, rootCmd return app, rootCmd
} }
func schemaVersion() (string, any) {
return "SchemaVersion", constants.JSONSchemaVersion
}
@@ -13,8 +13,7 @@ import (
"github.com/wagoodman/go-progress" "github.com/wagoodman/go-progress"
"github.com/anchore/clio" "github.com/anchore/clio"
"github.com/anchore/stereoscope" "github.com/anchore/syft/cmd/syft/cli/options"
"github.com/anchore/syft/cmd/syft/internal/options"
"github.com/anchore/syft/cmd/syft/internal/ui" "github.com/anchore/syft/cmd/syft/internal/ui"
"github.com/anchore/syft/internal" "github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/bus" "github.com/anchore/syft/internal/bus"
@@ -27,10 +26,11 @@ import (
"github.com/anchore/syft/syft/format/spdxtagvalue" "github.com/anchore/syft/syft/format/spdxtagvalue"
"github.com/anchore/syft/syft/format/syftjson" "github.com/anchore/syft/syft/format/syftjson"
"github.com/anchore/syft/syft/sbom" "github.com/anchore/syft/syft/sbom"
"github.com/anchore/syft/syft/source"
) )
const ( const (
attestExample = ` {{.appName}} {{.command}} --output [FORMAT] alpine:latest defaults to using images from a Docker daemon. If Docker is not present, the image is pulled directly from the registry attestExample = ` {{.appName}} {{.command}} --output [FORMAT] alpine:latest defaults to using images from a Docker daemon. If Docker is not present, the image is pulled directly from the registry
` `
attestSchemeHelp = "\n " + schemeHelpHeader + "\n" + imageSchemeHelp attestSchemeHelp = "\n " + schemeHelpHeader + "\n" + imageSchemeHelp
attestHelp = attestExample + attestSchemeHelp attestHelp = attestExample + attestSchemeHelp
@@ -42,8 +42,7 @@ type attestOptions struct {
options.Output `yaml:",inline" mapstructure:",squash"` options.Output `yaml:",inline" mapstructure:",squash"`
options.UpdateCheck `yaml:",inline" mapstructure:",squash"` options.UpdateCheck `yaml:",inline" mapstructure:",squash"`
options.Catalog `yaml:",inline" mapstructure:",squash"` options.Catalog `yaml:",inline" mapstructure:",squash"`
Attest options.Attest `yaml:"attest" mapstructure:"attest"` options.Attest `yaml:",inline" mapstructure:",squash"`
Cache options.Cache `json:"-" yaml:"cache" mapstructure:"cache"`
} }
func Attest(app clio.Application) *cobra.Command { func Attest(app clio.Application) *cobra.Command {
@@ -52,7 +51,7 @@ func Attest(app clio.Application) *cobra.Command {
opts := defaultAttestOptions() opts := defaultAttestOptions()
// template format explicitly not allowed // template format explicitly not allowed
opts.Template.Enabled = false opts.Format.Template.Enabled = false
return app.SetupCommand(&cobra.Command{ return app.SetupCommand(&cobra.Command{
Use: "attest --output [FORMAT] <IMAGE>", Use: "attest --output [FORMAT] <IMAGE>",
@@ -78,7 +77,6 @@ func defaultAttestOptions() attestOptions {
Output: defaultAttestOutputOptions(), Output: defaultAttestOutputOptions(),
UpdateCheck: options.DefaultUpdateCheck(), UpdateCheck: options.DefaultUpdateCheck(),
Catalog: options.DefaultCatalog(), Catalog: options.DefaultCatalog(),
Cache: options.DefaultCache(),
} }
} }
@@ -93,13 +91,14 @@ func defaultAttestOutputOptions() options.Output {
string(spdxtagvalue.ID), string(spdxtagvalue.ID),
}, },
Outputs: []string{syftjson.ID.String()}, Outputs: []string{syftjson.ID.String()},
OutputFile: options.OutputFile{ //nolint:staticcheck OutputFile: options.OutputFile{ // nolint:staticcheck
Enabled: false, // explicitly not allowed Enabled: false, // explicitly not allowed
}, },
Format: options.DefaultFormat(), Format: options.DefaultFormat(),
} }
} }
//nolint:funlen
func runAttest(ctx context.Context, id clio.Identification, opts *attestOptions, userInput string) error { func runAttest(ctx context.Context, id clio.Identification, opts *attestOptions, userInput string) error {
// TODO: what other validation here besides binary name? // TODO: what other validation here besides binary name?
if !commandExists(cosignBinName) { if !commandExists(cosignBinName) {
@@ -136,7 +135,7 @@ func writeSBOMToFormattedFile(s *sbom.SBOM, sbomFile io.Writer, opts *attestOpti
return fmt.Errorf("no output file provided") return fmt.Errorf("no output file provided")
} }
encs, err := opts.Encoders() encs, err := opts.Format.Encoders()
if err != nil { if err != nil {
return fmt.Errorf("unable to create encoders: %w", err) return fmt.Errorf("unable to create encoders: %w", err)
} }
@@ -233,7 +232,7 @@ func attestCommand(sbomFilepath string, opts *attestOptions, userInput string) (
} }
func predicateType(outputName string) string { func predicateType(outputName string) string {
// select the Cosign predicate type based on defined output type // Select Cosign predicate type based on defined output type
// As orientation, check: https://github.com/sigstore/cosign/blob/main/pkg/cosign/attestation/attestation.go // As orientation, check: https://github.com/sigstore/cosign/blob/main/pkg/cosign/attestation/attestation.go
switch strings.ToLower(outputName) { switch strings.ToLower(outputName) {
case "cyclonedx-json": case "cyclonedx-json":
@@ -248,11 +247,7 @@ func predicateType(outputName string) string {
} }
func generateSBOMForAttestation(ctx context.Context, id clio.Identification, opts *options.Catalog, userInput string) (*sbom.SBOM, error) { func generateSBOMForAttestation(ctx context.Context, id clio.Identification, opts *options.Catalog, userInput string) (*sbom.SBOM, error) {
if len(opts.From) > 1 || (len(opts.From) == 1 && opts.From[0] != stereoscope.RegistryTag) { src, err := getSource(opts, userInput, onlyContainerImages)
return nil, fmt.Errorf("attest requires use of an OCI registry directly, one or more of the specified sources is unsupported: %v", opts.From)
}
src, err := getSource(ctx, opts, userInput, stereoscope.RegistryTag)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -278,6 +273,13 @@ func generateSBOMForAttestation(ctx context.Context, id clio.Identification, opt
return s, nil return s, nil
} }
func onlyContainerImages(d *source.Detection) error {
if !d.IsContainerImage() {
return fmt.Errorf("attestations are only supported for oci images at this time")
}
return nil
}
func commandExists(cmd string) bool { func commandExists(cmd string) bool {
_, err := exec.LookPath(cmd) _, err := exec.LookPath(cmd)
return err == nil return err == nil
@@ -4,22 +4,17 @@ import (
"bytes" "bytes"
"context" "context"
"fmt" "fmt"
"io"
"os/exec" "os/exec"
"regexp" "regexp"
"strings" "strings"
"testing" "testing"
"github.com/google/go-cmp/cmp"
"github.com/scylladb/go-set/strset" "github.com/scylladb/go-set/strset"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/anchore/clio" "github.com/anchore/clio"
"github.com/anchore/clio/cliotestutils" "github.com/anchore/syft/cmd/syft/cli/options"
"github.com/anchore/syft/cmd/syft/internal"
"github.com/anchore/syft/cmd/syft/internal/options"
"github.com/anchore/syft/syft/sbom" "github.com/anchore/syft/syft/sbom"
"github.com/anchore/syft/syft/source" "github.com/anchore/syft/syft/source"
) )
@@ -272,60 +267,3 @@ func Test_buildSBOMForAttestation(t *testing.T) {
}) })
} }
} }
func Test_attestCLIWiring(t *testing.T) {
id := clio.Identification{
Name: "syft",
Version: "testing",
}
cfg := internal.AppClioSetupConfig(id, io.Discard)
tests := []struct {
name string
assertionFunc func(*testing.T, *cobra.Command, []string, ...any)
wantOpts attestOptions
args []string
env map[string]string
}{
{
name: "key flag is accepted",
args: []string{"some-image:some-tag", "--key", "some-cosign-key.key"},
assertionFunc: hasAttestOpts(options.Attest{Key: "some-cosign-key.key"}),
},
{
name: "key password is read from env",
args: []string{"some-image:some-tag", "--key", "cosign.key"},
env: map[string]string{
"SYFT_ATTEST_PASSWORD": "some-password",
},
assertionFunc: hasAttestOpts(options.Attest{
Key: "cosign.key",
Password: "some-password",
}),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.env != nil {
for k, v := range tt.env {
t.Setenv(k, v)
}
}
app := cliotestutils.NewApplication(t, cfg, tt.assertionFunc)
cmd := Attest(app)
cmd.SetArgs(tt.args)
err := cmd.Execute()
assert.NoError(t, err)
})
}
}
func hasAttestOpts(wantOpts options.Attest) cliotestutils.AssertionFunc {
return func(t *testing.T, _ *cobra.Command, _ []string, cfgs ...any) {
assert.Equal(t, len(cfgs), 1)
attestOpts, ok := cfgs[0].(*attestOptions)
require.True(t, ok)
if d := cmp.Diff(wantOpts, attestOpts.Attest); d != "" {
t.Errorf("mismatched attest options (-want +got):\n%s", d)
}
}
}
@@ -8,7 +8,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/anchore/clio" "github.com/anchore/clio"
"github.com/anchore/syft/cmd/syft/internal/options" "github.com/anchore/syft/cmd/syft/cli/options"
"github.com/anchore/syft/cmd/syft/internal/ui" "github.com/anchore/syft/cmd/syft/internal/ui"
"github.com/anchore/syft/internal" "github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/log" "github.com/anchore/syft/internal/log"
@@ -17,7 +17,7 @@ import (
const ( const (
convertExample = ` {{.appName}} {{.command}} img.syft.json -o spdx-json convert a syft SBOM to spdx-json, output goes to stdout 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}} 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 {{.appName}} {{.command}} - -o spdx-json convert an SBOM from STDIN to spdx-json
` `
) )
@@ -28,6 +28,7 @@ type ConvertOptions struct {
options.UpdateCheck `yaml:",inline" mapstructure:",squash"` options.UpdateCheck `yaml:",inline" mapstructure:",squash"`
} }
//nolint:dupl
func Convert(app clio.Application) *cobra.Command { func Convert(app clio.Application) *cobra.Command {
id := app.ID() id := app.ID()
@@ -39,14 +40,14 @@ func Convert(app clio.Application) *cobra.Command {
return app.SetupCommand(&cobra.Command{ return app.SetupCommand(&cobra.Command{
Use: "convert [SOURCE-SBOM] -o [FORMAT]", Use: "convert [SOURCE-SBOM] -o [FORMAT]",
Short: "Convert between SBOM formats", Short: "Convert between SBOM formats",
Long: "[Experimental] Convert SBOM files to, and from, SPDX, CycloneDX and Syft's format. For more info about data loss between formats see https://github.com/anchore/syft/wiki/format-conversion", Long: "[Experimental] Convert SBOM files to, and from, SPDX, CycloneDX and Syft's format. For more info about data loss between formats see https://github.com/anchore/syft#format-conversion-experimental",
Example: internal.Tprintf(convertExample, map[string]interface{}{ Example: internal.Tprintf(convertExample, map[string]interface{}{
"appName": id.Name, "appName": id.Name,
"command": "convert", "command": "convert",
}), }),
Args: validateConvertArgs, Args: validateConvertArgs,
PreRunE: applicationUpdateCheck(id, &opts.UpdateCheck), PreRunE: applicationUpdateCheck(id, &opts.UpdateCheck),
RunE: func(_ *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
restoreStdout := ui.CaptureStdoutToTraceLog() restoreStdout := ui.CaptureStdoutToTraceLog()
defer restoreStdout() defer restoreStdout()
@@ -5,7 +5,6 @@ import (
"github.com/anchore/clio" "github.com/anchore/clio"
"github.com/anchore/syft/cmd/syft/internal/ui" "github.com/anchore/syft/cmd/syft/internal/ui"
"github.com/anchore/syft/internal"
) )
func Packages(app clio.Application, scanCmd *cobra.Command) *cobra.Command { func Packages(app clio.Application, scanCmd *cobra.Command) *cobra.Command {
@@ -14,14 +13,11 @@ func Packages(app clio.Application, scanCmd *cobra.Command) *cobra.Command {
opts := defaultScanOptions() opts := defaultScanOptions()
cmd := app.SetupCommand(&cobra.Command{ cmd := app.SetupCommand(&cobra.Command{
Use: "packages [SOURCE]", Use: "packages [SOURCE]",
Short: scanCmd.Short, Short: scanCmd.Short,
Long: scanCmd.Long, Long: scanCmd.Long,
Args: scanCmd.Args, Args: scanCmd.Args,
Example: internal.Tprintf(scanHelp, map[string]interface{}{ Example: scanCmd.Example,
"appName": id.Name,
"command": "packages",
}),
PreRunE: applicationUpdateCheck(id, &opts.UpdateCheck), PreRunE: applicationUpdateCheck(id, &opts.UpdateCheck),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
restoreStdout := ui.CaptureStdoutToTraceLog() restoreStdout := ui.CaptureStdoutToTraceLog()
@@ -10,14 +10,11 @@ import (
"github.com/hashicorp/go-multierror" "github.com/hashicorp/go-multierror"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"go.yaml.in/yaml/v3" "gopkg.in/yaml.v3"
"github.com/anchore/clio" "github.com/anchore/clio"
"github.com/anchore/fangs"
"github.com/anchore/go-collections"
"github.com/anchore/stereoscope"
"github.com/anchore/stereoscope/pkg/image" "github.com/anchore/stereoscope/pkg/image"
"github.com/anchore/syft/cmd/syft/internal/options" "github.com/anchore/syft/cmd/syft/cli/options"
"github.com/anchore/syft/cmd/syft/internal/ui" "github.com/anchore/syft/cmd/syft/internal/ui"
"github.com/anchore/syft/internal" "github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/bus" "github.com/anchore/syft/internal/bus"
@@ -27,7 +24,6 @@ import (
"github.com/anchore/syft/syft" "github.com/anchore/syft/syft"
"github.com/anchore/syft/syft/sbom" "github.com/anchore/syft/syft/sbom"
"github.com/anchore/syft/syft/source" "github.com/anchore/syft/syft/source"
"github.com/anchore/syft/syft/source/sourceproviders"
) )
const ( const (
@@ -69,7 +65,6 @@ type scanOptions struct {
options.Output `yaml:",inline" mapstructure:",squash"` options.Output `yaml:",inline" mapstructure:",squash"`
options.UpdateCheck `yaml:",inline" mapstructure:",squash"` options.UpdateCheck `yaml:",inline" mapstructure:",squash"`
options.Catalog `yaml:",inline" mapstructure:",squash"` options.Catalog `yaml:",inline" mapstructure:",squash"`
Cache options.Cache `json:"-" yaml:"cache" mapstructure:"cache"`
} }
func defaultScanOptions() *scanOptions { func defaultScanOptions() *scanOptions {
@@ -77,10 +72,10 @@ func defaultScanOptions() *scanOptions {
Output: options.DefaultOutput(), Output: options.DefaultOutput(),
UpdateCheck: options.DefaultUpdateCheck(), UpdateCheck: options.DefaultUpdateCheck(),
Catalog: options.DefaultCatalog(), Catalog: options.DefaultCatalog(),
Cache: options.DefaultCache(),
} }
} }
//nolint:dupl
func Scan(app clio.Application) *cobra.Command { func Scan(app clio.Application) *cobra.Command {
id := app.ID() id := app.ID()
@@ -110,7 +105,7 @@ func (o *scanOptions) PostLoad() error {
} }
func (o *scanOptions) validateLegacyOptionsNotUsed() error { func (o *scanOptions) validateLegacyOptionsNotUsed() error {
if len(fangs.Flatten(o.ConfigFile)) == 0 { if o.Config.ConfigFile == "" {
return nil return nil
} }
@@ -122,33 +117,32 @@ func (o *scanOptions) validateLegacyOptionsNotUsed() error {
File any `yaml:"file" json:"file" mapstructure:"file"` File any `yaml:"file" json:"file" mapstructure:"file"`
} }
for _, f := range fangs.Flatten(o.ConfigFile) { by, err := os.ReadFile(o.Config.ConfigFile)
by, err := os.ReadFile(f) if err != nil {
if err != nil { return fmt.Errorf("unable to read config file during validations %q: %w", o.Config.ConfigFile, err)
return fmt.Errorf("unable to read config file during validations %q: %w", f, err)
}
var legacy legacyConfig
if err := yaml.Unmarshal(by, &legacy); err != nil {
return fmt.Errorf("unable to parse config file during validations %q: %w", f, err)
}
if legacy.DefaultImagePullSource != nil {
return fmt.Errorf("the config file option 'default-image-pull-source' has been removed, please use 'source.image.default-pull-source' instead")
}
if legacy.ExcludeBinaryOverlapByOwnership != nil {
return fmt.Errorf("the config file option 'exclude-binary-overlap-by-ownership' has been removed, please use 'package.exclude-binary-overlap-by-ownership' instead")
}
if legacy.BasePath != nil {
return fmt.Errorf("the config file option 'base-path' has been removed, please use 'source.base-path' instead")
}
if legacy.File != nil && reflect.TypeOf(legacy.File).Kind() == reflect.String {
return fmt.Errorf("the config file option 'file' has been removed, please use 'outputs' instead")
}
} }
var legacy legacyConfig
if err := yaml.Unmarshal(by, &legacy); err != nil {
return fmt.Errorf("unable to parse config file during validations %q: %w", o.Config.ConfigFile, err)
}
if legacy.DefaultImagePullSource != nil {
return fmt.Errorf("the config file option 'default-image-pull-source' has been removed, please use 'source.image.default-pull-source' instead")
}
if legacy.ExcludeBinaryOverlapByOwnership != nil {
return fmt.Errorf("the config file option 'exclude-binary-overlap-by-ownership' has been removed, please use 'relationships.exclude-binary-packages-with-file-ownership-overlap' instead")
}
if legacy.BasePath != nil {
return fmt.Errorf("the config file option 'base-path' has been removed, please use 'source.base-path' instead")
}
if legacy.File != nil && reflect.TypeOf(legacy.File).Kind() == reflect.String {
return fmt.Errorf("the config file option 'file' has been removed, please use 'outputs' instead")
}
return nil return nil
} }
@@ -156,35 +150,26 @@ func validateScanArgs(cmd *cobra.Command, args []string) error {
return validateArgs(cmd, args, "an image/directory argument is required") return validateArgs(cmd, args, "an image/directory argument is required")
} }
func validateArgs(cmd *cobra.Command, args []string, err string) error { func validateArgs(cmd *cobra.Command, args []string, error string) error {
if len(args) == 0 { if len(args) == 0 {
// in the case that no arguments are given we want to show the help text and return with a non-0 return code. // in the case that no arguments are given we want to show the help text and return with a non-0 return code.
if err := cmd.Help(); err != nil { if err := cmd.Help(); err != nil {
return fmt.Errorf("unable to display help: %w", err) return fmt.Errorf("unable to display help: %w", err)
} }
return fmt.Errorf("%v", err) return fmt.Errorf(error)
} }
return cobra.MaximumNArgs(1)(cmd, args) return cobra.MaximumNArgs(1)(cmd, args)
} }
// nolint:funlen
func runScan(ctx context.Context, id clio.Identification, opts *scanOptions, userInput string) error { func runScan(ctx context.Context, id clio.Identification, opts *scanOptions, userInput string) error {
writer, err := opts.SBOMWriter() writer, err := opts.SBOMWriter()
if err != nil { if err != nil {
return err return err
} }
sources := opts.From src, err := getSource(&opts.Catalog, userInput)
if len(sources) == 0 {
// extract a scheme if it matches any provider tag; this is a holdover for compatibility, using the --from flag is recommended
explicitSource, newUserInput := stereoscope.ExtractSchemeSource(userInput, allSourceProviderTags()...)
if explicitSource != "" {
sources = append(sources, explicitSource)
userInput = newUserInput
}
}
src, err := getSource(ctx, &opts.Catalog, userInput, sources...)
if err != nil { if err != nil {
return err return err
@@ -214,22 +199,23 @@ func runScan(ctx context.Context, id clio.Identification, opts *scanOptions, use
return nil return nil
} }
func getSource(ctx context.Context, opts *options.Catalog, userInput string, sources ...string) (source.Source, error) { func getSource(opts *options.Catalog, userInput string, filters ...func(*source.Detection) error) (source.Source, error) {
cfg := syft.DefaultGetSourceConfig(). detection, err := source.Detect(
WithRegistryOptions(opts.Registry.ToOptions()). userInput,
WithAlias(source.Alias{ source.DetectConfig{
Name: opts.Source.Name, DefaultImageSource: opts.Source.Image.DefaultPullSource,
Version: opts.Source.Version, },
Supplier: opts.Source.Supplier, )
}). if err != nil {
WithExcludeConfig(source.ExcludeConfig{ return nil, fmt.Errorf("could not deteremine source: %w", err)
Paths: opts.Exclusions, }
}).
WithBasePath(opts.Source.BasePath). for _, filter := range filters {
WithSources(sources...). if err := filter(detection); err != nil {
WithDefaultImagePullSource(opts.Source.Image.DefaultPullSource) return nil, err
}
}
var err error
var platform *image.Platform var platform *image.Platform
if opts.Platform != "" { if opts.Platform != "" {
@@ -237,20 +223,34 @@ func getSource(ctx context.Context, opts *options.Catalog, userInput string, sou
if err != nil { if err != nil {
return nil, fmt.Errorf("invalid platform: %w", err) return nil, fmt.Errorf("invalid platform: %w", err)
} }
cfg = cfg.WithPlatform(platform)
} }
if opts.Source.File.Digests != nil { hashers, err := file.Hashers(opts.Source.File.Digests...)
hashers, err := file.Hashers(opts.Source.File.Digests...)
if err != nil {
return nil, fmt.Errorf("invalid hash algorithm: %w", err)
}
cfg = cfg.WithDigestAlgorithms(hashers...)
}
src, err := syft.GetSource(ctx, userInput, cfg)
if err != nil { if err != nil {
return nil, fmt.Errorf("could not determine source: %w", err) return nil, fmt.Errorf("invalid hash algorithm: %w", err)
}
src, err := detection.NewSource(
source.DetectionSourceConfig{
Alias: source.Alias{
Name: opts.Source.Name,
Version: opts.Source.Version,
},
RegistryOptions: opts.Registry.ToOptions(),
Platform: platform,
Exclude: source.ExcludeConfig{
Paths: opts.Exclusions,
},
DigestAlgorithms: hashers,
BasePath: opts.Source.BasePath,
},
)
if err != nil {
if userInput == "power-user" {
bus.Notify("Note: the 'power-user' command has been removed.")
}
return nil, fmt.Errorf("failed to construct source from user input %q: %w", userInput, err)
} }
return src, nil return src, nil
@@ -398,13 +398,13 @@ func getExplanation(expErr task.ErrInvalidExpression) string {
if errors.Is(err, task.ErrNamesNotAllowed) { if errors.Is(err, task.ErrNamesNotAllowed) {
if expErr.Operation == task.SubSelectOperation { if expErr.Operation == task.SubSelectOperation {
return "However, " + err.Error() + ".\nIt seems like you are intending to add a cataloger in addition to the default set." return "However, " + err.Error() + ".\nIt seems like you are intending to add a cataloger in addition to the default set." // nolint:goconst
} }
return "However, " + err.Error() + "." return "However, " + err.Error() + "." // nolint:goconst
} }
if errors.Is(err, task.ErrTagsNotAllowed) { if errors.Is(err, task.ErrTagsNotAllowed) {
return "However, " + err.Error() + ".\nAdding groups of catalogers may result in surprising behavior (create inaccurate SBOMs)." return "However, " + err.Error() + ".\nAdding groups of catalogers may result in surprising behavior (create inaccurate SBOMs)." // nolint:goconst
} }
if errors.Is(err, task.ErrAllNotAllowed) { if errors.Is(err, task.ErrAllNotAllowed) {
@@ -445,7 +445,3 @@ func getHintPhrase(expErr task.ErrInvalidExpression) string {
func trimOperation(x string) string { func trimOperation(x string) string {
return strings.TrimLeft(x, "+-") return strings.TrimLeft(x, "+-")
} }
func allSourceProviderTags() []string {
return collections.TaggedValueSet[source.Provider]{}.Join(sourceproviders.All("", nil)...).Tags()
}
@@ -5,7 +5,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/anchore/syft/cmd/syft/internal/options" "github.com/anchore/syft/cmd/syft/cli/options"
) )
func Test_scanOptions_validateLegacyOptionsNotUsed(t *testing.T) { func Test_scanOptions_validateLegacyOptionsNotUsed(t *testing.T) {
@@ -29,7 +29,7 @@ func Test_scanOptions_validateLegacyOptionsNotUsed(t *testing.T) {
{ {
name: "config file with exclude-binary-overlap-by-ownership legacy option", name: "config file with exclude-binary-overlap-by-ownership legacy option",
cfg: "test-fixtures/scan-configs/with-exclude-binary-overlap-by-ownership.yaml", cfg: "test-fixtures/scan-configs/with-exclude-binary-overlap-by-ownership.yaml",
wantErr: assertErrorContains("package.exclude-binary-overlap-by-ownership"), wantErr: assertErrorContains("relationships.exclude-binary-packages-with-file-ownership-overlap"),
}, },
{ {
name: "config file with file string legacy option", name: "config file with file string legacy option",
@@ -11,8 +11,8 @@ import (
"github.com/anchore/clio" "github.com/anchore/clio"
hashiVersion "github.com/anchore/go-version" hashiVersion "github.com/anchore/go-version"
"github.com/anchore/syft/cmd/syft/cli/options"
"github.com/anchore/syft/cmd/syft/internal" "github.com/anchore/syft/cmd/syft/internal"
"github.com/anchore/syft/cmd/syft/internal/options"
"github.com/anchore/syft/internal/bus" "github.com/anchore/syft/internal/bus"
"github.com/anchore/syft/internal/log" "github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/event" "github.com/anchore/syft/syft/event"
@@ -28,7 +28,7 @@ var latestAppVersionURL = struct {
} }
func applicationUpdateCheck(id clio.Identification, check *options.UpdateCheck) func(cmd *cobra.Command, args []string) error { func applicationUpdateCheck(id clio.Identification, check *options.UpdateCheck) func(cmd *cobra.Command, args []string) error {
return func(_ *cobra.Command, _ []string) error { return func(cmd *cobra.Command, args []string) error {
if check.CheckForAppUpdate { if check.CheckForAppUpdate {
checkForApplicationUpdate(id) checkForApplicationUpdate(id)
} }
@@ -4,24 +4,14 @@ import (
"github.com/anchore/clio" "github.com/anchore/clio"
) )
var _ clio.FlagAdder = (*Attest)(nil)
type Attest struct { type Attest struct {
// IMPORTANT: do not show the attestation key/password in any YAML/JSON output (sensitive information) // IMPORTANT: do not show the attestation key/password in any YAML/JSON output (sensitive information)
Key secret `yaml:"key" json:"key" mapstructure:"key"` Key secret `yaml:"key" json:"key" mapstructure:"key"`
Password secret `yaml:"password" json:"password" mapstructure:"password"` Password secret `yaml:"password" json:"password" mapstructure:"password"`
} }
var _ interface { var _ clio.FlagAdder = (*Attest)(nil)
clio.FlagAdder
clio.FieldDescriber
} = (*Attest)(nil)
func (o *Attest) AddFlags(flags clio.FlagSet) { func (o Attest) AddFlags(flags clio.FlagSet) {
flags.StringVarP((*string)(&o.Key), "key", "k", "the key to use for the attestation") flags.StringVarP((*string)(&o.Key), "key", "k", "the key to use for the attestation")
} }
func (o *Attest) DescribeFields(descriptions clio.FieldDescriptionSet) {
descriptions.Add(&o.Password, `password to decrypt to given private key
additionally responds to COSIGN_PASSWORD env var`)
}
@@ -11,20 +11,16 @@ import (
"github.com/anchore/fangs" "github.com/anchore/fangs"
intFile "github.com/anchore/syft/internal/file" intFile "github.com/anchore/syft/internal/file"
"github.com/anchore/syft/internal/log" "github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/task"
"github.com/anchore/syft/syft" "github.com/anchore/syft/syft"
"github.com/anchore/syft/syft/cataloging" "github.com/anchore/syft/syft/cataloging"
"github.com/anchore/syft/syft/cataloging/filecataloging" "github.com/anchore/syft/syft/cataloging/filecataloging"
"github.com/anchore/syft/syft/cataloging/pkgcataloging" "github.com/anchore/syft/syft/cataloging/pkgcataloging"
"github.com/anchore/syft/syft/file/cataloger/executable"
"github.com/anchore/syft/syft/file/cataloger/filecontent" "github.com/anchore/syft/syft/file/cataloger/filecontent"
"github.com/anchore/syft/syft/pkg/cataloger/binary" "github.com/anchore/syft/syft/pkg/cataloger/binary"
"github.com/anchore/syft/syft/pkg/cataloger/dotnet"
"github.com/anchore/syft/syft/pkg/cataloger/golang" "github.com/anchore/syft/syft/pkg/cataloger/golang"
"github.com/anchore/syft/syft/pkg/cataloger/java" "github.com/anchore/syft/syft/pkg/cataloger/java"
"github.com/anchore/syft/syft/pkg/cataloger/javascript" "github.com/anchore/syft/syft/pkg/cataloger/javascript"
"github.com/anchore/syft/syft/pkg/cataloger/kernel" "github.com/anchore/syft/syft/pkg/cataloger/kernel"
"github.com/anchore/syft/syft/pkg/cataloger/nix"
"github.com/anchore/syft/syft/pkg/cataloger/python" "github.com/anchore/syft/syft/pkg/cataloger/python"
"github.com/anchore/syft/syft/source" "github.com/anchore/syft/syft/source"
) )
@@ -35,57 +31,41 @@ type Catalog struct {
DefaultCatalogers []string `yaml:"default-catalogers" json:"default-catalogers" mapstructure:"default-catalogers"` DefaultCatalogers []string `yaml:"default-catalogers" json:"default-catalogers" mapstructure:"default-catalogers"`
SelectCatalogers []string `yaml:"select-catalogers" json:"select-catalogers" mapstructure:"select-catalogers"` SelectCatalogers []string `yaml:"select-catalogers" json:"select-catalogers" mapstructure:"select-catalogers"`
Package packageConfig `yaml:"package" json:"package" mapstructure:"package"` Package packageConfig `yaml:"package" json:"package" mapstructure:"package"`
License licenseConfig `yaml:"license" json:"license" mapstructure:"license"`
File fileConfig `yaml:"file" json:"file" mapstructure:"file"` File fileConfig `yaml:"file" json:"file" mapstructure:"file"`
Scope string `yaml:"scope" json:"scope" mapstructure:"scope"` Scope string `yaml:"scope" json:"scope" mapstructure:"scope"`
Parallelism int `yaml:"parallelism" json:"parallelism" mapstructure:"parallelism"` // the number of catalog workers to run in parallel Parallelism int `yaml:"parallelism" json:"parallelism" mapstructure:"parallelism"` // the number of catalog workers to run in parallel
Relationships relationshipsConfig `yaml:"relationships" json:"relationships" mapstructure:"relationships"` Relationships relationshipsConfig `yaml:"relationships" json:"relationships" mapstructure:"relationships"`
Compliance complianceConfig `yaml:"compliance" json:"compliance" mapstructure:"compliance"`
Enrich []string `yaml:"enrich" json:"enrich" mapstructure:"enrich"`
// ecosystem-specific cataloger configuration // ecosystem-specific cataloger configuration
Dotnet dotnetConfig `yaml:"dotnet" json:"dotnet" mapstructure:"dotnet"`
Golang golangConfig `yaml:"golang" json:"golang" mapstructure:"golang"` Golang golangConfig `yaml:"golang" json:"golang" mapstructure:"golang"`
Java javaConfig `yaml:"java" json:"java" mapstructure:"java"` Java javaConfig `yaml:"java" json:"java" mapstructure:"java"`
JavaScript javaScriptConfig `yaml:"javascript" json:"javascript" mapstructure:"javascript"` JavaScript javaScriptConfig `yaml:"javascript" json:"javascript" mapstructure:"javascript"`
LinuxKernel linuxKernelConfig `yaml:"linux-kernel" json:"linux-kernel" mapstructure:"linux-kernel"` LinuxKernel linuxKernelConfig `yaml:"linux-kernel" json:"linux-kernel" mapstructure:"linux-kernel"`
Nix nixConfig `yaml:"nix" json:"nix" mapstructure:"nix"`
Python pythonConfig `yaml:"python" json:"python" mapstructure:"python"` Python pythonConfig `yaml:"python" json:"python" mapstructure:"python"`
// configuration for the source (the subject being analyzed) // configuration for the source (the subject being analyzed)
Registry registryConfig `yaml:"registry" json:"registry" mapstructure:"registry"` Registry registryConfig `yaml:"registry" json:"registry" mapstructure:"registry"`
From []string `yaml:"from" json:"from" mapstructure:"from"`
Platform string `yaml:"platform" json:"platform" mapstructure:"platform"` Platform string `yaml:"platform" json:"platform" mapstructure:"platform"`
Name string `yaml:"name" json:"name" mapstructure:"name"` // deprecated
Source sourceConfig `yaml:"source" json:"source" mapstructure:"source"` Source sourceConfig `yaml:"source" json:"source" mapstructure:"source"`
Exclusions []string `yaml:"exclude" json:"exclude" mapstructure:"exclude"` Exclusions []string `yaml:"exclude" json:"exclude" mapstructure:"exclude"`
// configuration for inclusion of unknown information within elements
Unknowns unknownsConfig `yaml:"unknowns" mapstructure:"unknowns"`
} }
var _ interface { var _ interface {
clio.FlagAdder clio.FlagAdder
clio.PostLoader clio.PostLoader
clio.FieldDescriber fangs.FieldDescriber
} = (*Catalog)(nil) } = (*Catalog)(nil)
func DefaultCatalog() Catalog { func DefaultCatalog() Catalog {
cfg := syft.DefaultCreateSBOMConfig()
return Catalog{ return Catalog{
Compliance: defaultComplianceConfig(),
Scope: source.SquashedScope.String(), Scope: source.SquashedScope.String(),
Package: defaultPackageConfig(), Package: defaultPackageConfig(),
License: defaultLicenseConfig(),
LinuxKernel: defaultLinuxKernelConfig(), LinuxKernel: defaultLinuxKernelConfig(),
Nix: defaultNixConfig(),
Dotnet: defaultDotnetConfig(),
Golang: defaultGolangConfig(),
Java: defaultJavaConfig(),
File: defaultFileConfig(), File: defaultFileConfig(),
Relationships: defaultRelationshipsConfig(), Relationships: defaultRelationshipsConfig(),
Unknowns: defaultUnknowns(),
Source: defaultSourceConfig(), Source: defaultSourceConfig(),
Parallelism: cfg.Parallelism, Parallelism: 1,
} }
} }
@@ -94,14 +74,11 @@ func (cfg Catalog) ToSBOMConfig(id clio.Identification) *syft.CreateSBOMConfig {
WithTool(id.Name, id.Version). WithTool(id.Name, id.Version).
WithParallelism(cfg.Parallelism). WithParallelism(cfg.Parallelism).
WithRelationshipsConfig(cfg.ToRelationshipsConfig()). WithRelationshipsConfig(cfg.ToRelationshipsConfig()).
WithComplianceConfig(cfg.ToComplianceConfig()).
WithUnknownsConfig(cfg.ToUnknownsConfig()).
WithSearchConfig(cfg.ToSearchConfig()). WithSearchConfig(cfg.ToSearchConfig()).
WithPackagesConfig(cfg.ToPackagesConfig()). WithPackagesConfig(cfg.ToPackagesConfig()).
WithLicenseConfig(cfg.ToLicenseConfig()).
WithFilesConfig(cfg.ToFilesConfig()). WithFilesConfig(cfg.ToFilesConfig()).
WithCatalogerSelection( WithCatalogerSelection(
cataloging.NewSelectionRequest(). pkgcataloging.NewSelectionRequest().
WithDefaults(cfg.DefaultCatalogers...). WithDefaults(cfg.DefaultCatalogers...).
WithExpression(cfg.SelectCatalogers...), WithExpression(cfg.SelectCatalogers...),
) )
@@ -122,20 +99,6 @@ func (cfg Catalog) ToRelationshipsConfig() cataloging.RelationshipsConfig {
} }
} }
func (cfg Catalog) ToComplianceConfig() cataloging.ComplianceConfig {
return cataloging.ComplianceConfig{
MissingName: cfg.Compliance.MissingName,
MissingVersion: cfg.Compliance.MissingVersion,
}
}
func (cfg Catalog) ToUnknownsConfig() cataloging.UnknownsConfig {
return cataloging.UnknownsConfig{
IncludeExecutablesWithoutPackages: cfg.Unknowns.ExecutablesWithoutPackages,
IncludeUnexpandedArchives: cfg.Unknowns.UnexpandedArchives,
}
}
func (cfg Catalog) ToFilesConfig() filecataloging.Config { func (cfg Catalog) ToFilesConfig() filecataloging.Config {
hashers, err := intFile.Hashers(cfg.File.Metadata.Digests...) hashers, err := intFile.Hashers(cfg.File.Metadata.Digests...)
if err != nil { if err != nil {
@@ -149,17 +112,6 @@ func (cfg Catalog) ToFilesConfig() filecataloging.Config {
Globs: cfg.File.Content.Globs, Globs: cfg.File.Content.Globs,
SkipFilesAboveSize: cfg.File.Content.SkipFilesAboveSize, SkipFilesAboveSize: cfg.File.Content.SkipFilesAboveSize,
}, },
Executable: executable.Config{
MIMETypes: executable.DefaultConfig().MIMETypes,
Globs: cfg.File.Executable.Globs,
},
}
}
func (cfg Catalog) ToLicenseConfig() cataloging.LicenseConfig {
return cataloging.LicenseConfig{
IncludeContent: cfg.License.Content,
Coverage: cfg.License.Coverage,
} }
} }
@@ -169,45 +121,26 @@ func (cfg Catalog) ToPackagesConfig() pkgcataloging.Config {
IncludeUnindexedArchives: cfg.Package.SearchUnindexedArchives, IncludeUnindexedArchives: cfg.Package.SearchUnindexedArchives,
} }
return pkgcataloging.Config{ return pkgcataloging.Config{
Binary: binary.DefaultClassifierCatalogerConfig(), Binary: binary.DefaultCatalogerConfig(),
Dotnet: dotnet.DefaultCatalogerConfig().
WithDepPackagesMustHaveDLL(cfg.Dotnet.DepPackagesMustHaveDLL).
WithDepPackagesMustClaimDLL(cfg.Dotnet.DepPackagesMustClaimDLL).
WithPropagateDLLClaimsToParents(cfg.Dotnet.PropagateDLLClaimsToParents).
WithRelaxDLLClaimsWhenBundlingDetected(cfg.Dotnet.RelaxDLLClaimsWhenBundlingDetected),
Golang: golang.DefaultCatalogerConfig(). Golang: golang.DefaultCatalogerConfig().
WithSearchLocalModCacheLicenses(*multiLevelOption(false, enrichmentEnabled(cfg.Enrich, task.Go, task.Golang), cfg.Golang.SearchLocalModCacheLicenses)). WithSearchLocalModCacheLicenses(cfg.Golang.SearchLocalModCacheLicenses).
WithLocalModCacheDir(cfg.Golang.LocalModCacheDir). WithLocalModCacheDir(cfg.Golang.LocalModCacheDir).
WithSearchLocalVendorLicenses(*multiLevelOption(false, enrichmentEnabled(cfg.Enrich, task.Go, task.Golang), cfg.Golang.SearchLocalVendorLicenses)). WithSearchRemoteLicenses(cfg.Golang.SearchRemoteLicenses).
WithLocalVendorDir(cfg.Golang.LocalVendorDir).
WithSearchRemoteLicenses(*multiLevelOption(false, enrichmentEnabled(cfg.Enrich, task.Go, task.Golang), cfg.Golang.SearchRemoteLicenses)).
WithProxy(cfg.Golang.Proxy). WithProxy(cfg.Golang.Proxy).
WithNoProxy(cfg.Golang.NoProxy). WithNoProxy(cfg.Golang.NoProxy),
WithMainModuleVersion(
golang.DefaultMainModuleVersionConfig().
WithFromContents(cfg.Golang.MainModuleVersion.FromContents).
WithFromBuildSettings(cfg.Golang.MainModuleVersion.FromBuildSettings).
WithFromLDFlags(cfg.Golang.MainModuleVersion.FromLDFlags),
),
JavaScript: javascript.DefaultCatalogerConfig(). JavaScript: javascript.DefaultCatalogerConfig().
WithIncludeDevDependencies(*multiLevelOption(false, cfg.JavaScript.IncludeDevDependencies)). WithSearchRemoteLicenses(cfg.JavaScript.SearchRemoteLicenses).
WithSearchRemoteLicenses(*multiLevelOption(false, enrichmentEnabled(cfg.Enrich, task.JavaScript, task.Node, task.NPM), cfg.JavaScript.SearchRemoteLicenses)).
WithNpmBaseURL(cfg.JavaScript.NpmBaseURL), WithNpmBaseURL(cfg.JavaScript.NpmBaseURL),
LinuxKernel: kernel.LinuxKernelCatalogerConfig{ LinuxKernel: kernel.LinuxKernelCatalogerConfig{
CatalogModules: cfg.LinuxKernel.CatalogModules, CatalogModules: cfg.LinuxKernel.CatalogModules,
}, },
Nix: nix.DefaultConfig().
WithCaptureOwnedFiles(cfg.Nix.CaptureOwnedFiles),
Python: python.CatalogerConfig{ Python: python.CatalogerConfig{
GuessUnpinnedRequirements: cfg.Python.GuessUnpinnedRequirements, GuessUnpinnedRequirements: cfg.Python.GuessUnpinnedRequirements,
}, },
JavaArchive: java.DefaultArchiveCatalogerConfig(). JavaArchive: java.DefaultArchiveCatalogerConfig().
WithUseMavenLocalRepository(*multiLevelOption(false, enrichmentEnabled(cfg.Enrich, task.Java, task.Maven), cfg.Java.UseMavenLocalRepository)). WithUseNetwork(cfg.Java.UseNetwork).
WithMavenLocalRepositoryDir(cfg.Java.MavenLocalRepositoryDir).
WithUseNetwork(*multiLevelOption(false, enrichmentEnabled(cfg.Enrich, task.Java, task.Maven), cfg.Java.UseNetwork)).
WithMavenBaseURL(cfg.Java.MavenURL). WithMavenBaseURL(cfg.Java.MavenURL).
WithArchiveTraversal(archiveSearch, cfg.Java.MaxParentRecursiveDepth). WithArchiveTraversal(archiveSearch, cfg.Java.MaxParentRecursiveDepth),
WithResolveTransitiveDependencies(cfg.Java.ResolveTransitiveDependencies),
} }
} }
@@ -219,9 +152,6 @@ func (cfg *Catalog) AddFlags(flags clio.FlagSet) {
flags.StringVarP(&cfg.Scope, "scope", "s", flags.StringVarP(&cfg.Scope, "scope", "s",
fmt.Sprintf("selection of layers to catalog, options=%v", validScopeValues)) fmt.Sprintf("selection of layers to catalog, options=%v", validScopeValues))
flags.StringArrayVarP(&cfg.From, "from", "",
"specify the source behavior to use (e.g. docker, registry, oci-dir, ...)")
flags.StringVarP(&cfg.Platform, "platform", "", flags.StringVarP(&cfg.Platform, "platform", "",
"an optional platform specifier for container image sources (e.g. 'linux/arm64', 'linux/arm64/v8', 'arm64', 'linux')") "an optional platform specifier for container image sources (e.g. 'linux/arm64', 'linux/arm64/v8', 'arm64', 'linux')")
@@ -231,9 +161,6 @@ func (cfg *Catalog) AddFlags(flags clio.FlagSet) {
flags.StringArrayVarP(&cfg.Catalogers, "catalogers", "", flags.StringArrayVarP(&cfg.Catalogers, "catalogers", "",
"enable one or more package catalogers") "enable one or more package catalogers")
flags.IntVarP(&cfg.Parallelism, "parallelism", "",
"number of cataloger workers to run in parallel")
if pfp, ok := flags.(fangs.PFlagSetProvider); ok { if pfp, ok := flags.(fangs.PFlagSetProvider); ok {
if err := pfp.PFlagSet().MarkDeprecated("catalogers", "use: override-default-catalogers and select-catalogers"); err != nil { if err := pfp.PFlagSet().MarkDeprecated("catalogers", "use: override-default-catalogers and select-catalogers"); err != nil {
panic(err) panic(err)
@@ -248,8 +175,13 @@ func (cfg *Catalog) AddFlags(flags clio.FlagSet) {
flags.StringArrayVarP(&cfg.SelectCatalogers, "select-catalogers", "", flags.StringArrayVarP(&cfg.SelectCatalogers, "select-catalogers", "",
"add, remove, and filter the catalogers to be used") "add, remove, and filter the catalogers to be used")
flags.StringArrayVarP(&cfg.Enrich, "enrich", "", flags.StringVarP(&cfg.Source.Name, "name", "",
fmt.Sprintf("enable package data enrichment from local and online sources (options: %s)", strings.Join(publicisedEnrichmentOptions, ", "))) "set the name of the target being analyzed")
if pfp, ok := flags.(fangs.PFlagSetProvider); ok {
flagSet := pfp.PFlagSet()
flagSet.Lookup("name").Deprecated = "use: source-name"
}
flags.StringVarP(&cfg.Source.Name, "source-name", "", flags.StringVarP(&cfg.Source.Name, "source-name", "",
"set the name of the target being analyzed") "set the name of the target being analyzed")
@@ -259,21 +191,20 @@ func (cfg *Catalog) AddFlags(flags clio.FlagSet) {
flags.StringVarP(&cfg.Source.BasePath, "base-path", "", flags.StringVarP(&cfg.Source.BasePath, "base-path", "",
"base directory for scanning, no links will be followed above this directory, and all paths will be reported relative to this directory") "base directory for scanning, no links will be followed above this directory, and all paths will be reported relative to this directory")
flags.StringVarP(&cfg.Source.Supplier, "source-supplier", "",
"the organization that supplied the component, which often may be the manufacturer, distributor, or repackager")
} }
func (cfg *Catalog) DescribeFields(descriptions fangs.FieldDescriptionSet) { func (cfg *Catalog) DescribeFields(descriptions fangs.FieldDescriptionSet) {
descriptions.Add(&cfg.Parallelism, `number of cataloger workers to run in parallel descriptions.Add(&cfg.Parallelism, "number of cataloger workers to run in parallel")
by default, when set to 0: this will be based on runtime.NumCPU * 4, if set to less than 0 it will be unbounded`)
descriptions.Add(&cfg.Enrich, fmt.Sprintf(`Enable data enrichment operations, which can utilize services such as Maven Central and NPM.
By default all enrichment is disabled, use: all to enable everything.
Available options are: %s`, strings.Join(publicisedEnrichmentOptions, ", ")))
} }
func (cfg *Catalog) PostLoad() error { func (cfg *Catalog) PostLoad() error {
if cfg.Name != "" {
log.Warnf("name parameter is deprecated. please use: source-name. name will be removed in a future version")
if cfg.Source.Name == "" {
cfg.Source.Name = cfg.Name
}
}
usingLegacyCatalogers := len(cfg.Catalogers) > 0 usingLegacyCatalogers := len(cfg.Catalogers) > 0
usingNewCatalogers := len(cfg.DefaultCatalogers) > 0 || len(cfg.SelectCatalogers) > 0 usingNewCatalogers := len(cfg.DefaultCatalogers) > 0 || len(cfg.SelectCatalogers) > 0
@@ -281,12 +212,21 @@ func (cfg *Catalog) PostLoad() error {
return fmt.Errorf("cannot use both 'catalogers' and 'select-catalogers'/'default-catalogers' flags") return fmt.Errorf("cannot use both 'catalogers' and 'select-catalogers'/'default-catalogers' flags")
} }
cfg.From = Flatten(cfg.From) flatten := func(l []string) []string {
var out []string
for _, v := range l {
for _, s := range strings.Split(v, ",") {
out = append(out, strings.TrimSpace(s))
}
}
sort.Strings(out)
cfg.Catalogers = Flatten(cfg.Catalogers) return out
cfg.DefaultCatalogers = Flatten(cfg.DefaultCatalogers) }
cfg.SelectCatalogers = Flatten(cfg.SelectCatalogers)
cfg.Enrich = Flatten(cfg.Enrich) cfg.Catalogers = flatten(cfg.Catalogers)
cfg.DefaultCatalogers = flatten(cfg.DefaultCatalogers)
cfg.SelectCatalogers = flatten(cfg.SelectCatalogers)
// for backwards compatibility // for backwards compatibility
cfg.DefaultCatalogers = append(cfg.DefaultCatalogers, cfg.Catalogers...) cfg.DefaultCatalogers = append(cfg.DefaultCatalogers, cfg.Catalogers...)
@@ -296,75 +236,5 @@ func (cfg *Catalog) PostLoad() error {
return fmt.Errorf("bad scope value %q", cfg.Scope) return fmt.Errorf("bad scope value %q", cfg.Scope)
} }
// the binary package exclusion code depends on the file overlap relationships being created upstream in processing
if !cfg.Relationships.PackageFileOwnershipOverlap && cfg.Package.ExcludeBinaryOverlapByOwnership {
return fmt.Errorf("cannot enable exclude-binary-overlap-by-ownership without enabling package-file-ownership-overlap")
}
return nil return nil
} }
func Flatten(commaSeparatedEntries []string) []string {
var out []string
for _, v := range commaSeparatedEntries {
for _, s := range strings.Split(v, ",") {
out = append(out, strings.TrimSpace(s))
}
}
sort.Strings(out)
return out
}
var publicisedEnrichmentOptions = []string{
"all",
task.Golang,
task.Java,
task.JavaScript,
}
func enrichmentEnabled(enrichDirectives []string, features ...string) *bool {
if len(enrichDirectives) == 0 {
return nil
}
enabled := func(features ...string) *bool {
for _, directive := range enrichDirectives {
enable := true
directive = strings.TrimPrefix(directive, "+") // +java and java are equivalent
if strings.HasPrefix(directive, "-") {
directive = directive[1:]
enable = false
}
for _, feature := range features {
if directive == feature {
return &enable
}
}
}
return nil
}
enableAll := enabled("all")
disableAll := enabled("none")
if disableAll != nil && *disableAll {
if enableAll != nil {
log.Warn("you have specified to both enable and disable all enrichment functionality, defaulting to disabled")
}
enableAll = ptr(false)
}
// check for explicit enable/disable of feature names
for _, feat := range features {
enableFeature := enabled(feat)
if enableFeature != nil {
return enableFeature
}
}
return enableAll
}
func ptr[T any](val T) *T {
return &val
}
@@ -57,14 +57,6 @@ func TestCatalog_PostLoad(t *testing.T) {
assert.Empty(t, options.Catalogers) assert.Empty(t, options.Catalogers)
}, },
}, },
{
name: "must have package overlap flag when pruning binaries by overlap",
options: Catalog{
Package: packageConfig{ExcludeBinaryOverlapByOwnership: true},
Relationships: relationshipsConfig{PackageFileOwnershipOverlap: false},
},
wantErr: assert.Error,
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
@@ -78,69 +70,3 @@ func TestCatalog_PostLoad(t *testing.T) {
}) })
} }
} }
func Test_enrichmentEnabled(t *testing.T) {
tests := []struct {
directives string
test string
expected *bool
}{
{
directives: "",
test: "java",
expected: nil,
},
{
directives: "none",
test: "java",
expected: ptr(false),
},
{
directives: "none,+java",
test: "java",
expected: ptr(true),
},
{
directives: "all,none",
test: "java",
expected: ptr(false),
},
{
directives: "all",
test: "java",
expected: ptr(true),
},
{
directives: "golang,js",
test: "java",
expected: nil,
},
{
directives: "golang,-js,java",
test: "java",
expected: ptr(true),
},
{
directives: "golang,js,-java",
test: "java",
expected: ptr(false),
},
{
directives: "all",
test: "java",
expected: ptr(true),
},
{
directives: "all,-java",
test: "java",
expected: ptr(false),
},
}
for _, test := range tests {
t.Run(test.directives, func(t *testing.T) {
got := enrichmentEnabled(Flatten([]string{test.directives}), test.test)
assert.Equal(t, test.expected, got)
})
}
}
@@ -2,11 +2,11 @@ package options
import "github.com/anchore/fangs" import "github.com/anchore/fangs"
// Config holds a reference to the specific config file(s) that were used to load application configuration // Config holds a reference to the specific config file that was used to load application configuration
type Config struct { type Config struct {
ConfigFile string `yaml:"config" json:"config" mapstructure:"config"` ConfigFile string `yaml:"config" json:"config" mapstructure:"config"`
} }
func (cfg *Config) DescribeFields(descriptions fangs.FieldDescriptionSet) { func (cfg *Config) DescribeFields(descriptions fangs.FieldDescriptionSet) {
descriptions.Add(&cfg.ConfigFile, "the configuration file(s) used to load application configuration") descriptions.Add(&cfg.ConfigFile, "the configuration file that was used to load application configuration")
} }
+43
View File
@@ -0,0 +1,43 @@
package options
import (
"fmt"
intFile "github.com/anchore/syft/internal/file"
"github.com/anchore/syft/syft/file"
)
type fileConfig struct {
Metadata fileMetadata `yaml:"metadata" json:"metadata" mapstructure:"metadata"`
Content fileContent `yaml:"content" json:"content" mapstructure:"content"`
}
type fileMetadata struct {
Selection file.Selection `yaml:"selection" json:"selection" mapstructure:"selection"`
Digests []string `yaml:"digests" json:"digests" mapstructure:"digests"`
}
type fileContent struct {
SkipFilesAboveSize int64 `yaml:"skip-files-above-size" json:"skip-files-above-size" mapstructure:"skip-files-above-size"`
Globs []string `yaml:"globs" json:"globs" mapstructure:"globs"`
}
func defaultFileConfig() fileConfig {
return fileConfig{
Metadata: fileMetadata{
Selection: file.FilesOwnedByPackageSelection,
Digests: []string{"sha1", "sha256"},
},
Content: fileContent{
SkipFilesAboveSize: 250 * intFile.KB,
},
}
}
func (c *fileConfig) PostLoad() error {
switch c.Metadata.Selection {
case file.NoFilesSelection, file.FilesOwnedByPackageSelection, file.AllFilesSelection:
return nil
}
return fmt.Errorf("invalid file metadata selection: %q", c.Metadata.Selection)
}
+129
View File
@@ -0,0 +1,129 @@
package options
import (
"fmt"
"github.com/hashicorp/go-multierror"
"github.com/anchore/clio"
"github.com/anchore/syft/syft/format/cyclonedxjson"
"github.com/anchore/syft/syft/format/cyclonedxxml"
"github.com/anchore/syft/syft/format/github"
"github.com/anchore/syft/syft/format/spdxjson"
"github.com/anchore/syft/syft/format/spdxtagvalue"
"github.com/anchore/syft/syft/format/syftjson"
"github.com/anchore/syft/syft/format/table"
"github.com/anchore/syft/syft/format/template"
"github.com/anchore/syft/syft/format/text"
"github.com/anchore/syft/syft/sbom"
)
var _ clio.PostLoader = (*Format)(nil)
// Format contains all user configuration for output formatting.
type Format struct {
Pretty *bool `yaml:"pretty" json:"pretty" mapstructure:"pretty"`
Template FormatTemplate `yaml:"template" json:"template" mapstructure:"template"`
SyftJSON FormatSyftJSON `yaml:"json" json:"json" mapstructure:"json"`
SPDXJSON FormatSPDXJSON `yaml:"spdx-json" json:"spdx-json" mapstructure:"spdx-json"`
CyclonedxJSON FormatCyclonedxJSON `yaml:"cyclonedx-json" json:"cyclonedx-json" mapstructure:"cyclonedx-json"`
CyclonedxXML FormatCyclonedxXML `yaml:"cyclonedx-xml" json:"cyclonedx-xml" mapstructure:"cyclonedx-xml"`
}
func (o *Format) PostLoad() error {
o.SyftJSON.Pretty = multiLevelOption[bool](false, o.Pretty, o.SyftJSON.Pretty)
o.SPDXJSON.Pretty = multiLevelOption[bool](false, o.Pretty, o.SPDXJSON.Pretty)
o.CyclonedxJSON.Pretty = multiLevelOption[bool](false, o.Pretty, o.CyclonedxJSON.Pretty)
o.CyclonedxXML.Pretty = multiLevelOption[bool](false, o.Pretty, o.CyclonedxXML.Pretty)
return nil
}
func DefaultFormat() Format {
return Format{
Template: DefaultFormatTemplate(),
SyftJSON: DefaultFormatJSON(),
SPDXJSON: DefaultFormatSPDXJSON(),
CyclonedxJSON: DefaultFormatCyclonedxJSON(),
CyclonedxXML: DefaultFormatCyclonedxXML(),
}
}
func (o *Format) Encoders() ([]sbom.FormatEncoder, error) {
// setup all encoders based on the configuration
var list encoderList
// in the future there will be application configuration options that can be used to set the default output format
list.addWithErr(template.ID)(o.Template.formatEncoders())
list.addWithErr(syftjson.ID)(o.SyftJSON.formatEncoders())
list.add(table.ID)(table.NewFormatEncoder())
list.add(text.ID)(text.NewFormatEncoder())
list.add(github.ID)(github.NewFormatEncoder())
list.addWithErr(cyclonedxxml.ID)(o.CyclonedxXML.formatEncoders())
list.addWithErr(cyclonedxjson.ID)(o.CyclonedxJSON.formatEncoders())
list.addWithErr(spdxjson.ID)(o.SPDXJSON.formatEncoders())
list.addWithErr(spdxtagvalue.ID)(spdxTagValueEncoders())
return list.encoders, list.err
}
// TODO: when application configuration is made for this format then this should be ported to the options object
// that is created for that configuration (as done with the template output option)
func spdxTagValueEncoders() ([]sbom.FormatEncoder, error) {
var (
encs []sbom.FormatEncoder
errs error
)
for _, v := range spdxtagvalue.SupportedVersions() {
enc, err := spdxtagvalue.NewFormatEncoderWithConfig(spdxtagvalue.EncoderConfig{Version: v})
if err != nil {
errs = multierror.Append(errs, err)
} else {
encs = append(encs, enc)
}
}
return encs, errs
}
type encoderList struct {
encoders []sbom.FormatEncoder
err error
}
func (l *encoderList) addWithErr(name sbom.FormatID) func([]sbom.FormatEncoder, error) {
return func(encs []sbom.FormatEncoder, err error) {
if err != nil {
l.err = multierror.Append(l.err, fmt.Errorf("unable to configure %q format encoder: %w", name, err))
return
}
for _, enc := range encs {
if enc == nil {
l.err = multierror.Append(l.err, fmt.Errorf("unable to configure %q format encoder: nil encoder returned", name))
continue
}
l.encoders = append(l.encoders, enc)
}
}
}
func (l *encoderList) add(name sbom.FormatID) func(...sbom.FormatEncoder) {
return func(encs ...sbom.FormatEncoder) {
for _, enc := range encs {
if enc == nil {
l.err = multierror.Append(l.err, fmt.Errorf("unable to configure %q format encoder: nil encoder returned", name))
continue
}
l.encoders = append(l.encoders, enc)
}
}
}
func multiLevelOption[T any](defaultValue T, option ...*T) *T {
result := defaultValue
for _, opt := range option {
if opt != nil {
result = *opt
}
}
return &result
}
@@ -0,0 +1,43 @@
package options
import (
"github.com/hashicorp/go-multierror"
"github.com/anchore/syft/syft/format/cyclonedxjson"
"github.com/anchore/syft/syft/sbom"
)
type FormatCyclonedxJSON struct {
Pretty *bool `yaml:"pretty" json:"pretty" mapstructure:"pretty"`
}
func DefaultFormatCyclonedxJSON() FormatCyclonedxJSON {
return FormatCyclonedxJSON{}
}
func (o FormatCyclonedxJSON) formatEncoders() ([]sbom.FormatEncoder, error) {
var (
encs []sbom.FormatEncoder
errs error
)
for _, v := range cyclonedxjson.SupportedVersions() {
enc, err := cyclonedxjson.NewFormatEncoderWithConfig(o.buildConfig(v))
if err != nil {
errs = multierror.Append(errs, err)
} else {
encs = append(encs, enc)
}
}
return encs, errs
}
func (o FormatCyclonedxJSON) buildConfig(version string) cyclonedxjson.EncoderConfig {
var pretty bool
if o.Pretty != nil {
pretty = *o.Pretty
}
return cyclonedxjson.EncoderConfig{
Version: version,
Pretty: pretty,
}
}
@@ -13,7 +13,7 @@ func TestFormatCyclonedxJSON_buildConfig(t *testing.T) {
ft := &FormatCyclonedxJSON{} ft := &FormatCyclonedxJSON{}
ft = setAllToNonZero(t, ft).(*FormatCyclonedxJSON) ft = setAllToNonZero(t, ft).(*FormatCyclonedxJSON)
subject := ft.config("Version") subject := ft.buildConfig("Version")
assertExpectedValue(t, subject) assertExpectedValue(t, subject)
} }
@@ -0,0 +1,43 @@
package options
import (
"github.com/hashicorp/go-multierror"
"github.com/anchore/syft/syft/format/cyclonedxxml"
"github.com/anchore/syft/syft/sbom"
)
type FormatCyclonedxXML struct {
Pretty *bool `yaml:"pretty" json:"pretty" mapstructure:"pretty"`
}
func DefaultFormatCyclonedxXML() FormatCyclonedxXML {
return FormatCyclonedxXML{}
}
func (o FormatCyclonedxXML) formatEncoders() ([]sbom.FormatEncoder, error) {
var (
encs []sbom.FormatEncoder
errs error
)
for _, v := range cyclonedxxml.SupportedVersions() {
enc, err := cyclonedxxml.NewFormatEncoderWithConfig(o.buildConfig(v))
if err != nil {
errs = multierror.Append(errs, err)
} else {
encs = append(encs, enc)
}
}
return encs, errs
}
func (o FormatCyclonedxXML) buildConfig(version string) cyclonedxxml.EncoderConfig {
var pretty bool
if o.Pretty != nil {
pretty = *o.Pretty
}
return cyclonedxxml.EncoderConfig{
Version: version,
Pretty: pretty,
}
}
@@ -10,6 +10,6 @@ func TestFormatCyclonedxXML_buildConfig(t *testing.T) {
ft := FormatCyclonedxXML{} ft := FormatCyclonedxXML{}
ftp := setAllToNonZero(t, &ft).(*FormatCyclonedxXML) ftp := setAllToNonZero(t, &ft).(*FormatCyclonedxXML)
subject := ftp.config("Version") subject := ftp.buildConfig("Version")
assertExpectedValue(t, subject) assertExpectedValue(t, subject)
} }
+43
View File
@@ -0,0 +1,43 @@
package options
import (
"github.com/hashicorp/go-multierror"
"github.com/anchore/syft/syft/format/spdxjson"
"github.com/anchore/syft/syft/sbom"
)
type FormatSPDXJSON struct {
Pretty *bool `yaml:"pretty" json:"pretty" mapstructure:"pretty"`
}
func DefaultFormatSPDXJSON() FormatSPDXJSON {
return FormatSPDXJSON{}
}
func (o FormatSPDXJSON) formatEncoders() ([]sbom.FormatEncoder, error) {
var (
encs []sbom.FormatEncoder
errs error
)
for _, v := range spdxjson.SupportedVersions() {
enc, err := spdxjson.NewFormatEncoderWithConfig(o.buildConfig(v))
if err != nil {
errs = multierror.Append(errs, err)
} else {
encs = append(encs, enc)
}
}
return encs, errs
}
func (o FormatSPDXJSON) buildConfig(v string) spdxjson.EncoderConfig {
var pretty bool
if o.Pretty != nil {
pretty = *o.Pretty
}
return spdxjson.EncoderConfig{
Version: v,
Pretty: pretty,
}
}
@@ -10,6 +10,6 @@ func TestFormatSPDXJSON_buildConfig(t *testing.T) {
ft := &FormatSPDXJSON{} ft := &FormatSPDXJSON{}
ft = setAllToNonZero(t, ft).(*FormatSPDXJSON) ft = setAllToNonZero(t, ft).(*FormatSPDXJSON)
subject := ft.config("Version") subject := ft.buildConfig("Version")
assertExpectedValue(t, subject) assertExpectedValue(t, subject)
} }
@@ -2,6 +2,7 @@ package options
import ( import (
"github.com/anchore/syft/syft/format/syftjson" "github.com/anchore/syft/syft/format/syftjson"
"github.com/anchore/syft/syft/sbom"
) )
type FormatSyftJSON struct { type FormatSyftJSON struct {
@@ -15,7 +16,12 @@ func DefaultFormatJSON() FormatSyftJSON {
} }
} }
func (o FormatSyftJSON) config() syftjson.EncoderConfig { func (o FormatSyftJSON) formatEncoders() ([]sbom.FormatEncoder, error) {
enc, err := syftjson.NewFormatEncoderWithConfig(o.buildConfig())
return []sbom.FormatEncoder{enc}, err
}
func (o FormatSyftJSON) buildConfig() syftjson.EncoderConfig {
var pretty bool var pretty bool
if o.Pretty != nil { if o.Pretty != nil {
pretty = *o.Pretty pretty = *o.Pretty
@@ -10,6 +10,6 @@ func TestFormatSyftJSON_buildConfig(t *testing.T) {
ft := &FormatSyftJSON{} ft := &FormatSyftJSON{}
ft = setAllToNonZero(t, ft).(*FormatSyftJSON) ft = setAllToNonZero(t, ft).(*FormatSyftJSON)
subject := ft.config() subject := ft.buildConfig()
assertExpectedValue(t, subject) assertExpectedValue(t, subject)
} }
@@ -3,6 +3,7 @@ package options
import ( import (
"github.com/anchore/clio" "github.com/anchore/clio"
"github.com/anchore/syft/syft/format/template" "github.com/anchore/syft/syft/format/template"
"github.com/anchore/syft/syft/sbom"
) )
var _ clio.FlagAdder = (*FormatTemplate)(nil) var _ clio.FlagAdder = (*FormatTemplate)(nil)
@@ -10,7 +11,6 @@ var _ clio.FlagAdder = (*FormatTemplate)(nil)
type FormatTemplate struct { type FormatTemplate struct {
Enabled bool `yaml:"-" json:"-" mapstructure:"-"` Enabled bool `yaml:"-" json:"-" mapstructure:"-"`
Path string `yaml:"path" json:"path" mapstructure:"path"` // -t template file to use for output Path string `yaml:"path" json:"path" mapstructure:"path"` // -t template file to use for output
Legacy bool `yaml:"legacy" json:"legacy" mapstructure:"legacy"`
} }
func DefaultFormatTemplate() FormatTemplate { func DefaultFormatTemplate() FormatTemplate {
@@ -26,9 +26,12 @@ func (o *FormatTemplate) AddFlags(flags clio.FlagSet) {
} }
} }
func (o FormatTemplate) config() template.EncoderConfig { func (o FormatTemplate) formatEncoders() ([]sbom.FormatEncoder, error) {
return template.EncoderConfig{ if !o.Enabled {
TemplatePath: o.Path, return nil, nil
Legacy: o.Legacy,
} }
enc, err := template.NewFormatEncoder(template.EncoderConfig{
TemplatePath: o.Path,
})
return []sbom.FormatEncoder{enc}, err
} }
+9
View File
@@ -0,0 +1,9 @@
package options
type golangConfig struct {
SearchLocalModCacheLicenses bool `json:"search-local-mod-cache-licenses" yaml:"search-local-mod-cache-licenses" mapstructure:"search-local-mod-cache-licenses"`
LocalModCacheDir string `json:"local-mod-cache-dir" yaml:"local-mod-cache-dir" mapstructure:"local-mod-cache-dir"`
SearchRemoteLicenses bool `json:"search-remote-licenses" yaml:"search-remote-licenses" mapstructure:"search-remote-licenses"`
Proxy string `json:"proxy" yaml:"proxy" mapstructure:"proxy"`
NoProxy string `json:"no-proxy" yaml:"no-proxy" mapstructure:"no-proxy"`
}
+7
View File
@@ -0,0 +1,7 @@
package options
type javaConfig struct {
UseNetwork bool `yaml:"use-network" json:"use-network" mapstructure:"use-network"`
MavenURL string `yaml:"maven-url" json:"maven-url" mapstructure:"maven-url"`
MaxParentRecursiveDepth int `yaml:"max-parent-recursive-depth" json:"max-parent-recursive-depth" mapstructure:"max-parent-recursive-depth"`
}
+6
View File
@@ -0,0 +1,6 @@
package options
type javaScriptConfig struct {
SearchRemoteLicenses bool `json:"search-remote-licenses" yaml:"search-remote-licenses" mapstructure:"search-remote-licenses"`
NpmBaseURL string `json:"npm-base-url" yaml:"npm-base-url" mapstructure:"npm-base-url"`
}
+11
View File
@@ -0,0 +1,11 @@
package options
type linuxKernelConfig struct {
CatalogModules bool `json:"catalog-modules" yaml:"catalog-modules" mapstructure:"catalog-modules"`
}
func defaultLinuxKernelConfig() linuxKernelConfig {
return linuxKernelConfig{
CatalogModules: true,
}
}
@@ -12,7 +12,6 @@ import (
"github.com/anchore/syft/syft/format/cyclonedxjson" "github.com/anchore/syft/syft/format/cyclonedxjson"
"github.com/anchore/syft/syft/format/cyclonedxxml" "github.com/anchore/syft/syft/format/cyclonedxxml"
"github.com/anchore/syft/syft/format/github" "github.com/anchore/syft/syft/format/github"
"github.com/anchore/syft/syft/format/purls"
"github.com/anchore/syft/syft/format/spdxjson" "github.com/anchore/syft/syft/format/spdxjson"
"github.com/anchore/syft/syft/format/spdxtagvalue" "github.com/anchore/syft/syft/format/spdxtagvalue"
"github.com/anchore/syft/syft/format/syftjson" "github.com/anchore/syft/syft/format/syftjson"
@@ -25,7 +24,6 @@ import (
var _ interface { var _ interface {
clio.FlagAdder clio.FlagAdder
clio.PostLoader clio.PostLoader
clio.FieldDescriber
} = (*Output)(nil) } = (*Output)(nil)
// Output has the standard output options syft accepts: multiple -o, --file, --template // Output has the standard output options syft accepts: multiple -o, --file, --template
@@ -72,15 +70,6 @@ func (o *Output) AddFlags(flags clio.FlagSet) {
fmt.Sprintf("report output format (<format>=<file> to output to a file), formats=%v", names)) fmt.Sprintf("report output format (<format>=<file> to output to a file), formats=%v", names))
} }
func (o *Output) DescribeFields(descriptions clio.FieldDescriptionSet) {
descriptions.Add(&o.Outputs, `the output format(s) of the SBOM report (options: syft-table, syft-text, syft-json, spdx-json, ...)
to specify multiple output files in differing formats, use a list:
output:
- "syft-json=<syft-json-output-file>"
- "spdx-json=<spdx-json-output-file>"
`)
}
func (o Output) SBOMWriter() (sbom.Writer, error) { func (o Output) SBOMWriter() (sbom.Writer, error) {
names := o.OutputNameSet() names := o.OutputNameSet()
@@ -90,7 +79,7 @@ func (o Output) SBOMWriter() (sbom.Writer, error) {
usesTemplateOutput := names.Has(string(template.ID)) usesTemplateOutput := names.Has(string(template.ID))
if usesTemplateOutput && o.Template.Path == "" { if usesTemplateOutput && o.Format.Template.Path == "" {
return nil, fmt.Errorf(`must specify path to template file when using "template" output format`) return nil, fmt.Errorf(`must specify path to template file when using "template" output format`)
} }
@@ -128,7 +117,6 @@ func supportedIDs() []sbom.FormatID {
table.ID, table.ID,
text.ID, text.ID,
template.ID, template.ID,
purls.ID,
// encoders that support multiple versions // encoders that support multiple versions
cyclonedxxml.ID, cyclonedxxml.ID,
@@ -3,9 +3,10 @@ package options
import ( import (
"fmt" "fmt"
"github.com/mitchellh/go-homedir"
"github.com/anchore/clio" "github.com/anchore/clio"
"github.com/anchore/fangs" "github.com/anchore/fangs"
"github.com/anchore/go-homedir"
"github.com/anchore/syft/syft/sbom" "github.com/anchore/syft/syft/sbom"
) )
@@ -27,7 +28,7 @@ func (o *OutputFile) AddFlags(flags clio.FlagSet) {
if pfp, ok := flags.(fangs.PFlagSetProvider); ok { if pfp, ok := flags.(fangs.PFlagSetProvider); ok {
flagSet := pfp.PFlagSet() flagSet := pfp.PFlagSet()
flagSet.Lookup("file").Deprecated = "use: --output FORMAT=PATH" flagSet.Lookup("file").Deprecated = "use: output"
} }
} }
} }
+18
View File
@@ -0,0 +1,18 @@
package options
import "github.com/anchore/syft/syft/cataloging"
type packageConfig struct {
SearchUnindexedArchives bool `yaml:"search-unindexed-archives" json:"search-unindexed-archives" mapstructure:"search-unindexed-archives"`
SearchIndexedArchives bool `yaml:"search-indexed-archives" json:"search-indexed-archives" mapstructure:"search-indexed-archives"`
ExcludeBinaryOverlapByOwnership bool `yaml:"exclude-binary-overlap-by-ownership" json:"exclude-binary-overlap-by-ownership" mapstructure:"exclude-binary-overlap-by-ownership"` // exclude synthetic binary packages owned by os package files
}
func defaultPackageConfig() packageConfig {
c := cataloging.DefaultArchiveSearchConfig()
return packageConfig{
SearchIndexedArchives: c.IncludeIndexedArchives,
SearchUnindexedArchives: c.IncludeUnindexedArchives,
ExcludeBinaryOverlapByOwnership: true,
}
}
+5
View File
@@ -0,0 +1,5 @@
package options
type pythonConfig struct {
GuessUnpinnedRequirements bool `json:"guess-unpinned-requirements" yaml:"guess-unpinned-requirements" mapstructure:"guess-unpinned-requirements"`
}
@@ -25,10 +25,7 @@ type registryConfig struct {
CACert string `yaml:"ca-cert" json:"ca-cert" mapstructure:"ca-cert"` CACert string `yaml:"ca-cert" json:"ca-cert" mapstructure:"ca-cert"`
} }
var _ interface { var _ clio.PostLoader = (*registryConfig)(nil)
clio.PostLoader
clio.FieldDescriber
} = (*registryConfig)(nil)
func (cfg *registryConfig) PostLoad() error { func (cfg *registryConfig) PostLoad() error {
// there may be additional credentials provided by env var that should be appended to the set of credentials // there may be additional credentials provided by env var that should be appended to the set of credentials
@@ -58,20 +55,6 @@ func (cfg *registryConfig) PostLoad() error {
return nil return nil
} }
func (cfg *registryConfig) DescribeFields(descriptions clio.FieldDescriptionSet) {
descriptions.Add(&cfg.InsecureSkipTLSVerify, "skip TLS verification when communicating with the registry")
descriptions.Add(&cfg.InsecureUseHTTP, "use http instead of https when connecting to the registry")
descriptions.Add(&cfg.CACert, "filepath to a CA certificate (or directory containing *.crt, *.cert, *.pem) used to generate the client certificate")
descriptions.Add(&cfg.Auth, `Authentication credentials for specific registries. Each entry describes authentication for a specific authority:
- authority: the registry authority URL the URL to the registry (e.g. "docker.io", "localhost:5000", etc.) (env: SYFT_REGISTRY_AUTH_AUTHORITY)
username: a username if using basic credentials (env: SYFT_REGISTRY_AUTH_USERNAME)
password: a corresponding password (env: SYFT_REGISTRY_AUTH_PASSWORD)
token: a token if using token-based authentication, mutually exclusive with username/password (env: SYFT_REGISTRY_AUTH_TOKEN)
tls-cert: filepath to the client certificate used for TLS authentication to the registry (env: SYFT_REGISTRY_AUTH_TLS_CERT)
tls-key: filepath to the client key used for TLS authentication to the registry (env: SYFT_REGISTRY_AUTH_TLS_KEY)
`)
}
func hasNonEmptyCredentials(username, password, token, tlsCert, tlsKey string) bool { func hasNonEmptyCredentials(username, password, token, tlsCert, tlsKey string) bool {
hasUserPass := username != "" && password != "" hasUserPass := username != "" && password != ""
hasToken := token != "" hasToken := token != ""
@@ -17,6 +17,6 @@ func defaultRelationshipsConfig() relationshipsConfig {
} }
func (r *relationshipsConfig) DescribeFields(descriptions fangs.FieldDescriptionSet) { func (r *relationshipsConfig) DescribeFields(descriptions fangs.FieldDescriptionSet) {
descriptions.Add(&r.PackageFileOwnership, "include package-to-file relationships that indicate which files are owned by which packages") descriptions.Add(&r.PackageFileOwnership, "include package-to-file relationships that indicate which files are owned by which packages.")
descriptions.Add(&r.PackageFileOwnershipOverlap, "include package-to-package relationships that indicate one package is owned by another due to files claimed to be owned by one package are also evidence of another package's existence") descriptions.Add(&r.PackageFileOwnershipOverlap, "include package-to-package relationships that indicate one package is owned by another due to files claimed to be owned by one package are also evidence of another package's existence.")
} }
+51
View File
@@ -0,0 +1,51 @@
package options
import (
"fmt"
"strings"
"github.com/scylladb/go-set/strset"
)
type sourceConfig struct {
Name string `json:"name" yaml:"name" mapstructure:"name"`
Version string `json:"version" yaml:"version" mapstructure:"version"`
BasePath string `yaml:"base-path" json:"base-path" mapstructure:"base-path"` // specify base path for all file paths
File fileSource `json:"file" yaml:"file" mapstructure:"file"`
Image imageSource `json:"image" yaml:"image" mapstructure:"image"`
}
type fileSource struct {
Digests []string `json:"digests" yaml:"digests" mapstructure:"digests"`
}
type imageSource struct {
DefaultPullSource string `json:"default-pull-source" yaml:"default-pull-source" mapstructure:"default-pull-source"`
}
func defaultSourceConfig() sourceConfig {
return sourceConfig{
File: fileSource{
Digests: []string{"sha256"},
},
Image: imageSource{
DefaultPullSource: "",
},
}
}
func (c imageSource) PostLoad() error {
return checkDefaultSourceValues(c.DefaultPullSource)
}
var validDefaultSourceValues = []string{"registry", "docker", "podman", ""}
func checkDefaultSourceValues(source string) error {
validValues := strset.New(validDefaultSourceValues...)
if !validValues.Has(source) {
validValuesString := strings.Join(validDefaultSourceValues, ", ")
return fmt.Errorf("%s is not a valid default source; please use one of the following: %s''", source, validValuesString)
}
return nil
}
@@ -10,9 +10,9 @@ import (
"strings" "strings"
"github.com/hashicorp/go-multierror" "github.com/hashicorp/go-multierror"
"github.com/mitchellh/go-homedir"
"github.com/scylladb/go-set/strset" "github.com/scylladb/go-set/strset"
"github.com/anchore/go-homedir"
"github.com/anchore/syft/internal/bus" "github.com/anchore/syft/internal/bus"
"github.com/anchore/syft/internal/log" "github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/format" "github.com/anchore/syft/syft/format"
@@ -6,10 +6,10 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/docker/docker/pkg/homedir"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/anchore/go-homedir"
"github.com/anchore/syft/syft/sbom" "github.com/anchore/syft/syft/sbom"
) )
@@ -228,8 +228,6 @@ func Test_newSBOMMultiWriter(t *testing.T) {
} }
func Test_newSBOMWriterDescription(t *testing.T) { func Test_newSBOMWriterDescription(t *testing.T) {
h, err := homedir.Dir()
require.NoError(t, err)
tests := []struct { tests := []struct {
name string name string
path string path string
@@ -238,7 +236,7 @@ func Test_newSBOMWriterDescription(t *testing.T) {
{ {
name: "expand home dir", name: "expand home dir",
path: "~/place.txt", path: "~/place.txt",
expected: filepath.Join(h, "place.txt"), expected: filepath.Join(homedir.Get(), "place.txt"),
}, },
{ {
name: "passthrough other paths", name: "passthrough other paths",
@@ -1,6 +1,6 @@
[TestHandler_handleAttestationStarted/attesting_in_progress/task_line - 1] [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] [TestHandler_handleAttestationStarted/attesting_in_progress/log - 1]
@@ -11,7 +11,7 @@
--- ---
[TestHandler_handleAttestationStarted/attesting_complete/task_line - 1] [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] [TestHandler_handleAttestationStarted/attesting_complete/log - 1]
@@ -1,8 +1,8 @@
[TestHandler_handleFetchImage/fetch_image_in_progress - 1] [TestHandler_handleFetchImage/fetch_image_in_progress - 1]
⠋ Loading image ━━━━━━━━━━━━━━━━━━━━ [current] the-image ⠋ Loading image ━━━━━━━━━━━━━━━━━━━━ [current] the-image
--- ---
[TestHandler_handleFetchImage/fetch_image_complete - 1] [TestHandler_handleFetchImage/fetch_image_complete - 1]
✔ Loaded image the-image ✔ Loaded image the-image
--- ---
@@ -1,8 +1,8 @@
[TestHandler_handleFileIndexingStarted/cataloging_in_progress - 1] [TestHandler_handleFileIndexingStarted/cataloging_in_progress - 1]
⠋ Indexing file system ━━━━━━━━━━━━━━━━━━━━ [current] /some/path ⠋ Indexing file system ━━━━━━━━━━━━━━━━━━━━ [current] /some/path
--- ---
[TestHandler_handleFileIndexingStarted/cataloging_complete - 1] [TestHandler_handleFileIndexingStarted/cataloging_complete - 1]
✔ Indexed file system /some/path ✔ Indexed file system /some/path
--- ---
@@ -1,8 +0,0 @@
[TestHandler_handlePullSourceStarted/snap_download_in_progress - 1]
⠋ Downloading snap file... ━━━━━━━━━━━━━━━━━━━━ example-app_1.0_amd64.snap
---
[TestHandler_handlePullSourceStarted/snap_download_complete - 1]
✔ Snap downloaded successfully example-app_1.0_amd64.snap
---
@@ -1,8 +1,8 @@
[TestHandler_handleReadImage/read_image_in_progress - 1] [TestHandler_handleReadImage/read_image_in_progress - 1]
⠋ Parsing image ━━━━━━━━━━━━━━━━━━━━ id ⠋ Parsing image ━━━━━━━━━━━━━━━━━━━━ id
--- ---
[TestHandler_handleReadImage/read_image_complete - 1] [TestHandler_handleReadImage/read_image_complete - 1]
✔ Parsed image id ✔ Parsed image id
--- ---
+1 -1
View File
@@ -61,7 +61,7 @@ type backgroundLineReader struct {
func (m *Handler) handleAttestationStarted(e partybus.Event) []tea.Model { func (m *Handler) handleAttestationStarted(e partybus.Event) []tea.Model {
reader, prog, taskInfo, err := syftEventParsers.ParseAttestationStartedEvent(e) reader, prog, taskInfo, err := syftEventParsers.ParseAttestationStartedEvent(e)
if err != nil { if err != nil {
log.WithFields("error", err).Debug("unable to parse event") log.WithFields("error", err).Warn("unable to parse event")
return nil return nil
} }
+1 -1
View File
@@ -89,7 +89,7 @@ func (cts catalogerTaskModel) View() string {
func (m *Handler) handleCatalogerTaskStarted(e partybus.Event) ([]tea.Model, tea.Cmd) { func (m *Handler) handleCatalogerTaskStarted(e partybus.Event) ([]tea.Model, tea.Cmd) {
mon, info, err := syftEventParsers.ParseCatalogerTaskStarted(e) mon, info, err := syftEventParsers.ParseCatalogerTaskStarted(e)
if err != nil { if err != nil {
log.WithFields("error", err).Debug("unable to parse event") log.WithFields("error", err).Warn("unable to parse event")
return nil, nil return nil, nil
} }
@@ -26,7 +26,7 @@ func TestHandler_handleCatalogerTaskStarted(t *testing.T) {
{ {
name: "cataloging task in progress", name: "cataloging task in progress",
eventFn: func(t *testing.T) partybus.Event { eventFn: func(t *testing.T) partybus.Event {
value := &monitor.TaskProgress{ value := &monitor.CatalogerTaskProgress{
AtomicStage: progress.NewAtomicStage("some stage"), AtomicStage: progress.NewAtomicStage("some stage"),
Manual: progress.NewManual(100), Manual: progress.NewManual(100),
} }
@@ -48,7 +48,7 @@ func TestHandler_handleCatalogerTaskStarted(t *testing.T) {
{ {
name: "cataloging sub task in progress", name: "cataloging sub task in progress",
eventFn: func(t *testing.T) partybus.Event { eventFn: func(t *testing.T) partybus.Event {
value := &monitor.TaskProgress{ value := &monitor.CatalogerTaskProgress{
AtomicStage: progress.NewAtomicStage("some stage"), AtomicStage: progress.NewAtomicStage("some stage"),
Manual: progress.NewManual(100), Manual: progress.NewManual(100),
} }
@@ -71,7 +71,7 @@ func TestHandler_handleCatalogerTaskStarted(t *testing.T) {
{ {
name: "cataloging sub task complete", name: "cataloging sub task complete",
eventFn: func(t *testing.T) partybus.Event { eventFn: func(t *testing.T) partybus.Event {
value := &monitor.TaskProgress{ value := &monitor.CatalogerTaskProgress{
AtomicStage: progress.NewAtomicStage("some stage"), AtomicStage: progress.NewAtomicStage("some stage"),
Manual: progress.NewManual(100), Manual: progress.NewManual(100),
} }
@@ -94,7 +94,7 @@ func TestHandler_handleCatalogerTaskStarted(t *testing.T) {
{ {
name: "cataloging sub task complete -- hide stage", name: "cataloging sub task complete -- hide stage",
eventFn: func(t *testing.T) partybus.Event { eventFn: func(t *testing.T) partybus.Event {
value := &monitor.TaskProgress{ value := &monitor.CatalogerTaskProgress{
AtomicStage: progress.NewAtomicStage("some stage"), AtomicStage: progress.NewAtomicStage("some stage"),
Manual: progress.NewManual(100), Manual: progress.NewManual(100),
} }
@@ -117,7 +117,7 @@ func TestHandler_handleCatalogerTaskStarted(t *testing.T) {
{ {
name: "cataloging sub task complete with removal", name: "cataloging sub task complete with removal",
eventFn: func(t *testing.T) partybus.Event { eventFn: func(t *testing.T) partybus.Event {
value := &monitor.TaskProgress{ value := &monitor.CatalogerTaskProgress{
AtomicStage: progress.NewAtomicStage("some stage"), AtomicStage: progress.NewAtomicStage("some stage"),
Manual: progress.NewManual(100), Manual: progress.NewManual(100),
} }
@@ -162,7 +162,7 @@ func TestHandler_handleCatalogerTaskStarted(t *testing.T) {
} }
// note: this line / event is not under test, only needed to show a sub status // note: this line / event is not under test, only needed to show a sub status
kickoffEvent := &monitor.TaskProgress{ kickoffEvent := &monitor.CatalogerTaskProgress{
AtomicStage: progress.NewAtomicStage(""), AtomicStage: progress.NewAtomicStage(""),
Manual: progress.NewManual(-1), Manual: progress.NewManual(-1),
} }
+1 -1
View File
@@ -12,7 +12,7 @@ import (
func (m *Handler) handleFetchImage(e partybus.Event) []tea.Model { func (m *Handler) handleFetchImage(e partybus.Event) []tea.Model {
imgName, prog, err := stereoEventParsers.ParseFetchImage(e) imgName, prog, err := stereoEventParsers.ParseFetchImage(e)
if err != nil { if err != nil {
log.WithFields("error", err).Debug("unable to parse event") log.WithFields("error", err).Warn("unable to parse event")
return nil return nil
} }

Some files were not shown because too many files have changed in this diff Show More