Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
dc7120f
chore: Disable bandit on tests
mmarseu Apr 11, 2024
6e5b6e1
chore: Add E704 to flake's ignore list
mmarseu Apr 8, 2024
206d9ac
tests: Stop creation of issues.json
mmarseu Apr 11, 2024
6e6ec8f
tests: Add integration tests
mmarseu Apr 11, 2024
f9d9945
tests: Fix exptected output of build-public integration test
mmarseu Apr 11, 2024
c124316
tests: Add integration test for a single amend operation
mmarseu May 23, 2024
b15857b
tests: fix crash in tests related to tool version
mmarseu May 24, 2024
fcc48c6
chore: Set package version to 0.0.0
mmarseu May 24, 2024
fd6d5cb
tests: Write validate report early
mmarseu May 24, 2024
a609fa1
tests: Add integration tests for general CLI options
mmarseu May 24, 2024
48ab024
tests: Add more integration tests
mmarseu May 24, 2024
f4bd4d2
tests: Add integration tests for merge
mmarseu May 27, 2024
b2e93c9
tests: Sort test classes alphabetically
mmarseu May 27, 2024
058d453
fix: properly catch FileNotFoundError in set with --from-file
mmarseu May 27, 2024
0b90019
fix: buffers are shared between multiple validation report handlers
mmarseu May 28, 2024
f0e1e22
tests: delete two useless tests
mmarseu May 28, 2024
b8ead80
refactor: improved error messages when input file not found
mmarseu May 28, 2024
a599c8d
refactor: improve error message for set
mmarseu May 28, 2024
db7686c
tests: Add integration tests for set
mmarseu May 28, 2024
dc8ef75
tests: Add integration tests for validate
mmarseu Jun 7, 2024
6d4e1aa
refactor: --help-operation now exits normally
mmarseu Jun 7, 2024
5c6e204
tests: add integration test for --help-operation
mmarseu Jun 7, 2024
16c9389
tests: add tests for #204
mmarseu Jun 11, 2024
801ce9a
tests: Fix expected data
mmarseu Jun 20, 2024
e6d1a2e
tests: Add tests for merge
mmarseu Jun 20, 2024
b115641
fix: regression in validate report handler
mmarseu Jul 1, 2024
3c83724
fix: set --version-range CLI option doesn't work
mmarseu Jul 4, 2024
e9108f3
tests: add integration test for set version ranges
mmarseu Jul 4, 2024
7c88219
tests: clarify reason for skipping
mmarseu Jul 4, 2024
0461ea1
tests: fix expected output of build-public
mmarseu Jul 10, 2024
5ddf60d
tests: Update build-public CLI
mmarseu Jul 10, 2024
3f9e124
tests: Fix integration test code
mmarseu Jul 10, 2024
37b4a8f
Add tests for all built-in schema versions
mmarseu Aug 6, 2024
e9173b5
Remove unreachable code
mmarseu Aug 8, 2024
a2724fe
Add tests for set remapping feature
mmarseu Aug 8, 2024
160192e
Further improve coverage in set
mmarseu Aug 8, 2024
1ceb9c1
Fix failing tests for validate
mmarseu Aug 8, 2024
79b8483
Improve coverage for validate
mmarseu Aug 8, 2024
e887b82
Configure isort profile in pyproject.toml
mmarseu Aug 8, 2024
60e3cd8
Add missing license headers
mmarseu Aug 8, 2024
cd2b1d9
Fix grammar error
mmarseu Aug 8, 2024
4303cee
Remove unnecessary debug message
mmarseu Aug 8, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions .flake8
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
max_line_length = 99

