Skip to content

Commit

Permalink
chore: add maven local artifact finding
Browse files Browse the repository at this point in the history
  • Loading branch information
tromai committed Sep 20, 2024
1 parent c215e06 commit 94f529e
Show file tree
Hide file tree
Showing 8 changed files with 324 additions and 121 deletions.
80 changes: 80 additions & 0 deletions src/macaron/artifact/local_artifact.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Copyright (c) 2024 - 2024, Oracle and/or its affiliates. All rights reserved.
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/.

"""This module declares types and utilities for handling local artifacts."""

import os
from collections.abc import Mapping

from packageurl import PackageURL

from macaron.artifact.maven import construct_maven_repository_path
from macaron.config.global_config import global_config


def get_local_artifact_repo_mapper() -> Mapping[str, str]:
"""Get A."""
local_artifact_mapper: dict[str, str] = {}

if global_config.local_maven_repo:
local_artifact_mapper["maven"] = global_config.local_maven_repo

if global_config.python_venv_path:
local_artifact_mapper["pypi"] = global_config.python_venv_path

return local_artifact_mapper


def construct_local_artifact_path_from_purl(
build_purl_type: str,
component_purl: PackageURL,
local_artifact_repo_mapper: Mapping[str, str],
) -> str | None:
"""Get B."""
local_artifact_repo = local_artifact_repo_mapper.get(build_purl_type)
if local_artifact_repo is None:
return None

artifact_path = None
match build_purl_type:
case "maven":
group = component_purl.namespace
artifact = component_purl.name
version = component_purl.version

if group is None or version is None:
return None

artifact_path = os.path.join(
local_artifact_repo,
"repository",
construct_maven_repository_path(group, artifact, version),
)
case "pypi":
# TODO: implement this.
pass
case _:
return None

return artifact_path


def get_local_artifact_paths(
purl: PackageURL,
build_tool_purl_types: list[str],
local_artifact_repo_mapper: Mapping[str, str],
) -> dict[str, str]:
"""Get C."""
result = {}

for build_purl_type in build_tool_purl_types:
local_artfiact_path = construct_local_artifact_path_from_purl(
build_purl_type=build_purl_type,
component_purl=purl,
local_artifact_repo_mapper=local_artifact_repo_mapper,
)

if local_artfiact_path and os.path.isdir(local_artfiact_path):
result[build_purl_type] = local_artfiact_path

return result
38 changes: 38 additions & 0 deletions src/macaron/artifact/maven.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,3 +140,41 @@ def create_maven_purl_from_artifact_filename(
)

return None


def construct_maven_repository_path(
group_id: str,
artifact_id: str | None = None,
version: str | None = None,
asset_name: str | None = None,
) -> str:
"""Construct a path to a folder or file on the registry, assuming Maven repository layout.
For more details regarding Maven repository layout, see the following:
- https://maven.apache.org/repository/layout.html
- https://maven.apache.org/guides/mini/guide-naming-conventions.html
Parameters
----------
group_id : str
The group id of a Maven package.
artifact_id : str
The artifact id of a Maven package.
version : str
The version of a Maven package.
asset_name : str
The asset name.
Returns
-------
str
The path to a folder or file on the registry.
"""
path = group_id.replace(".", "/")
if artifact_id:
path = "/".join([path, artifact_id])
if version:
path = "/".join([path, version])
if asset_name:
path = "/".join([path, asset_name])
return path
4 changes: 4 additions & 0 deletions src/macaron/slsa_analyzer/analyze_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ class ChecksOutputs(TypedDict):
"""The commit digest extracted from provenance, if applicable."""
provenance_verified: bool
"""True if the provenance exists and has been verified against a signed companion provenance."""
local_artifact_paths: dict[str, str]
# TODO this doc string for this variable need more informatino, to be revise later.
"""The mapping between build tool types and the directory that contains the corresponding artifacts."""


class AnalyzeContext:
Expand Down Expand Up @@ -106,6 +109,7 @@ def __init__(
provenance_repo_url=None,
provenance_commit_digest=None,
provenance_verified=False,
local_artifact_paths={},
)

@property
Expand Down
12 changes: 12 additions & 0 deletions src/macaron/slsa_analyzer/analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from sqlalchemy.orm import Session

