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
782 changed files with 8901 additions and 61721 deletions
+6 -27
View File
@@ -1,21 +1,11 @@
tools:
# we want to use a pinned version of binny to manage the toolchain (so binny manages itself!)
- name: binny
version:
want: v0.7.0
want: v0.6.3
method: github-release
with:
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
version:
want: v0.4.1
@@ -23,15 +13,13 @@ tools:
with:
repo: anchore/quill
# used for linting
- name: golangci-lint
version:
want: v1.57.2
want: v1.55.2
method: github-release
with:
repo: golangci/golangci-lint
# used for showing the changelog at release
- name: glow
version:
want: v1.5.1
@@ -39,15 +27,13 @@ tools:
with:
repo: charmbracelet/glow
# used for signing the checksums file at release
- name: cosign
version:
want: v2.2.4
want: v2.2.2
method: github-release
with:
repo: sigstore/cosign
# used in integration tests to verify JSON schemas
- name: yajsv
version:
want: v1.4.1
@@ -55,15 +41,13 @@ tools:
with:
repo: neilpa/yajsv
# used to release all artifacts
- name: goreleaser
version:
want: v1.25.1
want: v1.23.0
method: github-release
with:
repo: goreleaser/goreleaser
# used for organizing imports during static analysis
- name: gosimports
version:
want: v0.3.8
@@ -71,7 +55,6 @@ tools:
with:
repo: rinchsan/gosimports
# used at release to generate the changelog
- name: chronicle
version:
want: v0.8.0
@@ -79,7 +62,6 @@ tools:
with:
repo: anchore/chronicle
# used during static analysis for license compliance
- name: bouncer
version:
want: v0.4.0
@@ -87,7 +69,6 @@ tools:
with:
repo: wagoodman/go-bouncer
# used for showing benchmark testing
- name: benchstat
version:
want: latest
@@ -100,18 +81,16 @@ tools:
entrypoint: cmd/benchstat
module: golang.org/x/perf
# used for running all local and CI tasks
- name: task
version:
want: v3.36.0
want: v3.33.1
method: github-release
with:
repo: go-task/task
# used for triggering a release
- name: gh
version:
want: v2.48.0
want: v2.42.0
method: github-release
with:
repo: cli/cli
+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: ""
github:
+1 -1
View File
@@ -27,7 +27,7 @@ runs:
using: "composite"
steps:
# note: go mod and build is automatically cached on default with v4+
- uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 #v5.0.1
- uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe #v4.1.0
if: inputs.go-version != ''
with:
go-version: ${{ inputs.go-version }}
+1 -8
View File
@@ -1,19 +1,12 @@
version: 2
updates:
- package-ecosystem: gomod
directory: "/"
schedule:
interval: "daily"
open-pull-requests-limit: 10
labels:
- "dependencies"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
open-pull-requests-limit: 10
labels:
- "dependencies"
open-pull-requests-limit: 10
+6 -22
View File
@@ -56,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_labels = get_pr_labels(pr_number)
# print("schema files in pr: ", summarize_schema_files(pr_json_schema_files))
# print("og schema files: ", summarize_schema_files(og_json_schema_files))
@@ -78,8 +76,7 @@ def main(changed_files: str | None = None, merge_base_schema_files: str | None =
add_label(pr_number, JSON_SCHEMA_LABEL)
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
# change (major version bump). Warn about it on the PR via adding a breaking-change label...
@@ -87,8 +84,7 @@ def main(changed_files: str | None = None, merge_base_schema_files: str | None =
print("\nBreaking change detected...")
add_label(pr_number, BREAKING_CHANGE_LABEL)
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...
# removing schema files should never be allowed, we should warn about it on the PR via a comment...
@@ -123,7 +119,7 @@ def add_label(pr_number: str, label: str):
# 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)
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))
sys.exit(1)
@@ -132,7 +128,7 @@ def remove_label(pr_number: str, label: str):
# 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)
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))
sys.exit(1)
@@ -169,17 +165,6 @@ def get_pr_changed_files(pr_number: str) -> list[str]:
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]:
# get files matching "schema/json/schema-*.json"
files = []
@@ -191,8 +176,7 @@ def filter_to_schema_files(list_of_files: list[str]) -> list[str]:
def list_json_schema_files() -> list[str]:
# 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([f for f in glob.glob("schema/json/schema-*.json") if "latest" not in f])
return sort_json_schema_files(list(glob.glob("schema/json/schema-*.json")))
def run(command: str, **kwargs) -> subprocess.CompletedProcess:
@@ -213,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".
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]
-5
View File
@@ -60,11 +60,6 @@ class Labeler(unittest.TestCase):
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)
# 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__":
unittest.main()
+4 -3
View File
@@ -2,8 +2,8 @@
set -ue
BIN="syft"
VERSION_FILE="VERSION"
VERSION=$1
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
@@ -12,9 +12,10 @@ if [[ "$(curl -SsL https://api.github.com/repos/anchore/${BIN}/releases/tags/${V
exit 0
fi
echo "creating and publishing version file (${VERSION})"
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
+4 -4
View File
@@ -17,13 +17,13 @@ jobs:
# the job by event.
steps:
- name: Checkout code
uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Bootstrap environment
uses: ./.github/actions/bootstrap
- name: Restore base benchmark result
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 #v4.0.2
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
@@ -39,13 +39,13 @@ jobs:
OUTPUT="${OUTPUT//$'\r'/'%0D'}" # URL encode all '\r' characters
echo "result=$OUTPUT" >> $GITHUB_OUTPUT
- uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
- 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@331f8f5b4215f0445d3c07b4967662a32a2d3e31 #v2.9.0
uses: marocchino/sticky-pull-request-comment@efaaab3fd41a9c3de579aba759d2552635e590fd #v2.8.0
continue-on-error: true
with:
header: benchmark
+6 -6
View File
@@ -20,7 +20,7 @@ permissions:
jobs:
analyze:
name: Analyze
runs-on: ubuntu-22.04-4core-16gb
runs-on: ubuntu-latest
permissions:
security-events: write
@@ -36,16 +36,16 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b #v4.1.5
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
- name: Install Go
uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 #v5.0.1
uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 #v5.0.0
with:
go-version-file: go.mod
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@ccf74c947955fd1cf117aef6a0e4e66191ef6f61 #v3.25.4
uses: github/codeql-action/init@e5f05b81d5b6ff8cfa111c80c22c5fd02a384118 #v3.23.0
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -56,7 +56,7 @@ jobs:
# 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)
- name: Autobuild
uses: github/codeql-action/autobuild@ccf74c947955fd1cf117aef6a0e4e66191ef6f61 #v3.25.4
uses: github/codeql-action/autobuild@e5f05b81d5b6ff8cfa111c80c22c5fd02a384118 #v3.23.0
# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@@ -70,4 +70,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@ccf74c947955fd1cf117aef6a0e4e66191ef6f61 #v3.25.4
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 -3
View File
@@ -30,7 +30,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b #v4.1.5
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
- run: python .github/scripts/labeler.py
env:
@@ -40,7 +40,7 @@ jobs:
- name: Delete existing comment
if: ${{ hashFiles( env.CI_COMMENT_FILE ) == '' }}
uses: marocchino/sticky-pull-request-comment@331f8f5b4215f0445d3c07b4967662a32a2d3e31 #v2.9.0
uses: marocchino/sticky-pull-request-comment@efaaab3fd41a9c3de579aba759d2552635e590fd #v2.8.0
with:
header: ${{ env.COMMENT_HEADER }}
hide: true
@@ -48,7 +48,7 @@ jobs:
- name: Add comment
if: ${{ hashFiles( env.CI_COMMENT_FILE ) != '' }}
uses: marocchino/sticky-pull-request-comment@331f8f5b4215f0445d3c07b4967662a32a2d3e31 #v2.9.0
uses: marocchino/sticky-pull-request-comment@efaaab3fd41a9c3de579aba759d2552635e590fd #v2.8.0
with:
header: ${{ env.COMMENT_HEADER }}
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 }}
@@ -1,31 +0,0 @@
name: "Release"
on:
workflow_dispatch:
inputs:
version:
description: release version to update the version file with (prefixed with v)
required: true
workflow_call:
inputs:
version:
type: string
description: release version to update the version file with (prefixed with v)
required: true
jobs:
release:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b #v4.1.5
- name: Update version file
run: make ci-release-version-file
env:
RELEASE_VERSION: ${{ github.event.inputs.version }}
# 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 }}
+18 -24
View File
@@ -15,7 +15,7 @@ jobs:
environment: release
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b #v4.1.5
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
- name: Check if tag already exists
# note: this will fail if the tag already exists
@@ -24,7 +24,7 @@ jobs:
git tag ${{ github.event.inputs.version }}
- 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
with:
token: ${{ secrets.GITHUB_TOKEN }}
@@ -33,7 +33,7 @@ jobs:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- 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
with:
token: ${{ secrets.GITHUB_TOKEN }}
@@ -42,7 +42,7 @@ jobs:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- 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
with:
token: ${{ secrets.GITHUB_TOKEN }}
@@ -51,7 +51,7 @@ jobs:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- 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
with:
token: ${{ secrets.GITHUB_TOKEN }}
@@ -60,7 +60,7 @@ jobs:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- 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
with:
token: ${{ secrets.GITHUB_TOKEN }}
@@ -69,7 +69,7 @@ jobs:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- 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
with:
token: ${{ secrets.GITHUB_TOKEN }}
@@ -94,10 +94,8 @@ jobs:
permissions:
contents: write
packages: write
# required for goreleaser signs section with cosign
id-token: write
steps:
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b #v4.1.5
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
with:
fetch-depth: 0
@@ -105,13 +103,13 @@ jobs:
uses: ./.github/actions/bootstrap
- name: Login to Docker Hub
uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 #v3.1.0
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d #v3.0.0
with:
username: ${{ secrets.ANCHOREOSSWRITE_DH_USERNAME }}
password: ${{ secrets.ANCHOREOSSWRITE_DH_PAT }}
username: ${{ secrets.TOOLBOX_DOCKER_USER }}
password: ${{ secrets.TOOLBOX_DOCKER_PASS }}
- name: Login to GitHub Container Registry
uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 #v3.1.0
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d #v3.0.0
with:
registry: ghcr.io
username: ${{ github.actor }}
@@ -138,14 +136,17 @@ jobs:
# for creating the release (requires write access to packages and content)
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# 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@7ccf588e3cf3cc2611714c2eeae48550fbc17552 #v0.15.11
- uses: anchore/sbom-action@c7f031d9249a826a082ea14c79d3b686a51d485a #v0.15.3
continue-on-error: true
with:
artifact-name: sbom.spdx.json
- uses: 8398a7/action-slack@28ba43ae48961b90635b50953d216767a6bea486 #v3.16.2
- uses: 8398a7/action-slack@fbd6aa58ba854a740e11a35d0df80cb5d12101d8 #v3.15.1
continue-on-error: true
with:
status: ${{ job.status }}
@@ -154,10 +155,3 @@ jobs:
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_TOOLBOX_WEBHOOK_URL }}
if: ${{ success() }}
release-version-file:
needs: [release]
uses: ./.github/workflows/release-version-file.yaml
with:
version: ${{ github.event.inputs.version }}
secrets: inherit
+2 -2
View File
@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
if: github.repository == 'anchore/syft' # only run for main repo
steps:
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b #v4.1.5
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
- name: Bootstrap environment
uses: ./.github/actions/bootstrap
@@ -50,7 +50,7 @@ jobs:
app_id: ${{ secrets.TOKEN_APP_ID }}
private_key: ${{ secrets.TOKEN_APP_PRIVATE_KEY }}
- uses: peter-evans/create-pull-request@6d6857d36972b65feb161a90e484f2984215f83e #v6.0.5
- uses: peter-evans/create-pull-request@153407881ec5c347639a548ade7d8ad1d6740e38 #v5.0.2
with:
signoff: true
delete-branch: true
@@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest
if: github.repository == 'anchore/syft' # only run for main repo
steps:
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b #v4.1.5
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
- name: Bootstrap environment
uses: ./.github/actions/bootstrap
@@ -33,7 +33,7 @@ jobs:
app_id: ${{ secrets.TOKEN_APP_ID }}
private_key: ${{ secrets.TOKEN_APP_PRIVATE_KEY }}
- uses: peter-evans/create-pull-request@6d6857d36972b65feb161a90e484f2984215f83e #v6.0.5
- uses: peter-evans/create-pull-request@153407881ec5c347639a548ade7d8ad1d6740e38 #v5.0.2
with:
signoff: true
delete-branch: true
@@ -45,7 +45,7 @@ jobs:
Update CPE dictionary index based on the latest available CPE dictionary
token: ${{ steps.generate-token.outputs.token }}
- uses: 8398a7/action-slack@28ba43ae48961b90635b50953d216767a6bea486 #v3.16.2
- uses: 8398a7/action-slack@fbd6aa58ba854a740e11a35d0df80cb5d12101d8 #v3.15.1
with:
status: ${{ job.status }}
fields: workflow,eventName,job
@@ -17,9 +17,9 @@ jobs:
runs-on: ubuntu-latest
if: github.repository == 'anchore/syft' # only run for main repo
steps:
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b #v4.1.5
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
- uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 #v5.0.1
- uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 #v5.0.0
with:
go-version: ${{ env.GO_VERSION }}
stable: ${{ env.GO_STABLE_VERSION }}
@@ -44,7 +44,7 @@ jobs:
app_id: ${{ secrets.TOKEN_APP_ID }}
private_key: ${{ secrets.TOKEN_APP_PRIVATE_KEY }}
- uses: peter-evans/create-pull-request@6d6857d36972b65feb161a90e484f2984215f83e #v6.0.5
- uses: peter-evans/create-pull-request@153407881ec5c347639a548ade7d8ad1d6740e38 #v5.0.2
with:
signoff: true
delete-branch: true
+22 -34
View File
@@ -17,7 +17,7 @@ jobs:
name: "Static analysis"
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b #v4.1.5
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
- name: Bootstrap environment
uses: ./.github/actions/bootstrap
@@ -31,49 +31,37 @@ jobs:
name: "Unit tests"
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b #v4.1.5
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
- name: Bootstrap environment
uses: ./.github/actions/bootstrap
- name: Restore file executable test-fixture cache
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 #v4.0.2
with:
path: syft/file/cataloger/executable/test-fixtures/elf/bin
key: ${{ runner.os }}-unit-file-executable-elf-cache-${{ hashFiles( 'syft/file/cataloger/executable/test-fixtures/elf/cache.fingerprint' ) }}
- name: Restore file executable shared-info test-fixture cache
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 #v4.0.2
with:
path: syft/file/cataloger/executable/test-fixtures/shared-info/bin
key: ${{ runner.os }}-unit-file-executable-shared-info-cache-${{ hashFiles( 'syft/file/cataloger/executable/test-fixtures/shared-info/cache.fingerprint' ) }}
- name: Restore Java test-fixture cache
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 #v4.0.2
uses: actions/cache@e12d46a63a90f2fae62d114769bbf2a179198b5c #v3.3.3
with:
path: syft/pkg/cataloger/java/test-fixtures/java-builds/packages
key: ${{ runner.os }}-unit-java-cache-${{ hashFiles( 'syft/pkg/cataloger/java/test-fixtures/java-builds/cache.fingerprint' ) }}
- name: Restore RPM test-fixture cache
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 #v4.0.2
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@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 #v4.0.2
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@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 #v4.0.2
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@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 #v4.0.2
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' ) }}
@@ -87,7 +75,7 @@ jobs:
name: "Integration tests"
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b #v4.1.5
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
- name: Bootstrap environment
uses: ./.github/actions/bootstrap
@@ -96,10 +84,10 @@ jobs:
run: make validate-cyclonedx-schema
- name: Restore integration test cache
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 #v4.0.2
uses: actions/cache@e12d46a63a90f2fae62d114769bbf2a179198b5c #v3.3.3
with:
path: ${{ github.workspace }}/cmd/syft/internal/test/integration/test-fixtures/cache
key: ${{ runner.os }}-integration-test-cache-${{ hashFiles('/cmd/syft/internal/test/integration/test-fixtures/cache.fingerprint') }}
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
run: make integration
@@ -109,7 +97,7 @@ jobs:
name: "Build snapshot artifacts"
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b #v4.1.5
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
- name: Bootstrap environment
uses: ./.github/actions/bootstrap
@@ -122,7 +110,7 @@ jobs:
# why not use actions/upload-artifact? It is very slow (3 minutes to upload ~600MB of data, vs 10 seconds with this approach).
# see https://github.com/actions/upload-artifact/issues/199 for more info
- name: Upload snapshot artifacts
uses: actions/cache/save@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 #v4.0.2
uses: actions/cache/save@e12d46a63a90f2fae62d114769bbf2a179198b5c #v3.3.3
with:
# 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
@@ -139,14 +127,14 @@ jobs:
needs: [Build-Snapshot-Artifacts]
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b #v4.1.5
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
- name: Bootstrap environment
uses: ./.github/actions/bootstrap
- name: Download snapshot build
id: snapshot-cache
uses: actions/cache/restore@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 #v4.0.2
uses: actions/cache/restore@e12d46a63a90f2fae62d114769bbf2a179198b5c #v3.3.3
with:
path: |
snapshot
@@ -164,7 +152,7 @@ jobs:
- name: Restore install.sh test image cache
id: install-test-image-cache
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 #v4.0.2
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') }}
@@ -187,7 +175,7 @@ jobs:
needs: [Build-Snapshot-Artifacts]
runs-on: macos-latest
steps:
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b #v4.1.5
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
- name: Bootstrap environment
uses: ./.github/actions/bootstrap
@@ -199,7 +187,7 @@ jobs:
- name: Download snapshot build
id: snapshot-cache
uses: actions/cache/restore@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 #v4.0.2
uses: actions/cache/restore@e12d46a63a90f2fae62d114769bbf2a179198b5c #v3.3.3
with:
path: |
snapshot
@@ -214,7 +202,7 @@ jobs:
- name: Restore docker image cache for compare testing
id: mac-compare-testing-cache
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 #v4.0.2
uses: actions/cache@e12d46a63a90f2fae62d114769bbf2a179198b5c #v3.3.3
with:
path: image.tar
key: ${{ runner.os }}-${{ hashFiles('test/compare/mac.sh') }}
@@ -232,20 +220,20 @@ jobs:
needs: [Build-Snapshot-Artifacts]
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b #v4.1.5
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
- name: Bootstrap environment
uses: ./.github/actions/bootstrap
- name: Restore CLI test-fixture cache
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 #v4.0.2
uses: actions/cache@e12d46a63a90f2fae62d114769bbf2a179198b5c #v3.3.3
with:
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
id: snapshot-cache
uses: actions/cache/restore@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 #v4.0.2
uses: actions/cache/restore@e12d46a63a90f2fae62d114769bbf2a179198b5c #v3.3.3
with:
path: |
snapshot
+1
View File
@@ -32,6 +32,7 @@ VERSION
/test/results
coverage.txt
*.log
test/integration/test-fixtures/**/go.sum
# probable archives
.images
-8
View File
@@ -49,18 +49,10 @@ linters-settings:
# If lower than 0, disable the check.
# Default: 40
statements: 50
gocritic:
enabled-checks:
- deferInLoop
- ruleguard
settings:
ruleguard:
rules: "test/rules/rules.go"
output:
uniq-by-line: false
run:
timeout: 10m
tests: false
# do not enable...
# - deadcode # The owner seems to have abandoned the linter. Replaced by "unused".
-25
View File
@@ -247,28 +247,3 @@ docker_manifests:
- ghcr.io/anchore/syft:{{.Tag}}-arm64v8
- ghcr.io/anchore/syft:{{.Tag}}-ppc64le
- ghcr.io/anchore/syft:{{.Tag}}-s390x
sboms:
- artifacts: archive
# this is relative to the snapshot/dist directory, not the root of the repo
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
+1 -1
View File
@@ -234,7 +234,7 @@ rough outline how that works:
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.
- `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
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
+86 -100
View File
@@ -54,7 +54,6 @@ For commercial support options with Syft or Grype, please [contact Anchore](http
- Ruby (gem)
- Rust (cargo.lock)
- Swift (cocoapods, swift-package-manager)
- Wordpress plugins
## Installation
@@ -92,7 +91,7 @@ brew install syft
### Nix
**Note**: Nix packaging of Syft is [community maintained](https://github.com/NixOS/nixpkgs/blob/master/pkgs/tools/admin/syft/default.nix). Syft is 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
nix-env -i syft
@@ -122,8 +121,7 @@ syft <image> --scope all-layers
### Supported sources
Syft can generate an SBOM from a variety of sources including images, files, directories, and archives. Syft will attempt to
determine the type of source based on provided input, for example:
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)
@@ -136,24 +134,26 @@ syft path/to/image.sif
syft path/to/dir
```
To explicitly specify the source behavior, use the `--from` flag. Allowable options are:
Sources can be explicitly provided with a scheme:
```
docker use images from the Docker daemon
podman use images from the Podman daemon
containerd use images from the Containerd daemon
docker-archive use a tarball from disk for archives created from "docker save"
oci-archive use a tarball from disk for OCI archives (from Skopeo or otherwise)
oci-dir read directly from a path on disk for OCI layout directories (from Skopeo or otherwise)
singularity read directly from a Singularity Image Format (SIF) container on disk
dir read directly from a path on disk (any directory)
file read directly from a path on disk (any single file)
registry pull image directly from a registry (no container runtime required)
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 a source is not provided and Syft identifies the input as a potential image reference, Syft will attempt to resolve it using:
the Docker, Podman, and Containerd daemons followed by direct registry access, in that order.
This default behavior can be overridden with the `default-image-pull-source` configuration option (See [Configuration](#configuration) for more details).
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
@@ -228,12 +228,12 @@ syft <a directory> --select-catalogers "python"
Use the default set of catalogers and add a cataloger to the set:
```bash
syft ... --select-catalogers "+sbom-cataloger"
syft ... --catalogers "+sbom-cataloger"
```
Use the default set of catalogers but remove any catalogers that deal with RPMs:
```bash
syft ... --select-catalogers "-rpm"
syft ... --catalogers "-rpm"
```
Only scan with catalogers that:
@@ -305,7 +305,7 @@ Where the `formats` available are:
## Using templates
Syft lets you define custom output formats, using [Go templates](https://pkg.go.dev/text/template) relative to the Syft JSON output. Here's how it works:
Syft lets you define custom output formats, using [Go templates](https://pkg.go.dev/text/template). Here's how it works:
- Define your format as a Go template, and save this template as a file.
@@ -319,9 +319,9 @@ Syft lets you define custom output formats, using [Go templates](https://pkg.go.
Here's what the `csv.tmpl` file might look like:
```gotemplate
"Package","Version Installed", "Found by"
{{- range .artifacts}}
"{{.name}}","{{.version}}","{{.foundBy}}"
"Package","Version Installed","Found by"
{{- range .Artifacts}}
"{{.Name}}","{{.Version}}","{{.FoundBy}}"
{{- end}}
```
@@ -338,9 +338,6 @@ Syft also includes a vast array of utility templating functions from [sprig](htt
Lastly, Syft has custom templating functions defined in `./syft/format/template/encoder.go` to help parse the passed-in JSON structs.
> [!NOTE]
> If you have templates being used before Syft v0.102.0 that are no longer working. This is because templating keys were relative to the internal go structs before this version whereas now the keys are relative to the Syft JSON output. To get the legacy behavior back you can set the `format.template.legacy` option to `true` in your configuration.
## Multiple outputs
Syft can also output _multiple_ files in differing formats by appending
@@ -505,20 +502,19 @@ 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>"
# SYFT_OUTPUT env var / -o, --output flags
output:
- "syft-table"
output: "syft-table"
# suppress all output (except for the SBOM report)
# SYFT_QUIET env var / -q flag
# same as -q ; SYFT_QUIET env var
quiet: false
# enable/disable checking for application updates on startup
# SYFT_CHECK_FOR_APP_UPDATE env var
# 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
@@ -528,11 +524,15 @@ parallelism: 1
# exclude:
# - "/etc/**"
# - "./out/**/*.json"
# SYFT_EXCLUDE env var / --exclude flag
# 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")
# SYFT_PLATFORM env var / --platform flag
# same as --platform; SYFT_PLATFORM env var
platform: ""
# the search space to look for file and package data (options: all-layers, squashed)
@@ -541,24 +541,48 @@ 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])
# Use `syft cataloger list` for a list of catalogers you can specify
# DEPRECATED: please use default-catalogers and select-catalogers configuration options instead
# SYFT_CATALOGERS env var / --catalogers flag
# 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:
# set the base set of catalogers to use (defaults to 'image' or 'directory' depending on the scan source)
# SYFT_DEFAULT_CATALOGERS env var / --override-default-catalogers flag
default-catalogers: []
# add, remove, and filter the catalogers to be used
# SYFT_SELECT_CATALOGERS env var / --select-catalogers flag;
select-catalogers: []
# all format configuration
format:
# default value for all formats that support the "pretty" option (default is unset)
# SYFT_FORMAT_PRETTY env var
pretty:
# all syft-json format options
@@ -566,7 +590,6 @@ format:
# 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
# SYFT_FORMAT_JSON_PRETTY env var
pretty: false
# transform any syft-json output to conform to an approximation of the v11.0.1 schema. This includes:
@@ -576,30 +599,21 @@ format:
# 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 (it may change or break at any time).
# SYFT_FORMAT_JSON_LEGACY env var
# 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.
# SYFT_FORMAT_TEMPLATE_PATH env var / -t flag
# same as -t ; SYFT_TEMPLATE_PATH env var
path: ""
# if true, uses the go structs for the syft-json format for templating.
# if false, uses the syft-json output for templating (which follows the syft JSON schema exactly).
#
# Note: long term support for this option is not guaranteed (it may change or break at any time).
# SYFT_FORMAT_TEMPLATE_LEGACY env var
legacy: false
# 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
# SYFT_FORMAT_SPDX_JSON_PRETTY env var
pretty: false
# all cyclonedx-json format options
@@ -607,7 +621,6 @@ format:
# include space indention and newlines
# note: inherits default value from 'format.pretty' or 'false' if parent is unset
# SYFT_FORMAT_CYCLONEDX_JSON_PRETTY env var
pretty: false
# all cyclonedx-xml format options
@@ -615,7 +628,6 @@ format:
# include space indention
# note: inherits default value from 'format.pretty' or 'false' if parent is unset
# SYFT_FORMAT_CYCLONEDX_XML_PRETTY env var
pretty: false
@@ -624,17 +636,16 @@ file:
metadata:
# select which files should be captured by the file-metadata cataloger and included in the SBOM.
# Options include:
# - "all": capture all files from the search space
# - "owned-by-package": capture only files owned by packages
# - "none", "": do not capture any files
# - "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-by-package"
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"
- "sha1"
digests: ["sha256"]
# capture the contents of select files in the SBOM
content:
@@ -663,7 +674,6 @@ package:
# allows users to exclude synthetic binary packages from the sbom
# these packages are removed if an overlap with a non-synthetic package is found
# SYFT_PACKAGE_EXCLUDE_BINARY_OVERLAP_BY_OWNERSHIP env var
exclude-binary-overlap-by-ownership: true
@@ -691,24 +701,6 @@ golang:
# SYFT_GOLANG_NOPROXY env var
no-proxy: ""
# the go main module version discovered from binaries built with the go compiler will
# always show (devel) as the version. Use these options to control heuristics to guess
# a more accurate version from the binary.
main-module-version:
# look for LD flags that appear to be setting a version (e.g. -X main.version=1.0.0)
# SYFT_GOLANG_MAIN_MODULE_VERSION_FROM_LD_FLAGS env var
from-ld-flags: true
# use the build settings (e.g. vcs.version & vcs.time) to craft a v0 pseudo version
# (e.g. v0.0.0-20220308212642-53e6d0aaf6fb) when a more accurate version cannot be found otherwise.
# SYFT_GOLANG_MAIN_MODULE_VERSION_FROM_BUILD_SETTINGS env var
from-build-settings: true
# search for semver-like strings in the binary contents.
# SYFT_GOLANG_MAIN_MODULE_VERSION_FROM_CONTENTS env var
from-contents: true
java:
maven-url: "https://repo1.maven.org/maven2"
max-parent-recursive-depth: 5
@@ -740,23 +732,17 @@ javascript:
# 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
# SYFT_SOURCE_NAME env var; --source-name flag
name: ""
# alias version for the source
# SYFT_SOURCE_VERSION env var / --source-version flag
# SYFT_SOURCE_VERSION env var; --source-version flag
version: ""
# base directory for scanning, no links will be followed above this directory, and all paths will be
# reported relative to this directory
# SYFT_SOURCE_BASE_PATH env var
base-path: ''
# options affecting the file source type
# 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"
digests: ["sha256"]
image:
@@ -818,15 +804,15 @@ attest:
log:
# use structured logging
# SYFT_LOG_STRUCTURED env var
# same as SYFT_LOG_STRUCTURED env var
structured: false
# the log level; note: detailed logging suppress the ETUI
# SYFT_LOG_LEVEL env var
# same as SYFT_LOG_LEVEL env var
level: "error"
# location to write the log file (default is not to have a log file)
# SYFT_LOG_FILE env var
# same as SYFT_LOG_FILE env var
file: ""
```
-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.
+11 -28
View File
@@ -185,7 +185,7 @@ tasks:
- fixtures
vars:
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)
COVERAGE_THRESHOLD: 62
@@ -197,7 +197,7 @@ tasks:
integration:
desc: Run integration tests
cmds:
- "go test -v ./cmd/syft/internal/test/integration"
- "go test -v ./test/integration"
# exercise most of the CLI with the data race detector
- "go run -race cmd/syft/main.go alpine:latest"
@@ -265,9 +265,7 @@ tasks:
fingerprints:
desc: Generate test fixture fingerprints
generates:
- cmd/syft/internal/test/integration/test-fixtures/cache.fingerprint
- syft/file/cataloger/executable/test-fixtures/elf/cache.fingerprint
- syft/file/cataloger/executable/test-fixtures/shared-info/cache.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
@@ -276,22 +274,19 @@ tasks:
- test/install/cache.fingerprint
- test/cli/test-fixtures/cache.fingerprint
cmds:
# for EXECUTABLE unit test fixtures
- "cd syft/file/cataloger/executable/test-fixtures/elf && make cache.fingerprint"
- "cd syft/file/cataloger/executable/test-fixtures/shared-info && make cache.fingerprint"
# for IMAGE integration test fixtures
- "cd cmd/syft/internal/test/integration/test-fixtures && make cache.fingerprint"
# for BINARY unit test fixtures
- "cd test/integration/test-fixtures && make cache.fingerprint"
# for BINARY test fixtures
- "cd syft/pkg/cataloger/binary/test-fixtures && make cache.fingerprint"
# for JAVA BUILD unit test fixtures
# for JAVA BUILD test fixtures
- "cd syft/pkg/cataloger/java/test-fixtures/java-builds && make cache.fingerprint"
# for GO BINARY unit test fixtures
# for GO BINARY test fixtures
- "cd syft/pkg/cataloger/golang/test-fixtures/archs && make binaries.fingerprint"
# for RPM unit test fixtures
# for RPM test fixtures
- "cd syft/pkg/cataloger/redhat/test-fixtures && make rpms.fingerprint"
# for Kernel unit test fixtures
# for Kernel test fixtures
- "cd syft/pkg/cataloger/kernel/test-fixtures && make cache.fingerprint"
# for INSTALL test fixtures
# for INSTALL integration test fixtures
- "cd test/install && make cache.fingerprint"
# for CLI test fixtures
- "cd test/cli/test-fixtures && make cache.fingerprint"
@@ -299,8 +294,6 @@ tasks:
fixtures:
desc: Generate test fixtures
cmds:
- "cd syft/file/cataloger/executable/test-fixtures/elf && make"
- "cd syft/file/cataloger/executable/test-fixtures/shared-info && make"
- "cd syft/pkg/cataloger/java/test-fixtures/java-builds && make"
- "cd syft/pkg/cataloger/redhat/test-fixtures && make"
- "cd syft/pkg/cataloger/binary/test-fixtures && make"
@@ -390,7 +383,7 @@ tasks:
generate-cpe-dictionary-index:
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:
- "go generate"
@@ -470,16 +463,6 @@ tasks:
silent: true
- "{{ .RELEASE_CMD }}"
ci-release-version-file:
# desc: "[CI only] Update the version file"
deps: [tools]
cmds:
- task: ci-check
- ".github/scripts/update-version-file.sh {{ .RELEASE_VERSION }}"
requires:
vars:
- RELEASE_VERSION
## Cleanup targets #################################
+45 -4
View File
@@ -8,8 +8,13 @@ import (
"github.com/spf13/cobra"
"github.com/anchore/clio"
"github.com/anchore/syft/cmd/syft/internal"
"github.com/anchore/syft/cmd/syft/internal/commands"
"github.com/anchore/stereoscope"
"github.com/anchore/syft/cmd/syft/cli/commands"
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`.
@@ -29,7 +34,44 @@ func Command(id clio.Identification) *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)
@@ -44,7 +86,6 @@ func create(id clio.Identification, out io.Writer) (clio.Application, *cobra.Com
rootCmd.AddCommand(
scanCmd,
commands.Packages(app, scanCmd), // this is currently an alias for the scan command
commands.Cataloger(app),
commands.Attest(app),
commands.Convert(app),
clio.VersionCommand(id),
@@ -13,8 +13,7 @@ import (
"github.com/wagoodman/go-progress"
"github.com/anchore/clio"
"github.com/anchore/stereoscope"
"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/internal"
"github.com/anchore/syft/internal/bus"
@@ -27,10 +26,11 @@ import (
"github.com/anchore/syft/syft/format/spdxtagvalue"
"github.com/anchore/syft/syft/format/syftjson"
"github.com/anchore/syft/syft/sbom"
"github.com/anchore/syft/syft/source"
)
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
attestHelp = attestExample + attestSchemeHelp
@@ -42,7 +42,7 @@ type attestOptions struct {
options.Output `yaml:",inline" mapstructure:",squash"`
options.UpdateCheck `yaml:",inline" mapstructure:",squash"`
options.Catalog `yaml:",inline" mapstructure:",squash"`
Attest options.Attest `yaml:"attest" mapstructure:"attest"`
options.Attest `yaml:",inline" mapstructure:",squash"`
}
func Attest(app clio.Application) *cobra.Command {
@@ -232,7 +232,7 @@ func attestCommand(sbomFilepath string, opts *attestOptions, userInput 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
switch strings.ToLower(outputName) {
case "cyclonedx-json":
@@ -247,11 +247,7 @@ func predicateType(outputName string) string {
}
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) {
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)
src, err := getSource(opts, userInput, onlyContainerImages)
if err != nil {
return nil, err
@@ -277,6 +273,13 @@ func generateSBOMForAttestation(ctx context.Context, id clio.Identification, opt
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 {
_, err := exec.LookPath(cmd)
return err == nil
@@ -4,22 +4,17 @@ import (
"bytes"
"context"
"fmt"
"io"
"os/exec"
"regexp"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/scylladb/go-set/strset"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/anchore/clio"
"github.com/anchore/clio/cliotestutils"
"github.com/anchore/syft/cmd/syft/internal"
"github.com/anchore/syft/cmd/syft/internal/options"
"github.com/anchore/syft/cmd/syft/cli/options"
"github.com/anchore/syft/syft/sbom"
"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/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/internal"
"github.com/anchore/syft/internal/log"
@@ -47,7 +47,7 @@ func Convert(app clio.Application) *cobra.Command {
}),
Args: validateConvertArgs,
PreRunE: applicationUpdateCheck(id, &opts.UpdateCheck),
RunE: func(_ *cobra.Command, args []string) error {
RunE: func(cmd *cobra.Command, args []string) error {
restoreStdout := ui.CaptureStdoutToTraceLog()
defer restoreStdout()
@@ -5,7 +5,6 @@ import (
"github.com/anchore/clio"
"github.com/anchore/syft/cmd/syft/internal/ui"
"github.com/anchore/syft/internal"
)
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()
cmd := app.SetupCommand(&cobra.Command{
Use: "packages [SOURCE]",
Short: scanCmd.Short,
Long: scanCmd.Long,
Args: scanCmd.Args,
Example: internal.Tprintf(scanHelp, map[string]interface{}{
"appName": id.Name,
"command": "packages",
}),
Use: "packages [SOURCE]",
Short: scanCmd.Short,
Long: scanCmd.Long,
Args: scanCmd.Args,
Example: scanCmd.Example,
PreRunE: applicationUpdateCheck(id, &opts.UpdateCheck),
RunE: func(cmd *cobra.Command, args []string) error {
restoreStdout := ui.CaptureStdoutToTraceLog()
@@ -13,10 +13,8 @@ import (
"gopkg.in/yaml.v3"
"github.com/anchore/clio"
"github.com/anchore/go-collections"
"github.com/anchore/stereoscope"
"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/internal"
"github.com/anchore/syft/internal/bus"
@@ -26,7 +24,6 @@ import (
"github.com/anchore/syft/syft"
"github.com/anchore/syft/syft/sbom"
"github.com/anchore/syft/syft/source"
"github.com/anchore/syft/syft/source/sourceproviders"
)
const (
@@ -135,7 +132,7 @@ func (o *scanOptions) validateLegacyOptionsNotUsed() error {
}
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")
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 {
@@ -165,23 +162,14 @@ func validateArgs(cmd *cobra.Command, args []string, error string) error {
return cobra.MaximumNArgs(1)(cmd, args)
}
// nolint:funlen
func runScan(ctx context.Context, id clio.Identification, opts *scanOptions, userInput string) error {
writer, err := opts.SBOMWriter()
if err != nil {
return err
}
sources := opts.From
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...)
src, err := getSource(&opts.Catalog, userInput)
if err != nil {
return err
@@ -211,21 +199,23 @@ func runScan(ctx context.Context, id clio.Identification, opts *scanOptions, use
return nil
}
func getSource(ctx context.Context, opts *options.Catalog, userInput string, sources ...string) (source.Source, error) {
cfg := syft.DefaultGetSourceConfig().
WithRegistryOptions(opts.Registry.ToOptions()).
WithAlias(source.Alias{
Name: opts.Source.Name,
Version: opts.Source.Version,
}).
WithExcludeConfig(source.ExcludeConfig{
Paths: opts.Exclusions,
}).
WithBasePath(opts.Source.BasePath).
WithSources(sources...).
WithDefaultImagePullSource(opts.Source.Image.DefaultPullSource)
func getSource(opts *options.Catalog, userInput string, filters ...func(*source.Detection) error) (source.Source, error) {
detection, err := source.Detect(
userInput,
source.DetectConfig{
DefaultImageSource: opts.Source.Image.DefaultPullSource,
},
)
if err != nil {
return nil, fmt.Errorf("could not deteremine source: %w", err)
}
for _, filter := range filters {
if err := filter(detection); err != nil {
return nil, err
}
}
var err error
var platform *image.Platform
if opts.Platform != "" {
@@ -233,20 +223,34 @@ func getSource(ctx context.Context, opts *options.Catalog, userInput string, sou
if err != nil {
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...)
if err != nil {
return nil, fmt.Errorf("invalid hash algorithm: %w", err)
}
cfg = cfg.WithDigestAlgorithms(hashers...)
}
src, err := syft.GetSource(ctx, userInput, cfg)
hashers, err := file.Hashers(opts.Source.File.Digests...)
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
@@ -441,7 +445,3 @@ func getHintPhrase(expErr task.ErrInvalidExpression) string {
func trimOperation(x string) string {
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/anchore/syft/cmd/syft/internal/options"
"github.com/anchore/syft/cmd/syft/cli/options"
)
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",
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",
@@ -11,8 +11,8 @@ import (
"github.com/anchore/clio"
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/options"
"github.com/anchore/syft/internal/bus"
"github.com/anchore/syft/internal/log"
"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 {
return func(_ *cobra.Command, _ []string) error {
return func(cmd *cobra.Command, args []string) error {
if check.CheckForAppUpdate {
checkForApplicationUpdate(id)
}
@@ -4,8 +4,6 @@ import (
"github.com/anchore/clio"
)
var _ clio.FlagAdder = (*Attest)(nil)
type Attest struct {
// IMPORTANT: do not show the attestation key/password in any YAML/JSON output (sensitive information)
Key secret `yaml:"key" json:"key" mapstructure:"key"`
@@ -14,6 +12,6 @@ type Attest struct {
var _ clio.FlagAdder = (*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")
}
@@ -15,7 +15,6 @@ import (
"github.com/anchore/syft/syft/cataloging"
"github.com/anchore/syft/syft/cataloging/filecataloging"
"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/pkg/cataloger/binary"
"github.com/anchore/syft/syft/pkg/cataloger/golang"
@@ -46,8 +45,8 @@ type Catalog struct {
// configuration for the source (the subject being analyzed)
Registry registryConfig `yaml:"registry" json:"registry" mapstructure:"registry"`
From []string `yaml:"from" json:"from" mapstructure:"from"`
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"`
Exclusions []string `yaml:"exclude" json:"exclude" mapstructure:"exclude"`
}
@@ -63,7 +62,6 @@ func DefaultCatalog() Catalog {
Scope: source.SquashedScope.String(),
Package: defaultPackageConfig(),
LinuxKernel: defaultLinuxKernelConfig(),
Golang: defaultGolangConfig(),
File: defaultFileConfig(),
Relationships: defaultRelationshipsConfig(),
Source: defaultSourceConfig(),
@@ -114,10 +112,6 @@ func (cfg Catalog) ToFilesConfig() filecataloging.Config {
Globs: cfg.File.Content.Globs,
SkipFilesAboveSize: cfg.File.Content.SkipFilesAboveSize,
},
Executable: executable.Config{
MIMETypes: executable.DefaultConfig().MIMETypes,
Globs: cfg.File.Executable.Globs,
},
}
}
@@ -127,19 +121,13 @@ func (cfg Catalog) ToPackagesConfig() pkgcataloging.Config {
IncludeUnindexedArchives: cfg.Package.SearchUnindexedArchives,
}
return pkgcataloging.Config{
Binary: binary.DefaultClassifierCatalogerConfig(),
Binary: binary.DefaultCatalogerConfig(),
Golang: golang.DefaultCatalogerConfig().
WithSearchLocalModCacheLicenses(cfg.Golang.SearchLocalModCacheLicenses).
WithLocalModCacheDir(cfg.Golang.LocalModCacheDir).
WithSearchRemoteLicenses(cfg.Golang.SearchRemoteLicenses).
WithProxy(cfg.Golang.Proxy).
WithNoProxy(cfg.Golang.NoProxy).
WithMainModuleVersion(
golang.DefaultMainModuleVersionConfig().
WithFromContents(cfg.Golang.MainModuleVersion.FromContents).
WithFromBuildSettings(cfg.Golang.MainModuleVersion.FromBuildSettings).
WithFromLDFlags(cfg.Golang.MainModuleVersion.FromLDFlags),
),
WithNoProxy(cfg.Golang.NoProxy),
JavaScript: javascript.DefaultCatalogerConfig().
WithSearchRemoteLicenses(cfg.JavaScript.SearchRemoteLicenses).
WithNpmBaseURL(cfg.JavaScript.NpmBaseURL),
@@ -164,9 +152,6 @@ func (cfg *Catalog) AddFlags(flags clio.FlagSet) {
flags.StringVarP(&cfg.Scope, "scope", "s",
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", "",
"an optional platform specifier for container image sources (e.g. 'linux/arm64', 'linux/arm64/v8', 'arm64', 'linux')")
@@ -190,6 +175,14 @@ func (cfg *Catalog) AddFlags(flags clio.FlagSet) {
flags.StringArrayVarP(&cfg.SelectCatalogers, "select-catalogers", "",
"add, remove, and filter the catalogers to be used")
flags.StringVarP(&cfg.Source.Name, "name", "",
"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", "",
"set the name of the target being analyzed")
@@ -205,6 +198,13 @@ func (cfg *Catalog) DescribeFields(descriptions fangs.FieldDescriptionSet) {
}
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
usingNewCatalogers := len(cfg.DefaultCatalogers) > 0 || len(cfg.SelectCatalogers) > 0
@@ -224,8 +224,6 @@ func (cfg *Catalog) PostLoad() error {
return out
}
cfg.From = flatten(cfg.From)
cfg.Catalogers = flatten(cfg.Catalogers)
cfg.DefaultCatalogers = flatten(cfg.DefaultCatalogers)
cfg.SelectCatalogers = flatten(cfg.SelectCatalogers)
@@ -2,18 +2,14 @@ package options
import (
"fmt"
"sort"
"github.com/scylladb/go-set/strset"
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"`
Executable fileExecutable `yaml:"executable" json:"executable" mapstructure:"executable"`
Metadata fileMetadata `yaml:"metadata" json:"metadata" mapstructure:"metadata"`
Content fileContent `yaml:"content" json:"content" mapstructure:"content"`
}
type fileMetadata struct {
@@ -26,10 +22,6 @@ type fileContent struct {
Globs []string `yaml:"globs" json:"globs" mapstructure:"globs"`
}
type fileExecutable struct {
Globs []string `yaml:"globs" json:"globs" mapstructure:"globs"`
}
func defaultFileConfig() fileConfig {
return fileConfig{
Metadata: fileMetadata{
@@ -39,17 +31,10 @@ func defaultFileConfig() fileConfig {
Content: fileContent{
SkipFilesAboveSize: 250 * intFile.KB,
},
Executable: fileExecutable{
Globs: nil,
},
}
}
func (c *fileConfig) PostLoad() error {
digests := strset.New(c.Metadata.Digests...).List()
sort.Strings(digests)
c.Metadata.Digests = digests
switch c.Metadata.Selection {
case file.NoFilesSelection, file.FilesOwnedByPackageSelection, file.AllFilesSelection:
return nil
+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 = setAllToNonZero(t, ft).(*FormatCyclonedxJSON)
subject := ft.config("Version")
subject := ft.buildConfig("Version")
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{}
ftp := setAllToNonZero(t, &ft).(*FormatCyclonedxXML)
subject := ftp.config("Version")
subject := ftp.buildConfig("Version")
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 = setAllToNonZero(t, ft).(*FormatSPDXJSON)
subject := ft.config("Version")
subject := ft.buildConfig("Version")
assertExpectedValue(t, subject)
}
@@ -2,6 +2,7 @@ package options
import (
"github.com/anchore/syft/syft/format/syftjson"
"github.com/anchore/syft/syft/sbom"
)
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
if o.Pretty != nil {
pretty = *o.Pretty
@@ -10,6 +10,6 @@ func TestFormatSyftJSON_buildConfig(t *testing.T) {
ft := &FormatSyftJSON{}
ft = setAllToNonZero(t, ft).(*FormatSyftJSON)
subject := ft.config()
subject := ft.buildConfig()
assertExpectedValue(t, subject)
}
@@ -3,6 +3,7 @@ package options
import (
"github.com/anchore/clio"
"github.com/anchore/syft/syft/format/template"
"github.com/anchore/syft/syft/sbom"
)
var _ clio.FlagAdder = (*FormatTemplate)(nil)
@@ -10,7 +11,6 @@ var _ clio.FlagAdder = (*FormatTemplate)(nil)
type FormatTemplate struct {
Enabled bool `yaml:"-" json:"-" mapstructure:"-"`
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 {
@@ -26,9 +26,12 @@ func (o *FormatTemplate) AddFlags(flags clio.FlagSet) {
}
}
func (o FormatTemplate) config() template.EncoderConfig {
return template.EncoderConfig{
TemplatePath: o.Path,
Legacy: o.Legacy,
func (o FormatTemplate) formatEncoders() ([]sbom.FormatEncoder, error) {
if !o.Enabled {
return nil, nil
}
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"`
}
@@ -2,12 +2,9 @@ package options
import (
"fmt"
"sort"
"strings"
"github.com/scylladb/go-set/strset"
"github.com/anchore/syft/syft/source/sourceproviders"
)
type sourceConfig struct {
@@ -27,24 +24,16 @@ type imageSource struct {
}
func defaultSourceConfig() sourceConfig {
var digests []string
for _, alg := range sourceproviders.DefaultConfig().DigestAlgorithms {
digests = append(digests, alg.String())
}
return sourceConfig{
File: fileSource{
Digests: digests,
Digests: []string{"sha256"},
},
Image: imageSource{
DefaultPullSource: "",
},
}
}
func (c *fileSource) PostLoad() error {
digests := strset.New(c.Digests...).List()
sort.Strings(digests)
c.Digests = digests
return nil
}
func (c imageSource) PostLoad() error {
return checkDefaultSourceValues(c.DefaultPullSource)
}
-55
View File
@@ -1,55 +0,0 @@
package internal
import (
"io"
"os"
"github.com/anchore/clio"
"github.com/anchore/stereoscope"
ui2 "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"
)
func AppClioSetupConfig(id clio.Identification, out io.Writer) *clio.SetupConfig {
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,
ui2.New(ui2.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(_ *clio.State, _ error) {
stereoscope.Cleanup()
})
return clioCfg
}
-20
View File
@@ -1,20 +0,0 @@
package commands
import (
"github.com/spf13/cobra"
"github.com/anchore/clio"
)
func Cataloger(app clio.Application) *cobra.Command {
cmd := &cobra.Command{
Use: "cataloger",
Short: "Show available catalogers and configuration",
}
cmd.AddCommand(
CatalogerList(app),
)
return cmd
}
@@ -1,267 +0,0 @@
package commands
import (
"encoding/json"
"fmt"
"sort"
"strings"
"github.com/charmbracelet/lipgloss"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/scylladb/go-set/strset"
"github.com/spf13/cobra"
"github.com/anchore/clio"
"github.com/anchore/syft/internal/bus"
"github.com/anchore/syft/internal/task"
"github.com/anchore/syft/syft/cataloging/pkgcataloging"
)
type catalogerListOptions struct {
Output string `yaml:"output" json:"output" mapstructure:"output"`
DefaultCatalogers []string `yaml:"default-catalogers" json:"default-catalogers" mapstructure:"default-catalogers"`
SelectCatalogers []string `yaml:"select-catalogers" json:"select-catalogers" mapstructure:"select-catalogers"`
ShowHidden bool `yaml:"show-hidden" json:"show-hidden" mapstructure:"show-hidden"`
}
func (o *catalogerListOptions) AddFlags(flags clio.FlagSet) {
flags.StringVarP(&o.Output, "output", "o", "format to output the cataloger list (available: table, json)")
flags.StringArrayVarP(&o.DefaultCatalogers, "override-default-catalogers", "", "override the default catalogers with an expression")
flags.StringArrayVarP(&o.SelectCatalogers, "select-catalogers", "", "select catalogers with an expression")
flags.BoolVarP(&o.ShowHidden, "show-hidden", "s", "show catalogers that have been de-selected")
}
func defaultCatalogerListOptions() *catalogerListOptions {
return &catalogerListOptions{
DefaultCatalogers: []string{"all"},
}
}
func CatalogerList(app clio.Application) *cobra.Command {
opts := defaultCatalogerListOptions()
return app.SetupCommand(&cobra.Command{
Use: "list [OPTIONS]",
Short: "List available catalogers",
RunE: func(_ *cobra.Command, _ []string) error {
return runCatalogerList(opts)
},
}, opts)
}
func runCatalogerList(opts *catalogerListOptions) error {
factories := task.DefaultPackageTaskFactories()
allTasks, err := factories.Tasks(task.DefaultCatalogingFactoryConfig())
if err != nil {
return fmt.Errorf("unable to create cataloger tasks: %w", err)
}
report, err := catalogerListReport(opts, allTasks)
if err != nil {
return fmt.Errorf("unable to generate cataloger list report: %w", err)
}
bus.Report(report)
return nil
}
func catalogerListReport(opts *catalogerListOptions, allTasks []task.Task) (string, error) {
selectedTasks, selectionEvidence, err := task.Select(allTasks,
pkgcataloging.NewSelectionRequest().
WithDefaults(opts.DefaultCatalogers...).
WithExpression(opts.SelectCatalogers...),
)
if err != nil {
return "", fmt.Errorf("unable to select catalogers: %w", err)
}
var report string
switch opts.Output {
case "json":
report, err = renderCatalogerListJSON(selectedTasks, selectionEvidence, opts.DefaultCatalogers, opts.SelectCatalogers)
case "table", "":
if opts.ShowHidden {
report = renderCatalogerListTable(allTasks, selectionEvidence, opts.DefaultCatalogers, opts.SelectCatalogers)
} else {
report = renderCatalogerListTable(selectedTasks, selectionEvidence, opts.DefaultCatalogers, opts.SelectCatalogers)
}
}
if err != nil {
return "", fmt.Errorf("unable to render cataloger list: %w", err)
}
return report, nil
}
func renderCatalogerListJSON(tasks []task.Task, selection task.Selection, defaultSelections, selections []string) (string, error) {
type node struct {
Name string `json:"name"`
Tags []string `json:"tags"`
}
names, tagsByName := extractTaskInfo(tasks)
nodesByName := make(map[string]node)
for name := range tagsByName {
tagsSelected := selection.TokensByTask[name].SelectedOn.List()
if len(tagsSelected) == 1 && tagsSelected[0] == "all" {
tagsSelected = tagsByName[name]
}
sort.Strings(tagsSelected)
if tagsSelected == nil {
// ensure collections are not null
tagsSelected = []string{}
}
nodesByName[name] = node{
Name: name,
Tags: tagsSelected,
}
}
type document struct {
DefaultSelection []string `json:"default"`
Selection []string `json:"selection"`
Catalogers []node `json:"catalogers"`
}
if selections == nil {
// ensure collections are not null
selections = []string{}
}
doc := document{
DefaultSelection: defaultSelections,
Selection: selections,
}
for _, name := range names {
doc.Catalogers = append(doc.Catalogers, nodesByName[name])
}
by, err := json.Marshal(doc)
return string(by), err
}
func renderCatalogerListTable(tasks []task.Task, selection task.Selection, defaultSelections, selections []string) string {
t := table.NewWriter()
t.SetStyle(table.StyleLight)
t.AppendHeader(table.Row{"Cataloger", "Tags"})
names, tagsByName := extractTaskInfo(tasks)
rowsByName := make(map[string]table.Row)
for name, tags := range tagsByName {
rowsByName[name] = formatRow(name, tags, selection)
}
for _, name := range names {
t.AppendRow(rowsByName[name])
}
report := t.Render()
if len(selections) > 0 {
header := "Selected by expressions:\n"
for _, expr := range selections {
header += fmt.Sprintf(" - %q\n", expr)
}
report = header + report
}
if len(defaultSelections) > 0 {
header := "Default selections:\n"
for _, expr := range defaultSelections {
header += fmt.Sprintf(" - %q\n", expr)
}
report = header + report
}
return report
}
func formatRow(name string, tags []string, selection task.Selection) table.Row {
isIncluded := selection.Result.Has(name)
var selections *task.TokenSelection
if s, exists := selection.TokensByTask[name]; exists {
selections = &s
}
var formattedTags []string
for _, tag := range tags {
formattedTags = append(formattedTags, formatToken(tag, selections, isIncluded))
}
var tagStr string
if isIncluded {
tagStr = strings.Join(formattedTags, ", ")
} else {
tagStr = strings.Join(formattedTags, grey.Render(", "))
}
// TODO: selection should keep warnings (non-selections) in struct
return table.Row{
formatToken(name, selections, isIncluded),
tagStr,
}
}
var (
green = lipgloss.NewStyle().Foreground(lipgloss.Color("10")) // hi green
grey = lipgloss.NewStyle().Foreground(lipgloss.Color("8")) // dark grey
red = lipgloss.NewStyle().Foreground(lipgloss.Color("9")) // high red
)
func formatToken(token string, selection *task.TokenSelection, included bool) string {
if included && selection != nil {
// format all tokens in selection in green
if selection.SelectedOn.Has(token) {
return green.Render(token)
}
return token
}
// format all tokens in selection in red, all others in grey
if selection != nil && selection.DeselectedOn.Has(token) {
return red.Render(token)
}
return grey.Render(token)
}
func extractTaskInfo(tasks []task.Task) ([]string, map[string][]string) {
tagsByName := make(map[string][]string)
var names []string
for _, tsk := range tasks {
var tags []string
name := tsk.Name()
if s, ok := tsk.(task.Selector); ok {
set := strset.New(s.Selectors()...)
set.Remove(name)
tags = set.List()
sort.Strings(tags)
}
tagsByName[name] = tags
names = append(names, name)
}
sort.Strings(names)
return names, tagsByName
}
@@ -1,201 +0,0 @@
package commands
import (
"context"
"strings"
"testing"
"github.com/scylladb/go-set/strset"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/anchore/syft/internal/sbomsync"
"github.com/anchore/syft/internal/task"
"github.com/anchore/syft/syft/file"
)
var _ interface {
task.Task
task.Selector
} = (*dummyTask)(nil)
type dummyTask struct {
name string
selectors []string
}
func (d dummyTask) HasAllSelectors(s ...string) bool {
return strset.New(d.selectors...).Has(s...)
}
func (d dummyTask) Selectors() []string {
return d.selectors
}
func (d dummyTask) Name() string {
return d.name
}
func (d dummyTask) Execute(_ context.Context, _ file.Resolver, _ sbomsync.Builder) error {
panic("implement me")
}
func testTasks() []task.Task {
return []task.Task{
dummyTask{
name: "task1",
selectors: []string{"image", "a", "b", "1"},
},
dummyTask{
name: "task2",
selectors: []string{"image", "b", "c", "2"},
},
dummyTask{
name: "task3",
selectors: []string{"directory", "c", "d", "3"},
},
dummyTask{
name: "task4",
selectors: []string{"directory", "d", "e", "4"},
},
}
}
func Test_catalogerListReport(t *testing.T) {
tests := []struct {
name string
options *catalogerListOptions
want string
wantErr require.ErrorAssertionFunc
}{
{
name: "no expressions, table",
options: func() *catalogerListOptions {
c := defaultCatalogerListOptions()
c.Output = "table"
return c
}(),
want: `
Default selections:
- "all"
┌───────────┬────────────────────┐
│ CATALOGER │ TAGS │
├───────────┼────────────────────┤
│ task1 │ 1, a, b, image │
│ task2 │ 2, b, c, image │
│ task3 │ 3, c, d, directory │
│ task4 │ 4, d, directory, e │
└───────────┴────────────────────┘
`,
},
{
name: "no expressions, json",
options: func() *catalogerListOptions {
c := defaultCatalogerListOptions()
c.Output = "json"
return c
}(),
want: `
{"default":["all"],"selection":[],"catalogers":[{"name":"task1","tags":["1","a","b","image"]},{"name":"task2","tags":["2","b","c","image"]},{"name":"task3","tags":["3","c","d","directory"]},{"name":"task4","tags":["4","d","directory","e"]}]}
`,
},
{
name: "no expressions, default selection, table",
options: func() *catalogerListOptions {
c := defaultCatalogerListOptions()
c.Output = "table"
c.DefaultCatalogers = []string{
"image",
}
return c
}(),
want: `
Default selections:
- "image"
┌───────────┬────────────────┐
│ CATALOGER │ TAGS │
├───────────┼────────────────┤
│ task1 │ 1, a, b, image │
│ task2 │ 2, b, c, image │
└───────────┴────────────────┘
`,
},
{
name: "no expressions, default selection, json",
options: func() *catalogerListOptions {
c := defaultCatalogerListOptions()
c.Output = "json"
c.DefaultCatalogers = []string{
"image",
}
return c
}(),
want: `
{"default":["image"],"selection":[],"catalogers":[{"name":"task1","tags":["image"]},{"name":"task2","tags":["image"]}]}
`,
},
{
name: "with expressions, default selection, table",
options: func() *catalogerListOptions {
c := defaultCatalogerListOptions()
c.Output = "table"
c.DefaultCatalogers = []string{
"image",
}
c.SelectCatalogers = []string{
"-directory",
"+task3",
"-c",
"b",
}
return c
}(),
want: `
Default selections:
- "image"
Selected by expressions:
- "-directory"
- "+task3"
- "-c"
- "b"
┌───────────┬────────────────────┐
│ CATALOGER │ TAGS │
├───────────┼────────────────────┤
│ task1 │ 1, a, b, image │
│ task3 │ 3, c, d, directory │
└───────────┴────────────────────┘
`,
},
{
name: "with expressions, default selection, json",
options: func() *catalogerListOptions {
c := defaultCatalogerListOptions()
c.Output = "json"
c.DefaultCatalogers = []string{
"image",
}
c.SelectCatalogers = []string{
"-directory",
"+task3",
"-c",
"b",
}
return c
}(),
want: `
{"default":["image"],"selection":["-directory","+task3","-c","b"],"catalogers":[{"name":"task1","tags":["b","image"]},{"name":"task3","tags":["task3"]}]}
`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.wantErr == nil {
tt.wantErr = require.NoError
}
got, err := catalogerListReport(tt.options, testTasks())
tt.wantErr(t, err)
assert.Equal(t, strings.TrimSpace(tt.want), strings.TrimSpace(got))
})
}
}
@@ -1,20 +0,0 @@
package commands
import (
"os"
"testing"
gologgerredact "github.com/anchore/go-logger/adapter/redact"
"github.com/anchore/syft/internal/redact"
)
func TestMain(m *testing.M) {
// Initialize global state needed to test clio/cobra commands directly
// Should be kept minimal.
// Initialize redact store once for all tests in the commands package
// Redact store must be wired up here because syft will panic unless
// a redact store is wired up exactly once
redact.Set(gologgerredact.NewStore())
os.Exit(m.Run())
}
-56
View File
@@ -1,56 +0,0 @@
package options
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/anchore/syft/syft/file"
)
func Test_fileConfig_PostLoad(t *testing.T) {
tests := []struct {
name string
cfg fileConfig
assert func(t *testing.T, cfg fileConfig)
wantErr assert.ErrorAssertionFunc
}{
{
name: "deduplicate digests",
cfg: fileConfig{
Metadata: fileMetadata{
Selection: file.NoFilesSelection,
Digests: []string{"sha1", "sha1"},
},
},
assert: func(t *testing.T, cfg fileConfig) {
assert.Equal(t, []string{"sha1"}, cfg.Metadata.Digests)
},
},
{
name: "error on invalid selection",
cfg: fileConfig{
Metadata: fileMetadata{
Selection: file.Selection("invalid"),
},
},
wantErr: assert.Error,
},
{
name: "error on empty selection",
cfg: fileConfig{},
wantErr: assert.Error,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.wantErr == nil {
tt.wantErr = assert.NoError
}
tt.wantErr(t, tt.cfg.PostLoad())
if tt.assert != nil {
tt.assert(t, tt.cfg)
}
})
}
}
-60
View File
@@ -1,60 +0,0 @@
package options
import (
"github.com/anchore/clio"
"github.com/anchore/syft/syft/format"
"github.com/anchore/syft/syft/format/spdxtagvalue"
"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) {
return format.EncodersConfig{
Template: o.Template.config(),
SyftJSON: o.SyftJSON.config(),
SPDXJSON: o.SPDXJSON.config(format.AllVersions), // we support multiple versions, not just a single version
SPDXTagValue: spdxtagvalue.EncoderConfig{Version: format.AllVersions}, // we support multiple versions, not just a single version
CyclonedxJSON: o.CyclonedxJSON.config(format.AllVersions), // we support multiple versions, not just a single version
CyclonedxXML: o.CyclonedxXML.config(format.AllVersions), // we support multiple versions, not just a single version
}.Encoders()
}
func multiLevelOption[T any](defaultValue T, option ...*T) *T {
result := defaultValue
for _, opt := range option {
if opt != nil {
result = *opt
}
}
return &result
}
@@ -1,24 +0,0 @@
package options
import (
"github.com/anchore/syft/syft/format/cyclonedxjson"
)
type FormatCyclonedxJSON struct {
Pretty *bool `yaml:"pretty" json:"pretty" mapstructure:"pretty"`
}
func DefaultFormatCyclonedxJSON() FormatCyclonedxJSON {
return FormatCyclonedxJSON{}
}
func (o FormatCyclonedxJSON) config(version string) cyclonedxjson.EncoderConfig {
var pretty bool
if o.Pretty != nil {
pretty = *o.Pretty
}
return cyclonedxjson.EncoderConfig{
Version: version,
Pretty: pretty,
}
}
@@ -1,24 +0,0 @@
package options
import (
"github.com/anchore/syft/syft/format/cyclonedxxml"
)
type FormatCyclonedxXML struct {
Pretty *bool `yaml:"pretty" json:"pretty" mapstructure:"pretty"`
}
func DefaultFormatCyclonedxXML() FormatCyclonedxXML {
return FormatCyclonedxXML{}
}
func (o FormatCyclonedxXML) config(version string) cyclonedxxml.EncoderConfig {
var pretty bool
if o.Pretty != nil {
pretty = *o.Pretty
}
return cyclonedxxml.EncoderConfig{
Version: version,
Pretty: pretty,
}
}
@@ -1,24 +0,0 @@
package options
import (
"github.com/anchore/syft/syft/format/spdxjson"
)
type FormatSPDXJSON struct {
Pretty *bool `yaml:"pretty" json:"pretty" mapstructure:"pretty"`
}
func DefaultFormatSPDXJSON() FormatSPDXJSON {
return FormatSPDXJSON{}
}
func (o FormatSPDXJSON) config(v string) spdxjson.EncoderConfig {
var pretty bool
if o.Pretty != nil {
pretty = *o.Pretty
}
return spdxjson.EncoderConfig{
Version: v,
Pretty: pretty,
}
}
-38
View File
@@ -1,38 +0,0 @@
package options
import (
"strings"
"github.com/anchore/syft/syft/pkg/cataloger/golang"
)
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"`
MainModuleVersion golangMainModuleVersionConfig `json:"main-module-version" yaml:"main-module-version" mapstructure:"main-module-version"`
}
type golangMainModuleVersionConfig struct {
FromLDFlags bool `json:"from-ld-flags" yaml:"from-ld-flags" mapstructure:"from-ld-flags"`
FromContents bool `json:"from-contents" yaml:"from-contents" mapstructure:"from-contents"`
FromBuildSettings bool `json:"from-build-settings" yaml:"from-build-settings" mapstructure:"from-build-settings"`
}
func defaultGolangConfig() golangConfig {
def := golang.DefaultCatalogerConfig()
return golangConfig{
SearchLocalModCacheLicenses: def.SearchLocalModCacheLicenses,
LocalModCacheDir: def.LocalModCacheDir,
SearchRemoteLicenses: def.SearchRemoteLicenses,
Proxy: strings.Join(def.Proxies, ","),
NoProxy: strings.Join(def.NoProxy, ","),
MainModuleVersion: golangMainModuleVersionConfig{
FromLDFlags: def.MainModuleVersion.FromLDFlags,
FromContents: def.MainModuleVersion.FromContents,
FromBuildSettings: def.MainModuleVersion.FromBuildSettings,
},
}
}
-37
View File
@@ -1,37 +0,0 @@
package options
import (
"testing"
"github.com/stretchr/testify/assert"
)
func Test_fileSource_PostLoad(t *testing.T) {
tests := []struct {
name string
cfg fileSource
assert func(t *testing.T, cfg fileSource)
wantErr assert.ErrorAssertionFunc
}{
{
name: "deduplicate digests",
cfg: fileSource{
Digests: []string{"sha1", "sha1"},
},
assert: func(t *testing.T, cfg fileSource) {
assert.Equal(t, []string{"sha1"}, cfg.Digests)
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.wantErr == nil {
tt.wantErr = assert.NoError
}
tt.wantErr(t, tt.cfg.PostLoad())
if tt.assert != nil {
tt.assert(t, tt.cfg)
}
})
}
}
@@ -1,65 +0,0 @@
package integration
import (
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/source"
"github.com/stretchr/testify/require"
"testing"
)
func TestBinaryElfRelationships(t *testing.T) {
// node --> ["dependency of" nodes]
expectedGraph := map[string][]string{
"glibc": {
"libhello_world.so",
"syfttestfixture",
},
"libstdc++": {
"syfttestfixture",
},
"libhello_world.so": {
"syfttestfixture",
},
}
// run the test...
sbom, _ := catalogFixtureImage(t, "elf-test-fixtures", source.SquashedScope)
// get a mapping of package names to their IDs
nameToId := map[string]artifact.ID{}
recordPkgId := func(name string) {
pkgs := sbom.Artifacts.Packages.PackagesByName(name)
require.NotEmpty(t, pkgs, "expected package %q to be present in the SBOM", name)
for _, p := range pkgs {
nameToId[p.Name] = p.ID()
}
}
for name, depNames := range expectedGraph {
recordPkgId(name)
for _, depName := range depNames {
recordPkgId(depName)
}
}
for name, expectedDepNames := range expectedGraph {
pkgId := nameToId[name]
p := sbom.Artifacts.Packages.Package(pkgId)
require.NotNil(t, p, "expected package %q to be present in the SBOM", name)
rels := sbom.RelationshipsForPackage(*p, artifact.DependencyOfRelationship)
require.NotEmpty(t, rels, "expected package %q to have relationships", name)
toIds := map[artifact.ID]struct{}{}
for _, rel := range rels {
toIds[rel.To.ID()] = struct{}{}
}
for _, depName := range expectedDepNames {
depId := nameToId[depName]
_, exists := toIds[depId]
require.True(t, exists, "expected package %q to have a relationship to %q", name, depName)
}
}
}
@@ -1,352 +0,0 @@
package integration
import (
"fmt"
"go/ast"
"go/parser"
"go/token"
"go/types"
"os"
"path/filepath"
"reflect"
"strings"
"testing"
"github.com/bmatcuk/doublestar/v4"
"github.com/scylladb/go-set/strset"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_packageCatalogerExports(t *testing.T) {
// sanity check that we are actually finding exports
exports := packageCatalogerExports(t)
require.NotEmpty(t, exports)
expectAtLeast := map[string]*strset.Set{
"golang": strset.New("NewGoModuleFileCataloger", "NewGoModuleBinaryCataloger", "CatalogerConfig", "DefaultCatalogerConfig"),
}
for pkg, expected := range expectAtLeast {
actual, ok := exports[pkg]
require.True(t, ok, pkg)
if !assert.True(t, actual.Names().IsSubset(expected), pkg) {
t.Logf("missing: %s", strset.SymmetricDifference(expected, actual.Names()))
}
}
}
func Test_validatePackageCatalogerExport(t *testing.T) {
cases := []struct {
name string
export exportToken
wantErr assert.ErrorAssertionFunc
}{
// valid...
{
name: "valid constructor",
export: exportToken{
Name: "NewFooCataloger",
Type: "*ast.FuncType",
SignatureSize: 1,
ReturnTypeNames: []string{
"pkg.Cataloger",
},
},
},
{
name: "valid default config",
export: exportToken{
Name: "DefaultFooConfig",
Type: "*ast.FuncType",
SignatureSize: 0,
},
},
{
name: "valid config",
export: exportToken{
Name: "FooConfig",
Type: "*ast.StructType",
},
},
// invalid...
{
name: "constructor that returns a concrete type",
export: exportToken{
Name: "NewFooCataloger",
Type: "*ast.FuncType",
SignatureSize: 1,
ReturnTypeNames: []string{
"*generic.Cataloger",
},
},
wantErr: assert.Error,
},
{
name: "struct with constructor name",
export: exportToken{
Name: "NewFooCataloger",
Type: "*ast.StructType",
},
wantErr: assert.Error,
},
{
name: "struct with default config fn name",
export: exportToken{
Name: "DefaultFooConfig",
Type: "*ast.StructType",
},
wantErr: assert.Error,
},
{
name: "fn with struct name",
export: exportToken{
Name: "FooConfig",
Type: "*ast.FuncType",
},
wantErr: assert.Error,
},
{
name: "default config with parameters",
export: exportToken{
Name: "DefaultFooConfig",
Type: "*ast.FuncType",
SignatureSize: 1,
},
wantErr: assert.Error,
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
if c.wantErr == nil {
c.wantErr = assert.NoError
}
err := validatePackageCatalogerExport(t, "test", c.export)
c.wantErr(t, err)
})
}
}
func Test_PackageCatalogerConventions(t *testing.T) {
// look at each package in syft/pkg/cataloger...
// we want to make certain that only the following things are exported from the package:
// - function matching New*Cataloger (e.g. NewAptCataloger)
// - function matching Default*Config
// - struct matching *Config
//
// anything else that is exported should result in the test failing.
// note: this is meant to apply to things in static space, not methods on structs or within interfaces.
//
// this additionally ensures that:
// - any config struct has a Default*Config function to pair with it.
// - all cataloger constructors return pkg.Cataloger interface instead of a concrete type
exportsPerPackage := packageCatalogerExports(t)
//for debugging purposes...
//for pkg, exports := range exportsPerPackage {
// t.Log(pkg)
// for _, export := range exports {
// t.Logf(" %#v", export)
// }
//}
for pkg, exports := range exportsPerPackage {
for _, export := range exports.List() {
// assert the export name is valid...
assert.NoError(t, validatePackageCatalogerExport(t, pkg, export))
// assert that config structs have a Default*Config functions to pair with them...
if strings.Contains(export.Name, "Config") && !strings.Contains(export.Name, "Default") {
// this is a config struct, make certain there is a pairing with a Default*Config function
assert.True(t, exports.Has("Default"+export.Name), "cataloger config struct %q in pkg %q must have a 'Default%s' function", export.Name, pkg, export.Name)
}
}
}
}
func validatePackageCatalogerExport(t *testing.T, pkg string, export exportToken) error {
constructorMatches, err := doublestar.Match("New*Cataloger", export.Name)
require.NoError(t, err)
defaultConfigMatches, err := doublestar.Match("Default*Config", export.Name)
require.NoError(t, err)
configMatches, err := doublestar.Match("*Config", export.Name)
require.NoError(t, err)
switch {
case constructorMatches:
if !export.isFunction() {
return fmt.Errorf("constructor convention used for non-function in pkg=%q: %#v", pkg, export)
}
returnTypes := strset.New(export.ReturnTypeNames...)
if !returnTypes.Has("pkg.Cataloger") {
return fmt.Errorf("constructor convention is to return pkg.Cataloger and not concrete types. pkg=%q constructor=%q types=%+v", pkg, export.Name, strings.Join(export.ReturnTypeNames, ","))
}
case defaultConfigMatches:
if !export.isFunction() {
return fmt.Errorf("default config convention used for non-function in pkg=%q: %#v", pkg, export)
}
if export.SignatureSize != 0 {
return fmt.Errorf("default config convention used for non-zero signature size in pkg=%q: %#v", pkg, export)
}
case configMatches:
if !export.isStruct() {
return fmt.Errorf("config convention used for non-struct in pkg=%q: %#v", pkg, export)
}
default:
return fmt.Errorf("unexpected export in pkg=%q: %#v", pkg, export)
}
return nil
}
type exportToken struct {
Name string
Type string
SignatureSize int
ReturnTypeNames []string
}
func (e exportToken) isFunction() bool {
return strings.Contains(e.Type, "ast.FuncType")
}
func (e exportToken) isStruct() bool {
return strings.Contains(e.Type, "ast.StructType")
}
type exportTokenSet map[string]exportToken
func (s exportTokenSet) Names() *strset.Set {
set := strset.New()
for k := range s {
set.Add(k)
}
return set
}
func (s exportTokenSet) Has(name string) bool {
_, ok := s[name]
return ok
}
func (s exportTokenSet) Add(tokens ...exportToken) {
for _, t := range tokens {
if _, ok := s[t.Name]; ok {
panic("duplicate token name: " + t.Name)
}
s[t.Name] = t
}
}
func (s exportTokenSet) Remove(names ...string) {
for _, name := range names {
delete(s, name)
}
}
func (s exportTokenSet) List() []exportToken {
var tokens []exportToken
for _, t := range s {
tokens = append(tokens, t)
}
return tokens
}
func packageCatalogerExports(t *testing.T) map[string]exportTokenSet {
t.Helper()
catalogerPath := filepath.Join(repoRoot(t), "syft", "pkg", "cataloger")
ignorePaths := []string{
filepath.Join(catalogerPath, "common"),
filepath.Join(catalogerPath, "generic"),
}
exportsPerPackage := make(map[string]exportTokenSet)
err := filepath.Walk(catalogerPath, func(path string, info os.FileInfo, err error) error {
require.NoError(t, err)
if info.IsDir() ||
!strings.HasSuffix(info.Name(), ".go") ||
strings.HasSuffix(info.Name(), "_test.go") ||
strings.Contains(path, "test-fixtures") ||
strings.Contains(path, "internal") {
return nil
}
for _, ignorePath := range ignorePaths {
if strings.Contains(path, ignorePath) {
return nil
}
}
fset := token.NewFileSet()
node, err := parser.ParseFile(fset, path, nil, parser.ParseComments)
require.NoError(t, err)
pkg := node.Name.Name
for _, f := range node.Decls {
switch decl := f.(type) {
case *ast.GenDecl:
for _, spec := range decl.Specs {
switch spec := spec.(type) {
case *ast.TypeSpec:
if spec.Name.IsExported() {
if _, ok := exportsPerPackage[pkg]; !ok {
exportsPerPackage[pkg] = make(exportTokenSet)
}
exportsPerPackage[pkg].Add(exportToken{
Name: spec.Name.Name,
Type: reflect.TypeOf(spec.Type).String(),
})
}
}
}
case *ast.FuncDecl:
if decl.Recv == nil && decl.Name.IsExported() {
var returnTypes []string
if decl.Type.Results != nil {
for _, field := range decl.Type.Results.List {
// TODO: there is probably a better way to extract the specific type name
//ty := strings.Join(strings.Split(fmt.Sprint(field.Type), " "), ".")
ty := types.ExprString(field.Type)
returnTypes = append(returnTypes, ty)
}
}
if _, ok := exportsPerPackage[pkg]; !ok {
exportsPerPackage[pkg] = make(exportTokenSet)
}
exportsPerPackage[pkg].Add(exportToken{
Name: decl.Name.Name,
Type: reflect.TypeOf(decl.Type).String(),
SignatureSize: len(decl.Type.Params.List),
ReturnTypeNames: returnTypes,
})
}
}
}
return nil
})
require.NoError(t, err)
// remove exceptions
// these are known violations to the common convention that are allowed.
if vs, ok := exportsPerPackage["binary"]; ok {
vs.Remove("Classifier", "EvidenceMatcher", "FileContentsVersionMatcher", "DefaultClassifiers")
}
return exportsPerPackage
}
@@ -1,204 +0,0 @@
package integration
import (
"bytes"
"go/ast"
"go/parser"
"go/token"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"github.com/scylladb/go-set/strset"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/anchore/syft/internal/task"
"github.com/anchore/syft/syft/cataloging/pkgcataloging"
)
func TestAllPackageCatalogersReachableInTasks(t *testing.T) {
// we want to see if we can get a task for all package catalogers. This is a bit tricky since we
// don't have a nice way to find all cataloger names in the codebase. Instead, we'll look at the
// count of unique task names from the package task factory set and compare that with the known constructors
// from a source analysis... they should match.
// additionally, at this time they should either have a "directory" or "image" tag as well. If there is no tag
// on a cataloger task then the test should fail.
taskFactories := task.DefaultPackageTaskFactories()
taskTagsByName := make(map[string][]string)
for _, factory := range taskFactories {
tsk := factory(task.DefaultCatalogingFactoryConfig())
if taskTagsByName[tsk.Name()] != nil {
t.Fatalf("duplicate task name: %q", tsk.Name())
}
require.NotNil(t, tsk)
if sel, ok := tsk.(task.Selector); ok {
taskTagsByName[tsk.Name()] = sel.Selectors()
} else {
taskTagsByName[tsk.Name()] = []string{}
}
}
var constructorCount int
constructorsPerPackage := getCatalogerConstructors(t)
for _, constructors := range constructorsPerPackage {
constructorCount += constructors.Size()
}
assert.Equal(t, len(taskTagsByName), constructorCount, "mismatch in number of cataloger constructors and task names")
for taskName, tags := range taskTagsByName {
if taskName == "sbom-cataloger" {
continue // this is a special case
}
if !strset.New(tags...).HasAny(pkgcataloging.ImageTag, pkgcataloging.DirectoryTag) {
t.Errorf("task %q is missing 'directory' or 'image' a tag", taskName)
}
}
}
func TestAllPackageCatalogersRepresentedInSource(t *testing.T) {
// find all functions in syft/pkg/cataloger/** that either:
// - match the name glob "New*Cataloger"
// - are in cataloger.go and match the name glob "New*"
//
// Then:
// - keep track of all packages with cataloger constructors
// - keep track of all constructors
constructorsPerPackage := getCatalogerConstructors(t)
// look at the source file in internal/task/package_tasks.go:
// - ensure all go packages that have constructors are imported
// - ensure there is a reference to all package constructors
assertAllPackageCatalogersRepresented(t, constructorsPerPackage)
}
func getCatalogerConstructors(t *testing.T) map[string]*strset.Set {
t.Helper()
root := repoRoot(t)
catalogerPath := filepath.Join(root, "syft", "pkg", "cataloger")
constructorsPerPackage := make(map[string]*strset.Set)
err := filepath.Walk(catalogerPath, func(path string, info os.FileInfo, err error) error {
require.NoError(t, err)
// ignore directories and test files...
if info.IsDir() || strings.HasSuffix(info.Name(), "_test.go") {
return nil
}
partialResults := getConstructorsFromExpectedFile(t, path, info)
constructorsPerPackage = mergeConstructors(constructorsPerPackage, partialResults)
partialResults = getCatalogerConstructorsFromPackage(t, path, info)
constructorsPerPackage = mergeConstructors(constructorsPerPackage, partialResults)
return nil
})
require.NoError(t, err)
// remove some exceptions
delete(constructorsPerPackage, "generic") // this is not an actual cataloger
return constructorsPerPackage
}
func getConstructorsFromExpectedFile(t *testing.T, path string, info os.FileInfo) map[string][]string {
constructorsPerPackage := make(map[string][]string)
if !strings.HasSuffix(info.Name(), "cataloger.go") && !strings.HasSuffix(info.Name(), "catalogers.go") {
return nil
}
fset := token.NewFileSet()
node, err := parser.ParseFile(fset, path, nil, parser.ParseComments)
require.NoError(t, err)
for _, f := range node.Decls {
fn, ok := f.(*ast.FuncDecl)
if !ok || fn.Recv != nil || !strings.HasPrefix(fn.Name.Name, "New") {
continue
}
pkg := node.Name.Name
constructorsPerPackage[pkg] = append(constructorsPerPackage[pkg], fn.Name.Name)
}
return constructorsPerPackage
}
func getCatalogerConstructorsFromPackage(t *testing.T, path string, info os.FileInfo) map[string][]string {
constructorsPerPackage := make(map[string][]string)
if info.IsDir() || !strings.HasSuffix(info.Name(), ".go") {
return nil
}
fset := token.NewFileSet()
node, err := parser.ParseFile(fset, path, nil, parser.ParseComments)
require.NoError(t, err)
for _, f := range node.Decls {
fn, ok := f.(*ast.FuncDecl)
if !ok || fn.Recv != nil || !strings.HasPrefix(fn.Name.Name, "New") || !strings.HasSuffix(fn.Name.Name, "Cataloger") {
continue
}
pkg := node.Name.Name
constructorsPerPackage[pkg] = append(constructorsPerPackage[pkg], fn.Name.Name)
}
return constructorsPerPackage
}
func assertAllPackageCatalogersRepresented(t *testing.T, constructorsPerPackage map[string]*strset.Set) {
t.Helper()
contents, err := os.ReadFile(filepath.Join(repoRoot(t), "internal", "task", "package_tasks.go"))
require.NoError(t, err)
// ensure all packages (keys) are represented in the package_tasks.go file
for pkg, constructors := range constructorsPerPackage {
if !assert.True(t, bytes.Contains(contents, []byte(pkg)), "missing package %q", pkg) {
continue
}
for _, constructor := range constructors.List() {
assert.True(t, bytes.Contains(contents, []byte(constructor)), "missing constructor %q for package %q", constructor, pkg)
}
}
}
func repoRoot(t testing.TB) string {
t.Helper()
root, err := exec.Command("git", "rev-parse", "--show-toplevel").Output()
if err != nil {
t.Fatalf("unable to find repo root dir: %+v", err)
}
absRepoRoot, err := filepath.Abs(strings.TrimSpace(string(root)))
if err != nil {
t.Fatal("unable to get abs path to repo root:", err)
}
return absRepoRoot
}
func mergeConstructors(constructorsPerPackage map[string]*strset.Set, partialResults map[string][]string) map[string]*strset.Set {
for pkg, constructors := range partialResults {
if _, ok := constructorsPerPackage[pkg]; !ok {
constructorsPerPackage[pkg] = strset.New()
}
constructorsPerPackage[pkg].Add(constructors...)
}
return constructorsPerPackage
}
@@ -1,26 +0,0 @@
package integration
import (
"fmt"
"testing"
"github.com/scylladb/go-set/strset"
"github.com/anchore/syft/syft/source"
)
func TestRelationshipsUnique(t *testing.T) {
// This test is to ensure that the relationships are deduplicated in the final SBOM.
// It is not a test of the relationships themselves.
// This test is a regression test for #syft/2509
sbom, _ := catalogFixtureImage(t, "image-pkg-coverage", source.SquashedScope)
observedRelationships := strset.New()
for _, rel := range sbom.Relationships {
unique := fmt.Sprintf("%s:%s:%s", rel.From.ID(), rel.To.ID(), rel.Type)
if observedRelationships.Has(unique) {
t.Errorf("duplicate relationship found: %s", unique)
}
observedRelationships.Add(unique)
}
}
@@ -1,50 +0,0 @@
package integration
import (
"testing"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/sbom"
"github.com/anchore/syft/syft/source"
)
func TestSbomCataloger(t *testing.T) {
assertCount := func(t *testing.T, sbom sbom.SBOM, expectedGoModCatalogerPkgs int, expectedSbomCatalogerPkgs int) {
actualSbomPkgs := 0
actualGoModPkgs := 0
for p := range sbom.Artifacts.Packages.Enumerate(pkg.GoModulePkg) {
if p.FoundBy == "go-module-file-cataloger" {
actualGoModPkgs += 1
} else if p.FoundBy == "sbom-cataloger" {
actualSbomPkgs += 1
}
}
if actualGoModPkgs != expectedGoModCatalogerPkgs {
t.Errorf("unexpected number of packages from go mod cataloger: %d != %d", expectedGoModCatalogerPkgs, actualGoModPkgs)
}
if actualSbomPkgs != expectedSbomCatalogerPkgs {
t.Errorf("unexpected number of packages from sbom cataloger: %d != %d", expectedSbomCatalogerPkgs, actualSbomPkgs)
}
}
t.Run("default catalogers", func(t *testing.T) {
sbom, _ := catalogFixtureImage(t, "image-sbom-cataloger", source.SquashedScope, "+go-module-file-cataloger")
expectedSbomCatalogerPkgs := 0
expectedGoModCatalogerPkgs := 2
assertCount(t, sbom, expectedGoModCatalogerPkgs, expectedSbomCatalogerPkgs)
})
// The image contains a go.mod file with 2 dependencies and an spdx json sbom.
// The go.mod file contains 2 dependencies, and the sbom includes a go dependency
// that overlaps with the go.mod
t.Run("with sbom cataloger", func(t *testing.T) {
sbom, _ := catalogFixtureImage(t, "image-sbom-cataloger", source.SquashedScope, "+go-module-file-cataloger", "+sbom-cataloger")
expectedSbomCatalogerPkgs := 1
expectedGoModCatalogerPkgs := 2
assertCount(t, sbom, expectedGoModCatalogerPkgs, expectedSbomCatalogerPkgs)
})
}
@@ -1 +0,0 @@
../../../../../../syft/pkg/cataloger/binary/test-fixtures/elf-test-fixtures
@@ -1,10 +0,0 @@
{application,accept,
[{description,"Accept header(s) for Erlang/Elixir"},
{vsn,"0.3.5"},
{registered,[]},
{applications,[kernel,stdlib]},
{env,[]},
{modules, ['accept_encoding_header','accept_header','accept_neg','accept_parser']},
{maintainers,["Ilya Khaprov"]},
{licenses,["MIT"]},
{links,[{"Github","https://github.com/deadtrickster/accept"}]}]}.
@@ -1 +0,0 @@
a:5:{s:4:"name";s:9:"memcached";s:4:"date";s:10:"2022-01-11";s:4:"time";s:8:"15:23:47";s:7:"version";a:2:{s:7:"release";s:5:"3.2.0";s:3:"api";s:5:"3.2.0";}s:7:"license";a:2:{s:7:"attribs";a:1:{s:3:"uri";s:26:"http://www.php.net/license";}s:8:"_content";s:11:"PHP License";}}
@@ -1,17 +0,0 @@
<?php
/**
* @package Akismet
*/
/*
Plugin Name:Akismet Anti-spam: Spam Protection
Plugin URI: https://akismet.com/
Description: Used by millions, Akismet is quite possibly the best way in the world to <strong>protect your blog from spam</strong>. Akismet Anti-spam keeps your site protected even while you sleep. To get started: activate the Akismet plugin and then go to your Akismet Settings page to set up your API key.
Version: 5.3
Requires at least: 5.8
Requires PHP: 5.6.20
Author: Automattic - Anti-spam Team
Author URI: https://automattic.com/wordpress-plugins/
License: GPLv2 or later
Text Domain: akismet
*/
// rest of plugin's code ...
-15
View File
@@ -1,15 +0,0 @@
# Syft API Examples
This directory contains examples of how to use the Syft API.
- `create_simple_sbom`: Create a simple SBOM from scratch
- `create_custom_sbom`: Create an SBOM using as much custom configuration as possible, including a custom cataloger implementation
- `decode_sbom`: Take an existing SBOM file (of arbitrary format) and decode it into a Syft SBOM object
- `source_detection`: Shows how to detect what to catalog automatically from a user string (e.g. container image vs directory)
- `source_from_image`: Construct a source from a only a container image
You can run any of these examples from this directory with:
```bash
go run ./DIRECTORY_NAME
```
@@ -1,127 +0,0 @@
package main
import (
"context"
"fmt"
"io"
"path"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
)
/*
This is a contrived cataloger that attempts to capture useful APK files from the image as if it were a package.
This isn't a real cataloger, but it is a good example of how to use API elements to create a custom cataloger.
*/
var _ pkg.Cataloger = (*alpineConfigurationCataloger)(nil)
type alpineConfigurationCataloger struct {
}
func newAlpineConfigurationCataloger() pkg.Cataloger {
return alpineConfigurationCataloger{}
}
func (m alpineConfigurationCataloger) Name() string {
return "apk-configuration-cataloger"
}
func (m alpineConfigurationCataloger) Catalog(_ context.Context, resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) {
version, versionLocations, err := getVersion(resolver)
if err != nil {
return nil, nil, fmt.Errorf("unable to get alpine version: %w", err)
}
if len(versionLocations) == 0 {
// this doesn't mean we should stop cataloging, just that we don't have a version to use, thus no package to raise up
return nil, nil, nil
}
metadata, metadataLocations, err := newAlpineConfiguration(resolver)
if err != nil {
return nil, nil, err
}
var locations []file.Location
locations = append(locations, versionLocations...)
locations = append(locations, metadataLocations...)
p := newPackage(version, *metadata, locations...)
return []pkg.Package{p}, nil, nil
}
func newPackage(version string, metadata AlpineConfiguration, locations ...file.Location) pkg.Package {
return pkg.Package{
Name: "alpine-configuration",
Version: version,
Locations: file.NewLocationSet(locations...),
Type: pkg.Type("system-configuration"), // you can make up your own package type here or use an existing one
Metadata: metadata,
}
}
func newAlpineConfiguration(resolver file.Resolver) (*AlpineConfiguration, []file.Location, error) {
var locations []file.Location
keys, keyLocations, err := getAPKKeys(resolver)
if err != nil {
return nil, nil, err
}
locations = append(locations, keyLocations...)
return &AlpineConfiguration{
APKKeys: keys,
}, locations, nil
}
func getVersion(resolver file.Resolver) (string, []file.Location, error) {
locations, err := resolver.FilesByPath("/etc/alpine-release")
if err != nil {
return "", nil, fmt.Errorf("unable to get alpine version: %w", err)
}
if len(locations) == 0 {
return "", nil, nil
}
reader, err := resolver.FileContentsByLocation(locations[0])
if err != nil {
return "", nil, fmt.Errorf("unable to read alpine version: %w", err)
}
version, err := io.ReadAll(reader)
if err != nil {
return "", nil, fmt.Errorf("unable to read alpine version: %w", err)
}
return string(version), locations, nil
}
func getAPKKeys(resolver file.Resolver) (map[string]string, []file.Location, error) {
// name-to-content values
keyContent := make(map[string]string)
locations, err := resolver.FilesByGlob("/etc/apk/keys/*.rsa.pub")
if err != nil {
return nil, nil, fmt.Errorf("unable to get apk keys: %w", err)
}
for _, location := range locations {
basename := path.Base(location.RealPath)
reader, err := resolver.FileContentsByLocation(location)
content, err := io.ReadAll(reader)
if err != nil {
return nil, nil, fmt.Errorf("unable to read apk key content at %s: %w", location.RealPath, err)
}
keyContent[basename] = string(content)
}
return keyContent, locations, nil
}
type AlpineConfiguration struct {
APKKeys map[string]string `json:"apkKeys" yaml:"apkKeys"`
// Add more data you want to capture as part of the package metadata here...
}
-128
View File
@@ -1,128 +0,0 @@
package main
import (
"context"
"crypto"
"fmt"
"os"
"gopkg.in/yaml.v3"
"github.com/anchore/syft/syft"
"github.com/anchore/syft/syft/cataloging"
"github.com/anchore/syft/syft/cataloging/filecataloging"
"github.com/anchore/syft/syft/cataloging/pkgcataloging"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/sbom"
"github.com/anchore/syft/syft/source"
)
const defaultImage = "alpine:3.19"
func main() {
// automagically get a source.Source for arbitrary string input
src := getSource(imageReference())
// will catalog the given source and return a SBOM keeping in mind several configurable options
sbom := getSBOM(src)
// show a simple package summary
summarize(sbom)
// show the alpine-configuration cataloger results
showAlpineConfiguration(sbom)
}
func imageReference() string {
// read an image string reference from the command line or use a default
if len(os.Args) > 1 {
return os.Args[1]
}
return defaultImage
}
func getSource(input string) source.Source {
fmt.Println("detecting source type for input:", input, "...")
src, err := syft.GetSource(context.Background(), input, nil)
if err != nil {
panic(err)
}
return src
}
func getSBOM(src source.Source) sbom.SBOM {
fmt.Println("creating SBOM...")
cfg := syft.DefaultCreateSBOMConfig().
// run the catalogers in parallel (5 at a time concurrently max)
WithParallelism(5).
// bake a specific tool name and version into the SBOM
WithTool("my-tool", "v1.0").
// catalog all files with 3 digests
WithFilesConfig(
filecataloging.DefaultConfig().
WithSelection(file.AllFilesSelection).
WithHashers(
crypto.MD5,
crypto.SHA1,
crypto.SHA256,
),
).
// only use OS related catalogers that would have been used with the kind of
// source type (container image or directory), but also add a specific python cataloger
WithCatalogerSelection(
pkgcataloging.NewSelectionRequest().
WithSubSelections("os").
WithAdditions("python-package-cataloger"),
).
// which relationships to include
WithRelationshipsConfig(
cataloging.RelationshipsConfig{
PackageFileOwnership: true,
PackageFileOwnershipOverlap: true,
ExcludeBinaryPackagesWithFileOwnershipOverlap: true,
},
).
// add your own cataloger to the mix
WithCatalogers(
pkgcataloging.NewAlwaysEnabledCatalogerReference(
newAlpineConfigurationCataloger(),
),
)
s, err := syft.CreateSBOM(context.Background(), src, cfg)
if err != nil {
panic(err)
}
return *s
}
func summarize(s sbom.SBOM) {
fmt.Printf("Cataloged %d packages:\n", s.Artifacts.Packages.PackageCount())
for _, p := range s.Artifacts.Packages.Sorted() {
fmt.Printf(" - %s@%s (%s)\n", p.Name, p.Version, p.Type)
}
fmt.Println()
}
func showAlpineConfiguration(s sbom.SBOM) {
pkgs := s.Artifacts.Packages.PackagesByName("alpine-configuration")
if len(pkgs) == 0 {
fmt.Println("no alpine-configuration package found")
return
}
p := pkgs[0]
fmt.Printf("All 'alpine-configuration' packages: %s\n", p.Version)
meta, err := yaml.Marshal(p.Metadata)
if err != nil {
panic(err)
}
fmt.Println(string(meta))
}
-64
View File
@@ -1,64 +0,0 @@
package main
import (
"context"
"fmt"
"os"
"github.com/anchore/syft/syft"
"github.com/anchore/syft/syft/format"
"github.com/anchore/syft/syft/format/syftjson"
"github.com/anchore/syft/syft/sbom"
"github.com/anchore/syft/syft/source"
)
const defaultImage = "alpine:3.19"
func main() {
// automagically get a source.Source for arbitrary string input
src := getSource(imageReference())
// catalog the given source and return a SBOM
sbom := getSBOM(src)
// take the SBOM object and encode it into the syft-json representation
bytes := formatSBOM(sbom)
// show the SBOM!
fmt.Println(string(bytes))
}
func imageReference() string {
// read an image string reference from the command line or use a default
if len(os.Args) > 1 {
return os.Args[1]
}
return defaultImage
}
func getSource(input string) source.Source {
src, err := syft.GetSource(context.Background(), input, nil)
if err != nil {
panic(err)
}
return src
}
func getSBOM(src source.Source) sbom.SBOM {
s, err := syft.CreateSBOM(context.Background(), src, nil)
if err != nil {
panic(err)
}
return *s
}
func formatSBOM(s sbom.SBOM) []byte {
bytes, err := format.Encode(s, syftjson.NewFormatEncoder())
if err != nil {
panic(err)
}
return bytes
}
File diff suppressed because it is too large Load Diff

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