Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
4ccf2d6
fix: build public now considers different aggregates in compositions …
CBeck-96 Jun 3, 2024
7edf3e3
chore: black
CBeck-96 Jun 3, 2024
7209033
docs: updates function description
CBeck-96 Jun 10, 2024
b34f3eb
feat: first experimental designs
CBeck-96 Jun 13, 2024
4a0857a
feat: first working version of notice file creation
CBeck-96 Jun 25, 2024
9ff0051
feat: working version with plain and fancy implementation
CBeck-96 Jun 25, 2024
a31d5d7
chore: black + flake8
CBeck-96 Jun 25, 2024
227998c
refactor: removes redundant code
CBeck-96 Jun 26, 2024
75a7a14
docs: begin writing documentation
CBeck-96 Jun 26, 2024
720fc8f
tests: first draft of documentation
CBeck-96 Jun 26, 2024
b39e922
tests: changes order of commands and description blocks
CBeck-96 Jun 26, 2024
4ca582f
docs: fixes the order
CBeck-96 Jun 26, 2024
d356c66
docs: adds nimg convention for notice file
CBeck-96 Jun 26, 2024
d8b52ec
docs: fixes minor details
CBeck-96 Jun 26, 2024
97fcf8a
docs: fixes minor details
CBeck-96 Jun 26, 2024
7764c9b
docs: minor changes...
CBeck-96 Jun 26, 2024
a6cbf2c
chore: bump flake8 from 7.0.0 to 7.1.0 (#205)
dependabot[bot] Jun 17, 2024
c42a7a5
fix: takes nested components into consideration
CBeck-96 Jun 26, 2024
6305cc8
tests: includes sbom for tests
CBeck-96 Jun 26, 2024
aa20dbc
tests: adds tests for creation of notice file
CBeck-96 Jun 28, 2024
79e59eb
chore poetry lock
CBeck-96 Jun 28, 2024
5ad60ab
chore: isort+black
CBeck-96 Jun 28, 2024
fa5564d
fix: NoticeFGile to noticefile in tests -> flake8
CBeck-96 Jun 28, 2024
9b77632
chore: update sbomfucntions and build-public
CBeck-96 Jul 10, 2024
d5e2646
chore: rebase
CBeck-96 Jul 10, 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
32 changes: 31 additions & 1 deletion cdxev/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@
from cdxev import pkg
from cdxev.amend.operations import Operation
from cdxev.auxiliary.identity import Key, KeyType
from cdxev.auxiliary.output import write_sbom
from cdxev.auxiliary.output import write_notice_file, write_sbom
from cdxev.build_public_bom import build_public_bom
from cdxev.create_notice_file import create_notice_file
from cdxev.error import AppError, InputFileError
from cdxev.log import configure_logging
from cdxev.merge import merge
Expand Down Expand Up @@ -171,6 +172,7 @@ def create_parser() -> argparse.ArgumentParser:
create_validation_parser(subparsers)
create_set_parser(subparsers)
create_build_public_bom_parser(subparsers)
create_notice_file_parser(subparsers)

return parser

Expand Down Expand Up @@ -659,6 +661,26 @@ def create_build_public_bom_parser(
return parser


def create_notice_file_parser(
subparsers: argparse._SubParsersAction,
) -> argparse.ArgumentParser:
parser = subparsers.add_parser(
"create-notice-file",
help=(
"Creates a notice file with the licenses and copyright"
" statements of the components extracted from the provided SBOM."
),
)
parser.add_argument(
"input",
help="Path to a SBOM file.",
type=Path,
)
add_output_argument(parser)
parser.set_defaults(cmd_handler=invoke_create_notice_file, parser=parser)
return parser


def invoke_amend(args: argparse.Namespace) -> int:
if args.help_operation:
short_desc = args.operations_by_name[args.help_operation].short_description
Expand Down Expand Up @@ -892,5 +914,13 @@ def invoke_build_public_bom(args: argparse.Namespace) -> int:
return Status.OK


def invoke_create_notice_file(args: argparse.Namespace) -> int:
sbom, _ = read_sbom(args.input)
output = create_notice_file(sbom)
write_notice_file(output, args.output, sbom)

return Status.OK


if __name__ == "__main__": # pragma: no cover
sys.exit(main())
44 changes: 35 additions & 9 deletions cdxev/auxiliary/output.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,20 +42,27 @@ def write_sbom(
# No output file specified.
file = sys.stdout
else:
# Output has been specified but might be a file, directory or non-existent.
if destination.exists() and destination.is_dir():
filename = generate_filename(sbom)
destination = destination.joinpath(filename)
print("Writing output to: " + filename)
elif not destination.exists() and not destination.parent.exists():
# If the destination doesn't exist we should create it as a file. So first we
# make sure its parent directory exists.
destination.parent.mkdir(parents=True)
destination = create_destination_path(destination, sbom, generate_filename)
file = destination.open("w")

json.dump(sbom, file, indent=4)


def create_destination_path(
destination: Path, sbom: dict, generate_filename: t.Callable
) -> Path:
# Destination has been specified but might be a file, directory or non-existent.
if destination.exists() and destination.is_dir():
filename = generate_filename(sbom)
destination = destination.joinpath(filename)
print("Writing output to: " + filename)
elif not destination.exists() and not destination.parent.exists():
# If the destination doesn't exist we should create it as a file. So first we
# make sure its parent directory exists.
destination.parent.mkdir(parents=True)
return destination


def update_timestamp(sbom: dict) -> None:
"""Updates the SBOM timestamp to the current time."""
metadata = sbom.setdefault("metadata", {})
Expand Down Expand Up @@ -114,3 +121,22 @@ def update_version(sbom: dict) -> None:
version = sbom.setdefault("version", 0)
version += 1
sbom["version"] = version


def write_notice_file(
notice_file: str, destination: t.Optional[Path], sbom: dict
) -> None:

def create_notice_file_filename(sbom: dict) -> str:
return "notice_file_" + generate_filename(sbom) + ".txt"

file: t.TextIO
if destination is None:
# No output file specified.
file = sys.stdout
else:
destination = create_destination_path(
destination, sbom, create_notice_file_filename
)
file = destination.open("w")
file.write(notice_file)
72 changes: 72 additions & 0 deletions cdxev/create_notice_file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
from cdxev.auxiliary.sbomFunctions import extract_components


def extract_license(license: dict) -> str:
if license.get("expression", ""):
return license.get("expression", "")
elif license.get("license", {}).get("id", ""):
return license.get("license", {}).get("id", "")
else:
return license.get("license", {}).get("name", "")


def create_notice_file(sbom: dict) -> str:
product_name = sbom.get("metadata", {}).get("component", {}).get("name", "")
product_copyright = (
sbom.get("metadata", {}).get("component", {}).get("copyright", "")
)
if sbom.get("metadata", {}).get("licenses", []):
product_licenses = sbom.get("metadata", {}).get("licenses", [])
else:
product_licenses = (
sbom.get("metadata", {}).get("component", {}).get("licenses", [])
)

header = product_name + "\n"

if product_copyright:
header += product_copyright + "\n"
elif product_licenses:
for license in product_licenses:
header += extract_license(license) + ","
header.rstrip(",")

text_body = ""
components = extract_components(sbom.get("components", []))
for component in components:
component_information = component.get("name", "") + ":" + "\n"
component_license_information = ""
component_copyright_information = ""

if component.get("copyright", ""):
component_copyright_information = component.get("copyright", "")
for license in component.get("licenses", []):
if extract_license(license):
component_license_information += extract_license(license) + "\n"

if not component_copyright_information and not component_license_information:
component_information += "No license or copyright information available \n"

elif component_copyright_information and not component_license_information:
component_information += component_copyright_information + "\n"

elif not component_copyright_information and component_license_information:
component_information += component_license_information + "\n"

else:
component_information += component_copyright_information + "\n"
component_information += component_license_information + "\n"

text_body += component_information

text_body = text_body.removesuffix("\n")
notice_file = header
if text_body:
notice_file += (
"\n\n"
+ "This product includes material developed by third parties: \n"
+ "\n"
+ text_body
)

return notice_file
48 changes: 48 additions & 0 deletions docs/available_commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,54 @@ The following schema is a little more involved. It will delete any component who
"required": ["licenses"]
}

## create-notice-file

This command creates a notice file from a provided SBOM.

It extracts all copyright and license information from the SBOM and lists it below the according component.
If a component does not contain copyright or license information it will still be listed with the note "No license or copyright information available".
The resulting document is .txt file in the Format:

Product name
Copyright of the product


This product includes material developed by third parties:

Component 1: # if copyright and license(s) are provided
Copyright
License 1
License 2
...

Component 2: # if only copyright is provided
Copyright

Component 3: # if only license(s) are provided
License 1
License 2
...

Component 3: # if nether copyright nor license(s) are provided
No license or copyright information available

If the path to a folder is provided via the `--output` option, a file with the naming convention

"notice_file_`metadata.component.name`\_`metadata.component.version`\_\[`hash`, `metadata.timestamp`\].cdx.json.txt"

will be saved in the provided folder.
For example the notice file of extracted from a sbom with the `name` "Acme_Application" in `version` "9.1.1" that has the `timestamp` "20220217T101458" would be saved as "notice_file_Acme_Application_9.1.1_20220217T101458.cdx.json.txt".

cdx-ev create-notice-file bom.json -output=path_to_folder # a file "notice_file_metadata.component.name\_metadata.component.version\_\[hash, metadata.timestamp\].cdx.json.txt" is created in the provided folder

If the command is called only providing the path to a SBOM, the results are written to stdout.

cdx-ev create-notice-file bom.json # results will be written to stdout

If a file is specified via the `--output` option, the result will be written to this file.

cdx-ev create-notice-file bom.json -output=path_to_folder/notice_file.txt # results are saved in the file "notice_file.txt"

## merge

This command requires at least two input files, but can accept an arbitrary number. Inputs can either be specified directly as positional arguments on the command-line or using the `--from-folder <path>` option. Files specified as arguments are merged in the order they are given, files in the folder are merged in alphabetical order (see note below).
Expand Down
Loading