ignore =
# Bare except
E722
# Whitespace before : (conflicting with black, see https://black.readthedocs.io/en/stable/guides/using_black_with_other_tools.html)
E203
# Multiple statements on one line (def) : (conflicting with black, see https://black.readthedocs.io/en/stable/guides/using_black_with_other_tools.html)
Comment thread
mmarseu marked this conversation as resolved.
E704
# Bare except
E722
# Line break occurred before a binary operator (conflicting with black)
W503
# line break after binary operator (conflicting with black)
W504
W504
4 changes: 2 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,13 @@ jobs:
- name: Run black
run: poetry run black cdxev tests --check
- name: Run isort
run: poetry run isort cdxev/ tests/ --check-only --profile black
run: poetry run isort cdxev/ tests/ --check-only
- name: Run flake8
run: poetry run flake8 cdxev tests
- name: Run mypy
run: poetry run mypy --install-types --non-interactive --config-file=pyproject.toml
- name: Run bandit
run: poetry run bandit -r cdxev tests
run: poetry run bandit -c pyproject.toml -r cdxev

pytest:
runs-on: ubuntu-latest
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,4 @@ ENV/

# CycloneDX json files except for those in the tests directory
*.cdx.json
!tests/auxiliary/test_*/*.cdx.json
!tests/**/*.cdx.json
2 changes: 2 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,5 @@ repos:
rev: "1.7.7"
hooks:
- id: bandit
args: ["-c", "pyproject.toml"]
additional_dependencies: ["bandit[toml]"]
24 changes: 13 additions & 11 deletions cdxev/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def read_sbom(sbom_file: Path, file_type: Optional[str] = None) -> Tuple[dict, s
:raise FileTypeError: If *file_type* isn't specified and can't be guessed.
"""
if not sbom_file.is_file():
raise InputFileError("File not found.")
raise InputFileError(f"File not found: {sbom_file}")

if file_type is None:
file_type = sbom_file.suffix[1:]
Expand Down Expand Up @@ -664,7 +664,7 @@ def invoke_amend(args: argparse.Namespace) -> int:
print(long_desc)
print()

sys.exit()
return Status.OK

if not args.input:
usage_error("<input> argument missing.", args.parser)
Expand Down Expand Up @@ -815,7 +815,7 @@ def has_target() -> bool:
isinstance(target.key, cdxev.set.CoordinatesWithVersionRange)
and target.key.version_range is not None
):
updates[0]["id"]["version_range"] = target.key.version_range
updates[0]["id"]["version-range"] = target.key.version_range

else:
if has_target() or args.key is not None or args.value is not None:
Expand All @@ -825,15 +825,17 @@ def has_target() -> bool:
)

updates = []
with open(args.from_file) as from_file:
try:
try:
with open(args.from_file) as from_file:
updates = json.load(from_file)
except json.JSONDecodeError as ex:
raise InputFileError(
"Invalid JSON passed to --from-file",
None,
ex.lineno,
) from ex
except json.JSONDecodeError as ex:
raise InputFileError(
"Invalid JSON passed to --from-file",
None,
ex.lineno,
) from ex
except FileNotFoundError as ex:
raise InputFileError(f"File not found: {args.from_file}", None) from ex

sbom, _ = read_sbom(args.input)
cfg = cdxev.set.SetConfig(
Expand Down
2 changes: 1 addition & 1 deletion cdxev/error.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def __init__(
)

def __str__(self) -> str:
return f"{self.details.message}: {self.details.description}"
return str(self.details)


class InputFileError(AppError):
Expand Down
3 changes: 3 additions & 0 deletions cdxev/log.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ class LogMessage:
line_start: t.Optional[int] = None
"""The line where the error occurred in :py:attr:`file_name`."""

def __str__(self) -> str:
return f"{self.message} at [{self.module_name}]: {self.description}"


class LogMessageFormatter(logging.Formatter):
def format(self, record: logging.LogRecord) -> str: # noqa: N802
Expand Down
40 changes: 19 additions & 21 deletions cdxev/set.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,20 +192,19 @@ def _should_overwrite(
"Use the --force option to overwrite."
),
)
else: # pragma: no cover
if _prompt_for_overwrite(property, component_id):
return True

if _prompt_for_overwrite(property, component_id):
logger.debug(
f'Overwriting "{property}" on component "{component_id}" due to user choice.'
f'Not overwriting "{property}" on component "{component_id}" due to user choice.'
)
return True

logger.debug(
f'Not overwriting "{property}" on component "{component_id}" due to user choice.'
)
return False
return False


def _prompt_for_overwrite(property: str, component_id: ComponentIdentity) -> bool:
def _prompt_for_overwrite(
property: str, component_id: ComponentIdentity
) -> bool: # pragma: no cover
print(
f'The property "{property}" is already present on the component with id "{component_id}".'
)
Expand All @@ -232,8 +231,7 @@ def _update_id(
for key in old:
instance_list = map.pop(key)

if instance_list is None:
return
instance_list = t.cast(list[dict], instance_list)

for key in new:
map[key] = instance_list
Expand All @@ -247,6 +245,12 @@ def _do_update(component: dict, update: dict, ctx: Context) -> None:
remap = False

for prop in update_set:
if _should_update_id(prop):
original_id = original_id or ComponentIdentity.create(component, True)

if _should_remap(prop):
remap = True

if _should_delete(prop, component, update_set):
logger.debug(f'Deleting "{prop}" on component "{component_id}".')
del component[prop]
Expand All @@ -257,12 +261,6 @@ def _do_update(component: dict, update: dict, ctx: Context) -> None:
component[prop].append(update_set[prop])
continue

if _should_update_id(prop):
original_id = original_id or ComponentIdentity.create(component, True)

if _should_remap(prop):
remap = True

if prop not in component or _should_overwrite(
prop, component_id, ctx.config.force
):
Expand Down Expand Up @@ -349,12 +347,12 @@ def run(sbom: dict, updates: t.Sequence[dict[str, t.Any]], cfg: SetConfig) -> No

try:
_validate_update_list(updates, ctx)
except AppError:
msg = LogMessage(
except AppError as e:
raise AppError(
Comment thread
italvi marked this conversation as resolved.
"Set not performed",
f'Exception was raised while setting from file "{cfg.from_file}',
f"Invalid update record: {e.details.description}",
log_msg=e.details,
)
raise AppError(log_msg=msg)

ctx.component_map = _map_out_components(sbom)

Expand Down
9 changes: 5 additions & 4 deletions cdxev/validator/customreports.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,15 @@ def __init__(
self,
file_path: pathlib.Path,
target: t.Union[t.TextIO, pathlib.Path],
buffer: dict[str, list] = {"issues": []},
buffer: t.Optional[dict[str, list]] = None,
):
"""
Creates a new handler with the given target.

:param target: The target can be either a path to a file or a text stream object.
"""
super().__init__(logging.ERROR)
self.buffer = buffer
self.buffer = buffer if buffer is not None else {"issues": []}
self.target = target
self.file_path = file_path

Expand Down Expand Up @@ -110,15 +110,16 @@ def __init__(
self,
file_path: pathlib.Path,
target: t.Union[t.TextIO, pathlib.Path],
buffer: list = [],
buffer: t.Optional[list] = None,
):
"""
Creates a new handler with the given target.

:param target: The target can be either a path to a file or a text stream object.
"""
super().__init__(logging.ERROR)
self.buffer = buffer

self.buffer = buffer if buffer is not None else []
self.target = target
self.file_path = file_path

Expand Down
21 changes: 14 additions & 7 deletions cdxev/validator/validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,14 @@ def validate_sbom(
) -> int:
errors: list[str] = []
if (schema_path is not None) == bool(schema_type):
raise AssertionError(
raise AssertionError( # pragma: no cover
"Exactly one of schema_path or schema_type must be non-None"
)

if input_format == "json":
try:
spec_version: str = sbom["specVersion"]
except KeyError:
except (KeyError, TypeError):
raise AppError(
"Invalid SBOM",
"Failed to validate against built-in schema because 'specVersion' is missing. "
Expand All @@ -48,13 +48,16 @@ def validate_sbom(
sbom_schema = open_schema(spec_version, schema_type, schema_path)

if filename_regex is not None:
# Filename should be validated
filename_error = validate_filename(
file.name, filename_regex, sbom, schema_type
)
if filename_error:
if filename_regex == "" and schema_type == "default":
if filename_regex == "" and schema_type != "custom":
# Implicit validation against CycloneDX recommendations is only a warning
logger.warning(filename_error)
else:
# Explicit filename pattern or custom schema produces validation errors
errors.append("SBOM has the mistake: " + filename_error)

schema_spdx = Resource.from_contents(
Expand Down Expand Up @@ -175,15 +178,17 @@ def validate_sbom(
else:
errors.append(error_path + error.message)
sorted_errors = set(sorted(errors))

report_handler: t.Optional[logging.Handler] = None
if report_format == "warnings-ng":
# The following cast is safe because the caller of this function made sure that
# report_path is not None when report_format is not None.
warnings_ng_handler = WarningsNgReporter(file, t.cast(Path, report_path))
logger.addHandler(warnings_ng_handler)
report_handler = WarningsNgReporter(file, t.cast(Path, report_path))
logger.addHandler(report_handler)
elif report_format == "gitlab-code-quality":
# See comment above
gitlab_cq_handler = GitLabCQReporter(file, t.cast(Path, report_path))
logger.addHandler(gitlab_cq_handler)
report_handler = GitLabCQReporter(file, t.cast(Path, report_path))
logger.addHandler(report_handler)
if len(sorted_errors) == 0:
logger.info("SBOM is compliant to the provided specification schema")
return 0
Expand All @@ -198,4 +203,6 @@ def validate_sbom(
module_name=error_msg[0 : error_msg.find("has the mistake") - 1],
)
)
if report_handler is not None:
report_handler.close()
return 1
5 changes: 3 additions & 2 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 15 additions & 11 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
[tool.poetry]
name = "cyclonedx-editor-validator"
version = "0"
version = "0.0.0"
description = "Tool for creating, modifying and validating CycloneDX SBOMs."
authors = [
"Aleg Vilinski <aleg.vilinski@festo.com>",
"Christian Beck <christian.beck@festo.com>",
"Moritz Marseu <moritz.marseu@festo.com>"
"Moritz Marseu <moritz.marseu@festo.com>",
]
license = "GPL-3.0-or-later"
readme = "README.md"
packages = [{include = "cdxev"}]
packages = [{ include = "cdxev" }]

[tool.poetry.urls]
Documentation = 'https://festo-se.github.io/cyclonedx-editor-validator/'
Expand All @@ -23,13 +23,13 @@ cdx-ev = "cdxev.__main__:main"
[tool.poetry.dependencies]
python = "^3.9.0"
python-dateutil = "2.9.0.post0"
jsonschema = {version = "4.23.0", extras = ["format"]}
jsonschema = { version = "4.23.0", extras = ["format"] }
docstring-parser = "^0.16"
charset-normalizer = "^3.3.2"
pyicu = [
{version = "^2.13.1", platform = "darwin"},
{version = "^2.13.1", platform = "linux"}
]
{ version = "^2.13.1", platform = "darwin" },
{ version = "^2.13.1", platform = "linux" },
]
natsort = "^8.4.0"
univers = "30.12.0"

Expand All @@ -44,7 +44,7 @@ pytest = "8.3.2"
coverage = "7.6.1"
toml = "0.10.2"
typing-extensions = "4.12.2"
bandit = "1.7.9"
bandit = { version = "1.7.9", extras = ["toml"] }
isort = "5.13.2"
pre-commit = "3.8.0"

Expand All @@ -57,9 +57,7 @@ requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

[tool.semantic_release]
version_variable = [
"pyproject.toml:version"
]
version_variable = ["pyproject.toml:version"]
branch = "master"
upload_to_repository = false
upload_to_release = false
Expand All @@ -77,3 +75,9 @@ source = ["cdxev"]
omit = ["*__init__.py*"]

[tool.black]

[tool.bandit]
exclude_dirs = ["tests"]

[tool.isort]
profile = "black"
Loading