mirror of
https://github.com/anchore/syft.git
synced 2026-10-30 05:52:25 +01:00
Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8f6bdde666 | ||
|
|
ef43294d0e | ||
|
|
e1ad340c2d | ||
|
|
07f13049da | ||
|
|
6c7900f5b8 | ||
|
|
7018573bf7 | ||
|
|
f3ad8cf250 | ||
|
|
31f1d7dbf0 | ||
|
|
144ed725a7 | ||
|
|
dcec2bc352 |
@@ -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@d90b8d79de6dc1f58e83a1499aa58d6c93dc28de #v2.22.2
|
||||
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@d90b8d79de6dc1f58e83a1499aa58d6c93dc28de #v2.22.2
|
||||
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@d90b8d79de6dc1f58e83a1499aa58d6c93dc28de #v2.22.2
|
||||
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 }}
|
||||
@@ -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
|
||||
@@ -92,32 +92,11 @@ jobs:
|
||||
run: make integration
|
||||
|
||||
|
||||
Cli-Linux:
|
||||
# Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline
|
||||
name: "CLI tests (Linux)"
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 #v4.1.0
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
|
||||
- name: Restore CLI test-fixture cache
|
||||
uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 #v3.3.2
|
||||
with:
|
||||
path: ${{ github.workspace }}/test/cli/test-fixtures/cache
|
||||
key: ${{ runner.os }}-cli-test-cache-${{ hashFiles('test/cli/test-fixtures/cache.fingerprint') }}
|
||||
|
||||
- name: Run CLI Tests (Linux)
|
||||
run: make cli
|
||||
|
||||
|
||||
|
||||
Build-Snapshot-Artifacts:
|
||||
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
|
||||
@@ -148,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
|
||||
@@ -184,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
|
||||
@@ -204,3 +183,30 @@ jobs:
|
||||
|
||||
- name: Run install.sh tests (Mac)
|
||||
run: make install-test-ci-mac
|
||||
|
||||
|
||||
Cli-Linux:
|
||||
# Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline
|
||||
name: "CLI tests (Linux)"
|
||||
needs: [Build-Snapshot-Artifacts]
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
|
||||
- name: Restore CLI test-fixture cache
|
||||
uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 #v3.3.2
|
||||
with:
|
||||
path: ${{ github.workspace }}/test/cli/test-fixtures/cache
|
||||
key: ${{ runner.os }}-cli-test-cache-${{ hashFiles('test/cli/test-fixtures/cache.fingerprint') }}
|
||||
|
||||
- name: Download snapshot build
|
||||
uses: actions/cache/restore@704facf57e6136b1bc63b828d79edcd491f0ee84 #v3.3.2
|
||||
with:
|
||||
path: snapshot
|
||||
key: snapshot-build-${{ github.run_id }}
|
||||
|
||||
- name: Run CLI Tests (Linux)
|
||||
run: make cli
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -41,6 +41,7 @@ DIST_DIR := ./dist
|
||||
SNAPSHOT_DIR := ./snapshot
|
||||
CHANGELOG := CHANGELOG.md
|
||||
OS := $(shell uname | tr '[:upper:]' '[:lower:]')
|
||||
SNAPSHOT_BIN := $(realpath $(shell pwd)/$(SNAPSHOT_DIR)/$(OS)-build_$(OS)_amd64_v1/$(BIN))
|
||||
|
||||
ifndef VERSION
|
||||
$(error VERSION is not set)
|
||||
@@ -69,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 #################################
|
||||
@@ -160,8 +161,15 @@ validate-cyclonedx-schema:
|
||||
cd schema/cyclonedx && make
|
||||
|
||||
.PHONY: cli
|
||||
cli: ## Run CLI tests
|
||||
go test -count=1 -timeout=15m -v ./test/cli
|
||||
cli: $(SNAPSHOT_DIR) ## Run CLI tests
|
||||
chmod 755 "$(SNAPSHOT_BIN)"
|
||||
$(SNAPSHOT_BIN) version
|
||||
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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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...)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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(),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -257,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
|
||||
@@ -276,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
|
||||
@@ -332,7 +339,7 @@ func getDigestsFromArchive(archivePath string) ([]file.Digest, error) {
|
||||
|
||||
func (j *archiveParser) getLicenseFromFileInArchive() ([]pkg.License, error) {
|
||||
var fileLicenses []pkg.License
|
||||
for _, filename := range licenses.FileNames {
|
||||
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
|
||||
|
||||
@@ -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) {
|
||||
@@ -163,7 +126,6 @@ func TestParseJar(t *testing.T) {
|
||||
Value: "Apache-2.0",
|
||||
SPDXExpression: "Apache-2.0",
|
||||
Type: license.Concluded,
|
||||
URLs: internal.NewStringSet(),
|
||||
Locations: file.NewLocationSet(file.NewLocation("test-fixtures/java-builds/packages/example-java-app-gradle-0.1.0.jar")),
|
||||
},
|
||||
),
|
||||
@@ -237,7 +199,6 @@ func TestParseJar(t *testing.T) {
|
||||
Value: "Apache-2.0",
|
||||
SPDXExpression: "Apache-2.0",
|
||||
Type: license.Concluded,
|
||||
URLs: internal.NewStringSet(),
|
||||
Locations: file.NewLocationSet(file.NewLocation("test-fixtures/java-builds/packages/example-java-app-maven-0.1.0.jar")),
|
||||
},
|
||||
),
|
||||
@@ -594,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 {
|
||||
@@ -607,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 {
|
||||
@@ -748,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")),
|
||||
},
|
||||
},
|
||||
@@ -785,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")),
|
||||
},
|
||||
),
|
||||
@@ -1022,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 @@
|
||||
{}
|
||||
+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))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,9 +12,6 @@ func TestDirectoryScanCompletesWithinTimeout(t *testing.T) {
|
||||
// we want to pull the image ahead of the test as to not affect the timeout value
|
||||
pullDockerImage(t, image)
|
||||
|
||||
// run once in case we need to rebuild syft binary, so it doesn't affect the timeout value
|
||||
runSyftInDocker(t, nil, image, "--help")
|
||||
|
||||
var cmd *exec.Cmd
|
||||
var stdout, stderr string
|
||||
done := make(chan struct{})
|
||||
|
||||
+104
-107
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
@@ -12,11 +13,8 @@ import (
|
||||
"strings"
|
||||
"syscall"
|
||||
"testing"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/anchore/stereoscope/pkg/imagetest"
|
||||
)
|
||||
|
||||
@@ -30,6 +28,39 @@ func logOutputOnFailure(t testing.TB, cmd *exec.Cmd, stdout, stderr string) {
|
||||
}
|
||||
}
|
||||
|
||||
func setupPKI(t *testing.T, pw string) func() {
|
||||
err := os.Setenv("COSIGN_PASSWORD", pw)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cosignPath := filepath.Join(repoRoot(t), ".tmp/cosign")
|
||||
cmd := exec.Command(cosignPath, "generate-key-pair")
|
||||
stdout, stderr, _ := runCommand(cmd, nil)
|
||||
if cmd.ProcessState.ExitCode() != 0 {
|
||||
t.Log("STDOUT", stdout)
|
||||
t.Log("STDERR", stderr)
|
||||
t.Fatalf("could not generate keypair")
|
||||
}
|
||||
|
||||
return func() {
|
||||
err := os.Unsetenv("COSIGN_PASSWORD")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = os.Remove("cosign.key")
|
||||
if err != nil {
|
||||
t.Fatalf("could not cleanup cosign.key")
|
||||
}
|
||||
|
||||
err = os.Remove("cosign.pub")
|
||||
if err != nil {
|
||||
t.Fatalf("could not cleanup cosign.key")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getFixtureImage(t testing.TB, fixtureImageName string) string {
|
||||
t.Logf("obtaining fixture image for %s", fixtureImageName)
|
||||
imagetest.GetFixtureImage(t, "docker-archive", fixtureImageName)
|
||||
@@ -47,7 +78,7 @@ func pullDockerImage(t testing.TB, image string) {
|
||||
}
|
||||
|
||||
// docker run -v $(pwd)/sbom:/sbom cyclonedx/cyclonedx-cli:latest validate --input-format json --input-version v1_4 --input-file /sbom
|
||||
func runCycloneDXInDocker(_ testing.TB, env map[string]string, image string, f *os.File, args ...string) (*exec.Cmd, string, string) {
|
||||
func runCycloneDXInDocker(t testing.TB, env map[string]string, image string, f *os.File, args ...string) (*exec.Cmd, string, string) {
|
||||
allArgs := append(
|
||||
[]string{
|
||||
"run",
|
||||
@@ -71,7 +102,7 @@ func runSyftInDocker(t testing.TB, env map[string]string, image string, args ...
|
||||
"-e",
|
||||
"SYFT_CHECK_FOR_APP_UPDATE=false",
|
||||
"-v",
|
||||
fmt.Sprintf("%s:/syft", getSyftBinaryLocationByOS(t, "linux", runtime.GOARCH)),
|
||||
fmt.Sprintf("%s:/syft", getSyftBinaryLocationByOS(t, "linux")),
|
||||
image,
|
||||
"/syft",
|
||||
},
|
||||
@@ -188,6 +219,25 @@ func runCommandObj(t testing.TB, cmd *exec.Cmd, env map[string]string, expectErr
|
||||
return stdout, stderr
|
||||
}
|
||||
|
||||
func runCosign(t testing.TB, env map[string]string, args ...string) (*exec.Cmd, string, string) {
|
||||
cmd := getCommand(t, ".tmp/cosign", args...)
|
||||
if env == nil {
|
||||
env = make(map[string]string)
|
||||
}
|
||||
|
||||
stdout, stderr, err := runCommand(cmd, env)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("error running cosign: %+v", err)
|
||||
}
|
||||
|
||||
return cmd, stdout, stderr
|
||||
}
|
||||
|
||||
func getCommand(t testing.TB, location string, args ...string) *exec.Cmd {
|
||||
return exec.Command(filepath.Join(repoRoot(t), location), args...)
|
||||
}
|
||||
|
||||
func runCommand(cmd *exec.Cmd, env map[string]string) (string, string, error) {
|
||||
if env != nil {
|
||||
cmd.Env = append(os.Environ(), envMapToSlice(env)...)
|
||||
@@ -217,118 +267,28 @@ func getSyftCommand(t testing.TB, args ...string) *exec.Cmd {
|
||||
}
|
||||
|
||||
func getSyftBinaryLocation(t testing.TB) string {
|
||||
return getSyftBinaryLocationByOS(t, runtime.GOOS, runtime.GOARCH)
|
||||
if os.Getenv("SYFT_BINARY_LOCATION") != "" {
|
||||
// SYFT_BINARY_LOCATION is the absolute path to the snapshot binary
|
||||
return os.Getenv("SYFT_BINARY_LOCATION")
|
||||
}
|
||||
return getSyftBinaryLocationByOS(t, runtime.GOOS)
|
||||
}
|
||||
|
||||
func getSyftBinaryLocationByOS(t testing.TB, goOS, goArch string) string {
|
||||
func getSyftBinaryLocationByOS(t testing.TB, goOS string) string {
|
||||
// note: for amd64 we need to update the snapshot location with the v1 suffix
|
||||
// see : https://goreleaser.com/customization/build/#why-is-there-a-_v1-suffix-on-amd64-builds
|
||||
archPath := goArch
|
||||
if goArch == "amd64" {
|
||||
archPath := runtime.GOARCH
|
||||
if runtime.GOARCH == "amd64" {
|
||||
archPath = fmt.Sprintf("%s_v1", archPath)
|
||||
}
|
||||
|
||||
bin := ""
|
||||
// note: there is a subtle - vs _ difference between these versions
|
||||
switch goOS {
|
||||
case "windows", "darwin", "linux":
|
||||
bin = path.Join(repoRoot(t), fmt.Sprintf("snapshot/%s-build_%s_%s/syft", goOS, goOS, archPath))
|
||||
case "darwin", "linux":
|
||||
return path.Join(repoRoot(t), fmt.Sprintf("snapshot/%s-build_%s_%s/syft", goOS, goOS, archPath))
|
||||
default:
|
||||
t.Fatalf("unsupported OS: %s", goOS)
|
||||
return ""
|
||||
t.Fatalf("unsupported OS: %s", runtime.GOOS)
|
||||
}
|
||||
|
||||
envName := strings.ToUpper(fmt.Sprintf("SYFT_BINARY_LOCATION_%s_%s", goOS, goArch))
|
||||
if os.Getenv(envName) != bin {
|
||||
buildSyft(t, bin, goOS, goArch)
|
||||
// regardless if we have a successful build, don't attempt to keep building
|
||||
_ = os.Setenv(envName, bin)
|
||||
}
|
||||
|
||||
return bin
|
||||
}
|
||||
|
||||
func buildSyft(t testing.TB, outfile, goOS, goArch string) {
|
||||
dir := repoRoot(t)
|
||||
|
||||
start := time.Now()
|
||||
|
||||
stdout, stderr, err := buildSyftWithGo(dir, outfile, goOS, goArch)
|
||||
|
||||
took := time.Now().Sub(start).Round(time.Millisecond)
|
||||
if err == nil {
|
||||
if len(stderr) == 0 {
|
||||
t.Logf("binary is up to date: %s in %v", outfile, took)
|
||||
} else {
|
||||
t.Logf("built binary: %s in %v\naffected paths:\n%s", outfile, took, stderr)
|
||||
}
|
||||
} else {
|
||||
t.Fatalf("unable to build binary: %s -- %v\nSTDOUT:\n%s\nSTDERR:\n%s", outfile, err, stdout, stderr)
|
||||
}
|
||||
}
|
||||
|
||||
func buildSyftWithGo(dir, outfile, goOS, goArch string) (string, string, error) {
|
||||
d := yaml.NewDecoder(strings.NewReader(goreleaserYamlContents(dir)))
|
||||
type releaser struct {
|
||||
Builds []struct {
|
||||
ID string `yaml:"id"`
|
||||
LDFlags string `yaml:"ldflags"`
|
||||
} `yaml:"builds"`
|
||||
}
|
||||
r := releaser{}
|
||||
_ = d.Decode(&r)
|
||||
ldflags := ""
|
||||
for _, b := range r.Builds {
|
||||
if b.ID == "linux-build" {
|
||||
ldflags = executeTemplate(b.LDFlags, struct {
|
||||
Version string
|
||||
Commit string
|
||||
Date string
|
||||
Summary string
|
||||
}{
|
||||
Version: "SNAPSHOT", // should contain "SNAPSHOT" so update checks are skipped
|
||||
Commit: "COMMIT",
|
||||
Date: "DATE",
|
||||
Summary: "SUMMARY",
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
cmd := exec.Command("go",
|
||||
"build",
|
||||
"-v",
|
||||
"-o", outfile,
|
||||
"-trimpath",
|
||||
"-ldflags", ldflags,
|
||||
"./cmd/syft",
|
||||
)
|
||||
|
||||
cmd.Dir = dir
|
||||
stdout, stderr, err := runCommand(cmd, map[string]string{
|
||||
"CGO_ENABLED": "0",
|
||||
"GOOS": goOS,
|
||||
"GOARCH": goArch,
|
||||
})
|
||||
return stdout, stderr, err
|
||||
}
|
||||
|
||||
func goreleaserYamlContents(dir string) string {
|
||||
b, _ := os.ReadFile(path.Join(dir, ".goreleaser.yaml"))
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func executeTemplate(tpl string, data any) string {
|
||||
t, err := template.New("tpl").Parse(tpl)
|
||||
if err != nil {
|
||||
return fmt.Sprintf("ERROR: %v", err)
|
||||
}
|
||||
out := &bytes.Buffer{}
|
||||
err = t.Execute(out, data)
|
||||
if err != nil {
|
||||
return fmt.Sprintf("ERROR: %v", err)
|
||||
}
|
||||
return out.String()
|
||||
return ""
|
||||
}
|
||||
|
||||
func repoRoot(t testing.TB) string {
|
||||
@@ -343,3 +303,40 @@ func repoRoot(t testing.TB) string {
|
||||
}
|
||||
return absRepoRoot
|
||||
}
|
||||
|
||||
func testRetryIntervals(done <-chan struct{}) <-chan time.Duration {
|
||||
return exponentialBackoffDurations(250*time.Millisecond, 4*time.Second, 2, done)
|
||||
}
|
||||
|
||||
func exponentialBackoffDurations(minDuration, maxDuration time.Duration, step float64, done <-chan struct{}) <-chan time.Duration {
|
||||
sleepDurations := make(chan time.Duration)
|
||||
go func() {
|
||||
defer close(sleepDurations)
|
||||
retryLoop:
|
||||
for attempt := 0; ; attempt++ {
|
||||
duration := exponentialBackoffDuration(minDuration, maxDuration, step, attempt)
|
||||
|
||||
select {
|
||||
case sleepDurations <- duration:
|
||||
break
|
||||
case <-done:
|
||||
break retryLoop
|
||||
}
|
||||
|
||||
if duration == maxDuration {
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
return sleepDurations
|
||||
}
|
||||
|
||||
func exponentialBackoffDuration(minDuration, maxDuration time.Duration, step float64, attempt int) time.Duration {
|
||||
duration := time.Duration(float64(minDuration) * math.Pow(step, float64(attempt)))
|
||||
if duration < minDuration {
|
||||
return minDuration
|
||||
} else if duration > maxDuration {
|
||||
return maxDuration
|
||||
}
|
||||
return duration
|
||||
}
|
||||
|
||||
@@ -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