Skip to content

Commit

Permalink
Create SBOM components for yarn-classic 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 Nov 22, 2024
1 parent 8bd075e commit b233210
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 15 deletions.
30 changes: 26 additions & 4 deletions cachi2/core/package_managers/yarn_classic/main.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import logging
from typing import Iterable

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,
run_yarn_cmd,
)
from cachi2.core.package_managers.yarn_classic.project import Project
from cachi2.core.package_managers.yarn_classic.resolver import resolve_packages
from cachi2.core.package_managers.yarn_classic.resolver import YarnClassicPackage, resolve_packages
from cachi2.core.rooted_path import RootedPath

log = logging.getLogger(__name__)
Expand All @@ -28,22 +30,42 @@ 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}")

_verify_repository(project)
prefetch_env = _get_prefetch_environment_variables(output_dir)
_verify_corepack_yarn_version(project.source_dir, prefetch_env)
_fetch_dependencies(project.source_dir, prefetch_env)
resolve_packages(project)
packages = resolve_packages(project)
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:
Expand Down
55 changes: 44 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
Expand All @@ -10,6 +11,7 @@
from cachi2.core.models.output import BuildConfig, EnvironmentVariable, RequestOutput
from cachi2.core.models.sbom import Component
from cachi2.core.package_managers.yarn_classic.main import (
MIRROR_DIR,
_fetch_dependencies,
_generate_build_environment_variables,
_get_prefetch_environment_variables,
Expand Down Expand Up @@ -47,19 +49,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 @@ -68,26 +99,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 b233210

Please sign in to comment.