← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~jugmac00/lpcraft:provide-additional-repositories-via-cli into lpcraft:main

 

Jürgen Gmach has proposed merging ~jugmac00/lpcraft:provide-additional-repositories-via-cli into lpcraft:main.

Commit message:
Clean up CLI documentation

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~jugmac00/lpcraft/+git/lpcraft/+merge/428089
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~jugmac00/lpcraft:provide-additional-repositories-via-cli into lpcraft:main.
diff --git a/NEWS.rst b/NEWS.rst
index 75f4484..5dcd320 100644
--- a/NEWS.rst
+++ b/NEWS.rst
@@ -8,6 +8,8 @@ Version history
 - Add input properties, allowing jobs to use artifacts built by previous
   pipeline stages.
 
+- Enable providing additional repositories via CLI.
+
 0.0.24 (2022-08-05)
 ===================
 
diff --git a/docs/cli-interface.rst b/docs/cli-interface.rst
index 0c44c3f..38a98f1 100644
--- a/docs/cli-interface.rst
+++ b/docs/cli-interface.rst
@@ -20,15 +20,19 @@ lpcraft run optional arguments
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 - ``--apt-replace-repositories SOURCE_LINE``, e.g.
-  ``lpcraft run --apt-replace-repositories "deb http://archive.ubuntu.com/ubuntu/ focal main restricted"``.
-  Please note that the option is repeatable.
+  ``lpcraft run --apt-replace-repositories "deb http://archive.ubuntu.com/ubuntu/ focal main restricted"``
 
-- ``--set-env KEY=VALUE``, e.g.
-  ``lpcraft run --set-env="PIP_INDEX_URL=http://pypi.example.com/simple"``
+  This option is repeatable.
+
+- ``--package-repository`` (provide an additional repository), e.g.
+  ``lpcraft run --package-repository "deb http://archive.ubuntu.com/ubuntu/ focal main restricted"``
+  This option is repeatable.
 
 - ``--plugin-setting``, e.g.
   ``lpcraft run --plugin-setting="foo=bar"``
 
+  This option is repeatable.
+
 - ``--secrets``, e.g.
   ``lpcraft run --secrets="<path-to-configuration-file>"``
 
@@ -39,6 +43,10 @@ lpcraft run optional arguments
     key: secret
     another_key: another_secret
 
+- ``--set-env KEY=VALUE``, e.g.
+  ``lpcraft run --set-env="PIP_INDEX_URL=http://pypi.example.com/simple"``
+
+  This option is repeatable.
 
 lpcraft run-one
 ---------------
@@ -55,17 +63,21 @@ lpcraft run-one optional arguments
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 - ``--apt-replace-repositories SOURCE_LINE``, e.g.
-  ``lpcraft run-one --apt-replace-repositories "deb http://archive.ubuntu.com/ubuntu/ focal main restricted" test 0``.
-  Please note that the option is repeatable.
+  ``lpcraft run-one --apt-replace-repositories "deb http://archive.ubuntu.com/ubuntu/ focal main restricted" test 0``
 
-- ``--set-env KEY=VALUE``, e.g.
-  ``lpcraft run-one --set-env="PIP_INDEX_URL=http://pypi.example.com/simple"; test 0``.
+  This option is repeatable.
+
+- ``--package-repository`` (provide an additional repository), e.g.
+  ``lpcraft run-one --package-repository "deb http://archive.ubuntu.com/ubuntu/ focal main restricted" test 0``
+  This option is repeatable.
 
 - ``--plugin-setting``, e.g.
-  ``lpcraft run-one --plugin-setting="foo=bar" test 0``.
+  ``lpcraft run-one --plugin-setting="foo=bar" test 0``
+
+  This option is repeatable.
 
 - ``--secrets``, e.g.
-  ``lpcraft run-one --secrets="<path-to-configuration-file>" test 0``.
+  ``lpcraft run-one --secrets="<path-to-configuration-file>" test 0``
 
   The configuration file should look like...
 
