← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~ilkeremrekoc/launchpad:maven-cargo-metadata into launchpad:master

 

İlker Emre Koç has proposed merging ~ilkeremrekoc/launchpad:maven-cargo-metadata into launchpad:master.

Commit message:
Add metadata to the published craft packages

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~ilkeremrekoc/launchpad/+git/launchpad/+merge/486190

There isn't a reliable way to put the necessary metadata to Artifactory
during publishing of Cargo and Maven builds. As a result, we will
instead, add these metadata right after publishing instead.

-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~ilkeremrekoc/launchpad:maven-cargo-metadata into launchpad:master.
diff --git a/lib/lp/crafts/model/craftrecipebuildjob.py b/lib/lp/crafts/model/craftrecipebuildjob.py
index 8133aeb..11b9f1c 100644
--- a/lib/lp/crafts/model/craftrecipebuildjob.py
+++ b/lib/lp/crafts/model/craftrecipebuildjob.py
@@ -11,11 +11,14 @@ __all__ = [
 
 import json
 import os
+import re
 import subprocess
 import tempfile
 from configparser import NoSectionError
 
 import transaction
+import yaml
+from artifactory import ArtifactoryPath
 from lazr.delegates import delegate_to
 from lazr.enum import DBEnumeratedType, DBItem
 from storm.databases.postgres import JSON
@@ -273,7 +276,9 @@ class CraftPublishingJob(CraftRecipeBuildJobDerived):
                     )
 
                     # Publish the Rust crate
-                    self._publish_rust_crate(crate_dir, env_vars)
+                    self._publish_rust_crate(
+                        crate_dir, env_vars, crate_file.filename
+                    )
                 elif jar_file is not None and pom_file is not None:
                     # Download the jar file
                     jar_path = os.path.join(tmpdir, jar_file.filename)
@@ -297,6 +302,7 @@ class CraftPublishingJob(CraftRecipeBuildJobDerived):
                     self._publish_maven_artifact(
                         tmpdir,
                         env_vars,
+                        jar_file.filename,
                         jar_path,
                         pom_path,
                     )
@@ -312,7 +318,7 @@ class CraftPublishingJob(CraftRecipeBuildJobDerived):
             transaction.commit()
             raise
 
-    def _publish_rust_crate(self, extract_dir, env_vars):
+    def _publish_rust_crate(self, extract_dir, env_vars, artifact_name):
         """Publish Rust crates from the extracted crate directory.
 
         :param extract_dir: Path to the extracted crate directory
@@ -373,8 +379,10 @@ class CraftPublishingJob(CraftRecipeBuildJobDerived):
         if result.returncode != 0:
             raise Exception(f"Failed to publish crate: {result.stderr}")
 
+        self._publish_properties(cargo_publish_url, artifact_name)
+
     def _publish_maven_artifact(
-        self, work_dir, env_vars, jar_path=None, pom_path=None
+        self, work_dir, env_vars, artifact_name, jar_path=None, pom_path=None
     ):
         """Publish Maven artifacts.
 
@@ -435,6 +443,8 @@ class CraftPublishingJob(CraftRecipeBuildJobDerived):
                 f"Failed to publish Maven artifact: {result.stderr}"
             )
 
+        self._publish_properties(maven_publish_url, artifact_name)
+
     def _get_maven_settings_xml(self, username, password):
         """Generate Maven settings.xml content.
 
@@ -488,3 +498,69 @@ class CraftPublishingJob(CraftRecipeBuildJobDerived):
 
         # Combine all parts
         return header + schema + servers + profiles + active_profiles
+
+    def _publish_properties(
+        self, publish_url: str, artifact_name: str
+    ) -> None:
+        """Publish properties to the artifact in Artifactory."""
+
+        new_properties = {}
+
+        new_properties["soss.commit_id"] = self.build.revision_id
+        new_properties["soss.source_url"] = self._recipe_git_url()
+        new_properties["soss.type"] = "source"
+        new_properties["soss.license"] = self._get_license_metadata()
+
+        artifact = config.artifactory.base_url.aql(
+            "items.find",
+            {
+                "repo": self._extract_repository_name(publish_url),
+                "name": artifact_name,
+            },
+            ".include",
+            # We don't use "repo", but the AQL documentation says that
+            # non-admin users must include all of "name", "repo",
+            # and "path" in the include directive.
+            ["repo", "path", "name"],
+        )
+
+        path = ArtifactoryPath(artifact["path"], artifact["name"])
+        path.set_properties(new_properties)
+
+    def _extract_repository_name(self, url: str) -> str:
+        # Remove trailing slash if present
+        url = url.rstrip("/")
+
+        # Use regex to match the last part after the last slash
+        match = re.search(r"/([^/]+)$", url)
+
+        if match:
+            return match.group(1)
+        else:
+            raise Exception(
+                "Couldn't parse repository name from publishing URL"
+            )
+
+    def _recipe_git_url(self) -> str | None:
+        """Get the recipe git URL."""
+
+        craft_recipe = self.build.recipe
+        if craft_recipe.git_repository is not None:
+            return craft_recipe.git_repository.git_https_url
+        elif craft_recipe.git_repository_url is not None:
+            return craft_recipe.git_repository_url
+        else:
+            return None
+
+    def _get_license_metadata(self) -> str:
+        """Get the license metadata from the build files."""
+        for _, lfa, _ in self.build.getFiles():
+            if lfa.filename == "metadata.yaml":
+                lfa.open()
+                try:
+                    content = lfa.read().decode("utf-8")
+                    metadata = yaml.safe_load(content)
+                    return metadata.get("license")
+                finally:
+                    lfa.close()
+        return "unknown"

References