launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #28694
[Merge] ~jugmac00/lpcraft:add-additional-apt-repositories into lpcraft:main
Jürgen Gmach has proposed merging ~jugmac00/lpcraft:add-additional-apt-repositories into lpcraft:main.
Commit message:
Add new configuration option to provide additional apt repositories
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~jugmac00/lpcraft/+git/lpcraft/+merge/425829
--
Your team Launchpad code reviewers is requested to review the proposed merge of ~jugmac00/lpcraft:add-additional-apt-repositories into lpcraft:main.
diff --git a/NEWS.rst b/NEWS.rst
index 72603d6..8f0d35c 100644
--- a/NEWS.rst
+++ b/NEWS.rst
@@ -9,6 +9,8 @@ Version history
- Hide the internal ``run-one`` command from ``--help`` output.
+- Add new configuration option to provide additional apt repositories.
+
0.0.17 (2022-06-17)
===================
diff --git a/docs/configuration.rst b/docs/configuration.rst
index ecaa727..c4c5af4 100644
--- a/docs/configuration.rst
+++ b/docs/configuration.rst
@@ -50,6 +50,10 @@ Job definitions
``packages`` (optional)
Packages to install using ``apt`` as dependencies of this job.
+``additional-apt-repositories`` (optional)
+ Repositories which will be added to the already existing ones in
+ `/etc/apt/sources.list`.
+
``snaps`` (optional)
Snaps to install as dependencies of this job.
diff --git a/lpcraft/commands/run.py b/lpcraft/commands/run.py
index dd39c1c..60925af 100644
--- a/lpcraft/commands/run.py
+++ b/lpcraft/commands/run.py
@@ -9,6 +9,7 @@ import os
import shlex
from argparse import ArgumentParser, Namespace
from pathlib import Path, PurePath
+from tempfile import mkstemp
from typing import Dict, List, Optional, Set
from craft_cli import BaseCommand, EmitterMode, emit
@@ -226,19 +227,33 @@ def _install_apt_packages(
host_architecture: str,
remote_cwd: Path,
apt_replacement_repositories: Optional[List[str]],
+ additional_apt_repositories: Optional[List[str]],
environment: Optional[Dict[str, Optional[str]]],
) -> None:
- if apt_replacement_repositories:
- # replace sources.list
- lines = "\n".join(apt_replacement_repositories) + "\n"
+ if apt_replacement_repositories or additional_apt_repositories:
+ sources_list_path = "/etc/apt/sources.list"
+ _, tmpfile = mkstemp()
+ try:
+ instance.pull_file(
+ source=Path(sources_list_path), destination=Path(tmpfile)
+ )
+ except Exception as e:
+ raise CommandError(str(e), retcode=1)
+ with open(tmpfile) as f:
+ sources = f.read()
+ if apt_replacement_repositories:
+ sources = "\n".join(apt_replacement_repositories) + "\n"
+ if additional_apt_repositories:
+ sources += "\n" + "\n".join(additional_apt_repositories) + "\n"
with emit.open_stream("Replacing /etc/apt/sources.list") as stream:
instance.push_file_io(
- destination=PurePath("/etc/apt/sources.list"),
- content=io.BytesIO(lines.encode()),
+ destination=PurePath(sources_list_path),
+ content=io.BytesIO(sources.encode()),
file_mode="0644",
group="root",
user="root",
)
+
# update local repository information
apt_update = ["apt", "update"]
with emit.open_stream(f"Running {apt_update}") as stream:
@@ -316,6 +331,7 @@ def _run_job(
provider: Provider,
output: Optional[Path],
apt_replacement_repositories: Optional[List[str]] = None,
+ additional_apt_repositories: Optional[List[str]] = None,
env_from_cli: Optional[List[str]] = None,
plugin_settings: Optional[List[str]] = None,
) -> None:
@@ -406,6 +422,7 @@ def _run_job(
host_architecture=host_architecture,
remote_cwd=remote_cwd,
apt_replacement_repositories=apt_replacement_repositories,
+ additional_apt_repositories=additional_apt_repositories,
environment=environment,
)
for cmd in (pre_run_command, run_command, post_run_command):
@@ -518,6 +535,7 @@ class RunCommand(BaseCommand):
apt_replacement_repositories=(
args.apt_replace_repositories
),
+ additional_apt_repositories=job.additional_apt_repositories, # noqa: E501
env_from_cli=args.set_env,
plugin_settings=args.plugin_setting,
)
@@ -625,6 +643,7 @@ class RunOneCommand(BaseCommand):
provider,
args.output_directory,
apt_replacement_repositories=args.apt_replace_repositories,
+ additional_apt_repositories=job.additional_apt_repositories,
env_from_cli=args.set_env,
plugin_settings=args.plugin_setting,
)
diff --git a/lpcraft/commands/tests/test_run.py b/lpcraft/commands/tests/test_run.py
index 165d435..86c6df5 100644
--- a/lpcraft/commands/tests/test_run.py
+++ b/lpcraft/commands/tests/test_run.py
@@ -1995,6 +1995,89 @@ class TestRun(RunBaseTestCase):
execute_run.call_args_list,
)
+ @patch("lpcraft.commands.run.get_provider")
+ @patch("lpcraft.commands.run.get_host_architecture", return_value="amd64")
+ def test_run_with_additional_apt_repositories(
+ 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
+ additional_repositories = [
+ "deb https://canonical.example.org/artifactory/jammy-golang-backport focal main", # noqa: E501
+ "deb https://canonical.example.org/artifactory/jammy-golang-backport focal universe", # noqa: E501
+ ]
+ config = dedent(
+ f"""
+ pipeline:
+ - test
+ jobs:
+ test:
+ series: focal
+ architectures: amd64
+ run: ls -la
+ packages: [git]
+ additional-apt-repositories: {additional_repositories}
+ """
+ )
+ Path(".launchpad.yaml").write_text(config)
+
+ result = self.run_command("run")
+
+ self.assertEqual(0, result.exit_code)
+ self.assertEqual(
+ [
+ call(
+ ["apt", "update"],
+ cwd=Path("/root/lpcraft/project"),
+ env={},
+ stdout=ANY,
+ stderr=ANY,
+ ),
+ call(
+ ["apt", "install", "-y", "git"],
+ cwd=Path("/root/lpcraft/project"),
+ env={},
+ stdout=ANY,
+ stderr=ANY,
+ ),
+ call(
+ ["bash", "--noprofile", "--norc", "-ec", "ls -la"],
+ cwd=Path("/root/lpcraft/project"),
+ env={},
+ stdout=ANY,
+ stderr=ANY,
+ ),
+ ],
+ execute_run.call_args_list,
+ )
+ 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(
+ "\n".join(existing_repositories)
+ + "\n"
+ + "\n".join(additional_repositories)
+ + "\n",
+ file_contents,
+ )
+
class TestRunOne(RunBaseTestCase):
def test_config_file_not_under_project_directory(self):
@@ -2637,3 +2720,131 @@ class TestRunOne(RunBaseTestCase):
],
execute_run.call_args_list,
)
+
+ @patch("lpcraft.commands.run.get_provider")
+ @patch("lpcraft.commands.run.get_host_architecture", return_value="amd64")
+ def test_run_with_additional_apt_repositories(
+ 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
+ additional_repositories = [
+ "deb https://canonical.example.org/artifactory/jammy-golang-backport focal main", # noqa: E501
+ "deb https://canonical.example.org/artifactory/jammy-golang-backport focal universe", # noqa: E501
+ ]
+ config = dedent(
+ f"""
+ pipeline:
+ - test
+ jobs:
+ test:
+ series: focal
+ architectures: amd64
+ run: ls -la
+ packages: [git]
+ additional-apt-repositories: {additional_repositories}
+ """
+ )
+ Path(".launchpad.yaml").write_text(config)
+
+ result = self.run_command("run-one", "test", "0")
+
+ self.assertEqual(0, result.exit_code)
+ self.assertEqual(
+ [
+ call(
+ ["apt", "update"],
+ cwd=Path("/root/lpcraft/project"),
+ env={},
+ stdout=ANY,
+ stderr=ANY,
+ ),
+ call(
+ ["apt", "install", "-y", "git"],
+ cwd=Path("/root/lpcraft/project"),
+ env={},
+ stdout=ANY,
+ stderr=ANY,
+ ),
+ call(
+ ["bash", "--noprofile", "--norc", "-ec", "ls -la"],
+ cwd=Path("/root/lpcraft/project"),
+ env={},
+ stdout=ANY,
+ stderr=ANY,
+ ),
+ ],
+ execute_run.call_args_list,
+ )
+ 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(
+ "\n".join(existing_repositories)
+ + "\n"
+ + "\n".join(additional_repositories)
+ + "\n",
+ 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")
+ def test_fails_pulling_sources_list(
+ self,
+ mock_get_host_architecture,
+ mock_get_provider,
+ mock_get_project_path,
+ ):
+ launcher = Mock(spec=launch)
+ provider = makeLXDProvider(lxd_launcher=launcher)
+ mock_get_provider.return_value = provider
+ execute_run = LocalExecuteRun(self.tmp_project_path)
+ launcher.return_value.execute_run = execute_run
+ launcher.return_value.pull_file.side_effect = FileNotFoundError(
+ "File not found"
+ )
+ additional_repositories = [
+ "deb https://canonical.example.org/artifactory/jammy-golang-backport focal main", # noqa: E501
+ "deb https://canonical.example.org/artifactory/jammy-golang-backport focal universe", # noqa: E501
+ ]
+ config = dedent(
+ f"""
+ pipeline:
+ - test
+ jobs:
+ test:
+ series: focal
+ architectures: amd64
+ run: ls -la
+ packages: [git]
+ additional-apt-repositories: {additional_repositories}
+ """
+ )
+ Path(".launchpad.yaml").write_text(config)
+
+ result = self.run_command("run-one", "test", "0")
+
+ self.assertThat(
+ result,
+ MatchesStructure.byEquality(
+ exit_code=1, errors=[CommandError("File not found", retcode=1)]
+ ),
+ )
diff --git a/lpcraft/config.py b/lpcraft/config.py
index 0a4120d..f4e37cc 100644
--- a/lpcraft/config.py
+++ b/lpcraft/config.py
@@ -92,6 +92,7 @@ class Job(ModelConfigDefaults):
output: Optional[Output]
snaps: Optional[List[StrictStr]]
packages: Optional[List[StrictStr]]
+ additional_apt_repositories: Optional[List[StrictStr]]
plugin: Optional[StrictStr]
plugin_config: Optional[BaseConfig]
diff --git a/lpcraft/tests/test_config.py b/lpcraft/tests/test_config.py
index d34b718..6e039c0 100644
--- a/lpcraft/tests/test_config.py
+++ b/lpcraft/tests/test_config.py
@@ -403,3 +403,29 @@ class TestConfig(TestCase):
config = Config.load(path)
self.assertEqual("tox", config.jobs["test"][0].plugin)
+
+ def test_additional_apt_repositories(self):
+ path = self.create_config(
+ dedent(
+ """
+ pipeline:
+ - test
+
+ jobs:
+ test:
+ series: focal
+ architectures: amd64
+ packages: [nginx, apache2]
+ additional-apt-repositories: ["deb https://canonical.example.org/artifactory/jammy-golang-backport focal main"]
+ """ # noqa: E501
+ )
+ )
+
+ config = Config.load(path)
+
+ self.assertEqual(
+ [
+ "deb https://canonical.example.org/artifactory/jammy-golang-backport focal main" # noqa: E501
+ ],
+ config.jobs["test"][0].additional_apt_repositories,
+ )
Follow ups