← Back to team overview

launchpad-reviewers team mailing list archive

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

 

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

Commit message:
Add session to metadata publishing for artifactory

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

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

AQL (Artifactory Query Language needs a bearer token for
authentication. To add that and other session parameters to our
connection we used the previous artifactory publishing code.
    
Then we restored the commented out calls to property publishing
function and tests of it.

-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~ilkeremrekoc/launchpad:cargo-maven-metadata-session into launchpad:master.
diff --git a/lib/lp/crafts/model/craftrecipebuildjob.py b/lib/lp/crafts/model/craftrecipebuildjob.py
index c799b7f..eb2f835 100644
--- a/lib/lp/crafts/model/craftrecipebuildjob.py
+++ b/lib/lp/crafts/model/craftrecipebuildjob.py
@@ -17,6 +17,7 @@ import tempfile
 from configparser import NoSectionError
 from urllib.parse import urlparse
 
+import requests
 import transaction
 import yaml
 from artifactory import ArtifactoryPath
@@ -416,11 +417,7 @@ class CraftPublishingJob(CraftRecipeBuildJobDerived):
         if result.returncode != 0:
             raise Exception(f"Failed to publish crate: {result.stderr}")
 
-        # XXX ruinedyourlife 2025-06-06:
-        # The publish_properties method is not working as expected.
-        # Artifactory is giving us a 403.
-        # We should fix it, but for now we'll skip it.
-        # self._publish_properties(cargo_publish_url, artifact_name)
+        self._publish_properties(cargo_publish_url, artifact_name)
 
     def _publish_maven_artifact(
         self, work_dir, env_vars, artifact_name, jar_path=None, pom_path=None
@@ -484,11 +481,7 @@ class CraftPublishingJob(CraftRecipeBuildJobDerived):
                 f"Failed to publish Maven artifact: {result.stderr}"
             )
 
-        # XXX ruinedyourlife 2025-06-06:
-        # The publish_properties method is not working as expected.
-        # Artifactory is giving us a 403.
-        # We should fix it, but for now we'll skip it.
-        # self._publish_properties(maven_publish_url, artifact_name)
+        self._publish_properties(maven_publish_url, artifact_name)
 
     def _get_maven_settings_xml(self, username, password):
         """Generate Maven settings.xml content.
@@ -582,6 +575,26 @@ class CraftPublishingJob(CraftRecipeBuildJobDerived):
         # Combine all parts
         return header + schema + servers + proxies + profiles + active_profiles
 
+    def _make_session(self) -> requests.Session:
+        """Make a suitable requests session for talking to Artifactory."""
+
+        session = requests.Session()
+        session.trust_env = False
+        if config.launchpad.http_proxy:
+            session.proxies = {
+                "http": config.launchpad.http_proxy,
+                "https": config.launchpad.http_proxy,
+            }
+        if config.launchpad.ca_certificates_path is not None:
+            session.verify = config.launchpad.ca_certificates_path
+        write_creds = config.artifactory.write_credentials
+
+        if write_creds is not None:
+            session.headers.update(
+                {"Authorization": f"Bearer {write_creds.split(':', 1)[1]}"}
+            )
+        return session
+
     def _publish_properties(
         self, publish_url: str, artifact_name: str
     ) -> None:
@@ -597,8 +610,10 @@ class CraftPublishingJob(CraftRecipeBuildJobDerived):
         new_properties["soss.license"] = [self._get_license_metadata()]
 
         # Repo name is derived from the URL
-        # We assume the URL ends with the repository name
-        repo_name = publish_url.rstrip("/").split("/")[-1]
+        # We assume the URL ends with "index" as both Cargo and Maven use such
+        # indexes to publish artifacts. Thus, we also assume that the
+        # repository name is the second last part of the URL.
+        repo_name = publish_url.rstrip("/").split("/")[-2]
 
         root_path_str = self._extract_root_path(publish_url)
         if not root_path_str:
@@ -608,6 +623,8 @@ class CraftPublishingJob(CraftRecipeBuildJobDerived):
 
         # Search for the artifact in Artifactory using AQL
         root_path = ArtifactoryPath(root_path_str)
+        root_path.session = self._make_session()
+
         artifacts = root_path.aql(
             "items.find",
             {
@@ -621,8 +638,8 @@ class CraftPublishingJob(CraftRecipeBuildJobDerived):
 
         if not artifacts:
             raise NotFoundError(
-                f"Artifact '{artifact_name}' not found in repository \
-                '{repo_name}'"
+                f"Artifact '{artifact_name}' not found in repository: "
+                + f"'{repo_name}'"
             )
 
         if len(artifacts) > 1:
@@ -637,6 +654,7 @@ class CraftPublishingJob(CraftRecipeBuildJobDerived):
         artifact_path = ArtifactoryPath(
             root_path, artifact["repo"], artifact["path"], artifact["name"]
         )
+        artifact_path.session = self._make_session()
         artifact_path.set_properties(new_properties)
 
     def _extract_root_path(self, publish_url: str) -> str:
@@ -658,7 +676,7 @@ class CraftPublishingJob(CraftRecipeBuildJobDerived):
 
         craft_recipe = self.build.recipe
         if craft_recipe.git_repository is not None:
-            return craft_recipe.git_repository.git_https_url
+            return craft_recipe.git_repository.getCodebrowseUrl()
         elif craft_recipe.git_repository_url is not None:
             return craft_recipe.git_repository_url
         else:
diff --git a/lib/lp/crafts/tests/test_craftrecipebuildjob.py b/lib/lp/crafts/tests/test_craftrecipebuildjob.py
index 0dc0462..3fe124b 100644
--- a/lib/lp/crafts/tests/test_craftrecipebuildjob.py
+++ b/lib/lp/crafts/tests/test_craftrecipebuildjob.py
@@ -86,6 +86,7 @@ class TestCraftPublishingJob(TestCaseWithFactory):
         # Set up the Artifactory fixture
         self.base_url = "https://example.com/artifactory";
         self.repository_name = "repository"
+        self.publish_url = f"{self.base_url}/{self.repository_name}/index"
 
         self.artifactory = self.useFixture(
             FakeArtifactoryFixture(self.base_url, self.repository_name)
@@ -118,9 +119,9 @@ class TestCraftPublishingJob(TestCaseWithFactory):
         else:
             # Push with standard environment variables
             env_vars = {
-                "CARGO_PUBLISH_URL": f"{self.base_url}/repository",
+                "CARGO_PUBLISH_URL": self.publish_url,
                 "CARGO_PUBLISH_AUTH": "lp:token123",
-                "MAVEN_PUBLISH_URL": f"{self.base_url}/repository",
+                "MAVEN_PUBLISH_URL": self.publish_url,
                 "MAVEN_PUBLISH_AUTH": "lp:token123",
             }
             self.pushConfig(
@@ -416,8 +417,7 @@ class TestCraftPublishingJob(TestCaseWithFactory):
         self._setup_distribution("soss")
         self._setup_config(with_env_vars=True, with_http_proxy=True)
 
-        # lfa = self._create_crate_file()
-        self._create_crate_file()
+        lfa = self._create_crate_file()
 
         # Add a metadata file with license information
         license_value = "Apache-2.0"
@@ -457,29 +457,25 @@ class TestCraftPublishingJob(TestCaseWithFactory):
 
         self.patch(crbj_subprocess, "run", mock_run)
 
-        # XXX ruinedyourlife 2025-06-06:
-        # The publish_properties method is not working as expected.
-        # Artifactory is giving us a 403.
-        # We should fix it, but for now we'll skip it.
-        # original_publish_properties = CraftPublishingJob._publish_properties
+        original_publish_properties = CraftPublishingJob._publish_properties
 
-        # def mock_cargo_publish_properties(*args, **kwargs):
-        #     """Mock _publish_properties to deploy the crate to Artifactory
-        #     Fixture before testing.
+        def mock_cargo_publish_properties(*args, **kwargs):
+            """Mock _publish_properties to deploy the crate to Artifactory
+            Fixture before testing.
 
-        #     We need to do this in a nested function here because we need
-        #     to access the `lfa` variable which is created in the this test
-        #     setup above but the mocked function (and the lfa.open()) can
-        #     only be called inside the job's run method."""
+            We need to do this in a nested function here because we need
+            to access the `lfa` variable which is created in the this test
+            setup above but the mocked function (and the lfa.open()) can
+            only be called inside the job's run method."""
 
-        #     self._artifactory_put(args[1], "crates/test", args[2], lfa)
-        #     return original_publish_properties(*args, **kwargs)
+            self._artifactory_put(args[1], "crates/test", args[2], lfa)
+            return original_publish_properties(*args, **kwargs)
 
-        # self.patch(
-        #     CraftPublishingJob,
-        #     "_publish_properties",
-        #     mock_cargo_publish_properties,
-        # )
+        self.patch(
+            CraftPublishingJob,
+            "_publish_properties",
+            mock_cargo_publish_properties,
+        )
 
         # Create and run the job
         job = self.run_job(self.build)
@@ -521,30 +517,29 @@ class TestCraftPublishingJob(TestCaseWithFactory):
         env = kwargs["env"]
         self.assertIn("CARGO_HOME", env)
 
-        # XXX ruinedyourlife 2025-06-06:
-        # The publish_properties method is not working as expected.
-        # Artifactory is giving us a 403.
-        # We should fix it, but for now we'll skip it.
-        # # Verify that the artifact's metadata were uploaded to Artifactory
-        # artifact = self._artifactory_search("repository", lfa.filename)
-
-        # self.assertIsNotNone(artifact, "Artifact not found in Artifactory")
-
-        # self.assertEqual(artifact["repo"], "repository")
-        # self.assertEqual(artifact["name"], lfa.filename)
-        # self.assertEqual(artifact["path"], "crates/test")
-        # self.assertEqual(
-        #    artifact["properties"]["soss.commit_id"], "random-revision-id"
-        # )
-        # self.assertEqual(
-        #    artifact["properties"]["soss.source_url"],
-        #    removeSecurityProxy(self.recipe).git_repository.git_https_url,
-        # )
-        # self.assertEqual(artifact["properties"]["soss.type"], "source")
-        # self.assertEqual(
-        #     artifact["properties"]["soss.license"],
-        #     license_value
-        # )
+        # Verify that the artifact's metadata were uploaded to Artifactory
+        artifact = self._artifactory_search("repository", lfa.filename)
+
+        self.assertIsNotNone(artifact, "Artifact not found in Artifactory")
+
+        self.assertEqual(artifact["repo"], "repository")
+        self.assertEqual(artifact["name"], lfa.filename)
+
+        # Our artifactory fixture preserves the "index/" path used during
+        # publishing. This is not the case in a real Artifactory
+        # instance, so we need to remove the "index/" prefix.
+        path_without_index = artifact["path"].replace("index/", "")
+        self.assertEqual(path_without_index, "crates/test")
+
+        self.assertEqual(
+            artifact["properties"]["soss.commit_id"], "random-revision-id"
+        )
+        self.assertEqual(
+            artifact["properties"]["soss.source_url"],
+            removeSecurityProxy(self.recipe).git_repository.git_https_url,
+        )
+        self.assertEqual(artifact["properties"]["soss.type"], "source")
+        self.assertEqual(artifact["properties"]["soss.license"], license_value)
 
     def test_run_missing_maven_config(self):
         """
@@ -666,31 +661,27 @@ class TestCraftPublishingJob(TestCaseWithFactory):
 
         self.patch(crbj_subprocess, "run", mock_run)
 
-        # XXX ruinedyourlife 2025-06-06:
-        # The publish_properties method is not working as expected.
-        # Artifactory is giving us a 403.
-        # We should fix it, but for now we'll skip it.
-        # original_publish_properties = CraftPublishingJob._publish_properties
+        original_publish_properties = CraftPublishingJob._publish_properties
 
-        # def mock_maven_publish_properties(*args, **kwargs):
-        #     """Mock _publish_properties to deploy the crate to Artifactory
-        #     Fixture before testing.
+        def mock_maven_publish_properties(*args, **kwargs):
+            """Mock _publish_properties to deploy the crate to Artifactory
+            Fixture before testing.
 
-        #     We need to do this in a nested function here because we need
-        #     to access the `lfa` variable which is created in the this test
-        #     setup above but the mocked function (and the lfa.open()) can
-        #     only be called inside the job's run method."""
+            We need to do this in a nested function here because we need
+            to access the `lfa` variable which is created in the this test
+            setup above but the mocked function (and the lfa.open()) can
+            only be called inside the job's run method."""
 
-        #     self._artifactory_put(
-        #         args[1], "com/example/test-artifact/0.1.0", args[2], jar_lfa
-        #     )
-        #     return original_publish_properties(*args, **kwargs)
+            self._artifactory_put(
+                args[1], "com/example/test-artifact/0.1.0", args[2], jar_lfa
+            )
+            return original_publish_properties(*args, **kwargs)
 
-        # self.patch(
-        #     CraftPublishingJob,
-        #     "_publish_properties",
-        #     mock_maven_publish_properties,
-        # )
+        self.patch(
+            CraftPublishingJob,
+            "_publish_properties",
+            mock_maven_publish_properties,
+        )
 
         job = self.run_job(self.build)
 
@@ -750,30 +741,27 @@ class TestCraftPublishingJob(TestCaseWithFactory):
         kwargs = mvn_call[1]
         self.assertIn("cwd", kwargs)
 
-        # XXX ruinedyourlife 2025-06-06:
-        # The publish_properties method is not working as expected.
-        # Artifactory is giving us a 403.
-        # We should fix it, but for now we'll skip it.
-        # artifact = self._artifactory_search("repository", jar_lfa.filename)
-
-        # # Verify that the artifact's metadata were uploaded to Artifactory
-        # self.assertIsNotNone(artifact, "Artifact not found in Artifactory")
-
-        # self.assertEqual(artifact["repo"], "repository")
-        # self.assertEqual(artifact["name"], jar_lfa.filename)
-        # self.assertEqual(artifact["path"], "com/example/test-artifact/0.1.0")
-        # self.assertEqual(
-        #    artifact["properties"]["soss.commit_id"], "random-revision-id"
-        # )
-        # self.assertEqual(
-        #    artifact["properties"]["soss.source_url"],
-        #    git_repository.git_https_url,
-        # )
-        # self.assertEqual(artifact["properties"]["soss.type"], "source")
-        # self.assertEqual(
-        # artifact["properties"]["soss.license"],
-        # license_value
-        # )
+        artifact = self._artifactory_search("repository", jar_lfa.filename)
+
+        # Verify that the artifact's metadata were uploaded to Artifactory
+        self.assertIsNotNone(artifact, "Artifact not found in Artifactory")
+        self.assertEqual(artifact["repo"], "repository")
+        self.assertEqual(artifact["name"], jar_lfa.filename)
+
+        # Our artifactory fixture preserves the "index/" path used during
+        # publishing. This is not the case in a real Artifactory
+        # instance, so we need to remove the "index/" prefix.
+        path_without_index = artifact["path"].replace("index/", "")
+        self.assertEqual(path_without_index, "com/example/test-artifact/0.1.0")
+        self.assertEqual(
+            artifact["properties"]["soss.commit_id"], "random-revision-id"
+        )
+        self.assertEqual(
+            artifact["properties"]["soss.source_url"],
+            removeSecurityProxy(self.recipe).git_repository.git_https_url,
+        )
+        self.assertEqual(artifact["properties"]["soss.type"], "source")
+        self.assertEqual(artifact["properties"]["soss.license"], license_value)
 
     def test__publish_properties_sets_expected_properties(self):
         """Test that _publish_properties sets the correct properties in
@@ -796,12 +784,12 @@ class TestCraftPublishingJob(TestCaseWithFactory):
         removeSecurityProxy(self.build).revision_id = "random-revision-id"
 
         self._artifactory_put(
-            f"{self.base_url}/repository",
+            f"{self.base_url}/{self.repository_name}",
             "middle_folder",
             "artifact.file",
             MockBytesIO(b"dummy content"),
         )
-        job._publish_properties(f"{self.base_url}/repository", "artifact.file")
+        job._publish_properties(self.publish_url, "artifact.file")
         artifact = self._artifactory_search("repository", "artifact.file")
 
         self.assertIsNotNone(artifact, "Artifact not found in Artifactory")
@@ -829,7 +817,7 @@ class TestCraftPublishingJob(TestCaseWithFactory):
         self.assertRaises(
             NotFoundError,
             job._publish_properties,
-            f"{self.base_url}/repository",
+            self.publish_url,
             "missing-artifact.file",
         )
 
@@ -838,7 +826,7 @@ class TestCraftPublishingJob(TestCaseWithFactory):
         metadata.yaml is present."""
 
         self._artifactory_put(
-            f"{self.base_url}/repository",
+            f"{self.base_url}/{self.repository_name}",
             "some/path",
             "artifact.file",
             MockBytesIO(b"dummy content"),
@@ -847,7 +835,7 @@ class TestCraftPublishingJob(TestCaseWithFactory):
         job = getUtility(ICraftPublishingJobSource).create(self.build)
         job = removeSecurityProxy(job)
         job.run = lambda: job._publish_properties(
-            f"{self.base_url}/repository", "artifact.file"
+            self.publish_url, "artifact.file"
         )
 
         self.patch(
@@ -877,7 +865,7 @@ class TestCraftPublishingJob(TestCaseWithFactory):
         removeSecurityProxy(self.build).addFile(metadata_lfa)
 
         self._artifactory_put(
-            f"{self.base_url}/repository",
+            f"{self.base_url}/{self.repository_name}",
             "some/path",
             "artifact.file",
             MockBytesIO(b"dummy content"),
@@ -886,7 +874,7 @@ class TestCraftPublishingJob(TestCaseWithFactory):
         job = getUtility(ICraftPublishingJobSource).create(self.build)
         job = removeSecurityProxy(job)
         job.run = lambda: job._publish_properties(
-            f"{self.base_url}/repository", "artifact.file"
+            self.publish_url, "artifact.file"
         )
 
         self.patch(
@@ -917,7 +905,7 @@ class TestCraftPublishingJob(TestCaseWithFactory):
         removeSecurityProxy(self.build).addFile(metadata_lfa)
 
         self._artifactory_put(
-            f"{self.base_url}/repository",
+            f"{self.base_url}/{self.repository_name}",
             "some/path",
             "artifact.file",
             MockBytesIO(b"dummy content"),
@@ -926,7 +914,7 @@ class TestCraftPublishingJob(TestCaseWithFactory):
         job = getUtility(ICraftPublishingJobSource).create(self.build)
         job = removeSecurityProxy(job)
         job.run = lambda: job._publish_properties(
-            f"{self.base_url}/repository", "artifact.file"
+            self.publish_url, "artifact.file"
         )
 
         self.patch(
@@ -944,7 +932,7 @@ class TestCraftPublishingJob(TestCaseWithFactory):
         """Test that _publish_properties gets git_repository as source_url."""
 
         self._artifactory_put(
-            f"{self.base_url}/repository",
+            f"{self.base_url}/{self.repository_name}",
             "some/path",
             "artifact.file",
             MockBytesIO(b"dummy content"),
@@ -960,7 +948,7 @@ class TestCraftPublishingJob(TestCaseWithFactory):
             CraftPublishingJob, "_get_license_metadata", lambda self: "MIT"
         )
 
-        job._publish_properties(f"{self.base_url}/repository", "artifact.file")
+        job._publish_properties(self.publish_url, "artifact.file")
 
         artifact = self._artifactory_search("repository", "artifact.file")
         self.assertEqual(
@@ -973,7 +961,7 @@ class TestCraftPublishingJob(TestCaseWithFactory):
         source_url."""
 
         self._artifactory_put(
-            f"{self.base_url}/repository",
+            f"{self.base_url}/{self.repository_name}",
             "some/path",
             "artifact.file",
             MockBytesIO(b"dummy content"),
@@ -993,7 +981,7 @@ class TestCraftPublishingJob(TestCaseWithFactory):
             CraftPublishingJob, "_get_license_metadata", lambda self: "MIT"
         )
 
-        job._publish_properties(f"{self.base_url}/repository", "artifact.file")
+        job._publish_properties(self.publish_url, "artifact.file")
 
         artifact = self._artifactory_search("repository", "artifact.file")
         self.assertEqual(