← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~ruinedyourlife/launchpad-buildd:maven-virtual-remote into launchpad-buildd:master

 

Quentin Debhi has proposed merging ~ruinedyourlife/launchpad-buildd:maven-virtual-remote into launchpad-buildd:master.

Commit message:
Allow maven to pull plugins from AF and disallow external access

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~ruinedyourlife/launchpad-buildd/+git/launchpad-buildd/+merge/490354
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~ruinedyourlife/launchpad-buildd:maven-virtual-remote into launchpad-buildd:master.
diff --git a/lpbuildd/target/build_craft.py b/lpbuildd/target/build_craft.py
index 1e01c97..81cb27f 100644
--- a/lpbuildd/target/build_craft.py
+++ b/lpbuildd/target/build_craft.py
@@ -1,5 +1,6 @@
 import logging
 import os
+from urllib.parse import urlparse
 
 from lpbuildd.target.backend import check_path_escape
 from lpbuildd.target.build_snap import SnapChannelsAction
@@ -122,7 +123,7 @@ class BuildCraft(
                     "snap",
                     "install",
                     "--classic",
-                    "--channel=latest/edge/craftctl",
+                    "--channel=latest/edge",
                     "sourcecraft",
                 ]
             )
@@ -208,13 +209,14 @@ class BuildCraft(
 
         return cargo_vars
 
-    def setup_maven_credentials(self):
+    def setup_maven_credentials(self, proxy_env=None):
         """Set up Maven credential files for Artifactory virtual repository.
 
         Creates a Maven settings.xml file with:
         1. Server configuration for authentication
         2. Profile configuration for the virtual repository
         3. Mirror configuration to redirect all requests through Artifactory
+        4. Proxy configuration for HTTP requests
 
         Note: Multi-tenant Maven setups are complex due to how mirrors work.
         Instead, we use a single Artifactory virtual repository that shadows
@@ -234,7 +236,7 @@ class BuildCraft(
             return
 
         # Create .m2 directory for Maven configuration
-        m2_dir = os.path.join(self.buildd_path, ".m2")
+        m2_dir = os.path.join("/root", ".m2")
         self.backend.run(["mkdir", "-p", m2_dir])
 
         # Parse single repository URL, credentials, and name from env vars
@@ -254,17 +256,23 @@ class BuildCraft(
         if not repo_name or not repository.get("url"):
             return
 
+        # Extract proxy URL from environment if available
+        proxy_url = None
+        if proxy_env and "http_proxy" in proxy_env:
+            proxy_url = proxy_env["http_proxy"]
+
         # Generate Maven settings.xml
-        settings_xml = self._generate_maven_settings_xml(repo_name, repository)
+        settings_xml = self._generate_maven_settings_xml(repo_name, repository, proxy_url)
 
         with self.backend.open(os.path.join(m2_dir, "settings.xml"), "w") as f:
             f.write(settings_xml)
 
-    def _generate_maven_settings_xml(self, repo_name, repository):
+    def _generate_maven_settings_xml(self, repo_name, repository, proxy_url=None):
         """Generate Maven settings.xml for single virtual repository.
 
         :param repo_name: Name/ID of the repository
         :param repository: Dict with repository configuration
+        :param proxy_url: Optional proxy URL for Maven to use
         :return: XML content as string
         """
         # XML header
@@ -292,6 +300,23 @@ class BuildCraft(
             )
         servers += "    </servers>\n"
 
+        # Proxies section for HTTP proxy configuration
+        proxies = "    <proxies>\n"
+        if proxy_url:
+            # Parse proxy URL to extract host, port, and credentials
+            parsed_proxy = urlparse(proxy_url)
+            
+            proxies += (
+                "        <proxy>\n"
+                "            <id>default</id>\n"
+                "            <active>true</active>\n"
+                "            <protocol>http</protocol>\n"
+                f"            <host>{parsed_proxy.hostname}</host>\n"
+                f"            <port>{parsed_proxy.port or 80}</port>\n"
+                "        </proxy>\n"
+            )
+        proxies += "    </proxies>\n"
+
         # Profiles section with virtual repository
         profiles = "    <profiles>\n"
         profiles += (
@@ -328,23 +353,25 @@ class BuildCraft(
         )
 
         # Combine all parts
-        return header + schema + servers + profiles + mirrors + "</settings>"
+        return header + schema + servers + proxies + profiles + mirrors + "</settings>"
 
     def build(self):
         """Running build phase..."""
+        env = self.build_proxy_environment(
+            proxy_url=self.args.proxy_url,
+            use_fetch_service=self.args.use_fetch_service,
+        )
+        
         # Set up credential files before building
         cargo_env = self.setup_cargo_credentials()
-        self.setup_maven_credentials()
+        self.setup_maven_credentials(proxy_env=env)
 
         logger.info("Running build phase...")
         build_context_path = os.path.join(
             "/home/buildd", self.args.name, self.args.build_path
         )
         check_path_escape(self.buildd_path, build_context_path)
-        env = self.build_proxy_environment(
-            proxy_url=self.args.proxy_url,
-            use_fetch_service=self.args.use_fetch_service,
-        )
+        
         if cargo_env:
             env.update(cargo_env)
         if self.args.launchpad_instance:
diff --git a/lpbuildd/target/tests/test_build_craft.py b/lpbuildd/target/tests/test_build_craft.py
index 0e83514..98f2623 100644
--- a/lpbuildd/target/tests/test_build_craft.py
+++ b/lpbuildd/target/tests/test_build_craft.py
@@ -130,7 +130,7 @@ class TestBuildCraft(TestCase):
                     RanSnap(
                         "install",
                         "--classic",
-                        "--channel=latest/edge/craftctl",
+                        "--channel=latest/edge",
                         "sourcecraft",
                     ),
                     RanCommand(["mkdir", "-p", "/home/buildd"]),
@@ -159,7 +159,7 @@ class TestBuildCraft(TestCase):
                     RanSnap(
                         "install",
                         "--classic",
-                        "--channel=latest/edge/craftctl",
+                        "--channel=latest/edge",
                         "sourcecraft",
                     ),
                     RanCommand(["mkdir", "-p", "/home/buildd"]),
@@ -216,7 +216,7 @@ class TestBuildCraft(TestCase):
                     RanSnap(
                         "install",
                         "--classic",
-                        "--channel=latest/edge/craftctl",
+                        "--channel=latest/edge",
                         "sourcecraft",
                     ),
                     RanCommand(["mkdir", "-p", "/home/buildd"]),
@@ -258,7 +258,7 @@ class TestBuildCraft(TestCase):
                     RanSnap(
                         "install",
                         "--classic",
-                        "--channel=latest/edge/craftctl",
+                        "--channel=latest/edge",
                         "sourcecraft",
                     ),
                     RanCommand(["mkdir", "-p", "/home/buildd"]),
@@ -310,7 +310,7 @@ class TestBuildCraft(TestCase):
                     RanSnap(
                         "install",
                         "--classic",
-                        "--channel=latest/edge/craftctl",
+                        "--channel=latest/edge",
                         "sourcecraft",
                     ),
                     RanCommand(["rm", "-rf", "/var/lib/apt/lists"]),
@@ -410,7 +410,7 @@ class TestBuildCraft(TestCase):
                     RanSnap(
                         "install",
                         "--classic",
-                        "--channel=latest/edge/craftctl",
+                        "--channel=latest/edge",
                         "sourcecraft",
                     ),
                     RanCommand(["rm", "-rf", "/var/lib/apt/lists"]),
@@ -1305,7 +1305,7 @@ class TestBuildCraft(TestCase):
         build_craft.build()
 
         # Check that .m2/settings.xml was created correctly
-        maven_settings_path = "/home/buildd/test-image/.m2/settings.xml"
+        maven_settings_path = "/root/.m2/settings.xml"
         self.assertTrue(build_craft.backend.path_exists(maven_settings_path))
         with build_craft.backend.open(maven_settings_path) as f:
             settings_content = f.read()
@@ -1359,7 +1359,7 @@ class TestBuildCraft(TestCase):
             build_craft.backend.run.calls,
             MatchesListwise(
                 [
-                    RanCommand(["mkdir", "-p", "/home/buildd/test-image/.m2"]),
+                    RanCommand(["mkdir", "-p", "/root/.m2"]),
                     RanBuildCommand(
                         ["sourcecraft", "pack", "-v", "--destructive-mode"],
                         cwd="/home/buildd/test-image/.",
@@ -1384,7 +1384,7 @@ class TestBuildCraft(TestCase):
         build_craft.build()
 
         # Check that .m2/settings.xml was NOT created
-        maven_settings_path = "/home/buildd/test-image/.m2/settings.xml"
+        maven_settings_path = "/root/.m2/settings.xml"
         self.assertFalse(build_craft.backend.path_exists(maven_settings_path))
 
         # Verify only the build command was run (no mkdir for .m2)