@@ -73,3 +85,8 @@ lpcraft run-one optional arguments
 
     key: secret
     another_key: another_secret
+
+- ``--set-env KEY=VALUE``, e.g.
+  ``lpcraft run-one --set-env="PIP_INDEX_URL=http://pypi.example.com/simple"; test 0``
+
+  This option is repeatable.
diff --git a/lpcraft/commands/run.py b/lpcraft/commands/run.py
index f485601..d8ed3d1 100644
--- a/lpcraft/commands/run.py
+++ b/lpcraft/commands/run.py
@@ -21,7 +21,7 @@ from jinja2 import BaseLoader, Environment
 from pluggy import PluginManager
 
 from lpcraft import env
-from lpcraft.config import Config, Input, Job, Output, PackageRepository
+from lpcraft.config import Config, Input, Job, Output
 from lpcraft.errors import CommandError
 from lpcraft.plugin.manager import get_plugin_manager
 from lpcraft.plugins import PLUGINS
@@ -291,11 +291,11 @@ def _install_apt_packages(
     host_architecture: str,
     remote_cwd: Path,
     apt_replacement_repositories: Optional[List[str]],
-    additional_apt_repositories: Optional[List[PackageRepository]],
+    package_repositories: List[str],
     environment: Optional[Dict[str, Optional[str]]],
     secrets: Optional[Dict[str, str]],
 ) -> None:
-    if apt_replacement_repositories or additional_apt_repositories:
+    if apt_replacement_repositories or package_repositories:
         sources_list_path = "/etc/apt/sources.list"
 
         with NamedTemporaryFile(mode="w+") as tmpfile:
