mirror of
https://github.com/anchore/syft.git
synced 2026-10-26 12:02:23 +01:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8f6bdde666 | ||
|
|
ef43294d0e | ||
|
|
e1ad340c2d | ||
|
|
07f13049da | ||
|
|
6c7900f5b8 | ||
|
|
7018573bf7 | ||
|
|
f3ad8cf250 | ||
|
|
31f1d7dbf0 | ||
|
|
144ed725a7 | ||
|
|
dcec2bc352 | ||
|
|
1fe0921a5b | ||
|
|
538fe5ee1d | ||
|
|
2687100e6a | ||
|
|
fe7a417fb2 | ||
|
|
7732cd3b48 | ||
|
|
0302fc5b48 | ||
|
|
b899536814 | ||
|
|
d1120ad56e | ||
|
|
ef759038f5 |
@@ -0,0 +1,224 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import glob
|
||||
import subprocess
|
||||
import os
|
||||
import re
|
||||
|
||||
DRY_RUN = False
|
||||
|
||||
|
||||
def main(changed_files: str | None = None, merge_base_schema_files: str | None = None):
|
||||
global DRY_RUN
|
||||
|
||||
pr_number = os.environ.get("GITHUB_PR_NUMBER")
|
||||
comment_file_path = os.environ.get("CI_COMMENT_FILE")
|
||||
|
||||
if not comment_file_path:
|
||||
print("CI_COMMENT_FILE not set")
|
||||
sys.exit(1)
|
||||
|
||||
if not pr_number:
|
||||
DRY_RUN = True
|
||||
|
||||
if changed_files:
|
||||
DRY_RUN = True
|
||||
|
||||
# read lines from file... this is useful for local testing
|
||||
with open(changed_files) as f:
|
||||
pr_changed_files = f.read().splitlines()
|
||||
|
||||
with open(merge_base_schema_files) as f:
|
||||
og_json_schema_files = sort_json_schema_files(f.read().splitlines())
|
||||
|
||||
else:
|
||||
if not is_ci():
|
||||
print("Not in CI")
|
||||
sys.exit(1)
|
||||
|
||||
if not pr_number:
|
||||
print("Not a PR")
|
||||
sys.exit(1)
|
||||
|
||||
pr_changed_files = get_pr_changed_files(pr_number)
|
||||
# since we are running this in the context of the pull_request_target, the checkout is the merge base..
|
||||
# that is the main branch of the original repo, NOT the branch in the forked repo (or branch in the target
|
||||
# repo for non-forked PRs). This means we just need to list the current checkedout files to get a sense of
|
||||
# the changes before a merge.
|
||||
og_json_schema_files = list_json_schema_files()
|
||||
|
||||
pr_json_schema_files = filter_to_schema_files(pr_changed_files)
|
||||
|
||||
# print("schema files in pr: ", summarize_schema_files(pr_json_schema_files))
|
||||
# print("og schema files: ", summarize_schema_files(og_json_schema_files))
|
||||
|
||||
if not og_json_schema_files:
|
||||
print("No schema files found in merge base")
|
||||
sys.exit(1)
|
||||
|
||||
# pr_json_schema_files = set of PR files are added, removed, and changed files
|
||||
new_schema_files = set(pr_json_schema_files) - set(og_json_schema_files)
|
||||
removed_or_modified_schema_files = set(pr_json_schema_files) - set(new_schema_files)
|
||||
|
||||
print("new schemas: ", summarize_schema_files(new_schema_files))
|
||||
print("removed or modified schemas:", summarize_schema_files(removed_or_modified_schema_files))
|
||||
|
||||
# if there is a new or modified schema, we should add the "json-schema" label to the PR...
|
||||
if new_schema_files or removed_or_modified_schema_files:
|
||||
print("\nAdding json-schema label...")
|
||||
add_label(pr_number, "json-schema")
|
||||
else:
|
||||
remove_label(pr_number, "json-schema")
|
||||
|
||||
# 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...
|
||||
if is_breaking_change(new_schema_files, og_json_schema_files[-1]):
|
||||
print("\nBreaking change detected...")
|
||||
add_label(pr_number, "breaking-change")
|
||||
else:
|
||||
remove_label(pr_number, "breaking-change")
|
||||
|
||||
# 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...
|
||||
if removed_or_modified_schema_files:
|
||||
print("\nRemoved or modified schema detected...")
|
||||
schemas = sort_json_schema_files(list(removed_or_modified_schema_files))
|
||||
schemas_str = "\n".join([f" - {schema}" for schema in schemas])
|
||||
add_comment(comment_file_path, f"Detected modification or removal of existing json schemas:\n{schemas_str}", warning=True)
|
||||
|
||||
|
||||
def add_comment(comment_file_path: str, comment: str, warning: bool = False, important: bool = False):
|
||||
if warning or important:
|
||||
comment_lines = comment.splitlines()
|
||||
comment = "\n".join([f"> {line}" for line in comment_lines])
|
||||
|
||||
if warning:
|
||||
comment = f"> [!WARNING]\n{comment}"
|
||||
elif important:
|
||||
comment = f"> [!IMPORTANT]\n{comment}"
|
||||
|
||||
# create any parent directories if they don't exist
|
||||
os.makedirs(os.path.dirname(comment_file_path), exist_ok=True)
|
||||
|
||||
with open(comment_file_path, "w") as f:
|
||||
f.write(comment)
|
||||
|
||||
print(f"Comment file contents: {comment_file_path}")
|
||||
print(comment)
|
||||
|
||||
|
||||
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 with")
|
||||
print(str(result.stderr))
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
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 label PR with {label!r}")
|
||||
print(str(result.stderr))
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def major_version(semver: str) -> int:
|
||||
return int(semver.split(".")[0])
|
||||
|
||||
|
||||
def is_breaking_change(new_schema_files: set[str], latest_schema_file: str) -> bool:
|
||||
latest_major_version = major_version(get_semver(latest_schema_file))
|
||||
for file in new_schema_files:
|
||||
change_major_version = major_version(get_semver(file))
|
||||
if change_major_version > latest_major_version:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def summarize_schema_files(files: list[str]) -> list[str]:
|
||||
return [get_semver(file) for file in files]
|
||||
|
||||
|
||||
def is_ci() -> bool:
|
||||
return "CI" in os.environ
|
||||
|
||||
|
||||
def get_pr_changed_files(pr_number: str) -> list[str]:
|
||||
result = run(f"gh pr view {pr_number} --json files --jq '.files.[].path'", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||
if result.returncode != 0:
|
||||
print("Unable to get list of changed files in PR")
|
||||
print(str(result.stderr))
|
||||
sys.exit(1)
|
||||
|
||||
list_of_files = result.stdout.splitlines()
|
||||
return list_of_files
|
||||
|
||||
|
||||
def filter_to_schema_files(list_of_files: list[str]) -> list[str]:
|
||||
# get files matching "schema/json/schema-*.json"
|
||||
files = []
|
||||
for file in list_of_files:
|
||||
if re.match(r"^schema/json/schema-\d+\.\d+\.\d+\.json$", file):
|
||||
files.append(file)
|
||||
return sort_json_schema_files(files)
|
||||
|
||||
|
||||
def list_json_schema_files() -> list[str]:
|
||||
# list files in "schema/json" directory matching the pattern of "schema-*.json"
|
||||
return sort_json_schema_files(list(glob.glob("schema/json/schema-*.json")))
|
||||
|
||||
|
||||
def run(command: str, **kwargs) -> subprocess.CompletedProcess:
|
||||
if DRY_RUN:
|
||||
print(f"[DRY RUN] {command}")
|
||||
return subprocess.CompletedProcess(args=[command], returncode=0)
|
||||
print(f"[RUN] {command}")
|
||||
return subprocess.run(command, **kwargs)
|
||||
|
||||
|
||||
def get_semver(input_file: str) -> str:
|
||||
return input_file.split("-")[1].split(".json")[0]
|
||||
|
||||
|
||||
def sort_json_schema_files(files: list[str]) -> list[str]:
|
||||
# sort files by schema version, where the input looks like "schema/json/schema-1.12.1.json"
|
||||
# we should sort by the semantic version embedded within the basename, not the string
|
||||
# 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('.')])
|
||||
|
||||
return [f"schema/json/schema-{version}.json" for version in versions]
|
||||
|
||||
|
||||
# allow for test files that have line-by-line list of files:
|
||||
|
||||
# .binny.yaml
|
||||
# .github/actions/bootstrap/action.yaml
|
||||
# .github/scripts/goreleaser-install.sh
|
||||
# .github/workflows/release.yaml
|
||||
# .github/workflows/update-bootstrap-tools.yml
|
||||
# .github/workflows/update-cpe-dictionary-index.yml
|
||||
# .github/workflows/update-stereoscope-release.yml
|
||||
# .github/workflows/validations.yaml
|
||||
# .gitignore
|
||||
# .goreleaser.yaml
|
||||
# Makefile
|
||||
# Taskfile.yaml
|
||||
# schema/cyclonedx/Makefile
|
||||
|
||||
if __name__ == "__main__":
|
||||
# these are variables for a single file name that contains a list of files (line separated)
|
||||
changed_files = None
|
||||
merge_base_schema_files = None
|
||||
|
||||
if len(sys.argv) > 2:
|
||||
changed_files = sys.argv[1]
|
||||
merge_base_schema_files = sys.argv[2]
|
||||
|
||||
main(changed_files, merge_base_schema_files)
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
import subprocess
|
||||
|
||||
import labeler
|
||||
|
||||
class Labeler(unittest.TestCase):
|
||||
|
||||
def test_major_version(self):
|
||||
self.assertEqual(labeler.major_version("1.2.3"), 1)
|
||||
self.assertEqual(labeler.major_version("2.0.0"), 2)
|
||||
|
||||
def test_is_breaking_change(self):
|
||||
new_schema_files = ["schema/json/schema-2.0.0.json"]
|
||||
latest_schema_file = "schema/json/schema-1.2.0.json"
|
||||
self.assertTrue(labeler.is_breaking_change(new_schema_files, latest_schema_file))
|
||||
|
||||
new_schema_files = ["schema/json/schema-1.3.0.json"]
|
||||
latest_schema_file = "schema/json/schema-1.2.0.json"
|
||||
self.assertFalse(labeler.is_breaking_change(new_schema_files, latest_schema_file))
|
||||
|
||||
def test_summarize_schema_files(self):
|
||||
files = ["schema/json/schema-1.0.0.json", "schema/json/schema-2.0.0.json"]
|
||||
expected = ["1.0.0", "2.0.0"]
|
||||
self.assertEqual(labeler.summarize_schema_files(files), expected)
|
||||
|
||||
def test_is_ci(self):
|
||||
# Mock os.environ to simulate CI environment
|
||||
with patch.dict("os.environ", {"CI": "true"}):
|
||||
self.assertTrue(labeler.is_ci())
|
||||
|
||||
def test_get_pr_changed_files(self):
|
||||
expected_command = "gh pr view 123 --json files --jq '.files.[].path'"
|
||||
expected_output = "file1.json\nfile2.json\n"
|
||||
|
||||
subprocess.CompletedProcess.returncode = 0
|
||||
subprocess.CompletedProcess.stdout = expected_output
|
||||
with patch("labeler.run", return_value=subprocess.CompletedProcess) as mock_run:
|
||||
result = labeler.get_pr_changed_files("123")
|
||||
mock_run.assert_called_with(expected_command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||
self.assertEqual(result, ["file1.json", "file2.json"])
|
||||
|
||||
def test_filter_to_schema_files(self):
|
||||
input_files = ["schema/json/schema-1.0.0.json", "not_schema.txt", "schema/json/schema-2.0.0.json"]
|
||||
expected_files = ["schema/json/schema-1.0.0.json", "schema/json/schema-2.0.0.json"]
|
||||
self.assertEqual(labeler.filter_to_schema_files(input_files), expected_files)
|
||||
|
||||
# we should be strict about what files are allowed to be processed
|
||||
input_files = ["schema/json/schema-1.0.0extracontent.json", "schema/json/schema-1.0.0.md", "schema/json/schema-1.0.0.json.extracontent"]
|
||||
expected_files = []
|
||||
self.assertEqual(labeler.filter_to_schema_files(input_files), expected_files)
|
||||
|
||||
def test_get_semver(self):
|
||||
input_file = "schema/json/schema-1.0.0.json"
|
||||
expected_semver = "1.0.0"
|
||||
self.assertEqual(labeler.get_semver(input_file), expected_semver)
|
||||
|
||||
def test_sort_json_schema_files(self):
|
||||
files = ["schema/json/schema-1.12.1.json", "schema/json/schema-1.2.1.json"]
|
||||
expected_sorted_files = ["schema/json/schema-1.2.1.json", "schema/json/schema-1.12.1.json"]
|
||||
self.assertEqual(labeler.sort_json_schema_files(files), expected_sorted_files)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
# the job by event.
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 #v4.1.0
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe #v4.1.0
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@fdcae64e1484d349b3366718cdfef3d404390e85 #v2.22.1
|
||||
uses: github/codeql-action/init@0116bc2df50751f9724a2e35ef1f24d22f90e4e1 #v2.22.3
|
||||
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@fdcae64e1484d349b3366718cdfef3d404390e85 #v2.22.1
|
||||
uses: github/codeql-action/autobuild@0116bc2df50751f9724a2e35ef1f24d22f90e4e1 #v2.22.3
|
||||
|
||||
# ℹ️ 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@fdcae64e1484d349b3366718cdfef3d404390e85 #v2.22.1
|
||||
uses: github/codeql-action/analyze@0116bc2df50751f9724a2e35ef1f24d22f90e4e1 #v2.22.3
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
name: "Detect schema changes"
|
||||
|
||||
on:
|
||||
# IMPORTANT! This workflow is triggered by the `pull_request_target` event
|
||||
# which means that forked PRs will run with access secrets from the repo
|
||||
# it's forked from (the "target" repo).
|
||||
#
|
||||
# For this reason we only NEVER checkout the code from the pull request
|
||||
# (e.g. "ref: ${{ github.event.pull_request.head.sha }}") to prevent
|
||||
# accidentally running potentially untrusted code.
|
||||
#
|
||||
# By default the checkout will be:
|
||||
# - GITHUB_SHA: Last commit on the PR base branch
|
||||
# - GITHUB_REF: PR base branch
|
||||
#
|
||||
# ...unlike a typical PR where:
|
||||
# - GITHUB_SHA: Last merge commit on the GITHUB_REF branch
|
||||
# - GITHUB_REF: PR merge branch refs/pull/:prNumber/merge
|
||||
pull_request_target:
|
||||
|
||||
env:
|
||||
# note: this is used within hashFiles() so must be within the GITHUB_WORKSPACE path (or will silently fail)
|
||||
CI_COMMENT_FILE: .tmp/labeler-comment.txt
|
||||
# needs to be any string to uniquely identify the comment on a PR across multiple runs
|
||||
COMMENT_HEADER: "label-commentary"
|
||||
|
||||
jobs:
|
||||
label:
|
||||
name: "Label changes"
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
|
||||
|
||||
- run: python .github/scripts/labeler.py
|
||||
env:
|
||||
# note: this token has write access to the repo
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_PR_NUMBER: ${{ github.event.number }}
|
||||
|
||||
- name: Delete existing comment
|
||||
if: ${{ hashFiles( env.CI_COMMENT_FILE ) == '' }}
|
||||
uses: marocchino/sticky-pull-request-comment@efaaab3fd41a9c3de579aba759d2552635e590fd #v2.8.0
|
||||
with:
|
||||
header: ${{ env.COMMENT_HEADER }}
|
||||
hide: true
|
||||
hide_classify: "OUTDATED"
|
||||
|
||||
- name: Add comment
|
||||
if: ${{ hashFiles( env.CI_COMMENT_FILE ) != '' }}
|
||||
uses: marocchino/sticky-pull-request-comment@efaaab3fd41a9c3de579aba759d2552635e590fd #v2.8.0
|
||||
with:
|
||||
header: ${{ env.COMMENT_HEADER }}
|
||||
path: ${{ env.CI_COMMENT_FILE }}
|
||||
@@ -0,0 +1,25 @@
|
||||
name: "Release Homebrew"
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
homebrew:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Update Homebrew formula
|
||||
uses: dawidd6/action-homebrew-bump-formula@d3667e5ae14df19579e4414897498e3e88f2f458 # v3.10.0
|
||||
with:
|
||||
token: ${{ secrets.HOMEBREW_TOKEN }}
|
||||
org: anchore
|
||||
formula: syft
|
||||
|
||||
- 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 }}
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
environment: release
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 #v4.1.0
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
|
||||
|
||||
- name: Check if tag already exists
|
||||
# note: this will fail if the tag already exists
|
||||
@@ -95,7 +95,7 @@ jobs:
|
||||
contents: write
|
||||
packages: write
|
||||
steps:
|
||||
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 #v4.1.0
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'anchore/syft' # only run for main repo
|
||||
steps:
|
||||
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 #v4.1.0
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
|
||||
|
||||
- uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe #v4.1.0
|
||||
with:
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'anchore/syft' # only run for main repo
|
||||
steps:
|
||||
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 #v4.1.0
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
|
||||
|
||||
- uses: actions/setup-go@3df4ab11eba7bda6032a0b82a6bb43b11571feac #v4.0.0
|
||||
with:
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'anchore/syft' # only run for main repo
|
||||
steps:
|
||||
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 #v4.1.0
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
|
||||
|
||||
- uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe #v4.1.0
|
||||
with:
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
name: "Static analysis"
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 #v4.1.0
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
name: "Unit tests"
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 #v4.1.0
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
@@ -74,7 +74,7 @@ jobs:
|
||||
name: "Integration tests"
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 #v4.1.0
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
@@ -96,7 +96,7 @@ jobs:
|
||||
name: "Build snapshot artifacts"
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 #v4.1.0
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
@@ -127,7 +127,7 @@ jobs:
|
||||
needs: [Build-Snapshot-Artifacts]
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 #v4.1.0
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
|
||||
|
||||
- name: Download snapshot build
|
||||
uses: actions/cache/restore@704facf57e6136b1bc63b828d79edcd491f0ee84 #v3.3.2
|
||||
@@ -163,7 +163,7 @@ jobs:
|
||||
needs: [Build-Snapshot-Artifacts]
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 #v4.1.0
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
|
||||
|
||||
- name: Download snapshot build
|
||||
uses: actions/cache/restore@704facf57e6136b1bc63b828d79edcd491f0ee84 #v3.3.2
|
||||
@@ -191,7 +191,7 @@ jobs:
|
||||
needs: [Build-Snapshot-Artifacts]
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 #v4.1.0
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
|
||||
@@ -64,3 +64,8 @@ test/integration/test-fixtures/**/go.sum
|
||||
# attestation
|
||||
cosign.key
|
||||
cosign.pub
|
||||
|
||||
# Byte-compiled object files for python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
@@ -6,6 +6,7 @@ In order to test and develop in this repo you will need the following dependenci
|
||||
- Golang
|
||||
- docker
|
||||
- make
|
||||
- Python (>= 3.9)
|
||||
|
||||
### Docker settings for getting started
|
||||
Make sure you've updated your docker settings so the default docker socket path is available.
|
||||
|
||||
@@ -70,7 +70,7 @@ all: static-analysis test ## Run all linux-based checks (linting, license check,
|
||||
static-analysis: check-go-mod-tidy lint check-licenses check-json-schema-drift ## Run all static analysis checks
|
||||
|
||||
.PHONY: test
|
||||
test: unit integration validate-cyclonedx-schema benchmark cli ## Run all tests (currently unit, integration, linux compare, and cli tests)
|
||||
test: unit integration validate-cyclonedx-schema benchmark test-utils cli ## Run all tests (currently unit, integration, linux compare, and cli tests)
|
||||
|
||||
|
||||
## Bootstrapping targets #################################
|
||||
@@ -167,6 +167,10 @@ cli: $(SNAPSHOT_DIR) ## Run CLI tests
|
||||
SYFT_BINARY_LOCATION='$(SNAPSHOT_BIN)' \
|
||||
go test -count=1 -timeout=15m -v ./test/cli
|
||||
|
||||
.PHONY: test-utils
|
||||
test-utils:
|
||||
python .github/scripts/labeler_test.py
|
||||
|
||||
|
||||
## Benchmark test targets #################################
|
||||
|
||||
|
||||
@@ -7,10 +7,10 @@ import (
|
||||
|
||||
"github.com/iancoleman/strcase"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/scylladb/go-set/strset"
|
||||
|
||||
"github.com/anchore/clio"
|
||||
"github.com/anchore/fangs"
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger"
|
||||
golangCataloger "github.com/anchore/syft/syft/pkg/cataloger/golang"
|
||||
@@ -147,8 +147,8 @@ func (cfg Catalog) ToCatalogerConfig() cataloger.Config {
|
||||
var validDefaultSourceValues = []string{"registry", "docker", "podman", ""}
|
||||
|
||||
func checkDefaultSourceValues(source string) error {
|
||||
validValues := internal.NewStringSet(validDefaultSourceValues...)
|
||||
if !validValues.Contains(source) {
|
||||
validValues := strset.New(validDefaultSourceValues...)
|
||||
if !validValues.Has(source) {
|
||||
validValuesString := strings.Join(validDefaultSourceValues, ", ")
|
||||
return fmt.Errorf("%s is not a valid default source; please use one of the following: %s''", source, validValuesString)
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ require (
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
|
||||
github.com/acobaugh/osrelease v0.1.0
|
||||
github.com/anchore/bubbly v0.0.0-20230801194016-acdb4981b461
|
||||
github.com/anchore/clio v0.0.0-20230823172630-c42d666061af
|
||||
github.com/anchore/clio v0.0.0-20231016125544-c98a83e1c7fc
|
||||
github.com/anchore/fangs v0.0.0-20230818131516-2186b10924fe
|
||||
github.com/anchore/go-logger v0.0.0-20230725134548-c21dafa1ec5a
|
||||
github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb
|
||||
@@ -22,7 +22,7 @@ require (
|
||||
github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46
|
||||
github.com/bmatcuk/doublestar/v4 v4.6.0
|
||||
github.com/charmbracelet/bubbletea v0.24.2
|
||||
github.com/charmbracelet/lipgloss v0.8.0
|
||||
github.com/charmbracelet/lipgloss v0.9.1
|
||||
github.com/dave/jennifer v1.7.0
|
||||
github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da
|
||||
github.com/docker/distribution v2.8.3+incompatible // indirect
|
||||
@@ -34,7 +34,7 @@ require (
|
||||
github.com/go-git/go-billy/v5 v5.5.0
|
||||
github.com/go-git/go-git/v5 v5.9.0
|
||||
github.com/go-test/deep v1.1.0
|
||||
github.com/google/go-cmp v0.5.9
|
||||
github.com/google/go-cmp v0.6.0
|
||||
github.com/google/go-containerregistry v0.16.1
|
||||
github.com/google/licensecheck v0.3.1
|
||||
github.com/google/uuid v1.3.1
|
||||
@@ -70,7 +70,7 @@ require (
|
||||
github.com/xeipuuv/gojsonschema v1.2.0
|
||||
github.com/zyedidia/generic v1.2.2-0.20230320175451-4410d2372cb1
|
||||
golang.org/x/mod v0.13.0
|
||||
golang.org/x/net v0.16.0
|
||||
golang.org/x/net v0.17.0
|
||||
golang.org/x/term v0.13.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
modernc.org/sqlite v1.26.0
|
||||
@@ -153,7 +153,7 @@ require (
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.18 // indirect
|
||||
github.com/mattn/go-localereader v0.0.2-0.20220822084749-2491eb6c1c75 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
|
||||
@@ -96,8 +96,8 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/anchore/bubbly v0.0.0-20230801194016-acdb4981b461 h1:xGu4/uMWucwWV0YV3fpFIQZ6KVfS/Wfhmma8t0s0vRo=
|
||||
github.com/anchore/bubbly v0.0.0-20230801194016-acdb4981b461/go.mod h1:Ger02eh5NpPm2IqkPAy396HU1KlK3BhOeCljDYXySSk=
|
||||
github.com/anchore/clio v0.0.0-20230823172630-c42d666061af h1:dBVKZyMZeA0oZK0+aCCRoqxhxUvx/7xy/VEaLMMMnb0=
|
||||
github.com/anchore/clio v0.0.0-20230823172630-c42d666061af/go.mod h1:XryJ3CIF1T7SbacQV+OPykfKKIbfXnBssYfpjy2peUg=
|
||||
github.com/anchore/clio v0.0.0-20231016125544-c98a83e1c7fc h1:A1KFO+zZZmbNlz1+WKsCF0RKVx6XRoxsAG3lrqH9hUQ=
|
||||
github.com/anchore/clio v0.0.0-20231016125544-c98a83e1c7fc/go.mod h1:QeWvNzxsrUNxcs6haQo3OtISfXUXW0qAuiG4EQiz0GU=
|
||||
github.com/anchore/fangs v0.0.0-20230818131516-2186b10924fe h1:pVpLCGWdNeskAw7vGNdCAcGMezrNljHIqOc9HaOja5M=
|
||||
github.com/anchore/fangs v0.0.0-20230818131516-2186b10924fe/go.mod h1:82EGoxZTfBXSW0/zollEP+Qs3wkiKmip5yBT5j+eZpY=
|
||||
github.com/anchore/go-logger v0.0.0-20230725134548-c21dafa1ec5a h1:nJ2G8zWKASyVClGVgG7sfM5mwoZlZ2zYpIzN2OhjWkw=
|
||||
@@ -156,8 +156,8 @@ github.com/charmbracelet/bubbletea v0.24.2 h1:uaQIKx9Ai6Gdh5zpTbGiWpytMU+CfsPp06
|
||||
github.com/charmbracelet/bubbletea v0.24.2/go.mod h1:XdrNrV4J8GiyshTtx3DNuYkR1FDaJmO3l2nejekbsgg=
|
||||
github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=
|
||||
github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
|
||||
github.com/charmbracelet/lipgloss v0.8.0 h1:IS00fk4XAHcf8uZKc3eHeMUTCxUH6NkaTrdyCQk84RU=
|
||||
github.com/charmbracelet/lipgloss v0.8.0/go.mod h1:p4eYUZZJ/0oXTuCQKFF8mqyKCz0ja6y+7DniDDw5KKU=
|
||||
github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg=
|
||||
github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I=
|
||||
github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
@@ -366,8 +366,9 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-containerregistry v0.16.1 h1:rUEt426sR6nyrL3gt+18ibRcvYpKYdpsa5ZW7MA08dQ=
|
||||
github.com/google/go-containerregistry v0.16.1/go.mod h1:u0qB2l7mvtWVR5kNcbFIhFY1hLbf8eeGapA+vbFDCtQ=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
@@ -533,8 +534,8 @@ github.com/mattn/go-localereader v0.0.2-0.20220822084749-2491eb6c1c75 h1:P8UmIzZ
|
||||
github.com/mattn/go-localereader v0.0.2-0.20220822084749-2491eb6c1c75/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
||||
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
@@ -935,8 +936,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.16.0 h1:7eBu7KsSvFDtSXUIDbh3aqlK4DPsZ1rByC8PFfBThos=
|
||||
golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
|
||||
@@ -3,5 +3,5 @@ package internal
|
||||
const (
|
||||
// JSONSchemaVersion is the current schema version output by the JSON encoder
|
||||
// This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment.
|
||||
JSONSchemaVersion = "11.0.0"
|
||||
JSONSchemaVersion = "11.0.1"
|
||||
)
|
||||
|
||||
@@ -6,7 +6,8 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/scylladb/go-set/strset"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
)
|
||||
|
||||
@@ -40,7 +41,7 @@ func (z ZipFileManifest) Add(entry string, info os.FileInfo) {
|
||||
|
||||
// GlobMatch returns the path keys that match the given value(s).
|
||||
func (z ZipFileManifest) GlobMatch(patterns ...string) []string {
|
||||
uniqueMatches := internal.NewStringSet()
|
||||
uniqueMatches := strset.New()
|
||||
|
||||
for _, pattern := range patterns {
|
||||
for entry := range z {
|
||||
@@ -54,7 +55,7 @@ func (z ZipFileManifest) GlobMatch(patterns ...string) []string {
|
||||
}
|
||||
}
|
||||
|
||||
results := uniqueMatches.ToSlice()
|
||||
results := uniqueMatches.List()
|
||||
sort.Strings(results)
|
||||
|
||||
return results
|
||||
|
||||
@@ -1,23 +1,26 @@
|
||||
package licenses
|
||||
|
||||
import "github.com/anchore/syft/internal"
|
||||
|
||||
// all of these taken from https://github.com/golang/pkgsite/blob/8996ff632abee854aef1b764ca0501f262f8f523/internal/licenses/licenses.go#L338
|
||||
// which unfortunately is not exported. But fortunately is under BSD-style license.
|
||||
// which unfortunately is not exported. But fortunately is under BSD-style license. Take note that this list has
|
||||
// been manually updated to include more license filenames (see https://github.com/anchore/syft/pull/2227).
|
||||
|
||||
var (
|
||||
FileNames = []string{
|
||||
func FileNames() []string {
|
||||
return []string{
|
||||
"AL2.0",
|
||||
"COPYING",
|
||||
"COPYING.md",
|
||||
"COPYING.markdown",
|
||||
"COPYING.txt",
|
||||
"LGPL2.1",
|
||||
"LICENCE",
|
||||
"LICENCE.md",
|
||||
"LICENCE.markdown",
|
||||
"licence.txt",
|
||||
"LICENCE.txt",
|
||||
"LICENSE",
|
||||
"LICENSE.md",
|
||||
"LICENSE.markdown",
|
||||
"license.txt",
|
||||
"LICENSE.txt",
|
||||
"LICENSE-2.0.txt",
|
||||
"LICENCE-2.0.txt",
|
||||
@@ -48,6 +51,4 @@ var (
|
||||
"UNLICENSE",
|
||||
"UNLICENCE",
|
||||
}
|
||||
|
||||
FileNameSet = internal.NewStringSet(FileNames...)
|
||||
)
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -95,7 +95,7 @@ func separateLicenses(p pkg.Package) (spdx, other cyclonedx.Licenses, expression
|
||||
// singular expression case
|
||||
// only ID field here since we guarantee that the license is valid
|
||||
if value, exists := spdxlicense.ID(l.SPDXExpression); exists {
|
||||
if !l.URLs.Empty() {
|
||||
if len(l.URLs) > 0 {
|
||||
processLicenseURLs(l, value, &spdxc)
|
||||
continue
|
||||
}
|
||||
@@ -123,7 +123,7 @@ func separateLicenses(p pkg.Package) (spdx, other cyclonedx.Licenses, expression
|
||||
|
||||
// license string that are not valid spdx expressions or ids
|
||||
// we only use license Name here since we cannot guarantee that the license is a valid SPDX expression
|
||||
if !l.URLs.Empty() {
|
||||
if len(l.URLs) > 0 {
|
||||
processLicenseURLs(l, "", &otherc)
|
||||
continue
|
||||
}
|
||||
@@ -137,7 +137,7 @@ func separateLicenses(p pkg.Package) (spdx, other cyclonedx.Licenses, expression
|
||||
}
|
||||
|
||||
func processLicenseURLs(l pkg.License, spdxID string, populate *cyclonedx.Licenses) {
|
||||
for _, url := range l.URLs.ToSlice() {
|
||||
for _, url := range l.URLs {
|
||||
if spdxID == "" {
|
||||
*populate = append(*populate, cyclonedx.LicenseChoice{
|
||||
License: &cyclonedx.License{
|
||||
|
||||
@@ -4,9 +4,9 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/CycloneDX/cyclonedx-go"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/syft/license"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
@@ -191,7 +191,9 @@ func Test_encodeLicense(t *testing.T) {
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
assert.Equal(t, test.expected, encodeLicenses(test.input))
|
||||
if d := cmp.Diff(test.expected, encodeLicenses(test.input)); d != "" {
|
||||
t.Errorf("unexpected license (-want +got):\n%s", d)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -223,7 +225,7 @@ func TestDecodeLicenses(t *testing.T) {
|
||||
Value: "RandomLicense",
|
||||
// CycloneDX specification doesn't give a field for determining the license type
|
||||
Type: license.Declared,
|
||||
URLs: internal.NewStringSet(),
|
||||
URLs: []string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -243,7 +245,7 @@ func TestDecodeLicenses(t *testing.T) {
|
||||
Value: "MIT",
|
||||
SPDXExpression: "MIT",
|
||||
Type: license.Declared,
|
||||
URLs: internal.NewStringSet(),
|
||||
URLs: []string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -262,7 +264,7 @@ func TestDecodeLicenses(t *testing.T) {
|
||||
Value: "MIT AND GPL-3.0-only WITH Classpath-exception-2.0",
|
||||
SPDXExpression: "MIT AND GPL-3.0-only WITH Classpath-exception-2.0",
|
||||
Type: license.Declared,
|
||||
URLs: internal.NewStringSet(),
|
||||
URLs: []string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -20,6 +20,8 @@ func DownloadLocation(p pkg.Package) string {
|
||||
return NoneIfEmpty(metadata.URL)
|
||||
case pkg.NpmPackageJSONMetadata:
|
||||
return NoneIfEmpty(metadata.URL)
|
||||
case pkg.NpmPackageLockJSONMetadata:
|
||||
return NoneIfEmpty(metadata.Resolved)
|
||||
}
|
||||
}
|
||||
return NOASSERTION
|
||||
|
||||
@@ -46,6 +46,24 @@ func Test_DownloadLocation(t *testing.T) {
|
||||
},
|
||||
expected: NONE,
|
||||
},
|
||||
{
|
||||
name: "from npm package-lock should include resolved",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.NpmPackageLockJSONMetadata{
|
||||
Resolved: "http://package-lock.test",
|
||||
},
|
||||
},
|
||||
expected: "http://package-lock.test",
|
||||
},
|
||||
{
|
||||
name: "from npm package-lock empty should be NONE",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.NpmPackageLockJSONMetadata{
|
||||
Resolved: "",
|
||||
},
|
||||
},
|
||||
expected: NONE,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
|
||||
@@ -57,7 +57,7 @@ func redactor(values ...string) testutils.Redactor {
|
||||
`sha256:[A-Za-z0-9]{64}`: `sha256:redacted`,
|
||||
|
||||
// BOM refs
|
||||
`bom-ref="[a-zA-Z0-9\-:]+"`: `bom-ref:redacted`,
|
||||
`bom-ref="[a-zA-Z0-9\-:]+"`: `bom-ref="redacted"`,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@
|
||||
<property name="syft:location:0:path">/some/path/pkg1</property>
|
||||
</properties>
|
||||
</component>
|
||||
<component bom-ref="pkg:deb/debian/package-2@2.0.1?package-id=db4abfe497c180d3" type="library">
|
||||
<component bom-ref="pkg:deb/debian/package-2@2.0.1?package-id=ad5013466727018f" type="library">
|
||||
<name>package-2</name>
|
||||
<version>2.0.1</version>
|
||||
<cpe>cpe:2.3:*:some:package:2:*:*:*:*:*:*:*</cpe>
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
<property name="syft:location:0:path">/somefile-1.txt</property>
|
||||
</properties>
|
||||
</component>
|
||||
<component bom-ref="pkg:deb/debian/package-2@2.0.1?package-id=958443e2d9304af4" type="library">
|
||||
<component bom-ref="pkg:deb/debian/package-2@2.0.1?package-id=f27313b22a5ba330" type="library">
|
||||
<name>package-2</name>
|
||||
<version>2.0.1</version>
|
||||
<cpe>cpe:2.3:*:some:package:2:*:*:*:*:*:*:*</cpe>
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
},
|
||||
{
|
||||
"name": "package-2",
|
||||
"SPDXID": "SPDXRef-Package-deb-package-2-db4abfe497c180d3",
|
||||
"SPDXID": "SPDXRef-Package-deb-package-2-ad5013466727018f",
|
||||
"versionInfo": "2.0.1",
|
||||
"supplier": "NOASSERTION",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
@@ -78,7 +78,7 @@
|
||||
},
|
||||
{
|
||||
"spdxElementId": "SPDXRef-DocumentRoot-Directory-some-path",
|
||||
"relatedSpdxElement": "SPDXRef-Package-deb-package-2-db4abfe497c180d3",
|
||||
"relatedSpdxElement": "SPDXRef-Package-deb-package-2-ad5013466727018f",
|
||||
"relationshipType": "CONTAINS"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
},
|
||||
{
|
||||
"name": "package-2",
|
||||
"SPDXID": "SPDXRef-Package-deb-package-2-958443e2d9304af4",
|
||||
"SPDXID": "SPDXRef-Package-deb-package-2-f27313b22a5ba330",
|
||||
"versionInfo": "2.0.1",
|
||||
"supplier": "NOASSERTION",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
@@ -92,7 +92,7 @@
|
||||
},
|
||||
{
|
||||
"spdxElementId": "SPDXRef-DocumentRoot-Image-user-image-input",
|
||||
"relatedSpdxElement": "SPDXRef-Package-deb-package-2-958443e2d9304af4",
|
||||
"relatedSpdxElement": "SPDXRef-Package-deb-package-2-f27313b22a5ba330",
|
||||
"relationshipType": "CONTAINS"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
},
|
||||
{
|
||||
"name": "package-2",
|
||||
"SPDXID": "SPDXRef-Package-deb-package-2-958443e2d9304af4",
|
||||
"SPDXID": "SPDXRef-Package-deb-package-2-f27313b22a5ba330",
|
||||
"versionInfo": "2.0.1",
|
||||
"supplier": "NOASSERTION",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
@@ -214,7 +214,7 @@
|
||||
},
|
||||
{
|
||||
"spdxElementId": "SPDXRef-DocumentRoot-Image-user-image-input",
|
||||
"relatedSpdxElement": "SPDXRef-Package-deb-package-2-958443e2d9304af4",
|
||||
"relatedSpdxElement": "SPDXRef-Package-deb-package-2-f27313b22a5ba330",
|
||||
"relationshipType": "CONTAINS"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -61,7 +61,7 @@ ExternalRef: PACKAGE-MANAGER purl pkg:oci/user-image-input@sha256:2731251dc34951
|
||||
##### Package: package-2
|
||||
|
||||
PackageName: package-2
|
||||
SPDXID: SPDXRef-Package-deb-package-2-958443e2d9304af4
|
||||
SPDXID: SPDXRef-Package-deb-package-2-f27313b22a5ba330
|
||||
PackageVersion: 2.0.1
|
||||
PackageSupplier: NOASSERTION
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
@@ -97,6 +97,6 @@ Relationship: SPDXRef-Package-python-package-1-125840abc1c66dd7 CONTAINS SPDXRef
|
||||
Relationship: SPDXRef-Package-python-package-1-125840abc1c66dd7 CONTAINS SPDXRef-File-d1-f3-c6f5b29dca12661f
|
||||
Relationship: SPDXRef-Package-python-package-1-125840abc1c66dd7 CONTAINS SPDXRef-File-f2-f9e49132a4b96ccd
|
||||
Relationship: SPDXRef-DocumentRoot-Image-user-image-input CONTAINS SPDXRef-Package-python-package-1-125840abc1c66dd7
|
||||
Relationship: SPDXRef-DocumentRoot-Image-user-image-input CONTAINS SPDXRef-Package-deb-package-2-958443e2d9304af4
|
||||
Relationship: SPDXRef-DocumentRoot-Image-user-image-input CONTAINS SPDXRef-Package-deb-package-2-f27313b22a5ba330
|
||||
Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-DocumentRoot-Image-user-image-input
|
||||
|
||||
|
||||
+2
-2
@@ -20,7 +20,7 @@ FilesAnalyzed: false
|
||||
##### Package: package-2
|
||||
|
||||
PackageName: package-2
|
||||
SPDXID: SPDXRef-Package-deb-package-2-db4abfe497c180d3
|
||||
SPDXID: SPDXRef-Package-deb-package-2-ad5013466727018f
|
||||
PackageVersion: 2.0.1
|
||||
PackageSupplier: NOASSERTION
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
@@ -50,6 +50,6 @@ ExternalRef: PACKAGE-MANAGER purl a-purl-2
|
||||
##### Relationships
|
||||
|
||||
Relationship: SPDXRef-DocumentRoot-Directory-some-path CONTAINS SPDXRef-Package-python-package-1-9265397e5e15168a
|
||||
Relationship: SPDXRef-DocumentRoot-Directory-some-path CONTAINS SPDXRef-Package-deb-package-2-db4abfe497c180d3
|
||||
Relationship: SPDXRef-DocumentRoot-Directory-some-path CONTAINS SPDXRef-Package-deb-package-2-ad5013466727018f
|
||||
Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-DocumentRoot-Directory-some-path
|
||||
|
||||
|
||||
+2
-2
@@ -23,7 +23,7 @@ ExternalRef: PACKAGE-MANAGER purl pkg:oci/user-image-input@sha256:2731251dc34951
|
||||
##### Package: package-2
|
||||
|
||||
PackageName: package-2
|
||||
SPDXID: SPDXRef-Package-deb-package-2-958443e2d9304af4
|
||||
SPDXID: SPDXRef-Package-deb-package-2-f27313b22a5ba330
|
||||
PackageVersion: 2.0.1
|
||||
PackageSupplier: NOASSERTION
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
@@ -53,6 +53,6 @@ ExternalRef: PACKAGE-MANAGER purl a-purl-1
|
||||
##### Relationships
|
||||
|
||||
Relationship: SPDXRef-DocumentRoot-Image-user-image-input CONTAINS SPDXRef-Package-python-package-1-125840abc1c66dd7
|
||||
Relationship: SPDXRef-DocumentRoot-Image-user-image-input CONTAINS SPDXRef-Package-deb-package-2-958443e2d9304af4
|
||||
Relationship: SPDXRef-DocumentRoot-Image-user-image-input CONTAINS SPDXRef-Package-deb-package-2-f27313b22a5ba330
|
||||
Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-DocumentRoot-Image-user-image-input
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
stereoscopeFile "github.com/anchore/stereoscope/pkg/file"
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/syft/cpe"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/formats/internal/testutils"
|
||||
@@ -118,7 +117,7 @@ func Test_encodeDecodeFileMetadata(t *testing.T) {
|
||||
Value: "MIT",
|
||||
SPDXExpression: "MIT",
|
||||
Type: "MIT",
|
||||
URLs: internal.NewStringSet("https://example.org/license"),
|
||||
URLs: []string{"https://example.org/license"},
|
||||
Locations: file.LocationSet{},
|
||||
}),
|
||||
Language: "language",
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "db4abfe497c180d3",
|
||||
"id": "ad5013466727018f",
|
||||
"name": "package-2",
|
||||
"version": "2.0.1",
|
||||
"type": "deb",
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "9fd0b9f41034991d",
|
||||
"id": "aa0ca2c331576dfd",
|
||||
"name": "package-2",
|
||||
"version": "2.0.1",
|
||||
"type": "deb",
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "958443e2d9304af4",
|
||||
"id": "f27313b22a5ba330",
|
||||
"name": "package-2",
|
||||
"version": "2.0.1",
|
||||
"type": "deb",
|
||||
|
||||
@@ -214,11 +214,18 @@ func toLicenseModel(pkgLicenses []pkg.License) (modelLicenses []model.License) {
|
||||
if v := l.Locations.ToSlice(); v != nil {
|
||||
locations = v
|
||||
}
|
||||
|
||||
// format model must have allocated collections
|
||||
urls := l.URLs
|
||||
if urls == nil {
|
||||
urls = []string{}
|
||||
}
|
||||
|
||||
modelLicenses = append(modelLicenses, model.License{
|
||||
Value: l.Value,
|
||||
SPDXExpression: l.SPDXExpression,
|
||||
Type: l.Type,
|
||||
URLs: l.URLs.ToSlice(),
|
||||
URLs: urls,
|
||||
Locations: locations,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"github.com/google/go-cmp/cmp"
|
||||
|
||||
stereoscopeFile "github.com/anchore/stereoscope/pkg/file"
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/cpe"
|
||||
@@ -136,7 +135,7 @@ func toSyftLicenses(m []model.License) (p []pkg.License) {
|
||||
Value: l.Value,
|
||||
SPDXExpression: l.SPDXExpression,
|
||||
Type: l.Type,
|
||||
URLs: internal.NewStringSet(l.URLs...),
|
||||
URLs: l.URLs,
|
||||
Locations: file.NewLocationSet(l.Locations...),
|
||||
})
|
||||
}
|
||||
|
||||
+7
-7
@@ -4,8 +4,8 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/scylladb/go-set/strset"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
)
|
||||
@@ -124,13 +124,13 @@ func (c *Collection) addTypeToIndex(p Package) {
|
||||
}
|
||||
|
||||
func (c *Collection) addPathsToIndex(p Package) {
|
||||
observedPaths := internal.NewStringSet()
|
||||
observedPaths := strset.New()
|
||||
for _, l := range p.Locations.ToSlice() {
|
||||
if l.RealPath != "" && !observedPaths.Contains(l.RealPath) {
|
||||
if l.RealPath != "" && !observedPaths.Has(l.RealPath) {
|
||||
c.addPathToIndex(p.id, l.RealPath)
|
||||
observedPaths.Add(l.RealPath)
|
||||
}
|
||||
if l.VirtualPath != "" && l.RealPath != l.VirtualPath && !observedPaths.Contains(l.VirtualPath) {
|
||||
if l.VirtualPath != "" && l.RealPath != l.VirtualPath && !observedPaths.Has(l.VirtualPath) {
|
||||
c.addPathToIndex(p.id, l.VirtualPath)
|
||||
observedPaths.Add(l.VirtualPath)
|
||||
}
|
||||
@@ -173,13 +173,13 @@ func (c *Collection) deleteTypeFromIndex(p Package) {
|
||||
}
|
||||
|
||||
func (c *Collection) deletePathsFromIndex(p Package) {
|
||||
observedPaths := internal.NewStringSet()
|
||||
observedPaths := strset.New()
|
||||
for _, l := range p.Locations.ToSlice() {
|
||||
if l.RealPath != "" && !observedPaths.Contains(l.RealPath) {
|
||||
if l.RealPath != "" && !observedPaths.Has(l.RealPath) {
|
||||
c.deletePathFromIndex(p.id, l.RealPath)
|
||||
observedPaths.Add(l.RealPath)
|
||||
}
|
||||
if l.VirtualPath != "" && l.RealPath != l.VirtualPath && !observedPaths.Contains(l.VirtualPath) {
|
||||
if l.VirtualPath != "" && l.RealPath != l.VirtualPath && !observedPaths.Has(l.VirtualPath) {
|
||||
c.deletePathFromIndex(p.id, l.VirtualPath)
|
||||
observedPaths.Add(l.VirtualPath)
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
"github.com/facebookincubator/nvdtools/wfn"
|
||||
"github.com/scylladb/go-set/strset"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/cpe"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
@@ -118,13 +117,13 @@ func Generate(p pkg.Package) []cpe.CPE {
|
||||
return nil
|
||||
}
|
||||
|
||||
keys := internal.NewStringSet()
|
||||
keys := strset.New()
|
||||
cpes := make([]cpe.CPE, 0)
|
||||
for _, product := range products {
|
||||
for _, vendor := range vendors {
|
||||
// prevent duplicate entries...
|
||||
key := fmt.Sprintf("%s|%s|%s", product, vendor, p.Version)
|
||||
if keys.Contains(key) {
|
||||
if keys.Has(key) {
|
||||
continue
|
||||
}
|
||||
keys.Add(key)
|
||||
|
||||
@@ -44,6 +44,11 @@ func TestDpkgCataloger(t *testing.T) {
|
||||
Contains configuration files and directories required for
|
||||
authentication to work on Debian systems. This package is required
|
||||
on almost all installations.`,
|
||||
Depends: []string{
|
||||
"debconf (>= 0.5) | debconf-2.0",
|
||||
"debconf (>= 1.5.19) | cdebconf",
|
||||
"libpam-modules (>= 1.0.1-6)",
|
||||
},
|
||||
Files: []pkg.DpkgFileRecord{
|
||||
{
|
||||
Path: "/etc/pam.conf",
|
||||
@@ -112,6 +117,7 @@ func TestDpkgCataloger(t *testing.T) {
|
||||
SQLite is a C library that implements an SQL database engine.
|
||||
Programs that link with the SQLite library can have SQL database
|
||||
access without running a separate RDBMS process.`,
|
||||
Depends: []string{"libc6 (>= 2.29)"},
|
||||
Files: []pkg.DpkgFileRecord{
|
||||
{Path: "/usr/lib/aarch64-linux-gnu/libsqlite3.so.0.8.6", Digest: &file.Digest{
|
||||
Algorithm: "md5",
|
||||
|
||||
@@ -36,13 +36,15 @@ func newDpkgPackage(d pkg.DpkgMetadata, dbLocation file.Location, resolver file.
|
||||
Metadata: d,
|
||||
}
|
||||
|
||||
// the current entry only has what may have been listed in the status file, however, there are additional
|
||||
// files that are listed in multiple other locations. We should retrieve them all and merge the file lists
|
||||
// together.
|
||||
mergeFileListing(resolver, dbLocation, &p)
|
||||
if resolver != nil {
|
||||
// the current entry only has what may have been listed in the status file, however, there are additional
|
||||
// files that are listed in multiple other locations. We should retrieve them all and merge the file lists
|
||||
// together.
|
||||
mergeFileListing(resolver, dbLocation, &p)
|
||||
|
||||
// fetch additional data from the copyright file to derive the license information
|
||||
addLicenses(resolver, dbLocation, &p)
|
||||
// fetch additional data from the copyright file to derive the license information
|
||||
addLicenses(resolver, dbLocation, &p)
|
||||
}
|
||||
|
||||
p.SetID()
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/scylladb/go-set/strset"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
)
|
||||
|
||||
@@ -18,7 +20,7 @@ var (
|
||||
)
|
||||
|
||||
func parseLicensesFromCopyright(reader io.Reader) []string {
|
||||
findings := internal.NewStringSet()
|
||||
findings := strset.New()
|
||||
scanner := bufio.NewScanner(reader)
|
||||
|
||||
for scanner.Scan() {
|
||||
@@ -31,7 +33,7 @@ func parseLicensesFromCopyright(reader io.Reader) []string {
|
||||
}
|
||||
}
|
||||
|
||||
results := findings.ToSlice()
|
||||
results := findings.List()
|
||||
|
||||
sort.Strings(results)
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ func parseDpkgDB(resolver file.Resolver, env *generic.Environment, reader file.L
|
||||
pkgs = append(pkgs, newDpkgPackage(m, reader.Location, resolver, env.LinuxRelease))
|
||||
}
|
||||
|
||||
return pkgs, nil, nil
|
||||
return pkgs, associateRelationships(pkgs), nil
|
||||
}
|
||||
|
||||
// parseDpkgStatus is a parser function for Debian DB status contents, returning all Debian packages listed.
|
||||
@@ -63,6 +63,22 @@ func parseDpkgStatus(reader io.Reader) ([]pkg.DpkgMetadata, error) {
|
||||
return metadata, nil
|
||||
}
|
||||
|
||||
// dpkgExtractedMetadata is an adapter struct to capture the fields from the dpkg status file, however, the final
|
||||
// pkg.DpkgMetadata struct has different types for some fields (e.g. Provides, Depends, and PreDepends is []string, not a string).
|
||||
type dpkgExtractedMetadata struct {
|
||||
Package string `mapstructure:"Package"`
|
||||
Source string `mapstructure:"Source"`
|
||||
Version string `mapstructure:"Version"`
|
||||
SourceVersion string `mapstructure:"SourceVersion"`
|
||||
Architecture string `mapstructure:"Architecture"`
|
||||
Maintainer string `mapstructure:"Maintainer"`
|
||||
InstalledSize int `mapstructure:"InstalledSize"`
|
||||
Description string `mapstructure:"Description"`
|
||||
Provides string `mapstructure:"Provides"`
|
||||
Depends string `mapstructure:"Depends"`
|
||||
PreDepends string `mapstructure:"PreDepends"` // note: original doc is Pre-Depends
|
||||
}
|
||||
|
||||
// parseDpkgStatusEntry returns an individual Dpkg entry, or returns errEndOfPackages if there are no more packages to parse from the reader.
|
||||
func parseDpkgStatusEntry(reader *bufio.Reader) (*pkg.DpkgMetadata, error) {
|
||||
var retErr error
|
||||
@@ -77,22 +93,36 @@ func parseDpkgStatusEntry(reader *bufio.Reader) (*pkg.DpkgMetadata, error) {
|
||||
retErr = err
|
||||
}
|
||||
|
||||
entry := pkg.DpkgMetadata{}
|
||||
err = mapstructure.Decode(dpkgFields, &entry)
|
||||
raw := dpkgExtractedMetadata{}
|
||||
err = mapstructure.Decode(dpkgFields, &raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sourceName, sourceVersion := extractSourceVersion(entry.Source)
|
||||
sourceName, sourceVersion := extractSourceVersion(raw.Source)
|
||||
if sourceVersion != "" {
|
||||
entry.SourceVersion = sourceVersion
|
||||
entry.Source = sourceName
|
||||
raw.SourceVersion = sourceVersion
|
||||
raw.Source = sourceName
|
||||
}
|
||||
|
||||
if entry.Package == "" {
|
||||
if raw.Package == "" {
|
||||
return nil, retErr
|
||||
}
|
||||
|
||||
entry := pkg.DpkgMetadata{
|
||||
Package: raw.Package,
|
||||
Source: raw.Source,
|
||||
Version: raw.Version,
|
||||
SourceVersion: raw.SourceVersion,
|
||||
Architecture: raw.Architecture,
|
||||
Maintainer: raw.Maintainer,
|
||||
InstalledSize: raw.InstalledSize,
|
||||
Description: raw.Description,
|
||||
Provides: splitPkgList(raw.Provides),
|
||||
Depends: splitPkgList(raw.Depends),
|
||||
PreDepends: splitPkgList(raw.PreDepends),
|
||||
}
|
||||
|
||||
// there may be an optional conffiles section that we should persist as files
|
||||
if conffilesSection, exists := dpkgFields["Conffiles"]; exists && conffilesSection != nil {
|
||||
if sectionStr, ok := conffilesSection.(string); ok {
|
||||
@@ -108,6 +138,17 @@ func parseDpkgStatusEntry(reader *bufio.Reader) (*pkg.DpkgMetadata, error) {
|
||||
return &entry, retErr
|
||||
}
|
||||
|
||||
func splitPkgList(pkgList string) (ret []string) {
|
||||
fields := strings.Split(pkgList, ",")
|
||||
for _, field := range fields {
|
||||
field = strings.TrimSpace(field)
|
||||
if field != "" {
|
||||
ret = append(ret, field)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func extractAllFields(reader *bufio.Reader) (map[string]interface{}, error) {
|
||||
dpkgFields := make(map[string]interface{})
|
||||
var key string
|
||||
@@ -195,3 +236,79 @@ func handleNewKeyValue(line string) (key string, val interface{}, err error) {
|
||||
|
||||
return "", nil, fmt.Errorf("cannot parse field from line: '%s'", line)
|
||||
}
|
||||
|
||||
// associateRelationships will create relationships between packages based on the "Depends", "Pre-Depends", and "Provides"
|
||||
// fields for installed packages. if there is an installed package that has a dependency that is (somehow) not installed,
|
||||
// then that relationship (between the installed and uninstalled package) will NOT be created.
|
||||
func associateRelationships(pkgs []pkg.Package) (relationships []artifact.Relationship) {
|
||||
// map["provides" + "package"] -> packages that provide that package
|
||||
lookup := make(map[string][]pkg.Package)
|
||||
|
||||
// read provided and add as keys for lookup keys as well as package names
|
||||
for _, p := range pkgs {
|
||||
meta, ok := p.Metadata.(pkg.DpkgMetadata)
|
||||
if !ok {
|
||||
log.Warnf("cataloger failed to extract dpkg 'provides' metadata for package %+v", p.Name)
|
||||
continue
|
||||
}
|
||||
lookup[p.Name] = append(lookup[p.Name], p)
|
||||
for _, provides := range meta.Provides {
|
||||
k := stripVersionSpecifier(provides)
|
||||
lookup[k] = append(lookup[k], p)
|
||||
}
|
||||
}
|
||||
|
||||
// read "Depends" and "Pre-Depends" and match with keys
|
||||
for _, p := range pkgs {
|
||||
meta, ok := p.Metadata.(pkg.DpkgMetadata)
|
||||
if !ok {
|
||||
log.Warnf("cataloger failed to extract dpkg 'dependency' metadata for package %+v", p.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
var allDeps []string
|
||||
allDeps = append(allDeps, meta.Depends...)
|
||||
allDeps = append(allDeps, meta.PreDepends...)
|
||||
|
||||
for _, depSpecifier := range allDeps {
|
||||
deps := splitPackageChoice(depSpecifier)
|
||||
for _, dep := range deps {
|
||||
for _, depPkg := range lookup[dep] {
|
||||
relationships = append(relationships, artifact.Relationship{
|
||||
From: depPkg,
|
||||
To: p,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return relationships
|
||||
}
|
||||
|
||||
func stripVersionSpecifier(s string) string {
|
||||
// examples:
|
||||
// libgmp10 (>= 2:6.2.1+dfsg1) --> libgmp10
|
||||
// libgmp10 --> libgmp10
|
||||
// foo [i386] --> foo
|
||||
// default-mta | mail-transport-agent --> default-mta | mail-transport-agent
|
||||
// kernel-headers-2.2.10 [!hurd-i386] --> kernel-headers-2.2.10
|
||||
|
||||
items := internal.SplitAny(s, "[(<>=")
|
||||
if len(items) == 0 {
|
||||
return s
|
||||
}
|
||||
|
||||
return strings.TrimSpace(items[0])
|
||||
}
|
||||
|
||||
func splitPackageChoice(s string) (ret []string) {
|
||||
fields := strings.Split(s, "|")
|
||||
for _, field := range fields {
|
||||
field = strings.TrimSpace(field)
|
||||
if field != "" {
|
||||
ret = append(ret, stripVersionSpecifier(field))
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
@@ -11,9 +11,11 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
||||
)
|
||||
|
||||
@@ -48,6 +50,18 @@ func Test_parseDpkgStatus(t *testing.T) {
|
||||
* apt-cdrom to use removable media as a source for packages
|
||||
* apt-config as an interface to the configuration settings
|
||||
* apt-key as an interface to manage authentication keys`,
|
||||
Provides: []string{"apt-transport-https (= 1.8.2)"},
|
||||
Depends: []string{
|
||||
"adduser",
|
||||
"gpgv | gpgv2 | gpgv1",
|
||||
"debian-archive-keyring",
|
||||
"libapt-pkg5.0 (>= 1.7.0~alpha3~)",
|
||||
"libc6 (>= 2.15)",
|
||||
"libgcc1 (>= 1:3.0)",
|
||||
"libgnutls30 (>= 3.6.6)",
|
||||
"libseccomp2 (>= 1.0.1)",
|
||||
"libstdc++6 (>= 5.2)",
|
||||
},
|
||||
Files: []pkg.DpkgFileRecord{
|
||||
{
|
||||
Path: "/etc/apt/apt.conf.d/01autoremove",
|
||||
@@ -110,6 +124,18 @@ func Test_parseDpkgStatus(t *testing.T) {
|
||||
* apt-cdrom to use removable media as a source for packages
|
||||
* apt-config as an interface to the configuration settings
|
||||
* apt-key as an interface to manage authentication keys`,
|
||||
Provides: []string{"apt-transport-https (= 1.8.2)"},
|
||||
Depends: []string{
|
||||
"adduser",
|
||||
"gpgv | gpgv2 | gpgv1",
|
||||
"debian-archive-keyring",
|
||||
"libapt-pkg5.0 (>= 1.7.0~alpha3~)",
|
||||
"libc6 (>= 2.15)",
|
||||
"libgcc1 (>= 1:3.0)",
|
||||
"libgnutls30 (>= 3.6.6)",
|
||||
"libseccomp2 (>= 1.0.1)",
|
||||
"libstdc++6 (>= 5.2)",
|
||||
},
|
||||
Files: []pkg.DpkgFileRecord{},
|
||||
},
|
||||
},
|
||||
@@ -135,7 +161,9 @@ func Test_parseDpkgStatus(t *testing.T) {
|
||||
globe. It is updated periodically to reflect changes made by
|
||||
political bodies to time zone boundaries, UTC offsets, and
|
||||
daylight-saving rules.`,
|
||||
Files: []pkg.DpkgFileRecord{},
|
||||
Provides: []string{"tzdata-buster"},
|
||||
Depends: []string{"debconf (>= 0.5) | debconf-2.0"},
|
||||
Files: []pkg.DpkgFileRecord{},
|
||||
},
|
||||
{
|
||||
Package: "util-linux",
|
||||
@@ -149,6 +177,14 @@ func Test_parseDpkgStatus(t *testing.T) {
|
||||
important utilities included in this package allow you to view kernel
|
||||
messages, create new filesystems, view block device information,
|
||||
interface with real time clock, etc.`,
|
||||
Depends: []string{"fdisk", "login (>= 1:4.5-1.1~)"},
|
||||
PreDepends: []string{
|
||||
"libaudit1 (>= 1:2.2.1)", "libblkid1 (>= 2.31.1)", "libc6 (>= 2.25)",
|
||||
"libcap-ng0 (>= 0.7.9)", "libmount1 (>= 2.25)", "libpam0g (>= 0.99.7.1)",
|
||||
"libselinux1 (>= 2.6-3~)", "libsmartcols1 (>= 2.33)", "libsystemd0",
|
||||
"libtinfo6 (>= 6)", "libudev1 (>= 183)", "libuuid1 (>= 2.16)",
|
||||
"zlib1g (>= 1:1.1.4)",
|
||||
},
|
||||
Files: []pkg.DpkgFileRecord{
|
||||
{
|
||||
Path: "/etc/default/hwclock",
|
||||
@@ -397,3 +433,122 @@ func Test_handleNewKeyValue(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_stripVersionSpecifier(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "package name only",
|
||||
input: "test",
|
||||
want: "test",
|
||||
},
|
||||
{
|
||||
name: "with version",
|
||||
input: "test (1.2.3)",
|
||||
want: "test",
|
||||
},
|
||||
{
|
||||
name: "multiple packages",
|
||||
input: "test | other",
|
||||
want: "test | other",
|
||||
},
|
||||
{
|
||||
name: "with architecture specifiers",
|
||||
input: "test [amd64 i386]",
|
||||
want: "test",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equal(t, tt.want, stripVersionSpecifier(tt.input))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_associateRelationships(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
fixture string
|
||||
wantRelationships map[string][]string
|
||||
}{
|
||||
{
|
||||
name: "relationships for coreutils",
|
||||
fixture: "test-fixtures/status/coreutils-relationships",
|
||||
wantRelationships: map[string][]string{
|
||||
"coreutils": {"libacl1", "libattr1", "libc6", "libgmp10", "libselinux1"},
|
||||
"libacl1": {"libc6"},
|
||||
"libattr1": {"libc6"},
|
||||
"libc6": {"libgcc-s1"},
|
||||
"libgcc-s1": {"gcc-12-base", "libc6"},
|
||||
"libgmp10": {"libc6"},
|
||||
"libpcre2-8-0": {"libc6"},
|
||||
"libselinux1": {"libc6", "libpcre2-8-0"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "relationships from dpkg example docs",
|
||||
fixture: "test-fixtures/status/doc-examples",
|
||||
wantRelationships: map[string][]string{
|
||||
"made-up-package-1": {"kernel-headers-2.2.10", "hurd-dev", "gnumach-dev"},
|
||||
"made-up-package-2": {"libluajit5.1-dev", "liblua5.1-dev"},
|
||||
"made-up-package-3": {"foo", "bar"},
|
||||
// note that the "made-up-package-4" depends on "made-up-package-5" but not via the direct
|
||||
// package name, but through the "provides" virtual package name "virtual-package-5".
|
||||
"made-up-package-4": {"made-up-package-5"},
|
||||
// note that though there is a "default-mta | mail-transport-agent | not-installed"
|
||||
// dependency choice we raise up the packages that are installed for every choice.
|
||||
// In this case that means that "default-mta" and "mail-transport-agent".
|
||||
"mutt": {"libc6", "default-mta", "mail-transport-agent"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "relationships for libpam-runtime",
|
||||
fixture: "test-fixtures/status/libpam-runtime",
|
||||
wantRelationships: map[string][]string{
|
||||
"libpam-runtime": {"debconf1", "debconf-2.0", "debconf2", "cdebconf", "libpam-modules"},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
f, err := os.Open(tt.fixture)
|
||||
require.NoError(t, err)
|
||||
|
||||
reader := file.NewLocationReadCloser(file.NewLocation(tt.fixture), f)
|
||||
|
||||
pkgs, relationships, err := parseDpkgDB(nil, &generic.Environment{}, reader)
|
||||
require.NotEmpty(t, pkgs)
|
||||
require.NotEmpty(t, relationships)
|
||||
require.NoError(t, err)
|
||||
|
||||
if d := cmp.Diff(tt.wantRelationships, abstractRelationships(t, relationships)); d != "" {
|
||||
t.Errorf("unexpected relationships (-want +got):\n%s", d)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func abstractRelationships(t testing.TB, relationships []artifact.Relationship) map[string][]string {
|
||||
t.Helper()
|
||||
|
||||
abstracted := make(map[string][]string)
|
||||
for _, relationship := range relationships {
|
||||
fromPkg, ok := relationship.From.(pkg.Package)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
toPkg, ok := relationship.To.(pkg.Package)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// we build this backwards since we use DependencyOfRelationship instead of DependsOn
|
||||
abstracted[toPkg.Name] = append(abstracted[toPkg.Name], fromPkg.Name)
|
||||
}
|
||||
|
||||
return abstracted
|
||||
}
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
Package: coreutils
|
||||
Essential: yes
|
||||
Status: install ok installed
|
||||
Priority: required
|
||||
Section: utils
|
||||
Installed-Size: 20272
|
||||
Maintainer: Michael Stone <mstone@debian.org>
|
||||
Architecture: arm64
|
||||
Multi-Arch: foreign
|
||||
Version: 9.1-1
|
||||
Pre-Depends: libacl1 (>= 2.2.23), libattr1 (>= 1:2.4.44), libc6 (>= 2.34), libgmp10 (>= 2:6.2.1+dfsg1), libselinux1 (>= 3.1~)
|
||||
|
||||
Package: libacl1
|
||||
Status: install ok installed
|
||||
Priority: optional
|
||||
Section: libs
|
||||
Installed-Size: 101
|
||||
Maintainer: Guillem Jover <guillem@debian.org>
|
||||
Architecture: arm64
|
||||
Multi-Arch: same
|
||||
Source: acl
|
||||
Version: 2.3.1-3
|
||||
Depends: libc6 (>= 2.33)
|
||||
|
||||
Package: libc6
|
||||
Status: install ok installed
|
||||
Priority: optional
|
||||
Section: libs
|
||||
Installed-Size: 23127
|
||||
Maintainer: GNU Libc Maintainers <debian-glibc@lists.debian.org>
|
||||
Architecture: arm64
|
||||
Multi-Arch: same
|
||||
Source: glibc
|
||||
Version: 2.36-9+deb12u1
|
||||
Depends: libgcc-s1
|
||||
Recommends: libidn2-0 (>= 2.0.5~)
|
||||
Suggests: glibc-doc, debconf | debconf-2.0, libc-l10n, locales, libnss-nis, libnss-nisplus
|
||||
Breaks: aide (<< 0.17.3-4+b3), busybox (<< 1.30.1-6), chrony (<< 4.2-3~), fakechroot (<< 2.19-3.5), firefox (<< 91~), firefox-esr (<< 91~), gnumach-image-1.8-486 (<< 2:1.8+git20210923~), gnumach-image-1.8-486-dbg (<< 2:1.8+git20210923~), gnumach-image-1.8-xen-486 (<< 2:1.8+git20210923~), gnumach-image-1.8-xen-486-dbg (<< 2:1.8+git20210923~), hurd (<< 1:0.9.git20220301-2), ioquake3 (<< 1.36+u20200211.f2c61c1~dfsg-2~), iraf-fitsutil (<< 2018.07.06-4), libgegl-0.4-0 (<< 0.4.18), libtirpc1 (<< 0.2.3), locales (<< 2.36), locales-all (<< 2.36), macs (<< 2.2.7.1-3~), nocache (<< 1.1-1~), nscd (<< 2.36), openarena (<< 0.8.8+dfsg-4~), openssh-server (<< 1:8.1p1-5), python3-iptables (<< 1.0.0-2), r-cran-later (<< 0.7.5+dfsg-2), tinydns (<< 1:1.05-14), valgrind (<< 1:3.19.0-1~), wcc (<< 0.0.2+dfsg-3)
|
||||
|
||||
Package: libgcc-s1
|
||||
Protected: yes
|
||||
Status: install ok installed
|
||||
Priority: optional
|
||||
Section: libs
|
||||
Installed-Size: 147
|
||||
Maintainer: Debian GCC Maintainers <debian-gcc@lists.debian.org>
|
||||
Architecture: arm64
|
||||
Multi-Arch: same
|
||||
Source: gcc-12
|
||||
Version: 12.2.0-14
|
||||
Replaces: libgcc1 (<< 1:10)
|
||||
Provides: libgcc1 (= 1:12.2.0-14)
|
||||
Depends: gcc-12-base (= 12.2.0-14), libc6 (>= 2.35)
|
||||
|
||||
Package: gcc-12-base
|
||||
Status: install ok installed
|
||||
Priority: optional
|
||||
Section: libs
|
||||
Installed-Size: 100
|
||||
Maintainer: Debian GCC Maintainers <debian-gcc@lists.debian.org>
|
||||
Architecture: arm64
|
||||
Multi-Arch: same
|
||||
Source: gcc-12
|
||||
Version: 12.2.0-14
|
||||
Breaks: gnat (<< 7)
|
||||
|
||||
Package: libattr1
|
||||
Status: install ok installed
|
||||
Priority: optional
|
||||
Section: libs
|
||||
Installed-Size: 99
|
||||
Maintainer: Guillem Jover <guillem@debian.org>
|
||||
Architecture: arm64
|
||||
Multi-Arch: same
|
||||
Source: attr
|
||||
Version: 1:2.5.1-4
|
||||
Depends: libc6 (>= 2.17)
|
||||
|
||||
Package: libgmp10
|
||||
Status: install ok installed
|
||||
Priority: optional
|
||||
Section: libs
|
||||
Installed-Size: 855
|
||||
Maintainer: Debian Science Team <debian-science-maintainers@lists.alioth.debian.org>
|
||||
Architecture: arm64
|
||||
Multi-Arch: same
|
||||
Source: gmp
|
||||
Version: 2:6.2.1+dfsg1-1.1
|
||||
Depends: libc6 (>= 2.17)
|
||||
Breaks: libmath-gmp-perl (<< 2.20-1), libmath-prime-util-gmp-perl (<< 0.51-2), postgresql-pgmp (<< 1.0.3-1)
|
||||
|
||||
Package: libselinux1
|
||||
Status: install ok installed
|
||||
Priority: optional
|
||||
Section: libs
|
||||
Installed-Size: 223
|
||||
Maintainer: Debian SELinux maintainers <selinux-devel@lists.alioth.debian.org>
|
||||
Architecture: arm64
|
||||
Multi-Arch: same
|
||||
Source: libselinux (3.4-1)
|
||||
Version: 3.4-1+b6
|
||||
Depends: libc6 (>= 2.34), libpcre2-8-0 (>= 10.22)
|
||||
|
||||
Package: libpcre2-8-0
|
||||
Status: install ok installed
|
||||
Priority: optional
|
||||
Section: libs
|
||||
Installed-Size: 649
|
||||
Maintainer: Matthew Vernon <matthew@debian.org>
|
||||
Architecture: arm64
|
||||
Multi-Arch: same
|
||||
Source: pcre2
|
||||
Version: 10.42-1
|
||||
Depends: libc6 (>= 2.34)
|
||||
@@ -0,0 +1,46 @@
|
||||
Package: mutt
|
||||
Version: 1.3.17-1
|
||||
Depends: libc6 (>= 2.2.1), default-mta | mail-transport-agent | not-installed
|
||||
|
||||
Package: made-up-package-1
|
||||
Version: 1.0.0
|
||||
Source: glibc
|
||||
Depends: kernel-headers-2.2.10 [!hurd-i386],
|
||||
hurd-dev [hurd-i386], gnumach-dev [hurd-i386]
|
||||
|
||||
Package: made-up-package-2
|
||||
Version: 2.0.0
|
||||
Depends:
|
||||
libluajit5.1-dev [i386 amd64 kfreebsd-i386 armel armhf powerpc mips],
|
||||
liblua5.1-dev [hurd-i386 ia64 kfreebsd-amd64 s390x sparc],
|
||||
|
||||
Package: made-up-package-3
|
||||
Version: 3.0.0
|
||||
Depends: foo [i386], bar [amd64]
|
||||
|
||||
Package: made-up-package-4
|
||||
Version: 3.0.0
|
||||
Depends: virtual-package-5
|
||||
|
||||
Package: made-up-package-5
|
||||
Provides: virtual-package-5 (=1.0)
|
||||
|
||||
Package: foo
|
||||
|
||||
Package: bar
|
||||
|
||||
Package: kernel-headers-2.2.10
|
||||
|
||||
Package: hurd-dev
|
||||
|
||||
Package: gnumach-dev
|
||||
|
||||
Package: default-mta
|
||||
|
||||
Package: mail-transport-agent
|
||||
|
||||
Package: libc6
|
||||
|
||||
Package: libluajit5.1-dev
|
||||
|
||||
Package: liblua5.1-dev
|
||||
@@ -0,0 +1,23 @@
|
||||
Package: libpam-runtime
|
||||
Status: install ok installed
|
||||
Priority: required
|
||||
Section: admin
|
||||
Installed-Size: 876
|
||||
Maintainer: Sam Hartman <hartmans@debian.org>
|
||||
Architecture: all
|
||||
Multi-Arch: foreign
|
||||
Source: pam
|
||||
Version: 1.5.2-6+deb12u1
|
||||
Replaces: libpam0g-dev, libpam0g-util
|
||||
Depends: debconf1 (>= 0.5) | debconf-2.0, debconf2 (>= 1.5.19) | cdebconf, libpam-modules (>= 1.0.1-6)
|
||||
Conflicts: libpam0g-util
|
||||
|
||||
Package: debconf1
|
||||
|
||||
Package: debconf2
|
||||
|
||||
Package: debconf-2.0
|
||||
|
||||
Package: cdebconf
|
||||
|
||||
Package: libpam-modules
|
||||
@@ -1,6 +1,8 @@
|
||||
package dotnet
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/packageurl-go"
|
||||
@@ -9,13 +11,7 @@ import (
|
||||
)
|
||||
|
||||
func newDotnetDepsPackage(nameVersion string, lib dotnetDepsLibrary, locations ...file.Location) *pkg.Package {
|
||||
if lib.Type != "package" {
|
||||
return nil
|
||||
}
|
||||
|
||||
fields := strings.Split(nameVersion, "/")
|
||||
name := fields[0]
|
||||
version := fields[1]
|
||||
name, version := extractNameAndVersion(nameVersion)
|
||||
|
||||
m := pkg.DotnetDepsMetadata{
|
||||
Name: name,
|
||||
@@ -41,6 +37,27 @@ func newDotnetDepsPackage(nameVersion string, lib dotnetDepsLibrary, locations .
|
||||
return p
|
||||
}
|
||||
|
||||
func getDepsJSONFilePrefix(p string) string {
|
||||
r := regexp.MustCompile(`([^\/]+)\.deps\.json$`)
|
||||
match := r.FindStringSubmatch(p)
|
||||
if len(match) > 1 {
|
||||
return match[1]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func extractNameAndVersion(nameVersion string) (name, version string) {
|
||||
fields := strings.Split(nameVersion, "/")
|
||||
name = fields[0]
|
||||
version = fields[1]
|
||||
return
|
||||
}
|
||||
|
||||
func createNameAndVersion(name, version string) (nameVersion string) {
|
||||
nameVersion = fmt.Sprintf("%s/%s", name, version)
|
||||
return
|
||||
}
|
||||
|
||||
func packageURL(m pkg.DotnetDepsMetadata) string {
|
||||
var qualifiers packageurl.Qualifiers
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
@@ -13,8 +14,18 @@ import (
|
||||
|
||||
var _ generic.Parser = parseDotnetDeps
|
||||
|
||||
type dotnetRuntimeTarget struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type dotnetDepsTarget struct {
|
||||
Dependencies map[string]string `json:"dependencies"`
|
||||
Runtime map[string]struct{} `json:"runtime"`
|
||||
}
|
||||
type dotnetDeps struct {
|
||||
Libraries map[string]dotnetDepsLibrary `json:"libraries"`
|
||||
RuntimeTarget dotnetRuntimeTarget `json:"runtimeTarget"`
|
||||
Targets map[string]map[string]dotnetDepsTarget `json:"targets"`
|
||||
Libraries map[string]dotnetDepsLibrary `json:"libraries"`
|
||||
}
|
||||
|
||||
type dotnetDepsLibrary struct {
|
||||
@@ -24,27 +35,55 @@ type dotnetDepsLibrary struct {
|
||||
HashPath string `json:"hashPath"`
|
||||
}
|
||||
|
||||
//nolint:funlen
|
||||
func parseDotnetDeps(_ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
var pkgs []pkg.Package
|
||||
var pkgMap = make(map[string]pkg.Package)
|
||||
var relationships []artifact.Relationship
|
||||
|
||||
dec := json.NewDecoder(reader)
|
||||
|
||||
var p dotnetDeps
|
||||
if err := dec.Decode(&p); err != nil {
|
||||
var depsDoc dotnetDeps
|
||||
if err := dec.Decode(&depsDoc); err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to parse deps.json file: %w", err)
|
||||
}
|
||||
|
||||
var names []string
|
||||
rootName := getDepsJSONFilePrefix(reader.AccessPath())
|
||||
if rootName == "" {
|
||||
return nil, nil, fmt.Errorf("unable to determine root package name from deps.json file: %s", reader.AccessPath())
|
||||
}
|
||||
var rootPkg *pkg.Package
|
||||
for nameVersion, lib := range depsDoc.Libraries {
|
||||
name, _ := extractNameAndVersion(nameVersion)
|
||||
if lib.Type == "project" && name == rootName {
|
||||
rootPkg = newDotnetDepsPackage(
|
||||
nameVersion,
|
||||
lib,
|
||||
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
)
|
||||
}
|
||||
}
|
||||
if rootPkg == nil {
|
||||
return nil, nil, fmt.Errorf("unable to determine root package from deps.json file: %s", reader.AccessPath())
|
||||
}
|
||||
pkgs = append(pkgs, *rootPkg)
|
||||
pkgMap[createNameAndVersion(rootPkg.Name, rootPkg.Version)] = *rootPkg
|
||||
|
||||
for nameVersion := range p.Libraries {
|
||||
var names []string
|
||||
for nameVersion := range depsDoc.Libraries {
|
||||
names = append(names, nameVersion)
|
||||
}
|
||||
|
||||
// sort the names so that the order of the packages is deterministic
|
||||
sort.Strings(names)
|
||||
|
||||
for _, nameVersion := range names {
|
||||
lib := p.Libraries[nameVersion]
|
||||
// skip the root package
|
||||
name, version := extractNameAndVersion(nameVersion)
|
||||
if name == rootPkg.Name && version == rootPkg.Version {
|
||||
continue
|
||||
}
|
||||
|
||||
lib := depsDoc.Libraries[nameVersion]
|
||||
dotnetPkg := newDotnetDepsPackage(
|
||||
nameVersion,
|
||||
lib,
|
||||
@@ -53,8 +92,36 @@ func parseDotnetDeps(_ file.Resolver, _ *generic.Environment, reader file.Locati
|
||||
|
||||
if dotnetPkg != nil {
|
||||
pkgs = append(pkgs, *dotnetPkg)
|
||||
pkgMap[nameVersion] = *dotnetPkg
|
||||
}
|
||||
}
|
||||
|
||||
return pkgs, nil, nil
|
||||
for pkgNameVersion, target := range depsDoc.Targets[depsDoc.RuntimeTarget.Name] {
|
||||
for depName, depVersion := range target.Dependencies {
|
||||
depNameVersion := createNameAndVersion(depName, depVersion)
|
||||
depPkg, ok := pkgMap[depNameVersion]
|
||||
if !ok {
|
||||
log.Debug("unable to find package in map", depNameVersion)
|
||||
continue
|
||||
}
|
||||
p, ok := pkgMap[pkgNameVersion]
|
||||
if !ok {
|
||||
log.Debug("unable to find package in map", pkgNameVersion)
|
||||
continue
|
||||
}
|
||||
rel := artifact.Relationship{
|
||||
From: depPkg,
|
||||
To: p,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
}
|
||||
relationships = append(relationships, rel)
|
||||
}
|
||||
}
|
||||
|
||||
// sort the relationships for deterministic output
|
||||
// TODO: ideally this would be replaced with artifact.SortRelationships when one exists and is type agnostic.
|
||||
// this will only consider package-to-package relationships.
|
||||
pkg.SortRelationships(relationships)
|
||||
|
||||
return pkgs, relationships, nil
|
||||
}
|
||||
|
||||
@@ -12,202 +12,363 @@ import (
|
||||
func TestParseDotnetDeps(t *testing.T) {
|
||||
fixture := "test-fixtures/TestLibrary.deps.json"
|
||||
fixtureLocationSet := file.NewLocationSet(file.NewLocation(fixture))
|
||||
expected := []pkg.Package{
|
||||
{
|
||||
Name: "AWSSDK.Core",
|
||||
Version: "3.7.10.6",
|
||||
PURL: "pkg:nuget/AWSSDK.Core@3.7.10.6",
|
||||
Locations: fixtureLocationSet,
|
||||
Language: pkg.Dotnet,
|
||||
Type: pkg.DotnetPkg,
|
||||
MetadataType: pkg.DotnetDepsMetadataType,
|
||||
Metadata: pkg.DotnetDepsMetadata{
|
||||
Name: "AWSSDK.Core",
|
||||
Version: "3.7.10.6",
|
||||
Sha512: "sha512-kHBB+QmosVaG6DpngXQ8OlLVVNMzltNITfsRr68Z90qO7dSqJ2EHNd8dtBU1u3AQQLqqFHOY0lfmbpexeH6Pew==",
|
||||
Path: "awssdk.core/3.7.10.6",
|
||||
HashPath: "awssdk.core.3.7.10.6.nupkg.sha512",
|
||||
},
|
||||
rootPkg := pkg.Package{
|
||||
Name: "TestLibrary",
|
||||
Version: "1.0.0",
|
||||
PURL: "pkg:nuget/TestLibrary@1.0.0",
|
||||
Locations: fixtureLocationSet,
|
||||
Language: pkg.Dotnet,
|
||||
Type: pkg.DotnetPkg,
|
||||
MetadataType: pkg.DotnetDepsMetadataType,
|
||||
Metadata: pkg.DotnetDepsMetadata{
|
||||
Name: "TestLibrary",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
{
|
||||
Name: "Microsoft.Extensions.DependencyInjection.Abstractions",
|
||||
Version: "6.0.0",
|
||||
PURL: "pkg:nuget/Microsoft.Extensions.DependencyInjection.Abstractions@6.0.0",
|
||||
Locations: fixtureLocationSet,
|
||||
Language: pkg.Dotnet,
|
||||
Type: pkg.DotnetPkg,
|
||||
MetadataType: pkg.DotnetDepsMetadataType,
|
||||
Metadata: pkg.DotnetDepsMetadata{
|
||||
Name: "Microsoft.Extensions.DependencyInjection.Abstractions",
|
||||
Version: "6.0.0",
|
||||
Sha512: "sha512-xlzi2IYREJH3/m6+lUrQlujzX8wDitm4QGnUu6kUXTQAWPuZY8i+ticFJbzfqaetLA6KR/rO6Ew/HuYD+bxifg==",
|
||||
Path: "microsoft.extensions.dependencyinjection.abstractions/6.0.0",
|
||||
HashPath: "microsoft.extensions.dependencyinjection.abstractions.6.0.0.nupkg.sha512",
|
||||
},
|
||||
}
|
||||
testCommon := pkg.Package{
|
||||
Name: "TestCommon",
|
||||
Version: "1.0.0",
|
||||
PURL: "pkg:nuget/TestCommon@1.0.0",
|
||||
Locations: fixtureLocationSet,
|
||||
Language: pkg.Dotnet,
|
||||
Type: pkg.DotnetPkg,
|
||||
MetadataType: pkg.DotnetDepsMetadataType,
|
||||
Metadata: pkg.DotnetDepsMetadata{
|
||||
Name: "TestCommon",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
{
|
||||
Name: "Microsoft.Extensions.DependencyInjection",
|
||||
Version: "6.0.0",
|
||||
PURL: "pkg:nuget/Microsoft.Extensions.DependencyInjection@6.0.0",
|
||||
Locations: fixtureLocationSet,
|
||||
Language: pkg.Dotnet,
|
||||
Type: pkg.DotnetPkg,
|
||||
MetadataType: pkg.DotnetDepsMetadataType,
|
||||
Metadata: pkg.DotnetDepsMetadata{
|
||||
Name: "Microsoft.Extensions.DependencyInjection",
|
||||
Version: "6.0.0",
|
||||
Sha512: "sha512-k6PWQMuoBDGGHOQTtyois2u4AwyVcIwL2LaSLlTZQm2CYcJ1pxbt6jfAnpWmzENA/wfrYRI/X9DTLoUkE4AsLw==",
|
||||
Path: "microsoft.extensions.dependencyinjection/6.0.0",
|
||||
HashPath: "microsoft.extensions.dependencyinjection.6.0.0.nupkg.sha512",
|
||||
},
|
||||
}
|
||||
awssdkcore := pkg.Package{
|
||||
Name: "AWSSDK.Core",
|
||||
Version: "3.7.10.6",
|
||||
PURL: "pkg:nuget/AWSSDK.Core@3.7.10.6",
|
||||
Locations: fixtureLocationSet,
|
||||
Language: pkg.Dotnet,
|
||||
Type: pkg.DotnetPkg,
|
||||
MetadataType: pkg.DotnetDepsMetadataType,
|
||||
Metadata: pkg.DotnetDepsMetadata{
|
||||
Name: "AWSSDK.Core",
|
||||
Version: "3.7.10.6",
|
||||
Sha512: "sha512-kHBB+QmosVaG6DpngXQ8OlLVVNMzltNITfsRr68Z90qO7dSqJ2EHNd8dtBU1u3AQQLqqFHOY0lfmbpexeH6Pew==",
|
||||
Path: "awssdk.core/3.7.10.6",
|
||||
HashPath: "awssdk.core.3.7.10.6.nupkg.sha512",
|
||||
},
|
||||
{
|
||||
Name: "Microsoft.Extensions.Logging.Abstractions",
|
||||
Version: "6.0.0",
|
||||
PURL: "pkg:nuget/Microsoft.Extensions.Logging.Abstractions@6.0.0",
|
||||
Locations: fixtureLocationSet,
|
||||
Language: pkg.Dotnet,
|
||||
Type: pkg.DotnetPkg,
|
||||
MetadataType: pkg.DotnetDepsMetadataType,
|
||||
Metadata: pkg.DotnetDepsMetadata{
|
||||
Name: "Microsoft.Extensions.Logging.Abstractions",
|
||||
Version: "6.0.0",
|
||||
Sha512: "sha512-/HggWBbTwy8TgebGSX5DBZ24ndhzi93sHUBDvP1IxbZD7FDokYzdAr6+vbWGjw2XAfR2EJ1sfKUotpjHnFWPxA==",
|
||||
Path: "microsoft.extensions.logging.abstractions/6.0.0",
|
||||
HashPath: "microsoft.extensions.logging.abstractions.6.0.0.nupkg.sha512",
|
||||
},
|
||||
}
|
||||
msftDependencyInjectionAbstractions := pkg.Package{
|
||||
Name: "Microsoft.Extensions.DependencyInjection.Abstractions",
|
||||
Version: "6.0.0",
|
||||
PURL: "pkg:nuget/Microsoft.Extensions.DependencyInjection.Abstractions@6.0.0",
|
||||
Locations: fixtureLocationSet,
|
||||
Language: pkg.Dotnet,
|
||||
Type: pkg.DotnetPkg,
|
||||
MetadataType: pkg.DotnetDepsMetadataType,
|
||||
Metadata: pkg.DotnetDepsMetadata{
|
||||
Name: "Microsoft.Extensions.DependencyInjection.Abstractions",
|
||||
Version: "6.0.0",
|
||||
Sha512: "sha512-xlzi2IYREJH3/m6+lUrQlujzX8wDitm4QGnUu6kUXTQAWPuZY8i+ticFJbzfqaetLA6KR/rO6Ew/HuYD+bxifg==",
|
||||
Path: "microsoft.extensions.dependencyinjection.abstractions/6.0.0",
|
||||
HashPath: "microsoft.extensions.dependencyinjection.abstractions.6.0.0.nupkg.sha512",
|
||||
},
|
||||
{
|
||||
Name: "Microsoft.Extensions.Logging",
|
||||
Version: "6.0.0",
|
||||
PURL: "pkg:nuget/Microsoft.Extensions.Logging@6.0.0",
|
||||
Locations: fixtureLocationSet,
|
||||
Language: pkg.Dotnet,
|
||||
Type: pkg.DotnetPkg,
|
||||
MetadataType: pkg.DotnetDepsMetadataType,
|
||||
Metadata: pkg.DotnetDepsMetadata{
|
||||
Name: "Microsoft.Extensions.Logging",
|
||||
Version: "6.0.0",
|
||||
Sha512: "sha512-eIbyj40QDg1NDz0HBW0S5f3wrLVnKWnDJ/JtZ+yJDFnDj90VoPuoPmFkeaXrtu+0cKm5GRAwoDf+dBWXK0TUdg==",
|
||||
Path: "microsoft.extensions.logging/6.0.0",
|
||||
HashPath: "microsoft.extensions.logging.6.0.0.nupkg.sha512",
|
||||
},
|
||||
}
|
||||
msftDependencyInjection := pkg.Package{
|
||||
Name: "Microsoft.Extensions.DependencyInjection",
|
||||
Version: "6.0.0",
|
||||
PURL: "pkg:nuget/Microsoft.Extensions.DependencyInjection@6.0.0",
|
||||
Locations: fixtureLocationSet,
|
||||
Language: pkg.Dotnet,
|
||||
Type: pkg.DotnetPkg,
|
||||
MetadataType: pkg.DotnetDepsMetadataType,
|
||||
Metadata: pkg.DotnetDepsMetadata{
|
||||
Name: "Microsoft.Extensions.DependencyInjection",
|
||||
Version: "6.0.0",
|
||||
Sha512: "sha512-k6PWQMuoBDGGHOQTtyois2u4AwyVcIwL2LaSLlTZQm2CYcJ1pxbt6jfAnpWmzENA/wfrYRI/X9DTLoUkE4AsLw==",
|
||||
Path: "microsoft.extensions.dependencyinjection/6.0.0",
|
||||
HashPath: "microsoft.extensions.dependencyinjection.6.0.0.nupkg.sha512",
|
||||
},
|
||||
|
||||
{
|
||||
Name: "Microsoft.Extensions.Options",
|
||||
Version: "6.0.0",
|
||||
PURL: "pkg:nuget/Microsoft.Extensions.Options@6.0.0",
|
||||
Locations: fixtureLocationSet,
|
||||
Language: pkg.Dotnet,
|
||||
Type: pkg.DotnetPkg,
|
||||
MetadataType: pkg.DotnetDepsMetadataType,
|
||||
Metadata: pkg.DotnetDepsMetadata{
|
||||
Name: "Microsoft.Extensions.Options",
|
||||
Version: "6.0.0",
|
||||
Sha512: "sha512-dzXN0+V1AyjOe2xcJ86Qbo233KHuLEY0njf/P2Kw8SfJU+d45HNS2ctJdnEnrWbM9Ye2eFgaC5Mj9otRMU6IsQ==",
|
||||
Path: "microsoft.extensions.options/6.0.0",
|
||||
HashPath: "microsoft.extensions.options.6.0.0.nupkg.sha512",
|
||||
},
|
||||
}
|
||||
msftLoggingAbstractions := pkg.Package{
|
||||
Name: "Microsoft.Extensions.Logging.Abstractions",
|
||||
Version: "6.0.0",
|
||||
PURL: "pkg:nuget/Microsoft.Extensions.Logging.Abstractions@6.0.0",
|
||||
Locations: fixtureLocationSet,
|
||||
Language: pkg.Dotnet,
|
||||
Type: pkg.DotnetPkg,
|
||||
MetadataType: pkg.DotnetDepsMetadataType,
|
||||
Metadata: pkg.DotnetDepsMetadata{
|
||||
Name: "Microsoft.Extensions.Logging.Abstractions",
|
||||
Version: "6.0.0",
|
||||
Sha512: "sha512-/HggWBbTwy8TgebGSX5DBZ24ndhzi93sHUBDvP1IxbZD7FDokYzdAr6+vbWGjw2XAfR2EJ1sfKUotpjHnFWPxA==",
|
||||
Path: "microsoft.extensions.logging.abstractions/6.0.0",
|
||||
HashPath: "microsoft.extensions.logging.abstractions.6.0.0.nupkg.sha512",
|
||||
},
|
||||
{
|
||||
Name: "Microsoft.Extensions.Primitives",
|
||||
Version: "6.0.0",
|
||||
PURL: "pkg:nuget/Microsoft.Extensions.Primitives@6.0.0",
|
||||
Locations: fixtureLocationSet,
|
||||
Language: pkg.Dotnet,
|
||||
Type: pkg.DotnetPkg,
|
||||
MetadataType: pkg.DotnetDepsMetadataType,
|
||||
Metadata: pkg.DotnetDepsMetadata{
|
||||
Name: "Microsoft.Extensions.Primitives",
|
||||
Version: "6.0.0",
|
||||
Sha512: "sha512-9+PnzmQFfEFNR9J2aDTfJGGupShHjOuGw4VUv+JB044biSHrnmCIMD+mJHmb2H7YryrfBEXDurxQ47gJZdCKNQ==",
|
||||
Path: "microsoft.extensions.primitives/6.0.0",
|
||||
HashPath: "microsoft.extensions.primitives.6.0.0.nupkg.sha512",
|
||||
},
|
||||
}
|
||||
msftExtensionsLogging := pkg.Package{
|
||||
Name: "Microsoft.Extensions.Logging",
|
||||
Version: "6.0.0",
|
||||
PURL: "pkg:nuget/Microsoft.Extensions.Logging@6.0.0",
|
||||
Locations: fixtureLocationSet,
|
||||
Language: pkg.Dotnet,
|
||||
Type: pkg.DotnetPkg,
|
||||
MetadataType: pkg.DotnetDepsMetadataType,
|
||||
Metadata: pkg.DotnetDepsMetadata{
|
||||
Name: "Microsoft.Extensions.Logging",
|
||||
Version: "6.0.0",
|
||||
Sha512: "sha512-eIbyj40QDg1NDz0HBW0S5f3wrLVnKWnDJ/JtZ+yJDFnDj90VoPuoPmFkeaXrtu+0cKm5GRAwoDf+dBWXK0TUdg==",
|
||||
Path: "microsoft.extensions.logging/6.0.0",
|
||||
HashPath: "microsoft.extensions.logging.6.0.0.nupkg.sha512",
|
||||
},
|
||||
{
|
||||
Name: "Newtonsoft.Json",
|
||||
Version: "13.0.1",
|
||||
PURL: "pkg:nuget/Newtonsoft.Json@13.0.1",
|
||||
Locations: fixtureLocationSet,
|
||||
Language: pkg.Dotnet,
|
||||
Type: pkg.DotnetPkg,
|
||||
MetadataType: pkg.DotnetDepsMetadataType,
|
||||
Metadata: pkg.DotnetDepsMetadata{
|
||||
Name: "Newtonsoft.Json",
|
||||
Version: "13.0.1",
|
||||
Sha512: "sha512-ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==",
|
||||
Path: "newtonsoft.json/13.0.1",
|
||||
HashPath: "newtonsoft.json.13.0.1.nupkg.sha512",
|
||||
},
|
||||
}
|
||||
msftExtensionsOptions := pkg.Package{
|
||||
Name: "Microsoft.Extensions.Options",
|
||||
Version: "6.0.0",
|
||||
PURL: "pkg:nuget/Microsoft.Extensions.Options@6.0.0",
|
||||
Locations: fixtureLocationSet,
|
||||
Language: pkg.Dotnet,
|
||||
Type: pkg.DotnetPkg,
|
||||
MetadataType: pkg.DotnetDepsMetadataType,
|
||||
Metadata: pkg.DotnetDepsMetadata{
|
||||
Name: "Microsoft.Extensions.Options",
|
||||
Version: "6.0.0",
|
||||
Sha512: "sha512-dzXN0+V1AyjOe2xcJ86Qbo233KHuLEY0njf/P2Kw8SfJU+d45HNS2ctJdnEnrWbM9Ye2eFgaC5Mj9otRMU6IsQ==",
|
||||
Path: "microsoft.extensions.options/6.0.0",
|
||||
HashPath: "microsoft.extensions.options.6.0.0.nupkg.sha512",
|
||||
},
|
||||
{
|
||||
Name: "Serilog.Sinks.Console",
|
||||
Version: "4.0.1",
|
||||
PURL: "pkg:nuget/Serilog.Sinks.Console@4.0.1",
|
||||
Locations: fixtureLocationSet,
|
||||
Language: pkg.Dotnet,
|
||||
Type: pkg.DotnetPkg,
|
||||
MetadataType: pkg.DotnetDepsMetadataType,
|
||||
Metadata: pkg.DotnetDepsMetadata{
|
||||
Name: "Serilog.Sinks.Console",
|
||||
Version: "4.0.1",
|
||||
Sha512: "sha512-apLOvSJQLlIbKlbx+Y2UDHSP05kJsV7mou+fvJoRGs/iR+jC22r8cuFVMjjfVxz/AD4B2UCltFhE1naRLXwKNw==",
|
||||
Path: "serilog.sinks.console/4.0.1",
|
||||
HashPath: "serilog.sinks.console.4.0.1.nupkg.sha512",
|
||||
},
|
||||
}
|
||||
msftExtensionsPrimitives := pkg.Package{
|
||||
Name: "Microsoft.Extensions.Primitives",
|
||||
Version: "6.0.0",
|
||||
PURL: "pkg:nuget/Microsoft.Extensions.Primitives@6.0.0",
|
||||
Locations: fixtureLocationSet,
|
||||
Language: pkg.Dotnet,
|
||||
Type: pkg.DotnetPkg,
|
||||
MetadataType: pkg.DotnetDepsMetadataType,
|
||||
Metadata: pkg.DotnetDepsMetadata{
|
||||
Name: "Microsoft.Extensions.Primitives",
|
||||
Version: "6.0.0",
|
||||
Sha512: "sha512-9+PnzmQFfEFNR9J2aDTfJGGupShHjOuGw4VUv+JB044biSHrnmCIMD+mJHmb2H7YryrfBEXDurxQ47gJZdCKNQ==",
|
||||
Path: "microsoft.extensions.primitives/6.0.0",
|
||||
HashPath: "microsoft.extensions.primitives.6.0.0.nupkg.sha512",
|
||||
},
|
||||
{
|
||||
Name: "Serilog",
|
||||
Version: "2.10.0",
|
||||
PURL: "pkg:nuget/Serilog@2.10.0",
|
||||
Locations: fixtureLocationSet,
|
||||
Language: pkg.Dotnet,
|
||||
Type: pkg.DotnetPkg,
|
||||
MetadataType: pkg.DotnetDepsMetadataType,
|
||||
Metadata: pkg.DotnetDepsMetadata{
|
||||
Name: "Serilog",
|
||||
Version: "2.10.0",
|
||||
Sha512: "sha512-+QX0hmf37a0/OZLxM3wL7V6/ADvC1XihXN4Kq/p6d8lCPfgkRdiuhbWlMaFjR9Av0dy5F0+MBeDmDdRZN/YwQA==",
|
||||
Path: "serilog/2.10.0",
|
||||
HashPath: "serilog.2.10.0.nupkg.sha512",
|
||||
},
|
||||
}
|
||||
newtonsoftJson := pkg.Package{
|
||||
Name: "Newtonsoft.Json",
|
||||
Version: "13.0.1",
|
||||
PURL: "pkg:nuget/Newtonsoft.Json@13.0.1",
|
||||
Locations: fixtureLocationSet,
|
||||
Language: pkg.Dotnet,
|
||||
Type: pkg.DotnetPkg,
|
||||
MetadataType: pkg.DotnetDepsMetadataType,
|
||||
Metadata: pkg.DotnetDepsMetadata{
|
||||
Name: "Newtonsoft.Json",
|
||||
Version: "13.0.1",
|
||||
Sha512: "sha512-ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==",
|
||||
Path: "newtonsoft.json/13.0.1",
|
||||
HashPath: "newtonsoft.json.13.0.1.nupkg.sha512",
|
||||
},
|
||||
{
|
||||
Name: "System.Diagnostics.DiagnosticSource",
|
||||
Version: "6.0.0",
|
||||
PURL: "pkg:nuget/System.Diagnostics.DiagnosticSource@6.0.0",
|
||||
Locations: fixtureLocationSet,
|
||||
Language: pkg.Dotnet,
|
||||
Type: pkg.DotnetPkg,
|
||||
MetadataType: pkg.DotnetDepsMetadataType,
|
||||
Metadata: pkg.DotnetDepsMetadata{
|
||||
Name: "System.Diagnostics.DiagnosticSource",
|
||||
Version: "6.0.0",
|
||||
Sha512: "sha512-frQDfv0rl209cKm1lnwTgFPzNigy2EKk1BS3uAvHvlBVKe5cymGyHO+Sj+NLv5VF/AhHsqPIUUwya5oV4CHMUw==",
|
||||
Path: "system.diagnostics.diagnosticsource/6.0.0",
|
||||
HashPath: "system.diagnostics.diagnosticsource.6.0.0.nupkg.sha512",
|
||||
},
|
||||
}
|
||||
serilogSinksConsole := pkg.Package{
|
||||
Name: "Serilog.Sinks.Console",
|
||||
Version: "4.0.1",
|
||||
PURL: "pkg:nuget/Serilog.Sinks.Console@4.0.1",
|
||||
Locations: fixtureLocationSet,
|
||||
Language: pkg.Dotnet,
|
||||
Type: pkg.DotnetPkg,
|
||||
MetadataType: pkg.DotnetDepsMetadataType,
|
||||
Metadata: pkg.DotnetDepsMetadata{
|
||||
Name: "Serilog.Sinks.Console",
|
||||
Version: "4.0.1",
|
||||
Sha512: "sha512-apLOvSJQLlIbKlbx+Y2UDHSP05kJsV7mou+fvJoRGs/iR+jC22r8cuFVMjjfVxz/AD4B2UCltFhE1naRLXwKNw==",
|
||||
Path: "serilog.sinks.console/4.0.1",
|
||||
HashPath: "serilog.sinks.console.4.0.1.nupkg.sha512",
|
||||
},
|
||||
{
|
||||
Name: "System.Runtime.CompilerServices.Unsafe",
|
||||
Version: "6.0.0",
|
||||
PURL: "pkg:nuget/System.Runtime.CompilerServices.Unsafe@6.0.0",
|
||||
Locations: fixtureLocationSet,
|
||||
Language: pkg.Dotnet,
|
||||
Type: pkg.DotnetPkg,
|
||||
MetadataType: pkg.DotnetDepsMetadataType,
|
||||
Metadata: pkg.DotnetDepsMetadata{
|
||||
Name: "System.Runtime.CompilerServices.Unsafe",
|
||||
Version: "6.0.0",
|
||||
Sha512: "sha512-/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==",
|
||||
Path: "system.runtime.compilerservices.unsafe/6.0.0",
|
||||
HashPath: "system.runtime.compilerservices.unsafe.6.0.0.nupkg.sha512",
|
||||
},
|
||||
}
|
||||
serilog := pkg.Package{
|
||||
Name: "Serilog",
|
||||
Version: "2.10.0",
|
||||
PURL: "pkg:nuget/Serilog@2.10.0",
|
||||
Locations: fixtureLocationSet,
|
||||
Language: pkg.Dotnet,
|
||||
Type: pkg.DotnetPkg,
|
||||
MetadataType: pkg.DotnetDepsMetadataType,
|
||||
Metadata: pkg.DotnetDepsMetadata{
|
||||
Name: "Serilog",
|
||||
Version: "2.10.0",
|
||||
Sha512: "sha512-+QX0hmf37a0/OZLxM3wL7V6/ADvC1XihXN4Kq/p6d8lCPfgkRdiuhbWlMaFjR9Av0dy5F0+MBeDmDdRZN/YwQA==",
|
||||
Path: "serilog/2.10.0",
|
||||
HashPath: "serilog.2.10.0.nupkg.sha512",
|
||||
},
|
||||
}
|
||||
systemDiagnosticsDiagnosticsource := pkg.Package{
|
||||
Name: "System.Diagnostics.DiagnosticSource",
|
||||
Version: "6.0.0",
|
||||
PURL: "pkg:nuget/System.Diagnostics.DiagnosticSource@6.0.0",
|
||||
Locations: fixtureLocationSet,
|
||||
Language: pkg.Dotnet,
|
||||
Type: pkg.DotnetPkg,
|
||||
MetadataType: pkg.DotnetDepsMetadataType,
|
||||
Metadata: pkg.DotnetDepsMetadata{
|
||||
Name: "System.Diagnostics.DiagnosticSource",
|
||||
Version: "6.0.0",
|
||||
Sha512: "sha512-frQDfv0rl209cKm1lnwTgFPzNigy2EKk1BS3uAvHvlBVKe5cymGyHO+Sj+NLv5VF/AhHsqPIUUwya5oV4CHMUw==",
|
||||
Path: "system.diagnostics.diagnosticsource/6.0.0",
|
||||
HashPath: "system.diagnostics.diagnosticsource.6.0.0.nupkg.sha512",
|
||||
},
|
||||
}
|
||||
systemRuntimeCompilerServicesUnsafe := pkg.Package{
|
||||
Name: "System.Runtime.CompilerServices.Unsafe",
|
||||
Version: "6.0.0",
|
||||
PURL: "pkg:nuget/System.Runtime.CompilerServices.Unsafe@6.0.0",
|
||||
Locations: fixtureLocationSet,
|
||||
Language: pkg.Dotnet,
|
||||
Type: pkg.DotnetPkg,
|
||||
MetadataType: pkg.DotnetDepsMetadataType,
|
||||
Metadata: pkg.DotnetDepsMetadata{
|
||||
Name: "System.Runtime.CompilerServices.Unsafe",
|
||||
Version: "6.0.0",
|
||||
Sha512: "sha512-/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==",
|
||||
Path: "system.runtime.compilerservices.unsafe/6.0.0",
|
||||
HashPath: "system.runtime.compilerservices.unsafe.6.0.0.nupkg.sha512",
|
||||
},
|
||||
}
|
||||
|
||||
var expectedRelationships []artifact.Relationship
|
||||
pkgtest.TestFileParser(t, fixture, parseDotnetDeps, expected, expectedRelationships)
|
||||
expectedPkgs := []pkg.Package{
|
||||
awssdkcore,
|
||||
msftDependencyInjection,
|
||||
msftDependencyInjectionAbstractions,
|
||||
msftExtensionsLogging,
|
||||
msftLoggingAbstractions,
|
||||
msftExtensionsOptions,
|
||||
msftExtensionsPrimitives,
|
||||
newtonsoftJson,
|
||||
serilog,
|
||||
serilogSinksConsole,
|
||||
systemDiagnosticsDiagnosticsource,
|
||||
systemRuntimeCompilerServicesUnsafe,
|
||||
testCommon,
|
||||
rootPkg,
|
||||
}
|
||||
|
||||
// ┌── (✓ = is represented in the test)
|
||||
// ↓
|
||||
//
|
||||
// ✓ TestLibrary/1.0.0 (project)
|
||||
// ✓ ├── [a] Microsoft.Extensions.DependencyInjection/6.0.0 [file version: 6.0.21.52210]
|
||||
// ✓ │ ├── [b] Microsoft.Extensions.DependencyInjection.Abstractions/6.0.0 [file version: 6.0.21.52210]
|
||||
// ✓ │ └── [c!] System.Runtime.CompilerServices.Unsafe/6.0.0 [NO TARGET INFO]
|
||||
// ✓ ├── Microsoft.Extensions.Logging/6.0.0 [file version: 6.0.21.52210]
|
||||
// ✓ │ ├── Microsoft.Extensions.DependencyInjection/6.0.0 ...to [a]
|
||||
// ✓ │ ├── Microsoft.Extensions.DependencyInjection.Abstractions/6.0.0 ...to [b]
|
||||
// ✓ │ ├── Microsoft.Extensions.Logging.Abstractions/6.0.0 [file version: 6.0.21.52210]
|
||||
// ✓ │ ├── Microsoft.Extensions.Options/6.0.0 [file version: 6.0.21.52210]
|
||||
// ✓ │ │ ├── Microsoft.Extensions.DependencyInjection.Abstractions/6.0.0 ...to [b]
|
||||
// ✓ │ │ └── Microsoft.Extensions.Primitives/6.0.0 [file version: 6.0.21.52210]
|
||||
// ✓ │ │ └── System.Runtime.CompilerServices.Unsafe/6.0.0 ...to [c!]
|
||||
// ✓ │ └── System.Diagnostics.DiagnosticSource/6.0.0 [NO RUNTIME INFO]
|
||||
// ✓ │ └── System.Runtime.CompilerServices.Unsafe/6.0.0 ...to [c!]
|
||||
// ✓ ├── Newtonsoft.Json/13.0.1 [file version: 13.0.1.25517]
|
||||
// ✓ ├── [d] Serilog/2.10.0 [file version: 2.10.0.0]
|
||||
// ✓ ├── Serilog.Sinks.Console/4.0.1 [file version: 4.0.1.0]
|
||||
// ✓ │ └── Serilog/2.10.0 ...to [d]
|
||||
// ✓ └── [e!] TestCommon/1.0.0 [NOT SERVICEABLE / NO SHA]
|
||||
// ✓ └── AWSSDK.Core/3.7.10.6 [file version: 3.7.10.6]
|
||||
|
||||
expectedRelationships := []artifact.Relationship{
|
||||
{
|
||||
From: awssdkcore,
|
||||
To: testCommon,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: msftDependencyInjection,
|
||||
To: msftExtensionsLogging,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: msftDependencyInjection,
|
||||
To: rootPkg,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: msftDependencyInjectionAbstractions,
|
||||
To: msftDependencyInjection,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: msftDependencyInjectionAbstractions,
|
||||
To: msftExtensionsLogging,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: msftDependencyInjectionAbstractions,
|
||||
To: msftExtensionsOptions,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: msftExtensionsLogging,
|
||||
To: rootPkg,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: msftLoggingAbstractions,
|
||||
To: msftExtensionsLogging,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: msftExtensionsOptions,
|
||||
To: msftExtensionsLogging,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: msftExtensionsPrimitives,
|
||||
To: msftExtensionsOptions,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: newtonsoftJson,
|
||||
To: rootPkg,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: serilog,
|
||||
To: serilogSinksConsole,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: serilog,
|
||||
To: rootPkg,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: serilogSinksConsole,
|
||||
To: rootPkg,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: systemDiagnosticsDiagnosticsource,
|
||||
To: msftExtensionsLogging,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: systemRuntimeCompilerServicesUnsafe,
|
||||
To: msftDependencyInjection,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: systemRuntimeCompilerServicesUnsafe,
|
||||
To: msftExtensionsPrimitives,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: systemRuntimeCompilerServicesUnsafe,
|
||||
To: systemDiagnosticsDiagnosticsource,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: testCommon,
|
||||
To: rootPkg,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
}
|
||||
|
||||
pkgtest.TestFileParser(t, fixture, parseDotnetDeps, expectedPkgs, expectedRelationships)
|
||||
}
|
||||
|
||||
@@ -232,4 +232,4 @@
|
||||
"sha512": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
"github.com/go-git/go-git/v5/storage/memory"
|
||||
"github.com/scylladb/go-set/strset"
|
||||
|
||||
"github.com/anchore/syft/internal/licenses"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
@@ -31,6 +32,7 @@ type goLicenses struct {
|
||||
opts GoCatalogerOpts
|
||||
localModCacheResolver file.WritableResolver
|
||||
progress *monitor.CatalogerTask
|
||||
licenseFileNames *strset.Set
|
||||
}
|
||||
|
||||
func newGoLicenses(opts GoCatalogerOpts) goLicenses {
|
||||
@@ -42,6 +44,7 @@ func newGoLicenses(opts GoCatalogerOpts) goLicenses {
|
||||
RemoveOnCompletion: true,
|
||||
Title: "Downloading go mod",
|
||||
},
|
||||
licenseFileNames: strset.New(licenses.FileNames()...),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +80,7 @@ func modCacheResolver(modCacheDir string) file.WritableResolver {
|
||||
}
|
||||
|
||||
func (c *goLicenses) getLicenses(resolver file.Resolver, moduleName, moduleVersion string) (licenses []pkg.License, err error) {
|
||||
licenses, err = findLicenses(resolver,
|
||||
licenses, err = c.findLicenses(resolver,
|
||||
fmt.Sprintf(`**/go/pkg/mod/%s@%s/*`, processCaps(moduleName), moduleVersion),
|
||||
)
|
||||
if err != nil || len(licenses) > 0 {
|
||||
@@ -102,7 +105,7 @@ func (c *goLicenses) getLicensesFromLocal(moduleName, moduleVersion string) ([]p
|
||||
|
||||
// if we're running against a directory on the filesystem, it may not include the
|
||||
// user's homedir / GOPATH, so we defer to using the localModCacheResolver
|
||||
return findLicenses(c.localModCacheResolver, moduleSearchGlob(moduleName, moduleVersion))
|
||||
return c.findLicenses(c.localModCacheResolver, moduleSearchGlob(moduleName, moduleVersion))
|
||||
}
|
||||
|
||||
func (c *goLicenses) getLicensesFromRemote(moduleName, moduleVersion string) ([]pkg.License, error) {
|
||||
@@ -139,7 +142,37 @@ func (c *goLicenses) getLicensesFromRemote(moduleName, moduleVersion string) ([]
|
||||
log.Tracef("remote proxy walk failed for: %s", moduleName)
|
||||
}
|
||||
|
||||
return findLicenses(c.localModCacheResolver, moduleSearchGlob(moduleName, moduleVersion))
|
||||
return c.findLicenses(c.localModCacheResolver, moduleSearchGlob(moduleName, moduleVersion))
|
||||
}
|
||||
|
||||
func (c *goLicenses) findLicenses(resolver file.Resolver, globMatch string) (out []pkg.License, err error) {
|
||||
out = make([]pkg.License, 0)
|
||||
if resolver == nil {
|
||||
return
|
||||
}
|
||||
|
||||
locations, err := resolver.FilesByGlob(globMatch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, l := range locations {
|
||||
fileName := path.Base(l.RealPath)
|
||||
if c.licenseFileNames.Has(fileName) {
|
||||
contents, err := resolver.FileContentsByLocation(l)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
parsed, err := licenses.Parse(contents, l)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out = append(out, parsed...)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func moduleDir(moduleName, moduleVersion string) string {
|
||||
@@ -157,36 +190,6 @@ func requireCollection(licenses []pkg.License) []pkg.License {
|
||||
return licenses
|
||||
}
|
||||
|
||||
func findLicenses(resolver file.Resolver, globMatch string) (out []pkg.License, err error) {
|
||||
out = make([]pkg.License, 0)
|
||||
if resolver == nil {
|
||||
return
|
||||
}
|
||||
|
||||
locations, err := resolver.FilesByGlob(globMatch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, l := range locations {
|
||||
fileName := path.Base(l.RealPath)
|
||||
if licenses.FileNameSet.Contains(fileName) {
|
||||
contents, err := resolver.FileContentsByLocation(l)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
parsed, err := licenses.Parse(contents, l)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out = append(out, parsed...)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var capReplacer = regexp.MustCompile("[A-Z]")
|
||||
|
||||
func processCaps(s string) string {
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/internal/fileresolver"
|
||||
"github.com/anchore/syft/syft/license"
|
||||
@@ -37,7 +36,6 @@ func Test_LocalLicenseSearch(t *testing.T) {
|
||||
SPDXExpression: "Apache-2.0",
|
||||
Type: license.Concluded,
|
||||
Locations: file.NewLocationSet(loc1),
|
||||
URLs: internal.NewStringSet(),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -48,7 +46,6 @@ func Test_LocalLicenseSearch(t *testing.T) {
|
||||
SPDXExpression: "MIT",
|
||||
Type: license.Concluded,
|
||||
Locations: file.NewLocationSet(loc2),
|
||||
URLs: internal.NewStringSet(),
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -128,7 +125,6 @@ func Test_RemoteProxyLicenseSearch(t *testing.T) {
|
||||
SPDXExpression: "Apache-2.0",
|
||||
Type: license.Concluded,
|
||||
Locations: file.NewLocationSet(loc1),
|
||||
URLs: internal.NewStringSet(),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -139,7 +135,6 @@ func Test_RemoteProxyLicenseSearch(t *testing.T) {
|
||||
SPDXExpression: "MIT",
|
||||
Type: license.Concluded,
|
||||
Locations: file.NewLocationSet(loc2),
|
||||
URLs: internal.NewStringSet(),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -329,6 +329,10 @@ func (p *CatalogTester) assertPkgs(t *testing.T, pkgs []pkg.Package, relationshi
|
||||
opts = append(opts, p.compareOptions...)
|
||||
opts = append(opts, cmp.Reporter(&r))
|
||||
|
||||
// order should not matter
|
||||
pkg.Sort(p.expectedPkgs)
|
||||
pkg.Sort(pkgs)
|
||||
|
||||
if diff := cmp.Diff(p.expectedPkgs, pkgs, opts...); diff != "" {
|
||||
t.Log("Specific Differences:\n" + r.String())
|
||||
t.Errorf("unexpected packages from parsing (-expected +actual)\n%s", diff)
|
||||
@@ -341,6 +345,10 @@ func (p *CatalogTester) assertPkgs(t *testing.T, pkgs []pkg.Package, relationshi
|
||||
opts = append(opts, p.compareOptions...)
|
||||
opts = append(opts, cmp.Reporter(&r))
|
||||
|
||||
// order should not matter
|
||||
pkg.SortRelationships(p.expectedRelationships)
|
||||
pkg.SortRelationships(relationships)
|
||||
|
||||
if diff := cmp.Diff(p.expectedRelationships, relationships, opts...); diff != "" {
|
||||
t.Log("Specific Differences:\n" + r.String())
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"strings"
|
||||
|
||||
intFile "github.com/anchore/syft/internal/file"
|
||||
"github.com/anchore/syft/internal/licenses"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
@@ -177,6 +178,31 @@ func (j *archiveParser) discoverMainPackage() (*pkg.Package, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
licenses, name, version, err := j.parseLicenses(manifest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pkg.Package{
|
||||
// TODO: maybe select name should just have a pom properties in it?
|
||||
Name: name,
|
||||
Version: version,
|
||||
Language: pkg.Java,
|
||||
Licenses: pkg.NewLicenseSet(licenses...),
|
||||
Locations: file.NewLocationSet(
|
||||
j.location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
),
|
||||
Type: j.fileInfo.pkgType(),
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
VirtualPath: j.location.AccessPath(),
|
||||
Manifest: manifest,
|
||||
ArchiveDigests: digests,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (j *archiveParser) parseLicenses(manifest *pkg.JavaManifest) ([]pkg.License, string, string, error) {
|
||||
// we use j.location because we want to associate the license declaration with where we discovered the contents in the manifest
|
||||
// TODO: when we support locations of paths within archives we should start passing the specific manifest location object instead of the top jar
|
||||
licenses := pkg.NewLicensesFromLocation(j.location, selectLicenses(manifest)...)
|
||||
@@ -201,23 +227,17 @@ func (j *archiveParser) discoverMainPackage() (*pkg.Package, error) {
|
||||
licenses = append(licenses, pomLicenses...)
|
||||
}
|
||||
|
||||
return &pkg.Package{
|
||||
// TODO: maybe select name should just have a pom properties in it?
|
||||
Name: name,
|
||||
Version: version,
|
||||
Language: pkg.Java,
|
||||
Licenses: pkg.NewLicenseSet(licenses...),
|
||||
Locations: file.NewLocationSet(
|
||||
j.location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
),
|
||||
Type: j.fileInfo.pkgType(),
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
VirtualPath: j.location.AccessPath(),
|
||||
Manifest: manifest,
|
||||
ArchiveDigests: digests,
|
||||
},
|
||||
}, nil
|
||||
if len(licenses) == 0 {
|
||||
fileLicenses, err := j.getLicenseFromFileInArchive()
|
||||
if err != nil {
|
||||
return nil, "", "", err
|
||||
}
|
||||
if fileLicenses != nil {
|
||||
licenses = append(licenses, fileLicenses...)
|
||||
}
|
||||
}
|
||||
|
||||
return licenses, name, version, nil
|
||||
}
|
||||
|
||||
type parsedPomProject struct {
|
||||
@@ -237,7 +257,7 @@ func (j *archiveParser) guessMainPackageNameAndVersionFromPomInfo() (name, versi
|
||||
projects, _ := pomProjectByParentPath(j.archivePath, j.location, pomMatches)
|
||||
|
||||
for parentPath, propertiesObj := range properties {
|
||||
if propertiesObj.ArtifactID != "" && j.fileInfo.name != "" && strings.HasPrefix(propertiesObj.ArtifactID, j.fileInfo.name) {
|
||||
if artifactIDMatchesFilename(propertiesObj.ArtifactID, j.fileInfo.name) {
|
||||
pomPropertiesObject = propertiesObj
|
||||
if proj, exists := projects[parentPath]; exists {
|
||||
pomProjectObject = proj
|
||||
@@ -256,6 +276,13 @@ func (j *archiveParser) guessMainPackageNameAndVersionFromPomInfo() (name, versi
|
||||
return name, version, pomProjectObject.Licenses
|
||||
}
|
||||
|
||||
func artifactIDMatchesFilename(artifactID, fileName string) bool {
|
||||
if artifactID == "" || fileName == "" {
|
||||
return false
|
||||
}
|
||||
return strings.HasPrefix(artifactID, fileName) || strings.HasSuffix(fileName, artifactID)
|
||||
}
|
||||
|
||||
// discoverPkgsFromAllMavenFiles parses Maven POM properties/xml for a given
|
||||
// parent package, returning all listed Java packages found for each pom
|
||||
// properties discovered and potentially updating the given parentPkg with new
|
||||
@@ -310,6 +337,38 @@ func getDigestsFromArchive(archivePath string) ([]file.Digest, error) {
|
||||
return digests, nil
|
||||
}
|
||||
|
||||
func (j *archiveParser) getLicenseFromFileInArchive() ([]pkg.License, error) {
|
||||
var fileLicenses []pkg.License
|
||||
for _, filename := range licenses.FileNames() {
|
||||
licenseMatches := j.fileManifest.GlobMatch("/META-INF/" + filename)
|
||||
if len(licenseMatches) == 0 {
|
||||
// Try the root directory if it's not in META-INF
|
||||
licenseMatches = j.fileManifest.GlobMatch("/" + filename)
|
||||
}
|
||||
|
||||
if len(licenseMatches) > 0 {
|
||||
contents, err := intFile.ContentsFromZip(j.archivePath, licenseMatches...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to extract java license (%s): %w", j.location, err)
|
||||
}
|
||||
|
||||
for _, licenseMatch := range licenseMatches {
|
||||
licenseContents := contents[licenseMatch]
|
||||
parsed, err := licenses.Parse(strings.NewReader(licenseContents), j.location)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(parsed) > 0 {
|
||||
fileLicenses = append(fileLicenses, parsed...)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fileLicenses, nil
|
||||
}
|
||||
|
||||
func (j *archiveParser) discoverPkgsFromNestedArchives(parentPkg *pkg.Package) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
// we know that all java archives are zip formatted files, so we can use the shared zip helper
|
||||
return discoverPkgsFromZip(j.location, j.archivePath, j.contentPath, j.fileManifest, parentPkg)
|
||||
|
||||
@@ -11,10 +11,13 @@ import (
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/gookit/color"
|
||||
"github.com/scylladb/go-set/strset"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/license"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
@@ -38,47 +41,7 @@ func generateJavaBuildFixture(t *testing.T, fixturePath string) {
|
||||
cmd := exec.Command("make", makeTask)
|
||||
cmd.Dir = filepath.Join(cwd, "test-fixtures/java-builds/")
|
||||
|
||||
stderr, err := cmd.StderrPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("could not get stderr: %+v", err)
|
||||
}
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("could not get stdout: %+v", err)
|
||||
}
|
||||
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to start cmd: %+v", err)
|
||||
}
|
||||
|
||||
show := func(label string, reader io.ReadCloser) {
|
||||
scanner := bufio.NewScanner(reader)
|
||||
scanner.Split(bufio.ScanLines)
|
||||
for scanner.Scan() {
|
||||
t.Logf("%s: %s", label, scanner.Text())
|
||||
}
|
||||
}
|
||||
go show("out", stdout)
|
||||
go show("err", stderr)
|
||||
|
||||
if err := cmd.Wait(); err != nil {
|
||||
if exiterr, ok := err.(*exec.ExitError); ok {
|
||||
// The program has exited with an exit code != 0
|
||||
|
||||
// This works on both Unix and Windows. Although package
|
||||
// syscall is generally platform dependent, WaitStatus is
|
||||
// defined for both Unix and Windows and in both cases has
|
||||
// an ExitStatus() method with the same signature.
|
||||
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
|
||||
if status.ExitStatus() != 0 {
|
||||
t.Fatalf("failed to generate fixture: rc=%d", status.ExitStatus())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
t.Fatalf("unable to get generate fixture result: %+v", err)
|
||||
}
|
||||
}
|
||||
run(t, cmd)
|
||||
}
|
||||
|
||||
func TestParseJar(t *testing.T) {
|
||||
@@ -158,6 +121,14 @@ func TestParseJar(t *testing.T) {
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JavaPkg,
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.License{
|
||||
Value: "Apache-2.0",
|
||||
SPDXExpression: "Apache-2.0",
|
||||
Type: license.Concluded,
|
||||
Locations: file.NewLocationSet(file.NewLocation("test-fixtures/java-builds/packages/example-java-app-gradle-0.1.0.jar")),
|
||||
},
|
||||
),
|
||||
Metadata: pkg.JavaMetadata{
|
||||
VirtualPath: "test-fixtures/java-builds/packages/example-java-app-gradle-0.1.0.jar",
|
||||
Manifest: &pkg.JavaManifest{
|
||||
@@ -223,6 +194,14 @@ func TestParseJar(t *testing.T) {
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JavaPkg,
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.License{
|
||||
Value: "Apache-2.0",
|
||||
SPDXExpression: "Apache-2.0",
|
||||
Type: license.Concluded,
|
||||
Locations: file.NewLocationSet(file.NewLocation("test-fixtures/java-builds/packages/example-java-app-maven-0.1.0.jar")),
|
||||
},
|
||||
),
|
||||
Metadata: pkg.JavaMetadata{
|
||||
VirtualPath: "test-fixtures/java-builds/packages/example-java-app-maven-0.1.0.jar",
|
||||
Manifest: &pkg.JavaManifest{
|
||||
@@ -576,7 +555,7 @@ func TestParseNestedJar(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
expectedNameVersionPairSet := internal.NewStringSet()
|
||||
expectedNameVersionPairSet := strset.New()
|
||||
|
||||
makeKey := func(p *pkg.Package) string {
|
||||
if p == nil {
|
||||
@@ -589,24 +568,24 @@ func TestParseNestedJar(t *testing.T) {
|
||||
expectedNameVersionPairSet.Add(makeKey(&e))
|
||||
}
|
||||
|
||||
actualNameVersionPairSet := internal.NewStringSet()
|
||||
actualNameVersionPairSet := strset.New()
|
||||
for _, a := range actual {
|
||||
a := a
|
||||
key := makeKey(&a)
|
||||
actualNameVersionPairSet.Add(key)
|
||||
if !expectedNameVersionPairSet.Contains(key) {
|
||||
if !expectedNameVersionPairSet.Has(key) {
|
||||
t.Errorf("extra package: %s", a)
|
||||
}
|
||||
}
|
||||
|
||||
for _, key := range expectedNameVersionPairSet.ToSlice() {
|
||||
if !actualNameVersionPairSet.Contains(key) {
|
||||
for _, key := range expectedNameVersionPairSet.List() {
|
||||
if !actualNameVersionPairSet.Has(key) {
|
||||
t.Errorf("missing package: %s", key)
|
||||
}
|
||||
}
|
||||
|
||||
if len(actual) != len(expectedNameVersionPairSet) {
|
||||
t.Fatalf("unexpected package count: %d!=%d", len(actual), len(expectedNameVersionPairSet))
|
||||
if len(actual) != expectedNameVersionPairSet.Size() {
|
||||
t.Fatalf("unexpected package count: %d!=%d", len(actual), expectedNameVersionPairSet.Size())
|
||||
}
|
||||
|
||||
for _, a := range actual {
|
||||
@@ -730,7 +709,7 @@ func Test_newPackageFromMavenData(t *testing.T) {
|
||||
Value: "MIT",
|
||||
SPDXExpression: "MIT",
|
||||
Type: license.Declared,
|
||||
URLs: internal.NewStringSet("https://opensource.org/licenses/MIT"),
|
||||
URLs: []string{"https://opensource.org/licenses/MIT"},
|
||||
Locations: file.NewLocationSet(file.NewLocation("some-license-path")),
|
||||
},
|
||||
},
|
||||
@@ -767,7 +746,7 @@ func Test_newPackageFromMavenData(t *testing.T) {
|
||||
Value: "MIT",
|
||||
SPDXExpression: "MIT",
|
||||
Type: license.Declared,
|
||||
URLs: internal.NewStringSet("https://opensource.org/licenses/MIT"),
|
||||
URLs: []string{"https://opensource.org/licenses/MIT"},
|
||||
Locations: file.NewLocationSet(file.NewLocation("some-license-path")),
|
||||
},
|
||||
),
|
||||
@@ -1004,3 +983,228 @@ func Test_newPackageFromMavenData(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_artifactIDMatchesFilename(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
artifactID string
|
||||
fileName string // without version or extension
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "artifact id within file name",
|
||||
artifactID: "atlassian-extras-api",
|
||||
fileName: "com.atlassian.extras_atlassian-extras-api",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "file name within artifact id",
|
||||
artifactID: "atlassian-extras-api-something",
|
||||
fileName: "atlassian-extras-api",
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equal(t, tt.want, artifactIDMatchesFilename(tt.artifactID, tt.fileName))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_parseJavaArchive_regressions(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
fixtureName string
|
||||
expectedPkgs []pkg.Package
|
||||
expectedRelationships []artifact.Relationship
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "duplicate jar regression - go case (issue #2130)",
|
||||
fixtureName: "jackson-core-2.15.2",
|
||||
expectedPkgs: []pkg.Package{
|
||||
{
|
||||
Name: "jackson-core",
|
||||
Version: "2.15.2",
|
||||
Type: pkg.JavaPkg,
|
||||
Language: pkg.Java,
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
PURL: "pkg:maven/com.fasterxml.jackson.core/jackson-core@2.15.2",
|
||||
Locations: file.NewLocationSet(file.NewLocation("test-fixtures/jar-metadata/cache/jackson-core-2.15.2.jar")),
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicensesFromLocation(
|
||||
file.NewLocation("test-fixtures/jar-metadata/cache/jackson-core-2.15.2.jar"),
|
||||
"https://www.apache.org/licenses/LICENSE-2.0.txt",
|
||||
)...,
|
||||
),
|
||||
Metadata: pkg.JavaMetadata{
|
||||
VirtualPath: "test-fixtures/jar-metadata/cache/jackson-core-2.15.2.jar",
|
||||
Manifest: &pkg.JavaManifest{
|
||||
Main: map[string]string{
|
||||
"Build-Jdk-Spec": "1.8",
|
||||
"Bundle-Description": "Core Jackson processing abstractions",
|
||||
"Bundle-DocURL": "https://github.com/FasterXML/jackson-core",
|
||||
"Bundle-License": "https://www.apache.org/licenses/LICENSE-2.0.txt",
|
||||
"Bundle-ManifestVersion": "2",
|
||||
"Bundle-Name": "Jackson-core",
|
||||
"Bundle-SymbolicName": "com.fasterxml.jackson.core.jackson-core",
|
||||
"Bundle-Vendor": "FasterXML",
|
||||
"Bundle-Version": "2.15.2",
|
||||
"Created-By": "Apache Maven Bundle Plugin 5.1.8",
|
||||
"Export-Package": "com.fasterxml.jackson.core;version...snip",
|
||||
"Implementation-Title": "Jackson-core",
|
||||
"Implementation-Vendor": "FasterXML",
|
||||
"Implementation-Vendor-Id": "com.fasterxml.jackson.core",
|
||||
"Implementation-Version": "2.15.2",
|
||||
"Import-Package": "com.fasterxml.jackson.core;version=...snip",
|
||||
"Manifest-Version": "1.0",
|
||||
"Multi-Release": "true",
|
||||
"Require-Capability": `osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.8))"`,
|
||||
"Specification-Title": "Jackson-core",
|
||||
"Specification-Vendor": "FasterXML",
|
||||
"Specification-Version": "2.15.2",
|
||||
"Tool": "Bnd-6.3.1.202206071316",
|
||||
"X-Compile-Source-JDK": "1.8",
|
||||
"X-Compile-Target-JDK": "1.8",
|
||||
},
|
||||
},
|
||||
// not under test
|
||||
//ArchiveDigests: []file.Digest{{Algorithm: "sha1", Value: "d8bc1d9c428c96fe447e2c429fc4304d141024df"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "duplicate jar regression - bad case (issue #2130)",
|
||||
fixtureName: "com.fasterxml.jackson.core.jackson-core-2.15.2",
|
||||
expectedPkgs: []pkg.Package{
|
||||
{
|
||||
Name: "jackson-core",
|
||||
Version: "2.15.2",
|
||||
Type: pkg.JavaPkg,
|
||||
Language: pkg.Java,
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
PURL: "pkg:maven/com.fasterxml.jackson.core/jackson-core@2.15.2",
|
||||
Locations: file.NewLocationSet(file.NewLocation("test-fixtures/jar-metadata/cache/com.fasterxml.jackson.core.jackson-core-2.15.2.jar")),
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicensesFromLocation(
|
||||
file.NewLocation("test-fixtures/jar-metadata/cache/com.fasterxml.jackson.core.jackson-core-2.15.2.jar"),
|
||||
"https://www.apache.org/licenses/LICENSE-2.0.txt",
|
||||
)...,
|
||||
),
|
||||
Metadata: pkg.JavaMetadata{
|
||||
VirtualPath: "test-fixtures/jar-metadata/cache/com.fasterxml.jackson.core.jackson-core-2.15.2.jar",
|
||||
Manifest: &pkg.JavaManifest{
|
||||
Main: map[string]string{
|
||||
"Build-Jdk-Spec": "1.8",
|
||||
"Bundle-Description": "Core Jackson processing abstractions",
|
||||
"Bundle-DocURL": "https://github.com/FasterXML/jackson-core",
|
||||
"Bundle-License": "https://www.apache.org/licenses/LICENSE-2.0.txt",
|
||||
"Bundle-ManifestVersion": "2",
|
||||
"Bundle-Name": "Jackson-core",
|
||||
"Bundle-SymbolicName": "com.fasterxml.jackson.core.jackson-core",
|
||||
"Bundle-Vendor": "FasterXML",
|
||||
"Bundle-Version": "2.15.2",
|
||||
"Created-By": "Apache Maven Bundle Plugin 5.1.8",
|
||||
"Export-Package": "com.fasterxml.jackson.core;version...snip",
|
||||
"Implementation-Title": "Jackson-core",
|
||||
"Implementation-Vendor": "FasterXML",
|
||||
"Implementation-Vendor-Id": "com.fasterxml.jackson.core",
|
||||
"Implementation-Version": "2.15.2",
|
||||
"Import-Package": "com.fasterxml.jackson.core;version=...snip",
|
||||
"Manifest-Version": "1.0",
|
||||
"Multi-Release": "true",
|
||||
"Require-Capability": `osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.8))"`,
|
||||
"Specification-Title": "Jackson-core",
|
||||
"Specification-Vendor": "FasterXML",
|
||||
"Specification-Version": "2.15.2",
|
||||
"Tool": "Bnd-6.3.1.202206071316",
|
||||
"X-Compile-Source-JDK": "1.8",
|
||||
"X-Compile-Target-JDK": "1.8",
|
||||
},
|
||||
},
|
||||
// not under test
|
||||
//ArchiveDigests: []file.Digest{{Algorithm: "sha1", Value: "abd3e329270fc54a2acaceb45420fd5710ecefd5"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
pkgtest.NewCatalogTester().
|
||||
FromFile(t, generateJavaMetadataJarFixture(t, tt.fixtureName)).
|
||||
Expects(tt.expectedPkgs, tt.expectedRelationships).
|
||||
WithCompareOptions(cmpopts.IgnoreFields(pkg.JavaMetadata{}, "ArchiveDigests")).
|
||||
TestParser(t, parseJavaArchive)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func generateJavaMetadataJarFixture(t *testing.T, fixtureName string) string {
|
||||
fixturePath := filepath.Join("test-fixtures/jar-metadata/cache/", fixtureName+".jar")
|
||||
if _, err := os.Stat(fixturePath); !os.IsNotExist(err) {
|
||||
// fixture already exists...
|
||||
return fixturePath
|
||||
}
|
||||
|
||||
makeTask := filepath.Join("cache", fixtureName+".jar")
|
||||
t.Logf(color.Bold.Sprintf("Generating Fixture from 'make %s'", makeTask))
|
||||
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Errorf("unable to get cwd: %+v", err)
|
||||
}
|
||||
|
||||
cmd := exec.Command("make", makeTask)
|
||||
cmd.Dir = filepath.Join(cwd, "test-fixtures/jar-metadata")
|
||||
|
||||
run(t, cmd)
|
||||
|
||||
return fixturePath
|
||||
}
|
||||
|
||||
func run(t testing.TB, cmd *exec.Cmd) {
|
||||
|
||||
stderr, err := cmd.StderrPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("could not get stderr: %+v", err)
|
||||
}
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("could not get stdout: %+v", err)
|
||||
}
|
||||
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to start cmd: %+v", err)
|
||||
}
|
||||
|
||||
show := func(label string, reader io.ReadCloser) {
|
||||
scanner := bufio.NewScanner(reader)
|
||||
scanner.Split(bufio.ScanLines)
|
||||
for scanner.Scan() {
|
||||
t.Logf("%s: %s", label, scanner.Text())
|
||||
}
|
||||
}
|
||||
go show("out", stdout)
|
||||
go show("err", stderr)
|
||||
|
||||
if err := cmd.Wait(); err != nil {
|
||||
if exiterr, ok := err.(*exec.ExitError); ok {
|
||||
// The program has exited with an exit code != 0
|
||||
|
||||
// This works on both Unix and Windows. Although package
|
||||
// syscall is generally platform dependent, WaitStatus is
|
||||
// defined for both Unix and Windows and in both cases has
|
||||
// an ExitStatus() method with the same signature.
|
||||
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
|
||||
if status.ExitStatus() != 0 {
|
||||
t.Fatalf("failed to generate fixture: rc=%d", status.ExitStatus())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
t.Fatalf("unable to get generate fixture result: %+v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,27 +109,71 @@ func parseJavaManifest(path string, reader io.Reader) (*pkg.JavaManifest, error)
|
||||
}
|
||||
|
||||
func selectName(manifest *pkg.JavaManifest, filenameObj archiveFilename) string {
|
||||
var name string
|
||||
switch {
|
||||
case filenameObj.name != "":
|
||||
name = filenameObj.name
|
||||
case manifest.Main["Name"] != "":
|
||||
// Manifest original spec...
|
||||
name = manifest.Main["Name"]
|
||||
case manifest.Main["Bundle-Name"] != "":
|
||||
// BND tooling...
|
||||
name = manifest.Main["Bundle-Name"]
|
||||
case manifest.Main["Short-Name"] != "":
|
||||
// Jenkins...
|
||||
name = manifest.Main["Short-Name"]
|
||||
case manifest.Main["Extension-Name"] != "":
|
||||
// Jenkins...
|
||||
name = manifest.Main["Extension-Name"]
|
||||
case manifest.Main["Implementation-Title"] != "":
|
||||
// last ditch effort...
|
||||
name = manifest.Main["Implementation-Title"]
|
||||
// special case: from https://svn.apache.org/repos/asf/felix/releases/maven-bundle-plugin-1.2.0/doc/maven-bundle-plugin-bnd.html
|
||||
// "<Bundle-SymbolicName> is assumed to be "${groupId}.${artifactId}"."
|
||||
//
|
||||
// documentation from https://felix.apache.org/documentation/subprojects/apache-felix-maven-bundle-plugin-bnd.html
|
||||
// agrees this is the default behavior:
|
||||
//
|
||||
// - [1] if artifact.getFile is not null and the jar contains a OSGi Manifest with Bundle-SymbolicName property then that value is returned
|
||||
//
|
||||
// - [2] if groupId has only one section (no dots) and artifact.getFile is not null then the first package name with classes
|
||||
// is returned. eg. commons-logging:commons-logging -> org.apache.commons.logging
|
||||
//
|
||||
// - [3] if artifactId is equal to last section of groupId then groupId is returned. eg. org.apache.maven:maven -> org.apache.maven
|
||||
//
|
||||
// - [4] if artifactId starts with last section of groupId that portion is removed. eg. org.apache.maven:maven-core -> org.apache.maven.core
|
||||
// The computed symbolic name is also stored in the $(maven-symbolicname) property in case you want to add attributes or directives to it.
|
||||
//
|
||||
if manifest != nil {
|
||||
if strings.Contains(manifest.Main["Created-By"], "Apache Maven Bundle Plugin") {
|
||||
if v := manifest.Main["Bundle-SymbolicName"]; v != "" {
|
||||
// the problem with this approach is that we don't have a strong indication of the artifactId
|
||||
// not having a "." in it. However, by convention it is unlikely that an artifactId would have a ".".
|
||||
fields := strings.Split(v, ".")
|
||||
|
||||
// grab the last field, this is the artifactId. Note: because of [3] we do not know if this value is
|
||||
// correct. That is, a group id of "commons-logging" may have caused BND to swap out the reference to
|
||||
// "org.apache.commons.logging", which means we'd interpret this as an artifact id of "logging",
|
||||
// which is not correct.
|
||||
// [correct] https://mvnrepository.com/artifact/commons-logging/commons-logging
|
||||
// [still incorrect] https://mvnrepository.com/artifact/org.apache.commons.logging/org.apache.commons.logging
|
||||
return fields[len(fields)-1]
|
||||
}
|
||||
}
|
||||
}
|
||||
return name
|
||||
|
||||
// the filename tends to be the next-best reference for the package name
|
||||
if filenameObj.name != "" {
|
||||
if strings.Contains(filenameObj.name, ".") {
|
||||
// special case: this *might* be a group id + artifact id. By convention artifact ids do not have "." in them.
|
||||
fields := strings.Split(filenameObj.name, ".")
|
||||
return fields[len(fields)-1]
|
||||
}
|
||||
return filenameObj.name
|
||||
}
|
||||
|
||||
// remaining fields in the manifest is a bit of a free-for-all depending on the build tooling used and package maintainer preferences
|
||||
if manifest != nil {
|
||||
switch {
|
||||
case manifest.Main["Name"] != "":
|
||||
// Manifest original spec...
|
||||
return manifest.Main["Name"]
|
||||
case manifest.Main["Bundle-Name"] != "":
|
||||
// BND tooling... TODO: this does not seem accurate (I don't see a reference in the BND tooling docs for this)
|
||||
return manifest.Main["Bundle-Name"]
|
||||
case manifest.Main["Short-Name"] != "":
|
||||
// Jenkins...
|
||||
return manifest.Main["Short-Name"]
|
||||
case manifest.Main["Extension-Name"] != "":
|
||||
// Jenkins...
|
||||
return manifest.Main["Extension-Name"]
|
||||
case manifest.Main["Implementation-Title"] != "":
|
||||
// last ditch effort...
|
||||
return manifest.Main["Implementation-Title"]
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func selectVersion(manifest *pkg.JavaManifest, filenameObj archiveFilename) string {
|
||||
|
||||
@@ -160,6 +160,44 @@ func TestSelectName(t *testing.T) {
|
||||
archive: newJavaArchiveFilename("/something/omg.jar"),
|
||||
expected: "omg",
|
||||
},
|
||||
{
|
||||
desc: "Use the artifact ID baked by the Apache Maven Bundle Plugin",
|
||||
manifest: pkg.JavaManifest{
|
||||
Main: map[string]string{
|
||||
"Created-By": "Apache Maven Bundle Plugin",
|
||||
"Bundle-SymbolicName": "com.atlassian.gadgets.atlassian-gadgets-api",
|
||||
"Name": "foo",
|
||||
"Implementation-Title": "maven-wrapper",
|
||||
},
|
||||
},
|
||||
archive: newJavaArchiveFilename("/something/omg.jar"),
|
||||
expected: "atlassian-gadgets-api",
|
||||
},
|
||||
{
|
||||
// example: pkg:maven/org.apache.servicemix.bundles/org.apache.servicemix.bundles.spring-beans@5.3.26_1
|
||||
desc: "Apache Maven Bundle Plugin might bake a version in the created-by field",
|
||||
manifest: pkg.JavaManifest{
|
||||
Main: map[string]string{
|
||||
"Created-By": "Apache Maven Bundle Plugin 5.1.6",
|
||||
"Bundle-SymbolicName": "com.atlassian.gadgets.atlassian-gadgets-api",
|
||||
"Name": "foo",
|
||||
"Implementation-Title": "maven-wrapper",
|
||||
},
|
||||
},
|
||||
archive: newJavaArchiveFilename("/something/omg.jar"),
|
||||
expected: "atlassian-gadgets-api",
|
||||
},
|
||||
{
|
||||
desc: "Filename looks like a groupid + artifact id",
|
||||
manifest: pkg.JavaManifest{
|
||||
Main: map[string]string{
|
||||
"Name": "foo",
|
||||
"Implementation-Title": "maven-wrapper",
|
||||
},
|
||||
},
|
||||
archive: newJavaArchiveFilename("/something/com.atlassian.gadgets.atlassian-gadgets-api.jar"),
|
||||
expected: "atlassian-gadgets-api",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/vifraa/gopom"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/license"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
@@ -342,19 +341,18 @@ func Test_parsePomXMLProject(t *testing.T) {
|
||||
Value: "The Apache Software License, Version 2.0",
|
||||
SPDXExpression: "", // TODO: ideally we would parse this title to get Apache-2.0 (created issue #2210 https://github.com/anchore/syft/issues/2210)
|
||||
Type: license.Declared,
|
||||
URLs: internal.NewStringSet("http://www.apache.org/licenses/LICENSE-2.0.txt"),
|
||||
URLs: []string{"http://www.apache.org/licenses/LICENSE-2.0.txt"},
|
||||
Locations: file.NewLocationSet(jarLocation),
|
||||
},
|
||||
{
|
||||
Value: "MIT",
|
||||
SPDXExpression: "MIT",
|
||||
Type: license.Declared,
|
||||
URLs: internal.NewStringSet(),
|
||||
Locations: file.NewLocationSet(jarLocation),
|
||||
},
|
||||
{
|
||||
Type: license.Declared,
|
||||
URLs: internal.NewStringSet("https://opensource.org/license/unlicense/"),
|
||||
URLs: []string{"https://opensource.org/license/unlicense/"},
|
||||
Locations: file.NewLocationSet(jarLocation),
|
||||
},
|
||||
},
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
/cache
|
||||
@@ -0,0 +1,14 @@
|
||||
CACHE_DIR = cache
|
||||
CACHE_PATH = $(shell pwd)/cache
|
||||
|
||||
JACKSON_CORE = jackson-core-2.15.2
|
||||
SBT_JACKSON_CORE = com.fasterxml.jackson.core.jackson-core-2.15.2
|
||||
|
||||
$(CACHE_DIR):
|
||||
mkdir -p $(CACHE_DIR)
|
||||
|
||||
$(CACHE_DIR)/$(JACKSON_CORE).jar: $(CACHE_DIR)
|
||||
cd $(JACKSON_CORE) && zip -r $(CACHE_PATH)/$(JACKSON_CORE).jar .
|
||||
|
||||
$(CACHE_DIR)/$(SBT_JACKSON_CORE).jar: $(CACHE_DIR)
|
||||
cd $(SBT_JACKSON_CORE) && zip -r $(CACHE_PATH)/$(SBT_JACKSON_CORE).jar .
|
||||
@@ -0,0 +1,5 @@
|
||||
# Jar-Metadata test fixtures
|
||||
|
||||
Each directory is the name of a jar to be created (simply a zip) based on the contents of the directory.
|
||||
This prevents us from having to create real jars by hand or keep binaries in the repo. This also means we dont need the
|
||||
entire jar, only the necessary metadata for testing.
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
Manifest-Version: 1.0
|
||||
Bundle-License: https://www.apache.org/licenses/LICENSE-2.0.txt
|
||||
Bundle-SymbolicName: com.fasterxml.jackson.core.jackson-core
|
||||
Implementation-Vendor-Id: com.fasterxml.jackson.core
|
||||
Specification-Title: Jackson-core
|
||||
Bundle-DocURL: https://github.com/FasterXML/jackson-core
|
||||
Import-Package: com.fasterxml.jackson.core;version=...snip
|
||||
Require-Capability: osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.8))"
|
||||
Export-Package: com.fasterxml.jackson.core;version...snip
|
||||
Bundle-Name: Jackson-core
|
||||
Multi-Release: true
|
||||
Build-Jdk-Spec: 1.8
|
||||
Bundle-Description: Core Jackson processing abstractions
|
||||
Implementation-Title: Jackson-core
|
||||
Implementation-Version: 2.15.2
|
||||
Bundle-ManifestVersion: 2
|
||||
Specification-Vendor: FasterXML
|
||||
Bundle-Vendor: FasterXML
|
||||
Tool: Bnd-6.3.1.202206071316
|
||||
Implementation-Vendor: FasterXML
|
||||
Bundle-Version: 2.15.2
|
||||
X-Compile-Target-JDK: 1.8
|
||||
X-Compile-Source-JDK: 1.8
|
||||
Created-By: Apache Maven Bundle Plugin 5.1.8
|
||||
Specification-Version: 2.15.2
|
||||
+323
@@ -0,0 +1,323 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<!-- This module was also published with a richer model, Gradle metadata, -->
|
||||
<!-- which should be used instead. Do not delete the following line which -->
|
||||
<!-- is to indicate to Gradle or any Gradle module metadata file consumer -->
|
||||
<!-- that they should prefer consuming it instead. -->
|
||||
<!-- do_not_remove: published-with-gradle-metadata -->
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.fasterxml.jackson</groupId>
|
||||
<artifactId>jackson-base</artifactId>
|
||||
<version>2.15.2</version>
|
||||
</parent>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
<name>Jackson-core</name>
|
||||
<version>2.15.2</version>
|
||||
<packaging>jar</packaging>
|
||||
<description>Core Jackson processing abstractions (aka Streaming API), implementation for JSON</description>
|
||||
<licenses>
|
||||
<license>
|
||||
<name>The Apache Software License, Version 2.0</name>
|
||||
<url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
|
||||
<distribution>repo</distribution>
|
||||
</license>
|
||||
</licenses>
|
||||
<inceptionYear>2008</inceptionYear>
|
||||
|
||||
<url>https://github.com/FasterXML/jackson-core</url>
|
||||
<scm>
|
||||
<connection>scm:git:git@github.com:FasterXML/jackson-core.git</connection>
|
||||
<developerConnection>scm:git:git@github.com:FasterXML/jackson-core.git</developerConnection>
|
||||
<url>https://github.com/FasterXML/jackson-core</url>
|
||||
<tag>jackson-core-2.15.2</tag>
|
||||
</scm>
|
||||
|
||||
<properties>
|
||||
<!-- 03-May-2022: Change Java compatibility for Jackson-Core 2.14 from Java6 to Java8,
|
||||
still use Moditect to get JDK9+ module info support; need newer bundle plugin as well
|
||||
(can just defaults from `jackson-parent`)
|
||||
-->
|
||||
|
||||
<!-- 16-Nov-2022, tatu: [core#838] Verify Android SDK compatibility.
|
||||
Baseline compatibility:
|
||||
* Jackson 2.13 compatible with Android SDK 19 and up
|
||||
* Jackson 2.14 compatible with Android SDK 26 and up
|
||||
* Jackson 2.15 compatible with Android SDK 26 and up
|
||||
-->
|
||||
<version.android.sdk>26</version.android.sdk>
|
||||
<version.android.sdk.signature>0.5.1</version.android.sdk.signature>
|
||||
|
||||
<osgi.export>com.fasterxml.jackson.core;version=${project.version},
|
||||
com.fasterxml.jackson.core.*;version=${project.version}
|
||||
</osgi.export>
|
||||
<osgi.import>!ch.randelshofer.fastdoubleparser, *</osgi.import>
|
||||
|
||||
<!-- Generate PackageVersion.java into this directory. -->
|
||||
<packageVersion.dir>com/fasterxml/jackson/core/json</packageVersion.dir>
|
||||
<packageVersion.package>${project.groupId}.json</packageVersion.package>
|
||||
|
||||
<!-- for Reproducible Builds -->
|
||||
<project.build.outputTimestamp>2023-05-30T22:15:40Z</project.build.outputTimestamp>
|
||||
</properties>
|
||||
|
||||
<!-- Alas, need to include snapshot reference since otherwise can not find
|
||||
snapshot of parent... -->
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>sonatype-nexus-snapshots</id>
|
||||
<name>Sonatype Nexus Snapshots</name>
|
||||
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
|
||||
<releases><enabled>false</enabled></releases>
|
||||
<snapshots><enabled>true</enabled></snapshots>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.junit</groupId>
|
||||
<artifactId>junit-bom</artifactId>
|
||||
<version>5.9.2</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<build>
|
||||
|
||||
<plugins>
|
||||
|
||||
<!-- 26-Aug-2019, tatu: JaCoCo for code coverage -->
|
||||
<plugin>
|
||||
<groupId>org.jacoco</groupId>
|
||||
<artifactId>jacoco-maven-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>prepare-agent</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>report</id>
|
||||
<phase>test</phase>
|
||||
<goals>
|
||||
<goal>report</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- Important: enable enforcer plug-in: -->
|
||||
<plugin>
|
||||
<artifactId>maven-enforcer-plugin</artifactId>
|
||||
<executions> <!-- or? combine.children="merge"> -->
|
||||
<execution>
|
||||
<id>enforce-properties</id>
|
||||
<phase>validate</phase>
|
||||
<goals><goal>enforce</goal></goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-site-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>${version.plugin.surefire}</version>
|
||||
<configuration>
|
||||
<redirectTestOutputToFile>${surefire.redirectTestOutputToFile}</redirectTestOutputToFile>
|
||||
<excludes>
|
||||
<exclude>**/failing/**/*.java</exclude>
|
||||
</excludes>
|
||||
<!-- 13-Apr-2018, tatu: for debugging [core#400]
|
||||
<systemPropertyVariables>
|
||||
<com.fasterxml.jackson.core.util.BufferRecyclers.trackReusableBuffers>true</com.fasterxml.jackson.core.util.BufferRecyclers.trackReusableBuffers>
|
||||
</systemPropertyVariables>
|
||||
-->
|
||||
</configuration>
|
||||
</plugin>
|
||||
<!-- settings are fine, but needed to trigger execution! -->
|
||||
<plugin>
|
||||
<groupId>com.google.code.maven-replacer-plugin</groupId>
|
||||
<artifactId>replacer</artifactId>
|
||||
</plugin>
|
||||
|
||||
<!-- 04-Mar-2019, tatu: Add rudimentary JDK9+ module info. To build with JDK 8
|
||||
will have to use `moduleInfoFile` as anything else requires JDK 9+
|
||||
-->
|
||||
<plugin>
|
||||
<groupId>org.moditect</groupId>
|
||||
<artifactId>moditect-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
<!-- 03-Nov-2020, tatu: Add LICENSE from main level -->
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>build-helper-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<configuration>
|
||||
<promoteTransitiveDependencies>true</promoteTransitiveDependencies>
|
||||
<createDependencyReducedPom>true</createDependencyReducedPom>
|
||||
<minimizeJar>true</minimizeJar>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>shade-jackson-core</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<filters>
|
||||
<filter>
|
||||
<artifact>ch.randelshofer:fastdoubleparser</artifact>
|
||||
<excludes>
|
||||
<exclude>META-INF/versions/**/module-info.*</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
</filters>
|
||||
<relocations>
|
||||
<relocation>
|
||||
<pattern>ch/randelshofer/fastdoubleparser</pattern>
|
||||
<shadedPattern>com/fasterxml/jackson/core/io/doubleparser</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>META-INF/LICENSE</pattern>
|
||||
<shadedPattern>META-INF/FastDoubleParser-LICENSE</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>META-INF/NOTICE</pattern>
|
||||
<shadedPattern>META-INF/FastDoubleParser-NOTICE</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>META-INF/jackson-core-LICENSE</pattern>
|
||||
<shadedPattern>META-INF/LICENSE</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>META-INF/jackson-core-NOTICE</pattern>
|
||||
<shadedPattern>META-INF/NOTICE</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>META-INF/versions/11/ch/randelshofer/fastdoubleparser</pattern>
|
||||
<shadedPattern>META-INF/versions/11/com/fasterxml/jackson/core/io/doubleparser</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>META-INF/versions/17/ch/randelshofer/fastdoubleparser</pattern>
|
||||
<shadedPattern>META-INF/versions/17/com/fasterxml/jackson/core/io/doubleparser</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>META-INF/versions/19/ch/randelshofer/fastdoubleparser</pattern>
|
||||
<shadedPattern>META-INF/versions/19/com/fasterxml/jackson/core/io/doubleparser</shadedPattern>
|
||||
</relocation>
|
||||
</relocations>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>de.jjohannes</groupId>
|
||||
<artifactId>gradle-module-metadata-maven-plugin</artifactId>
|
||||
<!-- 27-Apr-2023, tatu: [core#999]: Need to exclude shaded FDP
|
||||
dependency from GMM
|
||||
-->
|
||||
<configuration>
|
||||
<removedDependencies>
|
||||
<dependency>
|
||||
<groupId>ch.randelshofer</groupId>
|
||||
<artifactId>fastdoubleparser</artifactId>
|
||||
</dependency>
|
||||
</removedDependencies>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifestEntries>
|
||||
<Multi-Release>true</Multi-Release>
|
||||
</manifestEntries>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!-- 23-Mar-2023, tatu: [core#965] Need to put back Gradle module metadata marker -->
|
||||
<plugin>
|
||||
<groupId>io.github.floverfelt</groupId>
|
||||
<artifactId>find-and-replace-maven-plugin</artifactId>
|
||||
<version>1.1.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>exec</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>find-and-replace</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<replacementType>file-contents</replacementType>
|
||||
<baseDir>${basedir}</baseDir>
|
||||
<findRegex><![CDATA[<modelVersion>4.0.0</modelVersion>]]></findRegex>
|
||||
<fileMask>dependency-reduced-pom.xml</fileMask>
|
||||
<replaceValue><![CDATA[ <!-- This module was also published with a richer model, Gradle metadata, -->
|
||||
<!-- which should be used instead. Do not delete the following line which -->
|
||||
<!-- is to indicate to Gradle or any Gradle module metadata file consumer -->
|
||||
<!-- that they should prefer consuming it instead. -->
|
||||
<!-- do_not_remove: published-with-gradle-metadata -->
|
||||
<modelVersion>4.0.0</modelVersion>]]></replaceValue>
|
||||
<recursive>false</recursive>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- 16-Nov-2022, tatu: [core#838] add verification of compatibility
|
||||
wrt Android SDK versions using AnimalSniffer with "gummy bears" signatures.
|
||||
To be run from CI, but manually with:
|
||||
mvn clean package animal-sniffer:check
|
||||
-->
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>animal-sniffer-maven-plugin</artifactId>
|
||||
<version>1.22</version>
|
||||
<configuration>
|
||||
<signature>
|
||||
<groupId>com.toasttab.android</groupId>
|
||||
<artifactId>gummy-bears-api-${version.android.sdk}</artifactId>
|
||||
<version>${version.android.sdk.signature}</version>
|
||||
</signature>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>ch.randelshofer</groupId>
|
||||
<artifactId>fastdoubleparser</artifactId>
|
||||
<version>0.9.0</version>
|
||||
</dependency>
|
||||
<!-- Test dependencies -->
|
||||
<dependency>
|
||||
<groupId>org.junit.vintage</groupId>
|
||||
<artifactId>junit-vintage-engine</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
Manifest-Version: 1.0
|
||||
Bundle-License: https://www.apache.org/licenses/LICENSE-2.0.txt
|
||||
Bundle-SymbolicName: com.fasterxml.jackson.core.jackson-core
|
||||
Implementation-Vendor-Id: com.fasterxml.jackson.core
|
||||
Specification-Title: Jackson-core
|
||||
Bundle-DocURL: https://github.com/FasterXML/jackson-core
|
||||
Import-Package: com.fasterxml.jackson.core;version=...snip
|
||||
Require-Capability: osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.8))"
|
||||
Export-Package: com.fasterxml.jackson.core;version...snip
|
||||
Bundle-Name: Jackson-core
|
||||
Multi-Release: true
|
||||
Build-Jdk-Spec: 1.8
|
||||
Bundle-Description: Core Jackson processing abstractions
|
||||
Implementation-Title: Jackson-core
|
||||
Implementation-Version: 2.15.2
|
||||
Bundle-ManifestVersion: 2
|
||||
Specification-Vendor: FasterXML
|
||||
Bundle-Vendor: FasterXML
|
||||
Tool: Bnd-6.3.1.202206071316
|
||||
Implementation-Vendor: FasterXML
|
||||
Bundle-Version: 2.15.2
|
||||
X-Compile-Target-JDK: 1.8
|
||||
X-Compile-Source-JDK: 1.8
|
||||
Created-By: Apache Maven Bundle Plugin 5.1.8
|
||||
Specification-Version: 2.15.2
|
||||
+323
@@ -0,0 +1,323 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<!-- This module was also published with a richer model, Gradle metadata, -->
|
||||
<!-- which should be used instead. Do not delete the following line which -->
|
||||
<!-- is to indicate to Gradle or any Gradle module metadata file consumer -->
|
||||
<!-- that they should prefer consuming it instead. -->
|
||||
<!-- do_not_remove: published-with-gradle-metadata -->
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.fasterxml.jackson</groupId>
|
||||
<artifactId>jackson-base</artifactId>
|
||||
<version>2.15.2</version>
|
||||
</parent>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
<name>Jackson-core</name>
|
||||
<version>2.15.2</version>
|
||||
<packaging>jar</packaging>
|
||||
<description>Core Jackson processing abstractions (aka Streaming API), implementation for JSON</description>
|
||||
<licenses>
|
||||
<license>
|
||||
<name>The Apache Software License, Version 2.0</name>
|
||||
<url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
|
||||
<distribution>repo</distribution>
|
||||
</license>
|
||||
</licenses>
|
||||
<inceptionYear>2008</inceptionYear>
|
||||
|
||||
<url>https://github.com/FasterXML/jackson-core</url>
|
||||
<scm>
|
||||
<connection>scm:git:git@github.com:FasterXML/jackson-core.git</connection>
|
||||
<developerConnection>scm:git:git@github.com:FasterXML/jackson-core.git</developerConnection>
|
||||
<url>https://github.com/FasterXML/jackson-core</url>
|
||||
<tag>jackson-core-2.15.2</tag>
|
||||
</scm>
|
||||
|
||||
<properties>
|
||||
<!-- 03-May-2022: Change Java compatibility for Jackson-Core 2.14 from Java6 to Java8,
|
||||
still use Moditect to get JDK9+ module info support; need newer bundle plugin as well
|
||||
(can just defaults from `jackson-parent`)
|
||||
-->
|
||||
|
||||
<!-- 16-Nov-2022, tatu: [core#838] Verify Android SDK compatibility.
|
||||
Baseline compatibility:
|
||||
* Jackson 2.13 compatible with Android SDK 19 and up
|
||||
* Jackson 2.14 compatible with Android SDK 26 and up
|
||||
* Jackson 2.15 compatible with Android SDK 26 and up
|
||||
-->
|
||||
<version.android.sdk>26</version.android.sdk>
|
||||
<version.android.sdk.signature>0.5.1</version.android.sdk.signature>
|
||||
|
||||
<osgi.export>com.fasterxml.jackson.core;version=${project.version},
|
||||
com.fasterxml.jackson.core.*;version=${project.version}
|
||||
</osgi.export>
|
||||
<osgi.import>!ch.randelshofer.fastdoubleparser, *</osgi.import>
|
||||
|
||||
<!-- Generate PackageVersion.java into this directory. -->
|
||||
<packageVersion.dir>com/fasterxml/jackson/core/json</packageVersion.dir>
|
||||
<packageVersion.package>${project.groupId}.json</packageVersion.package>
|
||||
|
||||
<!-- for Reproducible Builds -->
|
||||
<project.build.outputTimestamp>2023-05-30T22:15:40Z</project.build.outputTimestamp>
|
||||
</properties>
|
||||
|
||||
<!-- Alas, need to include snapshot reference since otherwise can not find
|
||||
snapshot of parent... -->
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>sonatype-nexus-snapshots</id>
|
||||
<name>Sonatype Nexus Snapshots</name>
|
||||
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
|
||||
<releases><enabled>false</enabled></releases>
|
||||
<snapshots><enabled>true</enabled></snapshots>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.junit</groupId>
|
||||
<artifactId>junit-bom</artifactId>
|
||||
<version>5.9.2</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<build>
|
||||
|
||||
<plugins>
|
||||
|
||||
<!-- 26-Aug-2019, tatu: JaCoCo for code coverage -->
|
||||
<plugin>
|
||||
<groupId>org.jacoco</groupId>
|
||||
<artifactId>jacoco-maven-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>prepare-agent</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>report</id>
|
||||
<phase>test</phase>
|
||||
<goals>
|
||||
<goal>report</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- Important: enable enforcer plug-in: -->
|
||||
<plugin>
|
||||
<artifactId>maven-enforcer-plugin</artifactId>
|
||||
<executions> <!-- or? combine.children="merge"> -->
|
||||
<execution>
|
||||
<id>enforce-properties</id>
|
||||
<phase>validate</phase>
|
||||
<goals><goal>enforce</goal></goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-site-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>${version.plugin.surefire}</version>
|
||||
<configuration>
|
||||
<redirectTestOutputToFile>${surefire.redirectTestOutputToFile}</redirectTestOutputToFile>
|
||||
<excludes>
|
||||
<exclude>**/failing/**/*.java</exclude>
|
||||
</excludes>
|
||||
<!-- 13-Apr-2018, tatu: for debugging [core#400]
|
||||
<systemPropertyVariables>
|
||||
<com.fasterxml.jackson.core.util.BufferRecyclers.trackReusableBuffers>true</com.fasterxml.jackson.core.util.BufferRecyclers.trackReusableBuffers>
|
||||
</systemPropertyVariables>
|
||||
-->
|
||||
</configuration>
|
||||
</plugin>
|
||||
<!-- settings are fine, but needed to trigger execution! -->
|
||||
<plugin>
|
||||
<groupId>com.google.code.maven-replacer-plugin</groupId>
|
||||
<artifactId>replacer</artifactId>
|
||||
</plugin>
|
||||
|
||||
<!-- 04-Mar-2019, tatu: Add rudimentary JDK9+ module info. To build with JDK 8
|
||||
will have to use `moduleInfoFile` as anything else requires JDK 9+
|
||||
-->
|
||||
<plugin>
|
||||
<groupId>org.moditect</groupId>
|
||||
<artifactId>moditect-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
<!-- 03-Nov-2020, tatu: Add LICENSE from main level -->
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>build-helper-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<configuration>
|
||||
<promoteTransitiveDependencies>true</promoteTransitiveDependencies>
|
||||
<createDependencyReducedPom>true</createDependencyReducedPom>
|
||||
<minimizeJar>true</minimizeJar>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>shade-jackson-core</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<filters>
|
||||
<filter>
|
||||
<artifact>ch.randelshofer:fastdoubleparser</artifact>
|
||||
<excludes>
|
||||
<exclude>META-INF/versions/**/module-info.*</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
</filters>
|
||||
<relocations>
|
||||
<relocation>
|
||||
<pattern>ch/randelshofer/fastdoubleparser</pattern>
|
||||
<shadedPattern>com/fasterxml/jackson/core/io/doubleparser</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>META-INF/LICENSE</pattern>
|
||||
<shadedPattern>META-INF/FastDoubleParser-LICENSE</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>META-INF/NOTICE</pattern>
|
||||
<shadedPattern>META-INF/FastDoubleParser-NOTICE</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>META-INF/jackson-core-LICENSE</pattern>
|
||||
<shadedPattern>META-INF/LICENSE</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>META-INF/jackson-core-NOTICE</pattern>
|
||||
<shadedPattern>META-INF/NOTICE</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>META-INF/versions/11/ch/randelshofer/fastdoubleparser</pattern>
|
||||
<shadedPattern>META-INF/versions/11/com/fasterxml/jackson/core/io/doubleparser</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>META-INF/versions/17/ch/randelshofer/fastdoubleparser</pattern>
|
||||
<shadedPattern>META-INF/versions/17/com/fasterxml/jackson/core/io/doubleparser</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>META-INF/versions/19/ch/randelshofer/fastdoubleparser</pattern>
|
||||
<shadedPattern>META-INF/versions/19/com/fasterxml/jackson/core/io/doubleparser</shadedPattern>
|
||||
</relocation>
|
||||
</relocations>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>de.jjohannes</groupId>
|
||||
<artifactId>gradle-module-metadata-maven-plugin</artifactId>
|
||||
<!-- 27-Apr-2023, tatu: [core#999]: Need to exclude shaded FDP
|
||||
dependency from GMM
|
||||
-->
|
||||
<configuration>
|
||||
<removedDependencies>
|
||||
<dependency>
|
||||
<groupId>ch.randelshofer</groupId>
|
||||
<artifactId>fastdoubleparser</artifactId>
|
||||
</dependency>
|
||||
</removedDependencies>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifestEntries>
|
||||
<Multi-Release>true</Multi-Release>
|
||||
</manifestEntries>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!-- 23-Mar-2023, tatu: [core#965] Need to put back Gradle module metadata marker -->
|
||||
<plugin>
|
||||
<groupId>io.github.floverfelt</groupId>
|
||||
<artifactId>find-and-replace-maven-plugin</artifactId>
|
||||
<version>1.1.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>exec</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>find-and-replace</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<replacementType>file-contents</replacementType>
|
||||
<baseDir>${basedir}</baseDir>
|
||||
<findRegex><![CDATA[<modelVersion>4.0.0</modelVersion>]]></findRegex>
|
||||
<fileMask>dependency-reduced-pom.xml</fileMask>
|
||||
<replaceValue><![CDATA[ <!-- This module was also published with a richer model, Gradle metadata, -->
|
||||
<!-- which should be used instead. Do not delete the following line which -->
|
||||
<!-- is to indicate to Gradle or any Gradle module metadata file consumer -->
|
||||
<!-- that they should prefer consuming it instead. -->
|
||||
<!-- do_not_remove: published-with-gradle-metadata -->
|
||||
<modelVersion>4.0.0</modelVersion>]]></replaceValue>
|
||||
<recursive>false</recursive>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- 16-Nov-2022, tatu: [core#838] add verification of compatibility
|
||||
wrt Android SDK versions using AnimalSniffer with "gummy bears" signatures.
|
||||
To be run from CI, but manually with:
|
||||
mvn clean package animal-sniffer:check
|
||||
-->
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>animal-sniffer-maven-plugin</artifactId>
|
||||
<version>1.22</version>
|
||||
<configuration>
|
||||
<signature>
|
||||
<groupId>com.toasttab.android</groupId>
|
||||
<artifactId>gummy-bears-api-${version.android.sdk}</artifactId>
|
||||
<version>${version.android.sdk.signature}</version>
|
||||
</signature>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>ch.randelshofer</groupId>
|
||||
<artifactId>fastdoubleparser</artifactId>
|
||||
<version>0.9.0</version>
|
||||
</dependency>
|
||||
<!-- Test dependencies -->
|
||||
<dependency>
|
||||
<groupId>org.junit.vintage</groupId>
|
||||
<artifactId>junit-vintage-engine</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -5,7 +5,8 @@ import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/scylladb/go-set/strset"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
@@ -51,7 +52,7 @@ func parseYarnLock(resolver file.Resolver, _ *generic.Environment, reader file.L
|
||||
|
||||
var pkgs []pkg.Package
|
||||
scanner := bufio.NewScanner(reader)
|
||||
parsedPackages := internal.NewStringSet()
|
||||
parsedPackages := strset.New()
|
||||
currentPackage := noPackage
|
||||
currentVersion := noVersion
|
||||
|
||||
@@ -60,7 +61,7 @@ func parseYarnLock(resolver file.Resolver, _ *generic.Environment, reader file.L
|
||||
|
||||
if packageName := findPackageName(line); packageName != noPackage {
|
||||
// When we find a new package, check if we have unsaved identifiers
|
||||
if currentPackage != noPackage && currentVersion != noVersion && !parsedPackages.Contains(currentPackage+"@"+currentVersion) {
|
||||
if currentPackage != noPackage && currentVersion != noVersion && !parsedPackages.Has(currentPackage+"@"+currentVersion) {
|
||||
pkgs = append(pkgs, newYarnLockPackage(resolver, reader.Location, currentPackage, currentVersion))
|
||||
parsedPackages.Add(currentPackage + "@" + currentVersion)
|
||||
}
|
||||
@@ -68,7 +69,7 @@ func parseYarnLock(resolver file.Resolver, _ *generic.Environment, reader file.L
|
||||
currentPackage = packageName
|
||||
} else if version := findPackageVersion(line); version != noVersion {
|
||||
currentVersion = version
|
||||
} else if packageName, version := findPackageAndVersion(line); packageName != noPackage && version != noVersion && !parsedPackages.Contains(packageName+"@"+version) {
|
||||
} else if packageName, version := findPackageAndVersion(line); packageName != noPackage && version != noVersion && !parsedPackages.Has(packageName+"@"+version) {
|
||||
pkgs = append(pkgs, newYarnLockPackage(resolver, reader.Location, packageName, version))
|
||||
parsedPackages.Add(packageName + "@" + version)
|
||||
|
||||
@@ -79,7 +80,7 @@ func parseYarnLock(resolver file.Resolver, _ *generic.Environment, reader file.L
|
||||
}
|
||||
|
||||
// check if we have valid unsaved data after end-of-file has reached
|
||||
if currentPackage != noPackage && currentVersion != noVersion && !parsedPackages.Contains(currentPackage+"@"+currentVersion) {
|
||||
if currentPackage != noPackage && currentVersion != noVersion && !parsedPackages.Has(currentPackage+"@"+currentVersion) {
|
||||
pkgs = append(pkgs, newYarnLockPackage(resolver, reader.Location, currentPackage, currentVersion))
|
||||
parsedPackages.Add(currentPackage + "@" + currentVersion)
|
||||
}
|
||||
|
||||
@@ -9,7 +9,8 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/scylladb/go-set/strset"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
@@ -105,7 +106,7 @@ func addLicenses(resolver file.Resolver, dbLocation file.Location, p *pkg.Packag
|
||||
return
|
||||
}
|
||||
|
||||
findings := internal.NewStringSet()
|
||||
findings := strset.New()
|
||||
scanner := bufio.NewScanner(licenseReader)
|
||||
scanner.Split(bufio.ScanWords)
|
||||
for scanner.Scan() {
|
||||
@@ -115,7 +116,7 @@ func addLicenses(resolver file.Resolver, dbLocation file.Location, p *pkg.Packag
|
||||
}
|
||||
}
|
||||
|
||||
licenseCandidates := findings.ToSlice()
|
||||
licenseCandidates := findings.List()
|
||||
p.Licenses = pkg.NewLicenseSet(pkg.NewLicensesFromLocation(*location, licenseCandidates...)...)
|
||||
p.Locations.Add(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.SupportingEvidenceAnnotation))
|
||||
}
|
||||
|
||||
@@ -29,5 +29,12 @@ func NewPythonIndexCataloger(cfg CatalogerConfig) *generic.Cataloger {
|
||||
// NewPythonPackageCataloger returns a new cataloger for python packages within egg or wheel installation directories.
|
||||
func NewPythonPackageCataloger() *generic.Cataloger {
|
||||
return generic.NewCataloger("python-package-cataloger").
|
||||
WithParserByGlobs(parseWheelOrEgg, eggInfoGlob, "**/*dist-info/METADATA", "**/*egg-info/PKG-INFO")
|
||||
WithParserByGlobs(
|
||||
parseWheelOrEgg,
|
||||
eggInfoGlob,
|
||||
"**/*dist-info/METADATA",
|
||||
"**/*egg-info/PKG-INFO",
|
||||
"**/*DIST-INFO/METADATA",
|
||||
"**/*EGG-INFO/PKG-INFO",
|
||||
)
|
||||
}
|
||||
|
||||
@@ -69,6 +69,43 @@ func Test_PackageCataloger(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "egg-info directory case sensitive",
|
||||
fixtures: []string{
|
||||
"test-fixtures/casesensitive/EGG-INFO/PKG-INFO",
|
||||
"test-fixtures/casesensitive/EGG-INFO/RECORD",
|
||||
"test-fixtures/casesensitive/EGG-INFO/top_level.txt",
|
||||
},
|
||||
expectedPackage: pkg.Package{
|
||||
Name: "requests",
|
||||
Version: "2.22.0",
|
||||
PURL: "pkg:pypi/requests@2.22.0",
|
||||
Type: pkg.PythonPkg,
|
||||
Language: pkg.Python,
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocations("Apache 2.0", file.NewLocation("test-fixtures/casesensitive/EGG-INFO/PKG-INFO")),
|
||||
),
|
||||
FoundBy: "python-package-cataloger",
|
||||
MetadataType: pkg.PythonPackageMetadataType,
|
||||
Metadata: pkg.PythonPackageMetadata{
|
||||
Name: "requests",
|
||||
Version: "2.22.0",
|
||||
Platform: "UNKNOWN",
|
||||
Author: "Kenneth Reitz",
|
||||
AuthorEmail: "me@kennethreitz.org",
|
||||
SitePackagesRootPath: "test-fixtures/casesensitive",
|
||||
Files: []pkg.PythonFileRecord{
|
||||
{Path: "requests-2.22.0.dist-info/INSTALLER", Digest: &pkg.PythonFileDigest{"sha256", "zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg"}, Size: "4"},
|
||||
{Path: "requests/__init__.py", Digest: &pkg.PythonFileDigest{"sha256", "PnKCgjcTq44LaAMzB-7--B2FdewRrE8F_vjZeaG9NhA"}, Size: "3921"},
|
||||
{Path: "requests/__pycache__/__version__.cpython-38.pyc"},
|
||||
{Path: "requests/__pycache__/utils.cpython-38.pyc"},
|
||||
{Path: "requests/__version__.py", Digest: &pkg.PythonFileDigest{"sha256", "Bm-GFstQaFezsFlnmEMrJDe8JNROz9n2XXYtODdvjjc"}, Size: "436"},
|
||||
{Path: "requests/utils.py", Digest: &pkg.PythonFileDigest{"sha256", "LtPJ1db6mJff2TJSJWKi7rBpzjPS3mSOrjC9zRhoD3A"}, Size: "30049"},
|
||||
},
|
||||
TopLevelPackages: []string{"requests"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "dist-info directory",
|
||||
fixtures: []string{
|
||||
@@ -109,6 +146,46 @@ func Test_PackageCataloger(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "dist-info directory case sensitive",
|
||||
fixtures: []string{
|
||||
"test-fixtures/casesensitive/DIST-INFO/METADATA",
|
||||
"test-fixtures/casesensitive/DIST-INFO/RECORD",
|
||||
"test-fixtures/casesensitive/DIST-INFO/top_level.txt",
|
||||
"test-fixtures/casesensitive/DIST-INFO/direct_url.json",
|
||||
},
|
||||
expectedPackage: pkg.Package{
|
||||
Name: "Pygments",
|
||||
Version: "2.6.1",
|
||||
PURL: "pkg:pypi/Pygments@2.6.1?vcs_url=git+https://github.com/python-test/test.git%40aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
Type: pkg.PythonPkg,
|
||||
Language: pkg.Python,
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocations("BSD License", file.NewLocation("test-fixtures/casesensitive/DIST-INFO/METADATA")),
|
||||
),
|
||||
FoundBy: "python-package-cataloger",
|
||||
MetadataType: pkg.PythonPackageMetadataType,
|
||||
Metadata: pkg.PythonPackageMetadata{
|
||||
Name: "Pygments",
|
||||
Version: "2.6.1",
|
||||
Platform: "any",
|
||||
Author: "Georg Brandl",
|
||||
AuthorEmail: "georg@python.org",
|
||||
SitePackagesRootPath: "test-fixtures/casesensitive",
|
||||
Files: []pkg.PythonFileRecord{
|
||||
{Path: "../../../bin/pygmentize", Digest: &pkg.PythonFileDigest{"sha256", "dDhv_U2jiCpmFQwIRHpFRLAHUO4R1jIJPEvT_QYTFp8"}, Size: "220"},
|
||||
{Path: "Pygments-2.6.1.dist-info/AUTHORS", Digest: &pkg.PythonFileDigest{"sha256", "PVpa2_Oku6BGuiUvutvuPnWGpzxqFy2I8-NIrqCvqUY"}, Size: "8449"},
|
||||
{Path: "Pygments-2.6.1.dist-info/RECORD"},
|
||||
{Path: "pygments/__pycache__/__init__.cpython-38.pyc"},
|
||||
{Path: "pygments/util.py", Digest: &pkg.PythonFileDigest{"sha256", "586xXHiJGGZxqk5PMBu3vBhE68DLuAe5MBARWrSPGxA"}, Size: "10778"},
|
||||
|
||||
{Path: "pygments/x_util.py", Digest: &pkg.PythonFileDigest{"sha256", "qpzzsOW31KT955agi-7NS--90I0iNiJCyLJQnRCHgKI="}, Size: "10778"},
|
||||
},
|
||||
TopLevelPackages: []string{"pygments", "something_else"},
|
||||
DirectURLOrigin: &pkg.PythonDirectURLOriginInfo{URL: "https://github.com/python-test/test.git", VCS: "git", CommitID: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "malformed-record",
|
||||
fixtures: []string{
|
||||
@@ -278,6 +355,8 @@ func Test_PackageCataloger_Globs(t *testing.T) {
|
||||
name: "obtain index files",
|
||||
fixture: "test-fixtures/glob-paths",
|
||||
expected: []string{
|
||||
"site-packages/v.DIST-INFO/METADATA",
|
||||
"site-packages/w.EGG-INFO/PKG-INFO",
|
||||
"site-packages/x.dist-info/METADATA",
|
||||
"site-packages/y.egg-info/PKG-INFO",
|
||||
"site-packages/z.egg-info",
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
Metadata-Version: 2.1
|
||||
Name: Pygments
|
||||
Version: 2.6.1
|
||||
Summary: Pygments is a syntax highlighting package written in Python.
|
||||
Home-page: https://pygments.org/
|
||||
Author: Georg Brandl
|
||||
Author-email: georg@python.org
|
||||
License: BSD License
|
||||
Keywords: syntax highlighting
|
||||
Platform: any
|
||||
Classifier: License :: OSI Approved :: BSD License
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: Intended Audience :: End Users/Desktop
|
||||
Classifier: Intended Audience :: System Administrators
|
||||
Classifier: Development Status :: 6 - Mature
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3.5
|
||||
Classifier: Programming Language :: Python :: 3.6
|
||||
Classifier: Programming Language :: Python :: 3.7
|
||||
Classifier: Programming Language :: Python :: 3.8
|
||||
Classifier: Programming Language :: Python :: Implementation :: CPython
|
||||
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
||||
Classifier: Operating System :: OS Independent
|
||||
Classifier: Topic :: Text Processing :: Filters
|
||||
Classifier: Topic :: Utilities
|
||||
Requires-Python: >=3.5
|
||||
|
||||
|
||||
Pygments
|
||||
~~~~~~~~
|
||||
|
||||
Pygments is a syntax highlighting package written in Python.
|
||||
|
||||
It is a generic syntax highlighter suitable for use in code hosting, forums,
|
||||
wikis or other applications that need to prettify source code. Highlights
|
||||
are:
|
||||
|
||||
* a wide range of over 500 languages and other text formats is supported
|
||||
* special attention is paid to details, increasing quality by a fair amount
|
||||
* support for new languages and formats are added easily
|
||||
* a number of output formats, presently HTML, LaTeX, RTF, SVG, all image formats that PIL supports and ANSI sequences
|
||||
* it is usable as a command-line tool and as a library
|
||||
|
||||
:copyright: Copyright 2006-2019 by the Pygments team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
../../../bin/pygmentize,sha256=dDhv_U2jiCpmFQwIRHpFRLAHUO4R1jIJPEvT_QYTFp8,220
|
||||
Pygments-2.6.1.dist-info/AUTHORS,sha256=PVpa2_Oku6BGuiUvutvuPnWGpzxqFy2I8-NIrqCvqUY,8449
|
||||
Pygments-2.6.1.dist-info/RECORD,,
|
||||
pygments/__pycache__/__init__.cpython-38.pyc,,
|
||||
pygments/util.py,sha256=586xXHiJGGZxqk5PMBu3vBhE68DLuAe5MBARWrSPGxA,10778
|
||||
pygments/x_util.py,sha256=qpzzsOW31KT955agi-7NS--90I0iNiJCyLJQnRCHgKI=,10778
|
||||
@@ -0,0 +1 @@
|
||||
{"url": "https://github.com/python-test/test.git", "vcs_info": {"commit_id": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "vcs": "git"}}
|
||||
@@ -0,0 +1,2 @@
|
||||
pygments
|
||||
something_else
|
||||
@@ -0,0 +1,134 @@
|
||||
Metadata-Version: 2.1
|
||||
Name: requests
|
||||
Version: 2.22.0
|
||||
Summary: Python HTTP for Humans.
|
||||
Home-page: http://python-requests.org
|
||||
Author: Kenneth Reitz
|
||||
Author-email: me@kennethreitz.org
|
||||
License: Apache 2.0
|
||||
Description: Requests: HTTP for Humans™
|
||||
==========================
|
||||
|
||||
[](https://pypi.org/project/requests/)
|
||||
[](https://pypi.org/project/requests/)
|
||||
[](https://pypi.org/project/requests/)
|
||||
[](https://codecov.io/github/requests/requests)
|
||||
[](https://github.com/requests/requests/graphs/contributors)
|
||||
[](https://saythanks.io/to/kennethreitz)
|
||||
|
||||
Requests is the only *Non-GMO* HTTP library for Python, safe for human
|
||||
consumption.
|
||||
|
||||

|
||||
|
||||
Behold, the power of Requests:
|
||||
|
||||
``` {.sourceCode .python}
|
||||
>>> import requests
|
||||
>>> r = requests.get('https://api.github.com/user', auth=('user', 'pass'))
|
||||
>>> r.status_code
|
||||
200
|
||||
>>> r.headers['content-type']
|
||||
'application/json; charset=utf8'
|
||||
>>> r.encoding
|
||||
'utf-8'
|
||||
>>> r.text
|
||||
u'{"type":"User"...'
|
||||
>>> r.json()
|
||||
{u'disk_usage': 368627, u'private_gists': 484, ...}
|
||||
```
|
||||
|
||||
See [the similar code, sans Requests](https://gist.github.com/973705).
|
||||
|
||||
[](http://docs.python-requests.org/)
|
||||
|
||||
Requests allows you to send *organic, grass-fed* HTTP/1.1 requests,
|
||||
without the need for manual labor. There's no need to manually add query
|
||||
strings to your URLs, or to form-encode your POST data. Keep-alive and
|
||||
HTTP connection pooling are 100% automatic, thanks to
|
||||
[urllib3](https://github.com/shazow/urllib3).
|
||||
|
||||
Besides, all the cool kids are doing it. Requests is one of the most
|
||||
downloaded Python packages of all time, pulling in over 11,000,000
|
||||
downloads every month. You don't want to be left out!
|
||||
|
||||
Feature Support
|
||||
---------------
|
||||
|
||||
Requests is ready for today's web.
|
||||
|
||||
- International Domains and URLs
|
||||
- Keep-Alive & Connection Pooling
|
||||
- Sessions with Cookie Persistence
|
||||
- Browser-style SSL Verification
|
||||
- Basic/Digest Authentication
|
||||
- Elegant Key/Value Cookies
|
||||
- Automatic Decompression
|
||||
- Automatic Content Decoding
|
||||
- Unicode Response Bodies
|
||||
- Multipart File Uploads
|
||||
- HTTP(S) Proxy Support
|
||||
- Connection Timeouts
|
||||
- Streaming Downloads
|
||||
- `.netrc` Support
|
||||
- Chunked Requests
|
||||
|
||||
Requests officially supports Python 2.7 & 3.4–3.7, and runs great on
|
||||
PyPy.
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
To install Requests, simply use [pipenv](http://pipenv.org/) (or pip, of
|
||||
course):
|
||||
|
||||
``` {.sourceCode .bash}
|
||||
$ pipenv install requests
|
||||
✨🍰✨
|
||||
```
|
||||
|
||||
Satisfaction guaranteed.
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
Fantastic documentation is available at
|
||||
<http://docs.python-requests.org/>, for a limited time only.
|
||||
|
||||
How to Contribute
|
||||
-----------------
|
||||
|
||||
1. Become more familiar with the project by reading our [Contributor's Guide](http://docs.python-requests.org/en/latest/dev/contributing/) and our [development philosophy](http://docs.python-requests.org/en/latest/dev/philosophy/).
|
||||
2. Check for open issues or open a fresh issue to start a discussion
|
||||
around a feature idea or a bug. There is a [Contributor
|
||||
Friendly](https://github.com/requests/requests/issues?direction=desc&labels=Contributor+Friendly&page=1&sort=updated&state=open)
|
||||
tag for issues that should be ideal for people who are not very
|
||||
familiar with the codebase yet.
|
||||
3. Fork [the repository](https://github.com/requests/requests) on
|
||||
GitHub to start making your changes to the **master** branch (or
|
||||
branch off of it).
|
||||
4. Write a test which shows that the bug was fixed or that the feature
|
||||
works as expected.
|
||||
5. Send a pull request and bug the maintainer until it gets merged and
|
||||
published. :) Make sure to add yourself to
|
||||
[AUTHORS](https://github.com/requests/requests/blob/master/AUTHORS.rst).
|
||||
|
||||
|
||||
Platform: UNKNOWN
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: Natural Language :: English
|
||||
Classifier: License :: OSI Approved :: Apache Software License
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 2
|
||||
Classifier: Programming Language :: Python :: 2.7
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3.5
|
||||
Classifier: Programming Language :: Python :: 3.6
|
||||
Classifier: Programming Language :: Python :: 3.7
|
||||
Classifier: Programming Language :: Python :: Implementation :: CPython
|
||||
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
||||
Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*
|
||||
Description-Content-Type: text/markdown
|
||||
Provides-Extra: security
|
||||
Provides-Extra: socks
|
||||
@@ -0,0 +1,69 @@
|
||||
Metadata-Version: 1.1
|
||||
Name: mxnet
|
||||
Version: 1.8.0
|
||||
Summary: <!--- Licensed to the Apache Software Foundation (ASF) under one -->
|
||||
<!--- or more contributor license agreements. See the NOTICE file -->
|
||||
<!--- distributed with this work for additional information -->
|
||||
<!--- regarding copyright ownership. The ASF licenses this file -->
|
||||
<!--- to you under the Apache License, Version 2.0 (the -->
|
||||
<!--- "License"); you may not use this file except in compliance -->
|
||||
<!--- with the License. You may obtain a copy of the License at -->
|
||||
|
||||
<!--- http://www.apache.org/licenses/LICENSE-2.0 -->
|
||||
|
||||
<!--- Unless required by applicable law or agreed to in writing, -->
|
||||
<!--- software distributed under the License is distributed on an -->
|
||||
<!--- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -->
|
||||
<!--- KIND, either express or implied. See the License for the -->
|
||||
<!--- specific language governing permissions and limitations -->
|
||||
<!--- under the License. -->
|
||||
|
||||
MXNet Python Package
|
||||
====================
|
||||
This directory and nested files contain MXNet Python package and language binding.
|
||||
|
||||
## Installation
|
||||
To install MXNet Python package, visit MXNet [Install Instruction](https://mxnet.apache.org/get_started)
|
||||
|
||||
|
||||
## Running the unit tests
|
||||
|
||||
For running unit tests, you will need the [nose PyPi package](https://pypi.python.org/pypi/nose). To install:
|
||||
```bash
|
||||
pip install --upgrade nose
|
||||
```
|
||||
|
||||
Once ```nose``` is installed, run the following from MXNet root directory (please make sure the installation path of ```nosetests``` is included in your ```$PATH``` environment variable):
|
||||
```
|
||||
nosetests tests/python/unittest
|
||||
nosetests tests/python/train
|
||||
|
||||
```
|
||||
|
||||
Home-page: https://github.com/apache/incubator-mxnet
|
||||
Author: UNKNOWN
|
||||
Author-email: UNKNOWN
|
||||
License: UNKNOWN
|
||||
Description: UNKNOWN
|
||||
Platform: UNKNOWN
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: Intended Audience :: Education
|
||||
Classifier: Intended Audience :: Science/Research
|
||||
Classifier: License :: OSI Approved :: Apache Software License
|
||||
Classifier: Programming Language :: C++
|
||||
Classifier: Programming Language :: Cython
|
||||
Classifier: Programming Language :: Other
|
||||
Classifier: Programming Language :: Perl
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 2.7
|
||||
Classifier: Programming Language :: Python :: 3.4
|
||||
Classifier: Programming Language :: Python :: 3.5
|
||||
Classifier: Programming Language :: Python :: 3.6
|
||||
Classifier: Programming Language :: Python :: Implementation :: CPython
|
||||
Classifier: Topic :: Scientific/Engineering
|
||||
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
||||
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
||||
Classifier: Topic :: Software Development
|
||||
Classifier: Topic :: Software Development :: Libraries
|
||||
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
||||
@@ -0,0 +1,6 @@
|
||||
requests-2.22.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
requests/__init__.py,sha256=PnKCgjcTq44LaAMzB-7--B2FdewRrE8F_vjZeaG9NhA,3921
|
||||
requests/__pycache__/__version__.cpython-38.pyc,,
|
||||
requests/__pycache__/utils.cpython-38.pyc,,
|
||||
requests/__version__.py,sha256=Bm-GFstQaFezsFlnmEMrJDe8JNROz9n2XXYtODdvjjc,436
|
||||
requests/utils.py,sha256=LtPJ1db6mJff2TJSJWKi7rBpzjPS3mSOrjC9zRhoD3A,30049
|
||||
@@ -0,0 +1 @@
|
||||
requests
|
||||
@@ -0,0 +1 @@
|
||||
bogus
|
||||
@@ -0,0 +1 @@
|
||||
bogus
|
||||
@@ -4,7 +4,8 @@ import (
|
||||
"bufio"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/scylladb/go-set/strset"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
@@ -13,7 +14,7 @@ import (
|
||||
|
||||
var _ generic.Parser = parseGemFileLockEntries
|
||||
|
||||
var sectionsOfInterest = internal.NewStringSet("GEM", "GIT", "PATH", "PLUGIN SOURCE")
|
||||
var sectionsOfInterest = strset.New("GEM", "GIT", "PATH", "PLUGIN SOURCE")
|
||||
|
||||
// parseGemFileLockEntries is a parser function for Gemfile.lock contents, returning all Gems discovered.
|
||||
func parseGemFileLockEntries(_ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
@@ -30,7 +31,7 @@ func parseGemFileLockEntries(_ file.Resolver, _ *generic.Environment, reader fil
|
||||
// start of section
|
||||
currentSection = sanitizedLine
|
||||
continue
|
||||
} else if !sectionsOfInterest.Contains(currentSection) {
|
||||
} else if !sectionsOfInterest.Has(currentSection) {
|
||||
// skip this line, we're in the wrong section
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
@@ -67,7 +68,17 @@ func parsePackageResolved(_ file.Resolver, _ *generic.Environment, reader file.L
|
||||
}
|
||||
}
|
||||
|
||||
var pins, err = pinsForVersion(packageResolvedData, packageResolvedData["version"].(float64))
|
||||
if packageResolvedData["version"] == nil {
|
||||
log.Trace("no version found in Package.resolved file, skipping")
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
version, ok := packageResolvedData["version"].(float64)
|
||||
if !ok {
|
||||
return nil, nil, fmt.Errorf("failed to parse Package.resolved file: version is not a number")
|
||||
}
|
||||
|
||||
var pins, err = pinsForVersion(packageResolvedData, version)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
package swift
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
@@ -80,3 +84,24 @@ func TestParsePackageResolved(t *testing.T) {
|
||||
|
||||
pkgtest.TestFileParser(t, fixture, parsePackageResolved, expectedPkgs, expectedRelationships)
|
||||
}
|
||||
|
||||
func TestParsePackageResolved_empty(t *testing.T) {
|
||||
// regression for https://github.com/anchore/syft/issues/2225
|
||||
fixture := "test-fixtures/empty-packages.resolved"
|
||||
|
||||
pkgtest.TestFileParser(t, fixture, parsePackageResolved, nil, nil)
|
||||
|
||||
dir := t.TempDir()
|
||||
fixture = filepath.Join(dir, "Package.resolved")
|
||||
_, err := os.Create(fixture)
|
||||
require.NoError(t, err)
|
||||
|
||||
pkgtest.TestFileParser(t, fixture, parsePackageResolved, nil, nil)
|
||||
}
|
||||
|
||||
func TestParsePackageResolved_versionNotANumber(t *testing.T) {
|
||||
// regression for https://github.com/anchore/syft/issues/2225
|
||||
fixture := "test-fixtures/bad-version-packages.resolved"
|
||||
|
||||
pkgtest.NewCatalogTester().FromFile(t, fixture).WithError().TestParser(t, parsePackageResolved)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"version" : "2"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -14,16 +14,49 @@ var _ FileOwner = (*DpkgMetadata)(nil)
|
||||
|
||||
// DpkgMetadata represents all captured data for a Debian package DB entry; available fields are described
|
||||
// at http://manpages.ubuntu.com/manpages/xenial/man1/dpkg-query.1.html in the --showformat section.
|
||||
// Additional information about how these fields are used can be found at
|
||||
// - https://www.debian.org/doc/debian-policy/ch-controlfields.html
|
||||
// - https://www.debian.org/doc/debian-policy/ch-relationships.html
|
||||
// - https://www.debian.org/doc/debian-policy/ch-binary.html#s-virtual-pkg
|
||||
// - https://www.debian.org/doc/debian-policy/ch-relationships.html#s-virtual
|
||||
|
||||
type DpkgMetadata struct {
|
||||
Package string `mapstructure:"Package" json:"package"`
|
||||
Source string `mapstructure:"Source" json:"source" cyclonedx:"source"`
|
||||
Version string `mapstructure:"Version" json:"version"`
|
||||
SourceVersion string `mapstructure:"SourceVersion" json:"sourceVersion" cyclonedx:"sourceVersion"`
|
||||
Architecture string `mapstructure:"Architecture" json:"architecture"`
|
||||
Maintainer string `mapstructure:"Maintainer" json:"maintainer"`
|
||||
InstalledSize int `mapstructure:"InstalledSize" json:"installedSize" cyclonedx:"installedSize"`
|
||||
Description string `mapstructure:"Description" hash:"ignore" json:"-"`
|
||||
Files []DpkgFileRecord `json:"files"`
|
||||
Package string `json:"package"`
|
||||
Source string `json:"source" cyclonedx:"source"`
|
||||
Version string `json:"version"`
|
||||
SourceVersion string `json:"sourceVersion" cyclonedx:"sourceVersion"`
|
||||
|
||||
// Architecture can include the following sets of values depending on context and the control file used:
|
||||
// - a unique single word identifying a Debian machine architecture as described in Architecture specification string (https://www.debian.org/doc/debian-policy/ch-customized-programs.html#s-arch-spec) .
|
||||
// - an architecture wildcard identifying a set of Debian machine architectures, see Architecture wildcards (https://www.debian.org/doc/debian-policy/ch-customized-programs.html#s-arch-wildcard-spec). any matches all Debian machine architectures and is the most frequently used.
|
||||
// - "all", which indicates an architecture-independent package.
|
||||
// - "source", which indicates a source package.
|
||||
Architecture string `json:"architecture"`
|
||||
|
||||
// Maintainer is the package maintainer’s name and email address. The name must come first, then the email
|
||||
// address inside angle brackets <> (in RFC822 format).
|
||||
Maintainer string `json:"maintainer"`
|
||||
|
||||
InstalledSize int `json:"installedSize" cyclonedx:"installedSize"`
|
||||
|
||||
// Description contains a description of the binary package, consisting of two parts, the synopsis or the short
|
||||
// description, and the long description (in a multiline format).
|
||||
Description string `hash:"ignore" json:"-"`
|
||||
|
||||
// Provides is a virtual package that is provided by one or more packages. A virtual package is one which appears
|
||||
// in the Provides control field of another package. The effect is as if the package(s) which provide a particular
|
||||
// virtual package name had been listed by name everywhere the virtual package name appears. (See also Virtual packages)
|
||||
Provides []string `json:"provides,omitempty"`
|
||||
|
||||
// Depends This declares an absolute dependency. A package will not be configured unless all of the packages listed in
|
||||
// its Depends field have been correctly configured (unless there is a circular dependency).
|
||||
Depends []string `json:"depends,omitempty"`
|
||||
|
||||
// PreDepends is like Depends, except that it also forces dpkg to complete installation of the packages named
|
||||
// before even starting the installation of the package which declares the pre-dependency.
|
||||
PreDepends []string `json:"preDepends,omitempty"`
|
||||
|
||||
Files []DpkgFileRecord `json:"files"`
|
||||
}
|
||||
|
||||
// DpkgFileRecord represents a single file attributed to a debian package.
|
||||
|
||||
+36
-15
@@ -4,7 +4,8 @@ import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/scylladb/go-set/strset"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
@@ -23,11 +24,11 @@ var _ sort.Interface = (*Licenses)(nil)
|
||||
// this is different for licenses since we're only looking for evidence
|
||||
// of where a license was declared/concluded for a given package
|
||||
type License struct {
|
||||
Value string `json:"value"`
|
||||
SPDXExpression string `json:"spdxExpression"`
|
||||
Type license.Type `json:"type"`
|
||||
URLs internal.StringSet `hash:"ignore"`
|
||||
Locations file.LocationSet `hash:"ignore"`
|
||||
Value string
|
||||
SPDXExpression string
|
||||
Type license.Type
|
||||
URLs []string `hash:"ignore"`
|
||||
Locations file.LocationSet `hash:"ignore"`
|
||||
}
|
||||
|
||||
type Licenses []License
|
||||
@@ -77,7 +78,6 @@ func NewLicenseFromType(value string, t license.Type) License {
|
||||
Value: value,
|
||||
SPDXExpression: spdxExpression,
|
||||
Type: t,
|
||||
URLs: internal.NewStringSet(),
|
||||
Locations: file.NewLocationSet(),
|
||||
}
|
||||
}
|
||||
@@ -109,11 +109,16 @@ func NewLicenseFromLocations(value string, locations ...file.Location) License {
|
||||
|
||||
func NewLicenseFromURLs(value string, urls ...string) License {
|
||||
l := NewLicense(value)
|
||||
for _, u := range urls {
|
||||
if u != "" {
|
||||
l.URLs.Add(u)
|
||||
s := strset.New()
|
||||
for _, url := range urls {
|
||||
if url != "" {
|
||||
s.Add(url)
|
||||
}
|
||||
}
|
||||
|
||||
l.URLs = s.List()
|
||||
sort.Strings(l.URLs)
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
@@ -123,12 +128,15 @@ func NewLicenseFromFields(value, url string, location *file.Location) License {
|
||||
l.Locations.Add(*location)
|
||||
}
|
||||
if url != "" {
|
||||
l.URLs.Add(url)
|
||||
l.URLs = append(l.URLs, url)
|
||||
}
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// this is a bit of a hack to not infinitely recurse when hashing a license
|
||||
// Merge two licenses into a new license object. If the merge is not possible due to unmergeable fields
|
||||
// (e.g. different values for Value, SPDXExpression, Type, or any non-collection type) an error is returned.
|
||||
// TODO: this is a bit of a hack to not infinitely recurse when hashing a license
|
||||
func (s License) Merge(l License) (*License, error) {
|
||||
sHash, err := artifact.IDByHash(s)
|
||||
if err != nil {
|
||||
@@ -145,11 +153,24 @@ func (s License) Merge(l License) (*License, error) {
|
||||
return nil, fmt.Errorf("cannot merge licenses with different hash")
|
||||
}
|
||||
|
||||
s.URLs.Add(l.URLs.ToSlice()...)
|
||||
if s.Locations.Empty() && l.Locations.Empty() {
|
||||
// try to keep s.URLs unallocated unless necessary (which is the default state from the constructor)
|
||||
if len(l.URLs) > 0 {
|
||||
s.URLs = append(s.URLs, l.URLs...)
|
||||
}
|
||||
|
||||
if len(s.URLs) > 0 {
|
||||
s.URLs = strset.New(s.URLs...).List()
|
||||
sort.Strings(s.URLs)
|
||||
}
|
||||
|
||||
if l.Locations.Empty() {
|
||||
return &s, nil
|
||||
}
|
||||
|
||||
s.Locations.Add(l.Locations.ToSlice()...)
|
||||
// since the set instance has a reference type (map) we must make a new instance
|
||||
locations := file.NewLocationSet(s.Locations.ToSlice()...)
|
||||
locations.Add(l.Locations.ToSlice()...)
|
||||
s.Locations = locations
|
||||
|
||||
return &s, nil
|
||||
}
|
||||
|
||||
@@ -3,9 +3,8 @@ package pkg
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/license"
|
||||
)
|
||||
@@ -97,7 +96,7 @@ func TestLicenseSet_Add(t *testing.T) {
|
||||
Value: "MIT",
|
||||
SPDXExpression: "MIT",
|
||||
Type: license.Declared,
|
||||
URLs: internal.NewStringSet("https://example.com"),
|
||||
URLs: []string{"https://example.com"},
|
||||
Locations: file.NewLocationSet(file.NewLocation("/place")),
|
||||
},
|
||||
},
|
||||
@@ -115,14 +114,13 @@ func TestLicenseSet_Add(t *testing.T) {
|
||||
Value: "MIT",
|
||||
SPDXExpression: "MIT",
|
||||
Type: license.Concluded,
|
||||
URLs: internal.NewStringSet(),
|
||||
Locations: file.NewLocationSet(),
|
||||
},
|
||||
{
|
||||
Value: "MIT",
|
||||
SPDXExpression: "MIT",
|
||||
Type: license.Declared,
|
||||
URLs: internal.NewStringSet("https://example.com"),
|
||||
URLs: []string{"https://example.com"},
|
||||
Locations: file.NewLocationSet(file.NewLocation("/place")),
|
||||
},
|
||||
},
|
||||
@@ -133,7 +131,32 @@ func TestLicenseSet_Add(t *testing.T) {
|
||||
s := NewLicenseSet()
|
||||
s.Add(tt.licenses...)
|
||||
testMe := s.ToSlice()
|
||||
assert.Equal(t, tt.want, testMe)
|
||||
if d := cmp.Diff(tt.want, testMe, cmp.Comparer(defaultLicenseComparer)); d != "" {
|
||||
t.Errorf("unexpected license set (-want +got):\n%s", d)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func defaultLocationComparer(x, y file.Location) bool {
|
||||
return cmp.Equal(x.Coordinates, y.Coordinates) && cmp.Equal(x.VirtualPath, y.VirtualPath)
|
||||
}
|
||||
|
||||
func defaultLicenseComparer(x, y License) bool {
|
||||
return cmp.Equal(x, y, cmp.Comparer(defaultLocationComparer), cmp.Comparer(
|
||||
func(x, y file.LocationSet) bool {
|
||||
xs := x.ToSlice()
|
||||
ys := y.ToSlice()
|
||||
if len(xs) != len(ys) {
|
||||
return false
|
||||
}
|
||||
for i, xe := range xs {
|
||||
ye := ys[i]
|
||||
if !defaultLocationComparer(xe, ye) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
+134
-5
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/license"
|
||||
)
|
||||
|
||||
func Test_Hash(t *testing.T) {
|
||||
@@ -18,11 +19,8 @@ func Test_Hash(t *testing.T) {
|
||||
loc2 := file.NewLocation("place!")
|
||||
loc2.FileSystemID = "fs2" // important! there is a different file system ID
|
||||
|
||||
lic1 := NewLicenseFromLocations("MIT", loc1)
|
||||
lic2 := NewLicenseFromLocations("MIT", loc2)
|
||||
|
||||
lic1.URLs.Add("foo")
|
||||
lic2.URLs.Add("bar") // we also want to check the URLs are ignored
|
||||
lic1 := NewLicenseFromFields("MIT", "foo", &loc1)
|
||||
lic2 := NewLicenseFromFields("MIT", "bar", &loc2)
|
||||
|
||||
hash1, err := artifact.IDByHash(lic1)
|
||||
require.NoError(t, err)
|
||||
@@ -97,3 +95,134 @@ func Test_Sort(t *testing.T) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func TestLicense_Merge(t *testing.T) {
|
||||
locA := file.NewLocation("a")
|
||||
locB := file.NewLocation("b")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
subject License
|
||||
other License
|
||||
want License
|
||||
wantErr require.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "valid merge",
|
||||
subject: License{
|
||||
Value: "MIT",
|
||||
SPDXExpression: "MIT",
|
||||
Type: license.Declared,
|
||||
URLs: []string{
|
||||
"b", "a",
|
||||
},
|
||||
Locations: file.NewLocationSet(locA),
|
||||
},
|
||||
other: License{
|
||||
Value: "MIT",
|
||||
SPDXExpression: "MIT",
|
||||
Type: license.Declared,
|
||||
URLs: []string{
|
||||
"c", "d",
|
||||
},
|
||||
Locations: file.NewLocationSet(locB),
|
||||
},
|
||||
want: License{
|
||||
Value: "MIT",
|
||||
SPDXExpression: "MIT",
|
||||
Type: license.Declared,
|
||||
URLs: []string{
|
||||
"a", "b", "c", "d",
|
||||
},
|
||||
Locations: file.NewLocationSet(locA, locB),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "mismatched value",
|
||||
subject: License{
|
||||
Value: "DIFFERENT!!",
|
||||
SPDXExpression: "MIT",
|
||||
Type: license.Declared,
|
||||
URLs: []string{
|
||||
"b", "a",
|
||||
},
|
||||
Locations: file.NewLocationSet(locA),
|
||||
},
|
||||
other: License{
|
||||
Value: "MIT",
|
||||
SPDXExpression: "MIT",
|
||||
Type: license.Declared,
|
||||
URLs: []string{
|
||||
"c", "d",
|
||||
},
|
||||
Locations: file.NewLocationSet(locB),
|
||||
},
|
||||
wantErr: require.Error,
|
||||
},
|
||||
{
|
||||
name: "mismatched spdx expression",
|
||||
subject: License{
|
||||
Value: "MIT",
|
||||
SPDXExpression: "DIFFERENT!!",
|
||||
Type: license.Declared,
|
||||
URLs: []string{
|
||||
"b", "a",
|
||||
},
|
||||
Locations: file.NewLocationSet(locA),
|
||||
},
|
||||
other: License{
|
||||
Value: "MIT",
|
||||
SPDXExpression: "MIT",
|
||||
Type: license.Declared,
|
||||
URLs: []string{
|
||||
"c", "d",
|
||||
},
|
||||
Locations: file.NewLocationSet(locB),
|
||||
},
|
||||
wantErr: require.Error,
|
||||
},
|
||||
{
|
||||
name: "mismatched type",
|
||||
subject: License{
|
||||
Value: "MIT",
|
||||
SPDXExpression: "MIT",
|
||||
Type: license.Concluded,
|
||||
URLs: []string{
|
||||
"b", "a",
|
||||
},
|
||||
Locations: file.NewLocationSet(locA),
|
||||
},
|
||||
other: License{
|
||||
Value: "MIT",
|
||||
SPDXExpression: "MIT",
|
||||
Type: license.Declared,
|
||||
URLs: []string{
|
||||
"c", "d",
|
||||
},
|
||||
Locations: file.NewLocationSet(locB),
|
||||
},
|
||||
wantErr: require.Error,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.wantErr == nil {
|
||||
tt.wantErr = require.NoError
|
||||
}
|
||||
|
||||
subjectLocationLen := len(tt.subject.Locations.ToSlice())
|
||||
subjectURLLen := len(tt.subject.URLs)
|
||||
|
||||
got, err := tt.subject.Merge(tt.other)
|
||||
tt.wantErr(t, err)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
require.NotNilf(t, got, "expected a non-nil license")
|
||||
assert.Equal(t, tt.want, *got)
|
||||
// prove we don't modify the subject
|
||||
assert.Equal(t, subjectLocationLen, len(tt.subject.Locations.ToSlice()))
|
||||
assert.Equal(t, subjectURLLen, len(tt.subject.URLs))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,47 @@
|
||||
package pkg
|
||||
|
||||
import "github.com/anchore/syft/syft/artifact"
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
)
|
||||
|
||||
func NewRelationships(catalog *Collection) []artifact.Relationship {
|
||||
rels := RelationshipsByFileOwnership(catalog)
|
||||
rels = append(rels, RelationshipsEvidentBy(catalog)...)
|
||||
return rels
|
||||
}
|
||||
|
||||
// SortRelationships takes a set of package-to-package relationships and sorts them in a stable order by name and version.
|
||||
// Note: this does not consider package-to-other, other-to-package, or other-to-other relationships.
|
||||
// TODO: ideally this should be replaced with a more type-agnostic sort function that resides in the artifact package.
|
||||
func SortRelationships(rels []artifact.Relationship) {
|
||||
sort.SliceStable(rels, func(i, j int) bool {
|
||||
return relationshipLess(rels[i], rels[j])
|
||||
})
|
||||
}
|
||||
|
||||
func relationshipLess(i, j artifact.Relationship) bool {
|
||||
iFrom, ok1 := i.From.(Package)
|
||||
iTo, ok2 := i.To.(Package)
|
||||
jFrom, ok3 := j.From.(Package)
|
||||
jTo, ok4 := j.To.(Package)
|
||||
|
||||
if !(ok1 && ok2 && ok3 && ok4) {
|
||||
return false
|
||||
}
|
||||
|
||||
if iFrom.Name != jFrom.Name {
|
||||
return iFrom.Name < jFrom.Name
|
||||
}
|
||||
if iFrom.Version != jFrom.Version {
|
||||
return iFrom.Version < jFrom.Version
|
||||
}
|
||||
if iTo.Name != jTo.Name {
|
||||
return iTo.Name < jTo.Name
|
||||
}
|
||||
if iTo.Version != jTo.Version {
|
||||
return iTo.Version < jTo.Version
|
||||
}
|
||||
return i.Type < j.Type
|
||||
}
|
||||
|
||||
@@ -247,6 +247,8 @@ var dirOnlyTestCases = []testCase{
|
||||
"Serilog.Sinks.Console": "4.0.1",
|
||||
"System.Diagnostics.DiagnosticSource": "6.0.0",
|
||||
"System.Runtime.CompilerServices.Unsafe": "6.0.0",
|
||||
"TestCommon": "1.0.0",
|
||||
"TestLibrary": "1.0.0",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -4,13 +4,11 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/scylladb/go-set/strset"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/stereoscope/pkg/imagetest"
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger"
|
||||
@@ -59,8 +57,8 @@ func BenchmarkImagePackageCatalogers(b *testing.B) {
|
||||
func TestPkgCoverageImage(t *testing.T) {
|
||||
sbom, _ := catalogFixtureImage(t, "image-pkg-coverage", source.SquashedScope, nil)
|
||||
|
||||
observedLanguages := internal.NewStringSet()
|
||||
definedLanguages := internal.NewStringSet()
|
||||
observedLanguages := strset.New()
|
||||
definedLanguages := strset.New()
|
||||
for _, l := range pkg.AllLanguages {
|
||||
definedLanguages.Add(l.String())
|
||||
}
|
||||
@@ -76,8 +74,8 @@ func TestPkgCoverageImage(t *testing.T) {
|
||||
definedLanguages.Remove(pkg.Erlang.String())
|
||||
definedLanguages.Remove(pkg.Elixir.String())
|
||||
|
||||
observedPkgs := internal.NewStringSet()
|
||||
definedPkgs := internal.NewStringSet()
|
||||
observedPkgs := strset.New()
|
||||
definedPkgs := strset.New()
|
||||
for _, p := range pkg.AllPkgs {
|
||||
definedPkgs.Add(string(p))
|
||||
}
|
||||
@@ -148,29 +146,37 @@ func TestPkgCoverageImage(t *testing.T) {
|
||||
observedPkgs.Remove(string(pkg.UnknownPkg))
|
||||
definedPkgs.Remove(string(pkg.UnknownPkg))
|
||||
|
||||
missingLang := strset.Difference(definedLanguages, observedLanguages)
|
||||
extraLang := strset.Difference(observedLanguages, definedLanguages)
|
||||
|
||||
// ensure that integration test cases stay in sync with the available catalogers
|
||||
if diff := cmp.Diff(definedLanguages, observedLanguages); diff != "" {
|
||||
t.Errorf("language coverage incomplete (languages=%d, coverage=%d)", len(definedLanguages), len(observedLanguages))
|
||||
t.Errorf("definedLanguages mismatch observedLanguages (-want +got):\n%s", diff)
|
||||
if missingLang.Size() > 0 || extraLang.Size() > 0 {
|
||||
t.Errorf("language coverage incomplete (languages=%d, coverage=%d)", definedLanguages.Size(), observedLanguages.Size())
|
||||
t.Errorf("unexpected languages: %s", extraLang.List())
|
||||
t.Errorf("missing languages: %s", missingLang.List())
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(definedPkgs, observedPkgs); diff != "" {
|
||||
t.Errorf("package coverage incomplete (packages=%d, coverage=%d)", len(definedPkgs), len(observedPkgs))
|
||||
t.Errorf("definedPkgs mismatch observedPkgs (-want +got):\n%s", diff)
|
||||
missingPkgs := strset.Difference(definedPkgs, observedPkgs)
|
||||
extraPkgs := strset.Difference(observedPkgs, definedPkgs)
|
||||
|
||||
if missingPkgs.Size() > 0 || extraPkgs.Size() > 0 {
|
||||
t.Errorf("package coverage incomplete (packages=%d, coverage=%d)", definedPkgs.Size(), observedPkgs.Size())
|
||||
t.Errorf("unexpected packages: %s", extraPkgs.List())
|
||||
t.Errorf("missing packages: %s", missingPkgs.List())
|
||||
}
|
||||
}
|
||||
|
||||
func TestPkgCoverageDirectory(t *testing.T) {
|
||||
sbom, _ := catalogDirectory(t, "test-fixtures/image-pkg-coverage")
|
||||
|
||||
observedLanguages := internal.NewStringSet()
|
||||
definedLanguages := internal.NewStringSet()
|
||||
observedLanguages := strset.New()
|
||||
definedLanguages := strset.New()
|
||||
for _, l := range pkg.AllLanguages {
|
||||
definedLanguages.Add(l.String())
|
||||
}
|
||||
|
||||
observedPkgs := internal.NewStringSet()
|
||||
definedPkgs := internal.NewStringSet()
|
||||
observedPkgs := strset.New()
|
||||
definedPkgs := strset.New()
|
||||
for _, p := range pkg.AllPkgs {
|
||||
definedPkgs.Add(string(p))
|
||||
}
|
||||
@@ -237,12 +243,12 @@ func TestPkgCoverageDirectory(t *testing.T) {
|
||||
definedPkgs.Remove(string(pkg.KbPkg))
|
||||
|
||||
// ensure that integration test commonTestCases stay in sync with the available catalogers
|
||||
if len(observedLanguages) < len(definedLanguages) {
|
||||
t.Errorf("language coverage incomplete (languages=%d, coverage=%d)", len(definedLanguages), len(observedLanguages))
|
||||
if observedLanguages.Size() < definedLanguages.Size() {
|
||||
t.Errorf("language coverage incomplete (languages=%d, coverage=%d)", definedLanguages.Size(), observedLanguages.Size())
|
||||
}
|
||||
|
||||
if len(observedPkgs) < len(definedPkgs) {
|
||||
t.Errorf("package coverage incomplete (packages=%d, coverage=%d)", len(definedPkgs), len(observedPkgs))
|
||||
if observedPkgs.Size() < definedPkgs.Size() {
|
||||
t.Errorf("package coverage incomplete (packages=%d, coverage=%d)", definedPkgs.Size(), observedPkgs.Size())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,8 +257,8 @@ func TestPkgCoverageCatalogerConfiguration(t *testing.T) {
|
||||
// for which that cataloger isn't enabled by defauly
|
||||
sbom, _ := catalogFixtureImage(t, "image-pkg-coverage", source.SquashedScope, []string{"rust"})
|
||||
|
||||
observedLanguages := internal.NewStringSet()
|
||||
definedLanguages := internal.NewStringSet()
|
||||
observedLanguages := strset.New()
|
||||
definedLanguages := strset.New()
|
||||
definedLanguages.Add("rust")
|
||||
|
||||
for actualPkg := range sbom.Artifacts.Packages.Enumerate() {
|
||||
|
||||
@@ -5,14 +5,15 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/scylladb/go-set/strset"
|
||||
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
func TestNpmPackageLockDirectory(t *testing.T) {
|
||||
sbom, _ := catalogDirectory(t, "test-fixtures/npm-lock")
|
||||
|
||||
foundPackages := internal.NewStringSet()
|
||||
foundPackages := strset.New()
|
||||
|
||||
for actualPkg := range sbom.Artifacts.Packages.Enumerate(pkg.NpmPkg) {
|
||||
for _, actualLocation := range actualPkg.Locations.ToSlice() {
|
||||
@@ -25,16 +26,16 @@ func TestNpmPackageLockDirectory(t *testing.T) {
|
||||
|
||||
// ensure that integration test commonTestCases stay in sync with the available catalogers
|
||||
const expectedPackageCount = 6
|
||||
if len(foundPackages) != expectedPackageCount {
|
||||
t.Errorf("found the wrong set of npm package-lock.json packages (expected: %d, actual: %d)", expectedPackageCount, len(foundPackages))
|
||||
if foundPackages.Size() != expectedPackageCount {
|
||||
t.Errorf("found the wrong set of npm package-lock.json packages (expected: %d, actual: %d)", expectedPackageCount, foundPackages.Size())
|
||||
}
|
||||
}
|
||||
|
||||
func TestYarnPackageLockDirectory(t *testing.T) {
|
||||
sbom, _ := catalogDirectory(t, "test-fixtures/yarn-lock")
|
||||
|
||||
foundPackages := internal.NewStringSet()
|
||||
expectedPackages := internal.NewStringSet("async@0.9.2", "async@3.2.3", "merge-objects@1.0.5", "should-type@1.3.0", "@4lolo/resize-observer-polyfill@1.5.2")
|
||||
foundPackages := strset.New()
|
||||
expectedPackages := strset.New("async@0.9.2", "async@3.2.3", "merge-objects@1.0.5", "should-type@1.3.0", "@4lolo/resize-observer-polyfill@1.5.2")
|
||||
|
||||
for actualPkg := range sbom.Artifacts.Packages.Enumerate(pkg.NpmPkg) {
|
||||
for _, actualLocation := range actualPkg.Locations.ToSlice() {
|
||||
@@ -46,9 +47,9 @@ func TestYarnPackageLockDirectory(t *testing.T) {
|
||||
}
|
||||
|
||||
// ensure that integration test commonTestCases stay in sync with the available catalogers
|
||||
if len(foundPackages) != len(expectedPackages) {
|
||||
t.Errorf("found the wrong set of yarn.lock packages (expected: %d, actual: %d)", len(expectedPackages), len(foundPackages))
|
||||
if foundPackages.Size() != expectedPackages.Size() {
|
||||
t.Errorf("found the wrong set of yarn.lock packages (expected: %d, actual: %d)", expectedPackages.Size(), foundPackages.Size())
|
||||
} else if !reflect.DeepEqual(foundPackages, expectedPackages) {
|
||||
t.Errorf("found the wrong set of yarn.lock packages (expected: %+q, actual: %+q)", expectedPackages.ToSlice(), foundPackages.ToSlice())
|
||||
t.Errorf("found the wrong set of yarn.lock packages (expected: %+q, actual: %+q)", expectedPackages.List(), foundPackages.List())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user