Skip to content

Commit

Permalink
yarn-v1: Create SBOM components from packages
Browse files Browse the repository at this point in the history
After a successful pre-fetching of all packages, report all downloaded
packages as components in the final SBOM.

Create the `Component` object from each package based on package
attributes.

Dev packages should have `cdx:npm:package:development` property, that is
added to the component if package is marked for development -> `dev`
attribute is set to True.

Move the rest of the unit test logic to `test_fetch_yarn_source` from
its predecessor in yarn-berry implementation.

closes containerbuildsystem#636

Signed-off-by: Michal Šoltis <[email protected]>
  • Loading branch information
slimreaper35 committed Dec 9, 2024
1 parent 6039292 commit b44d0a7
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 13 deletions.
26 changes: 24 additions & 2 deletions cachi2/core/package_managers/yarn_classic/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from cachi2.core.errors import PackageManagerError, PackageRejected
from cachi2.core.models.input import Request
from cachi2.core.models.output import Component, EnvironmentVariable, RequestOutput
from cachi2.core.models.property_semantics import PropertySet
from cachi2.core.package_managers.yarn.utils import (
VersionsRange,
extract_yarn_version_from_env,
Expand Down Expand Up @@ -40,14 +41,16 @@ def _ensure_mirror_dir_exists(output_dir: RootedPath) -> None:
for package in request.yarn_classic_packages:
package_path = request.source_dir.join_within_root(package.path)
_ensure_mirror_dir_exists(request.output_dir)
_resolve_yarn_project(Project.from_source_dir(package_path), request.output_dir)
components.extend(
_resolve_yarn_project(Project.from_source_dir(package_path), request.output_dir)
)

return RequestOutput.from_obj_list(
components, _generate_build_environment_variables(), project_files=[]
)


def _resolve_yarn_project(project: Project, output_dir: RootedPath) -> None:
def _resolve_yarn_project(project: Project, output_dir: RootedPath) -> list[Component]:
"""Process a request for a single yarn source directory."""
log.info(f"Fetching the yarn dependencies at the subpath {project.source_dir}")

Expand All @@ -58,6 +61,25 @@ def _resolve_yarn_project(project: Project, output_dir: RootedPath) -> None:
packages = resolve_packages(project, output_dir.join_within_root(MIRROR_DIR))
_verify_no_offline_mirror_collisions(packages)

return _create_sbom_components(packages)


def _create_sbom_components(packages: Iterable[YarnClassicPackage]) -> list[Component]:
"""Create SBOM components from the given yarn packages."""
result = []
for package in packages:
properties = PropertySet(npm_development=package.dev).to_properties()
result.append(
Component(
name=package.name,
purl=package.purl,
version=package.version,
properties=properties,
)
)

return result


def _fetch_dependencies(source_dir: RootedPath, env: dict[str, str]) -> None:
"""Fetch dependencies using 'yarn install'.
Expand Down
54 changes: 43 additions & 11 deletions tests/unit/package_managers/yarn_classic/test_main.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import itertools
import json
from pathlib import Path
from typing import Any, Iterable
Expand Down Expand Up @@ -54,19 +55,48 @@ def test_generate_build_environment_variables(


@pytest.mark.parametrize(
"input_request, components",
[
"input_request, package_components",
(
pytest.param(
[{"type": "yarn-classic", "path": "."}],
[],
[
[
Component(
name="foo",
purl="pkg:npm/[email protected]",
version="1.0.0",
),
Component(name="bar", purl="pkg:npm/[email protected]", version="2.0.0"),
],
],
id="single_input_package",
),
pytest.param(
[{"type": "yarn-classic", "path": "."}, {"type": "yarn-classic", "path": "./path"}],
[],
[
[
Component(
name="foo",
purl="pkg:npm/[email protected]",
version="1.0.0",
),
],
[
Component(
name="bar",
purl="pkg:npm/[email protected]",
version="2.0.0",
),
Component(
name="baz",
purl="pkg:npm/[email protected]",
version="3.0.0",
),
],
],
id="multiple_input_packages",
),
],
),
indirect=["input_request"],
)
@mock.patch("cachi2.core.package_managers.yarn_classic.main._resolve_yarn_project")
Expand All @@ -75,26 +105,28 @@ def test_fetch_yarn_source(
mock_create_project: mock.Mock,
mock_resolve_yarn: mock.Mock,
input_request: Request,
package_components: list[Component],
yarn_classic_env_variables: list[EnvironmentVariable],
components: list[Component],
) -> None:
expected_output = RequestOutput(
components=components,
build_config=BuildConfig(environment_variables=yarn_classic_env_variables),
)
package_dirs = [
input_request.source_dir.join_within_root(p.path) for p in input_request.packages
]
projects = [_prepare_project(path, {}) for path in package_dirs]

mock_create_project.side_effect = projects
mock_resolve_yarn.side_effect = package_components

output = fetch_yarn_source(input_request)

mock_create_project.assert_has_calls([mock.call(path) for path in package_dirs])
mock_resolve_yarn.assert_has_calls([mock.call(p, input_request.output_dir) for p in projects])

assert input_request.output_dir.join_within_root("deps/yarn-classic").path.exists()
expected_output = RequestOutput(
components=list(itertools.chain.from_iterable(package_components)),
build_config=BuildConfig(environment_variables=yarn_classic_env_variables),
)
assert output == expected_output
assert input_request.output_dir.join_within_root(MIRROR_DIR).path.exists()


@mock.patch("cachi2.core.package_managers.yarn_classic.main.resolve_packages")
Expand Down

0 comments on commit b44d0a7

Please sign in to comment.