@@ -310,9 +310,8 @@ def _install_apt_packages(
 
         if apt_replacement_repositories:
             sources = "\n".join(apt_replacement_repositories) + "\n"
-        if additional_apt_repositories:
-            for repository in additional_apt_repositories:
-                sources += "\n" + "\n".join(repository.sources_list_lines())
+        if package_repositories:
+            sources += "\n" + "\n".join(package_repositories)
             if secrets:
                 template = Environment(loader=BaseLoader()).from_string(
                     sources
@@ -406,8 +405,8 @@ def _run_job(
     job_index: int,
     provider: Provider,
     output: Optional[Path],
-    apt_replacement_repositories: Optional[List[str]] = None,
-    additional_apt_repositories: Optional[List[PackageRepository]] = None,
+    apt_replacement_repositories: Optional[List[str]],
+    package_repositories: List[str],
     env_from_cli: Optional[List[str]] = None,
     plugin_settings: Optional[List[str]] = None,
     secrets: Optional[Dict[str, str]] = None,
@@ -500,7 +499,7 @@ def _run_job(
                 host_architecture=host_architecture,
                 remote_cwd=remote_cwd,
                 apt_replacement_repositories=apt_replacement_repositories,
-                additional_apt_repositories=additional_apt_repositories,
+                package_repositories=package_repositories,
                 environment=environment,
                 secrets=secrets,
             )
@@ -606,6 +605,13 @@ class RunCommand(BaseCommand):
             type=Path,
             help="Pass in a YAML-based configuration file for secrets.",
         )
+        parser.add_argument(
+            "--package-repository",
+            action="append",
+            default=[],
+            dest="package_repositories",
+            help="Provide an additional package repository.",
+        )
 
     def run(self, args: Namespace) -> int:
         """Run the command."""
@@ -635,6 +641,11 @@ class RunCommand(BaseCommand):
                             launched_instances.append(
                                 _get_job_instance_name(provider, job)
                             )
+                            package_repositories = []
+                            for group in job.package_repositories:
+                                for repository in group.sources_list_lines():
+                                    package_repositories.append(repository)
+                            package_repositories += args.package_repositories
                             _run_job(
                                 config,
                                 job_name,
@@ -644,7 +655,7 @@ class RunCommand(BaseCommand):
                                 apt_replacement_repositories=(
                                     args.apt_replace_repositories
                                 ),
-                                additional_apt_repositories=job.package_repositories,  # noqa: E501
+                                package_repositories=package_repositories,  # noqa: E501
                                 env_from_cli=args.set_env,
                                 plugin_settings=args.plugin_setting,
                                 secrets=secrets,
@@ -737,6 +748,13 @@ class RunOneCommand(BaseCommand):
             type=Path,
             help="Pass in a YAML-based configuration file for secrets.",
         )
+        parser.add_argument(
+            "--package-repository",
+            action="append",
+            default=[],
+            dest="package_repositories",
+            help="Provide an additional package repository.",
+        )
 
     def run(self, args: Namespace) -> int:
         """Run the command."""
@@ -759,6 +777,11 @@ class RunOneCommand(BaseCommand):
             with open(args.secrets_file) as f:
                 content = f.read()
             secrets = yaml.safe_load(content)
+        package_repositories = []
+        for group in job.package_repositories:
+            for repository in group.sources_list_lines():
+                package_repositories.append(repository)
+        package_repositories += args.package_repositories
         try:
             _run_job(
                 config,
@@ -767,7 +790,7 @@ class RunOneCommand(BaseCommand):
                 provider,
                 args.output_directory,
                 apt_replacement_repositories=args.apt_replace_repositories,
-                additional_apt_repositories=job.package_repositories,
+                package_repositories=package_repositories,
                 env_from_cli=args.set_env,
                 plugin_settings=args.plugin_setting,
                 secrets=secrets,
diff --git a/lpcraft/commands/tests/test_run.py b/lpcraft/commands/tests/test_run.py
index c60e0d7..9940c5b 100644
--- a/lpcraft/commands/tests/test_run.py
+++ b/lpcraft/commands/tests/test_run.py
@@ -2446,7 +2446,7 @@ class TestRun(RunBaseTestCase):
 
     @patch("lpcraft.commands.run.get_provider")
     @patch("lpcraft.commands.run.get_host_architecture", return_value="amd64")
-    def test_run_with_additional_apt_repositories_with_secrets(
+    def test_provide_package_repositories_via_config_with_secrets(
         self, mock_get_host_architecture, mock_get_provider
     ):
         existing_repositories = [
@@ -2539,6 +2539,61 @@ class TestRun(RunBaseTestCase):
             file_contents,
         )
 
+    @patch("lpcraft.commands.run.get_provider")
+    @patch("lpcraft.commands.run.get_host_architecture", return_value="amd64")
+    def test_run_provide_package_repositories_via_cli(
+        self, mock_get_host_architecture, mock_get_provider
+    ):
+        existing_repositories = [
+            "deb http://archive.ubuntu.com/ubuntu/ focal main restricted",
+            "deb-src http://archive.ubuntu.com/ubuntu/ focal main restricted",
+        ]
+
+        def fake_pull_file(source: Path, destination: Path) -> None:
+            destination.write_text("\n".join(existing_repositories))
+
+        launcher = Mock(spec=launch)
+        provider = makeLXDProvider(lxd_launcher=launcher)
+        mock_get_provider.return_value = provider
+        execute_run = launcher.return_value.execute_run
+        execute_run.return_value = subprocess.CompletedProcess([], 0)
+        launcher.return_value.pull_file.side_effect = fake_pull_file
+
+        config = dedent(
+            """
+            pipeline:
+                - test
+            jobs:
+                test:
+                    series: focal
+                    architectures: amd64
+                    run: ls -la
+                    packages: [git]
+            """  # noqa: E501
+        )
+        Path(".launchpad.yaml").write_text(config)
+
+        result = self.run_command("run", "--package-repository", "one more")
+
+        self.assertEqual(0, result.exit_code)
+
+        mock_info = launcher.return_value.push_file_io.call_args_list
+        self.assertEqual(
+            Path("/etc/apt/sources.list"), mock_info[0][1]["destination"]
+        )
+
+        file_contents = mock_info[0][1]["content"].read().decode()
+        self.assertEqual(
+            dedent(
+                """\
+            deb http://archive.ubuntu.com/ubuntu/ focal main restricted
+            deb-src http://archive.ubuntu.com/ubuntu/ focal main restricted
+            one more
+            """  # noqa: E501
+            ),
+            file_contents,
+        )
+
     @patch("lpcraft.env.get_managed_environment_project_path")
     @patch("lpcraft.commands.run.get_provider")
     @patch("lpcraft.commands.run.get_host_architecture", return_value="amd64")
@@ -3463,7 +3518,7 @@ class TestRunOne(RunBaseTestCase):
 
     @patch("lpcraft.commands.run.get_provider")
     @patch("lpcraft.commands.run.get_host_architecture", return_value="amd64")
-    def test_run_with_additional_apt_repositories_with_secrets(
+    def test_provide_package_repositories_via_config_with_secrets(
         self, mock_get_host_architecture, mock_get_provider
     ):
         existing_repositories = [
@@ -3558,6 +3613,63 @@ class TestRunOne(RunBaseTestCase):
             file_contents,
         )
 
+    @patch("lpcraft.commands.run.get_provider")
+    @patch("lpcraft.commands.run.get_host_architecture", return_value="amd64")
+    def test_run_provide_package_repositories_via_clix(
+        self, mock_get_host_architecture, mock_get_provider
+    ):
+        existing_repositories = [
+            "deb http://archive.ubuntu.com/ubuntu/ focal main restricted",
+            "deb-src http://archive.ubuntu.com/ubuntu/ focal main restricted",
+        ]
+
+        def fake_pull_file(source: Path, destination: Path) -> None:
+            destination.write_text("\n".join(existing_repositories))
+
+        launcher = Mock(spec=launch)
+        provider = makeLXDProvider(lxd_launcher=launcher)
+        mock_get_provider.return_value = provider
+        execute_run = launcher.return_value.execute_run
+        execute_run.return_value = subprocess.CompletedProcess([], 0)
+        launcher.return_value.pull_file.side_effect = fake_pull_file
+
+        config = dedent(
+            """
+            pipeline:
+                - test
+            jobs:
+                test:
+                    series: focal
+                    architectures: amd64
+                    run: ls -la
+                    packages: [git]
+            """  # noqa: E501
+        )
+        Path(".launchpad.yaml").write_text(config)
+
+        result = self.run_command(
+            "run-one", "--package-repository", "one more", "test", "0"
+        )
+
+        self.assertEqual(0, result.exit_code)
+
+        mock_info = launcher.return_value.push_file_io.call_args_list
+        self.assertEqual(
+            Path("/etc/apt/sources.list"), mock_info[0][1]["destination"]
+        )
+
+        file_contents = mock_info[0][1]["content"].read().decode()
+        self.assertEqual(
+            dedent(
+                """\
+            deb http://archive.ubuntu.com/ubuntu/ focal main restricted
+            deb-src http://archive.ubuntu.com/ubuntu/ focal main restricted
+            one more
+            """  # noqa: E501
+            ),
+            file_contents,
+        )
+
     @patch("lpcraft.env.get_managed_environment_project_path")
     @patch("lpcraft.commands.run.get_provider")
     @patch("lpcraft.commands.run.get_host_architecture", return_value="amd64")
diff --git a/lpcraft/config.py b/lpcraft/config.py
index e8638da..d8d2045 100644
--- a/lpcraft/config.py
+++ b/lpcraft/config.py
@@ -167,7 +167,7 @@ class Job(ModelConfigDefaults):
     input: Optional[Input]
     snaps: Optional[List[StrictStr]]
     packages: Optional[List[StrictStr]]
-    package_repositories: Optional[List[PackageRepository]]
+    package_repositories: List[PackageRepository] = []
     plugin: Optional[StrictStr]
     plugin_config: Optional[BaseConfig]
 

Follow ups