Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
2 changes: 2 additions & 0 deletions scanpipe/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,8 @@ def results_download(self, request, *args, **kwargs):
output_file = output.to_cyclonedx(project, **output_kwargs)
elif format == "attribution":
output_file = output.to_attribution(project)
elif format == "ort":
output_file = output.to_ort(project)
else:
message = {"status": f"Format {format} not supported."}
return Response(message, status=status.HTTP_400_BAD_REQUEST)
Expand Down
3 changes: 2 additions & 1 deletion scanpipe/management/commands/output.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from scanpipe.management.commands import ProjectCommand
from scanpipe.pipes import output

SUPPORTED_FORMATS = ["json", "csv", "xlsx", "attribution", "spdx", "cyclonedx"]
SUPPORTED_FORMATS = ["json", "csv", "xlsx", "attribution", "spdx", "cyclonedx", "ort"]


class Command(ProjectCommand):
Expand Down Expand Up @@ -84,6 +84,7 @@ def handle_output(self, output_format):
"spdx": output.to_spdx,
"cyclonedx": output.to_cyclonedx,
"attribution": output.to_attribution,
"ort": output.to_ort,
}.get(output_format)

if not output_function:
Expand Down
91 changes: 91 additions & 0 deletions scanpipe/pipes/ort.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# SPDX-License-Identifier: Apache-2.0
#
# http://nexb.com and https://github.com/aboutcode-org/scancode.io
# The ScanCode.io software is licensed under the Apache License version 2.0.
# Data generated with ScanCode.io is provided as-is without warranties.
# ScanCode is a trademark of nexB Inc.
#
# You may not use this software except in compliance with the License.
# You may obtain a copy of the License at: http://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.
#
# Data Generated with ScanCode.io is provided on an "AS IS" BASIS, WITHOUT WARRANTIES
# OR CONDITIONS OF ANY KIND, either express or implied. No content created from
# ScanCode.io should be considered or used as legal advice. Consult an Attorney
# for any legal advice.
#
# ScanCode.io is a free software code scanning tool from nexB Inc. and others.
# Visit https://github.com/aboutcode-org/scancode.io for support and download.


from dataclasses import asdict
from dataclasses import dataclass
from dataclasses import field
from pathlib import Path

import yaml


@dataclass
class SourceArtifact:
url: str


@dataclass
class Dependency:
id: str
purl: str
sourceArtifact: SourceArtifact
declaredLicenses: list = field(default_factory=list)


@dataclass
class Project:
projectName: str
dependencies: list[Dependency] = field(default_factory=list)

@classmethod
def from_yaml(cls, yaml_str: str):
"""Create a Project object from a YAML string."""
data = yaml.safe_load(yaml_str)
deps = [
Dependency(
id=dependency["id"],
purl=dependency["purl"],
sourceArtifact=SourceArtifact(**dependency["sourceArtifact"]),
declaredLicenses=dependency.get("declaredLicenses", []),
)
for dependency in data.get("dependencies", [])
]
return cls(projectName=data["projectName"], dependencies=deps)

@classmethod
def from_file(cls, filepath: str | Path):
"""Create a Project object by loading a YAML file."""
return cls.from_yaml(Path(filepath).read_text(encoding="utf-8"))

def to_yaml(self) -> str:
"""Dump the Project object back to a YAML string."""
return yaml.safe_dump(asdict(self), sort_keys=False, allow_unicode=True)


def to_ort(project):
dependencies = []
for package in project.discoveredpackages.all():
dependency = Dependency(
id=f"{package.type}::{package.name}:{package.version}",
purl=package.purl,
sourceArtifact=SourceArtifact(url=package.download_url),
declaredLicenses=[package.get_declared_license_expression_spdx()],
)
dependencies.append(dependency)

project = Project(
projectName=project.name,
dependencies=dependencies,
)

return project.to_yaml()
14 changes: 14 additions & 0 deletions scanpipe/pipes/output.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
from scanpipe.models import ProjectMessage
from scanpipe.pipes import docker
from scanpipe.pipes import flag
from scanpipe.pipes import ort
from scanpipe.pipes import spdx

scanpipe_app = apps.get_app_config("scanpipe")
Expand Down Expand Up @@ -1058,10 +1059,23 @@ def to_attribution(project):
return output_file


def to_ort(project):
"""
Generate a ORT compatible "package-list.yml" output.
The output file is created in the ``project`` "output/" directory.
Return the path of the generated output file.
"""
output_file = project.get_output_file_path("results", "package-list.yml")
ort_yml = ort.to_ort(project)
output_file.write_text(ort_yml)
return output_file


FORMAT_TO_FUNCTION_MAPPING = {
"json": to_json,
"xlsx": to_xlsx,
"spdx": to_spdx,
"cyclonedx": to_cyclonedx,
"attribution": to_attribution,
"ort": to_ort,
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@
<a href="{% url 'project_results' project.slug 'attribution' %}" class="dropdown-item">
<strong>Attribution</strong>
</a>
<a href="{% url 'project_results' project.slug 'ort' %}" class="dropdown-item">
<strong>ORT (package-list)</strong>
</a>
</div>
</div>
</div>
3 changes: 3 additions & 0 deletions scanpipe/templates/scanpipe/includes/project_downloads.html
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,8 @@
<a class="tag is-success is-medium" href="{% url 'project_results' project.slug 'attribution' %}">
<span class="icon mr-1"><i class="fa-solid fa-download"></i></span>Attribution
</a>
<a class="tag is-success is-medium" href="{% url 'project_results' project.slug 'ort' %}">
<span class="icon mr-1"><i class="fa-solid fa-download"></i></span>ORT
</a>
</div>
</article>
2 changes: 2 additions & 0 deletions scanpipe/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -1630,6 +1630,8 @@ def get(self, request, *args, **kwargs):
output_file = output.to_cyclonedx(project, **output_kwargs)
elif format == "attribution":
output_file = output.to_attribution(project)
elif format == "ort":
output_file = output.to_ort(project)
else:
raise Http404("Format not supported.")

Expand Down