from macaron import __version__
from macaron.artifact.local_artifact import get_local_artifact_paths, get_local_artifact_repo_mapper
from macaron.config.defaults import defaults
from macaron.config.global_config import global_config
from macaron.config.target_config import Configuration
Expand Down Expand Up @@ -471,6 +472,17 @@ def run_single(
analyze_ctx.dynamic_data["provenance_repo_url"] = provenance_repo_url
analyze_ctx.dynamic_data["provenance_commit_digest"] = provenance_commit_digest

discovered_build_toosl = (
analyze_ctx.dynamic_data["build_spec"]["tools"] + analyze_ctx.dynamic_data["build_spec"]["purl_tools"]
)
build_tools_purl_types = [build_tool.purl_type for build_tool in discovered_build_toosl]
analyze_ctx.dynamic_data["local_artifact_paths"] = get_local_artifact_paths(
# The PURL is definitely valid here.
PackageURL.from_string(analyze_ctx.component.purl),
build_tools_purl_types,
local_artifact_repo_mapper=get_local_artifact_repo_mapper(),
)

analyze_ctx.check_results = registry.scan(analyze_ctx)

return Record(
Expand Down
47 changes: 5 additions & 42 deletions src/macaron/slsa_analyzer/package_registry/jfrog_maven_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

import requests

from macaron.artifact.maven import construct_maven_repository_path
from macaron.config.defaults import defaults
from macaron.errors import ConfigurationError
from macaron.json_tools import JsonType
Expand Down Expand Up @@ -199,44 +200,6 @@ def is_detected(self, build_tool: BaseBuildTool) -> bool:
return True
return False

def construct_maven_repository_path(
self,
group_id: str,
artifact_id: str | None = None,
version: str | None = None,
asset_name: str | None = None,
) -> str:
"""Construct a path to a folder or file on the registry, assuming Maven repository layout.
For more details regarding Maven repository layout, see the following:
- https://maven.apache.org/repository/layout.html
- https://maven.apache.org/guides/mini/guide-naming-conventions.html
Parameters
----------
group_id : str
The group id of a Maven package.
artifact_id : str
The artifact id of a Maven package.
version : str
The version of a Maven package.
asset_name : str
The asset name.
Returns
-------
str
The path to a folder or file on the registry.
"""
path = group_id.replace(".", "/")
if artifact_id:
path = "/".join([path, artifact_id])
if version:
path = "/".join([path, version])
if asset_name:
path = "/".join([path, asset_name])
return path

def fetch_artifact_ids(self, group_id: str) -> list[str]:
"""Get all artifact ids under a group id.
Expand All @@ -253,7 +216,7 @@ def fetch_artifact_ids(self, group_id: str) -> list[str]:
The artifacts ids under the group.
"""
folder_info_url = self.construct_folder_info_url(
folder_path=self.construct_maven_repository_path(group_id),
folder_path=construct_maven_repository_path(group_id),
)

try:
Expand Down Expand Up @@ -442,7 +405,7 @@ def fetch_asset_names(
list[str]
The list of asset names.
"""
folder_path = self.construct_maven_repository_path(
folder_path = construct_maven_repository_path(
group_id=group_id,
artifact_id=artifact_id,
version=version,
Expand Down Expand Up @@ -617,7 +580,7 @@ def fetch_asset_metadata(
JFrogMavenAssetMetadata | None
The asset's metadata, or ``None`` if the metadata cannot be retrieved.
"""
file_path = self.construct_maven_repository_path(
file_path = construct_maven_repository_path(
group_id=group_id,
artifact_id=artifact_id,
version=version,
Expand Down Expand Up @@ -800,7 +763,7 @@ def construct_asset_url(
str
The URL to the asset, which can be use for downloading the asset.
"""
group_path = self.construct_maven_repository_path(group_id)
group_path = construct_maven_repository_path(group_id)
return urlunsplit(
SplitResult(
scheme="https",
Expand Down
108 changes: 108 additions & 0 deletions tests/artifact/test_local_artifact.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# Copyright (c) 2024 - 2024, Oracle and/or its affiliates. All rights reserved.
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/.

"""Test the local artifact utilities."""

import tempfile
from collections.abc import Mapping

import pytest
from packageurl import PackageURL

from macaron.artifact.local_artifact import construct_local_artifact_path_from_purl, get_local_artifact_paths


@pytest.mark.parametrize(
("build_purl_type", "purl_str", "local_artifact_repo_mapper", "expectation"),
[
pytest.param(
"maven",
"pkg:maven/com.google.guava/[email protected]",
{"maven": "/home/foo/.m2"},
"/home/foo/.m2/repository/com/google/guava/guava/33.2.1-jre",
id="A maven type PURL with available local maven repo",
),
pytest.param(
"maven",
"pkg:maven/com.google.guava/[email protected]",
{},
None,
id="A maven type PURL without an available local maven repo",
),
pytest.param(
"maven",
"pkg:maven/com.google.guava/[email protected]",
{"pypi": "/home/foo/.venv"},
None,
id="A maven type PURL without an available local maven repo but there is a Python venv",
),
pytest.param(
"maven",
"pkg:maven/com.google.guava/guava",
{"maven": "/home/foo/.m2"},
None,
id="A maven type PURL with missing version and an available local maven repo",
),
pytest.param(
"maven",
"pkg:maven/guava",
{"maven": "/home/foo/.m2"},
None,
id="A maven type PURL with missing groupd Id and an available local maven repo",
),
pytest.param(
"maven",
"pkg:github/oracle/macaron",
{"maven": "/home/foo/.m2"},
None,
id="A git type PURL and an available local maven repo",
),
],
)
def test_construct_local_artifact_path_from_purl(
build_purl_type: str,
purl_str: str,
local_artifact_repo_mapper: Mapping[str, str],
expectation: str,
) -> None:
"""Test constructing a local artifact path from a given purl."""
component_purl = PackageURL.from_string(purl_str)
assert (
construct_local_artifact_path_from_purl(
build_purl_type=build_purl_type,
component_purl=component_purl,
local_artifact_repo_mapper=local_artifact_repo_mapper,
)
== expectation
)


@pytest.mark.parametrize(
("purl_str", "build_tool_purl_types"),
[
pytest.param(
"pkg:maven/com.google.guava/[email protected]",
["maven", "pypi"],
id="A maven type PURL where multiple build tool types are discovered",
),
],
)
def test_get_local_artifact_paths_non_existing(
purl_str: str,
build_tool_purl_types: list[str],
) -> None:
"""Test getting local artifact paths of non existing artifacts.
The local artifact repos are available.
"""
purl = PackageURL.from_string(purl_str)
with tempfile.TemporaryDirectory() as temp_dir:
local_artifact_repo_mapper = {
"maven": temp_dir,
"pypi": temp_dir,
}
assert not get_local_artifact_paths(
purl=purl,
build_tool_purl_types=build_tool_purl_types,
local_artifact_repo_mapper=local_artifact_repo_mapper,
)
Loading

0 comments on commit 94f529e

Please sign in to comment.