Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

yarn: SBOM components #739

Merged
3 changes: 3 additions & 0 deletions cachi2/core/checksum.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ def to_sri(self) -> str:
base64_sha = base64.b64encode(bytes_sha).decode("utf-8")
return f"{self.algorithm}-{base64_sha}"

def __str__(self) -> str:
return f"{self.algorithm}:{self.hexdigest}"

@classmethod
def from_sri(cls, sri: str) -> "ChecksumInfo":
"""Convert the input Subresource Integrity value to ChecksumInfo."""
Expand Down
3 changes: 1 addition & 2 deletions cachi2/core/package_managers/npm.py
Original file line number Diff line number Diff line change
Expand Up @@ -358,8 +358,7 @@ def get_purl(
else: # dep_type == "https"
qualifiers = {"download_url": resolved_url}
if integrity:
algorithm, digest = ChecksumInfo.from_sri(integrity)
qualifiers["checksum"] = f"{algorithm}:{digest}"
qualifiers["checksum"] = str(ChecksumInfo.from_sri(integrity))

return PackageURL(
type="npm",
Expand Down
75 changes: 31 additions & 44 deletions cachi2/core/package_managers/yarn_classic/main.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import logging
import re
from collections import Counter
from pathlib import Path
from typing import Any, Iterable
from urllib.parse import urlparse

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 All @@ -21,6 +20,10 @@
YarnClassicPackage,
resolve_packages,
)
from cachi2.core.package_managers.yarn_classic.utils import (
get_git_tarball_mirror_name,
get_tarball_mirror_name,
)
from cachi2.core.rooted_path import RootedPath

log = logging.getLogger(__name__)
Expand Down Expand Up @@ -59,24 +62,45 @@ def _ensure_mirror_dir_exists(output_dir: RootedPath) -> None:
for package in request.yarn_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)
packages = resolve_packages(project)
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 Expand Up @@ -182,10 +206,10 @@ def _verify_no_offline_mirror_collisions(packages: Iterable[YarnClassicPackage])
tarballs = []
for p in packages:
if isinstance(p, (RegistryPackage, UrlPackage)):
tarball_name = _get_tarball_mirror_name(p.url)
tarball_name = get_tarball_mirror_name(p.url)
tarballs.append(tarball_name)
elif isinstance(p, GitPackage):
tarball_name = _get_git_tarball_mirror_name(p.url)
tarball_name = get_git_tarball_mirror_name(p.url)
tarballs.append(tarball_name)
else:
# file, link, and workspace packages are not copied to the offline mirror
Expand All @@ -197,42 +221,5 @@ def _verify_no_offline_mirror_collisions(packages: Iterable[YarnClassicPackage])
raise PackageManagerError(f"Duplicate tarballs detected: {', '.join(duplicate_tarballs)}")


# https://github.com/yarnpkg/yarn/blob/7cafa512a777048ce0b666080a24e80aae3d66a9/src/fetchers/tarball-fetcher.js#L21
RE_URL_NAME_MATCH = r"/(?:(@[^/]+)(?:\/|%2f))?[^/]+/(?:-|_attachments)/(?:@[^/]+\/)?([^/]+)$"


# https://github.com/yarnpkg/yarn/blob/7cafa512a777048ce0b666080a24e80aae3d66a9/src/fetchers/tarball-fetcher.js#L65
def _get_tarball_mirror_name(url: str) -> str:
parsed_url = urlparse(url)
path = Path(parsed_url.path)

match = re.search(RE_URL_NAME_MATCH, str(path))

if match is not None:
scope, tarball_basename = match.groups()
package_filename = f"{scope}-{tarball_basename}" if scope else tarball_basename
else:
package_filename = path.name

return package_filename


# https://github.com/yarnpkg/yarn/blob/7cafa512a777048ce0b666080a24e80aae3d66a9/src/fetchers/git-fetcher.js#L40
def _get_git_tarball_mirror_name(url: str) -> str:
parsed_url = urlparse(url)
path = Path(parsed_url.path)

package_filename = path.name
hash = parsed_url.fragment

if hash:
package_filename = f"{package_filename}-{hash}"

if package_filename.startswith(":"):
package_filename = package_filename[1:]

return package_filename


# References
# [yarn_classic_trait]: https://github.com/yarnpkg/berry/blob/13d5b3041794c33171808fdce635461ff4ab5c4e/packages/yarnpkg-core/sources/Project.ts#L434
Loading
Loading