Skip to content

Commit 06015ef

Browse files
authored
Merge pull request #887 from aboutcode-org/create-federate-sbom-command
Create federate SBOM command
2 parents 7497f9a + 229ddd2 commit 06015ef

4 files changed

Lines changed: 147 additions & 7 deletions

File tree

docs/source/purldb/rest_api.rst

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,28 @@ Using cURL to reindex a package:
547547
"status": "pkg:maven/org.elasticsearch/elasticsearch@7.17.9 has been queued for reindexing"
548548
}
549549
550+
sbom
551+
^^^^^
552+
553+
Generate a CycloneDX SBOM from this package instance.
554+
555+
Using cURL to get an SBOM for a package:
556+
557+
.. code-block:: console
558+
559+
api_url="https://public.purldb.io/api/packages/0bbdcf88-ad07-4970-9272-7d5f4c82cc7b/sbom/"
560+
content_type="Content-Type: application/json"
561+
562+
curl -X GET "$api_url" -H "$content_type"
563+
564+
.. code-block:: json
565+
566+
{
567+
"$schema": "http://cyclonedx.org/schema/bom-1.6.schema.json",
568+
"bomFormat": "CycloneDX",
569+
"specVersion": "1.6",
570+
}
571+
550572
Filter by checksum
551573
~~~~~~~~~~~~~~~~~~
552574

minecode/management/commands/federate_packages.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,7 @@
1818
from minecode_pipelines import pipes
1919
from packagedb import models as packagedb_models
2020

21-
"""
22-
Utility command to find license oddities.
23-
"""
21+
2422
logger = logging.getLogger(__name__)
2523
logging.basicConfig(stream=sys.stdout)
2624
logger.setLevel(logging.INFO)
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
#
2+
# Copyright (c) nexB Inc. and others. All rights reserved.
3+
# purldb is a trademark of nexB Inc.
4+
# SPDX-License-Identifier: Apache-2.0
5+
# See http://www.apache.org/licenses/LICENSE-2.0 for the license text.
6+
# See https://github.com/aboutcode-org/purldb for support or download.
7+
# See https://aboutcode.org for more information about nexB OSS projects.
8+
#
9+
10+
import logging
11+
import sys
12+
from pathlib import Path
13+
14+
from aboutcode.federated import DataFederation
15+
from commoncode import fileutils
16+
from minecode.management import federatedcode
17+
from minecode.management.commands import VerboseCommand
18+
from minecode_pipelines import pipes
19+
from packagedb import models as packagedb_models
20+
from packagedb import sbom
21+
22+
23+
logger = logging.getLogger(__name__)
24+
logging.basicConfig(stream=sys.stdout)
25+
logger.setLevel(logging.INFO)
26+
27+
TRACE = False
28+
if TRACE:
29+
logger.setLevel(logging.DEBUG)
30+
31+
32+
PACKAGE_BATCH_SIZE = 1000
33+
34+
35+
def commit_message(commit_batch, total_commit_batch="many"):
36+
from django.conf import settings
37+
38+
author_name = settings.FEDERATEDCODE_GIT_SERVICE_NAME
39+
author_email = settings.FEDERATEDCODE_GIT_SERVICE_EMAIL
40+
tool_name = "pkg:github/aboutcode-org/purldb"
41+
42+
return f"""\
43+
Save CycloneDX SBOMs from PurlDB ({commit_batch}/{total_commit_batch})
44+
45+
Tool: {tool_name}@v{settings.PURLDB_VERSION}
46+
Reference: https://{settings.ALLOWED_HOSTS[0]}
47+
48+
Signed-off-by: {author_name} <{author_email}>
49+
"""
50+
51+
52+
class Command(VerboseCommand):
53+
help = "Save and commit CycloneDX SBOMs, generated from PackageDB package data. to FederatedCode repos."
54+
55+
def add_arguments(self, parser):
56+
parser.add_argument(
57+
"-d",
58+
"--working-directory",
59+
type=str,
60+
required=False,
61+
help="Directory where FederatedCode repos will be cloned",
62+
)
63+
64+
def handle(self, *args, **options):
65+
logger.setLevel(self.get_verbosity(**options))
66+
working_dir = options.get("working_directory")
67+
if working_dir:
68+
working_path = Path(working_dir)
69+
else:
70+
working_path = Path(fileutils.get_temp_dir())
71+
72+
# Clone data and config repo
73+
data_federation = DataFederation.from_url(
74+
name="aboutcode-data",
75+
remote_root_url="https://github.com/aboutcode-data",
76+
)
77+
data_cluster = data_federation.get_cluster("cyclonedx16_sboms")
78+
79+
# TODO: do something more efficient
80+
files_to_commit = []
81+
commit_batch = 1
82+
for i, package in enumerate(
83+
packagedb_models.Package.objects.all().iterator(chunk_size=PACKAGE_BATCH_SIZE), start=1
84+
):
85+
package_repo_name, datafile_path = data_cluster.get_datafile_repo_and_path(
86+
purl=package.purl
87+
)
88+
_, package_repo = federatedcode.get_or_create_repository(
89+
repo_name=package_repo_name,
90+
working_path=working_path,
91+
logger=logger.log,
92+
)
93+
package_sbom_data = sbom.to_cyclonedx(package)
94+
sbom_file = pipes.write_package_data_to_file(
95+
repo=package_repo,
96+
relative_api_package_metadata_datafile_path=datafile_path,
97+
package_data=package_sbom_data,
98+
)
99+
if sbom_file not in files_to_commit:
100+
files_to_commit.append(sbom_file)
101+
102+
if len(files_to_commit) == PACKAGE_BATCH_SIZE:
103+
federatedcode.commit_and_push_changes(
104+
commit_message=commit_message(commit_batch),
105+
repo=package_repo,
106+
files_to_commit=files_to_commit,
107+
logger=logger.log,
108+
)
109+
logger.log(f"Committed {i} SBOMs to {package_repo_name}")
110+
files_to_commit.clear()
111+
commit_batch += 1
112+
113+
if files_to_commit:
114+
federatedcode.commit_and_push_changes(
115+
commit_message=commit_message(commit_batch),
116+
repo=package_repo,
117+
files_to_commit=files_to_commit,
118+
logger=logger.log,
119+
)
120+
logger.log(f"Committed {i} SBOMs to {package_repo_name}")
121+
files_to_commit.clear()
122+
commit_batch += 1

packagedb/sbom.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434

3535
def get_cyclonedx_bom(package):
3636
"""
37-
Return a CycloneDX `Bom` object filled with provided `project` data.
37+
Return a CycloneDX `Bom` object filled with data from `package`.
3838
See https://cyclonedx.org/use-cases/#dependency-graph
3939
"""
4040

@@ -76,9 +76,7 @@ def sort_bom_with_schema_ordering(bom_as_dict, schema_version):
7676

7777
def to_cyclonedx(package, cyclonedx_version="1.6"):
7878
"""
79-
Generate output for the provided ``project`` in CycloneDX BOM format.
80-
The output file is created in the ``project`` "output/" directory.
81-
Return the path of the generated output file.
79+
Return a CycloneDX SBOM of `package` as a Python dictionary.
8280
"""
8381
schema_version = SchemaVersion.from_version(cyclonedx_version)
8482

0 commit comments

Comments
 (0)