launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #27784
[Merge] ~jugmac00/lpcraft:declare-snap-dependencies-for-jobs into lpcraft:main
Jürgen Gmach has proposed merging ~jugmac00/lpcraft:declare-snap-dependencies-for-jobs into lpcraft:main.
Commit message:
Declare snap dependencies for jobs
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~jugmac00/lpcraft/+git/lpcraft/+merge/412478
--
Your team Launchpad code reviewers is requested to review the proposed merge of ~jugmac00/lpcraft:declare-snap-dependencies-for-jobs into lpcraft:main.
diff --git a/lpcraft/commands/run.py b/lpcraft/commands/run.py
index 02e844a..79637a1 100644
--- a/lpcraft/commands/run.py
+++ b/lpcraft/commands/run.py
@@ -5,6 +5,7 @@ from argparse import Namespace
from pathlib import Path
from craft_cli import emit
+from craft_providers.actions.snap_installer import install_from_store
from lpcraft import env
from lpcraft.config import Config
@@ -35,7 +36,7 @@ def run(args: Namespace) -> int:
f"does not set 'run'"
)
- cmd = ["bash", "--noprofile", "--norc", "-ec", job.run]
+ run_cmd = ["bash", "--noprofile", "--norc", "-ec", job.run]
emit.progress(
f"Launching environment for {job.series}/{host_architecture}"
@@ -46,10 +47,22 @@ def run(args: Namespace) -> int:
series=job.series,
architecture=host_architecture,
) as instance:
+ if job.snaps:
+ emit.progress("Installing snaps")
+ for snap in job.snaps:
+ emit.progress(f"Running `snap install` {snap}")
+ install_from_store(
+ executor=instance,
+ snap_name=snap,
+ channel="stable",
+ classic=True,
+ )
+
+ run_cmd = ["bash", "--noprofile", "--norc", "-ec", job.run]
emit.progress("Running the job")
- with emit.open_stream(f"Running {cmd}") as stream:
+ with emit.open_stream(f"Running {run_cmd}") as stream:
proc = instance.execute_run(
- cmd,
+ run_cmd,
cwd=env.get_managed_environment_project_path(),
env=job.environment,
stdout=stream,
diff --git a/lpcraft/commands/tests/test_run.py b/lpcraft/commands/tests/test_run.py
index 5b8b6bc..49d9679 100644
--- a/lpcraft/commands/tests/test_run.py
+++ b/lpcraft/commands/tests/test_run.py
@@ -418,3 +418,90 @@ class TestRun(CommandBaseTestCase):
],
execute_run.call_args_list,
)
+
+ @patch("lpcraft.commands.run.get_provider")
+ @patch("lpcraft.commands.run.get_host_architecture", return_value="amd64")
+ def test_install_snaps(
+ self, mock_get_host_architecture, mock_get_provider
+ ):
+ launcher = Mock(spec=launch)
+ provider = self.makeLXDProvider(lxd_launcher=launcher)
+ mock_get_provider.return_value = provider
+ execute_run = launcher.return_value.execute_run
+ execute_run.return_value = subprocess.CompletedProcess([], 0)
+ config = dedent(
+ """
+ pipeline:
+ - test
+
+ jobs:
+ test:
+ series: focal
+ architectures: amd64
+ run: tox
+ snaps: [chromium, firefox]
+ """
+ )
+ Path(".launchpad.yaml").write_text(config)
+
+ result = self.run_command("run")
+
+ self.assertEqual(0, result.exit_code)
+ self.assertEqual(
+ [
+ call(
+ [
+ "snap",
+ "download",
+ "chromium",
+ "--channel=stable",
+ "--basename=chromium",
+ "--target-directory=/tmp",
+ ],
+ check=True,
+ capture_output=True,
+ ),
+ call(
+ [
+ "snap",
+ "install",
+ "/tmp/chromium.snap",
+ "--classic",
+ "--dangerous",
+ ],
+ check=True,
+ capture_output=True,
+ ),
+ call(
+ [
+ "snap",
+ "download",
+ "firefox",
+ "--channel=stable",
+ "--basename=firefox",
+ "--target-directory=/tmp",
+ ],
+ check=True,
+ capture_output=True,
+ ),
+ call(
+ [
+ "snap",
+ "install",
+ "/tmp/firefox.snap",
+ "--classic",
+ "--dangerous",
+ ],
+ check=True,
+ capture_output=True,
+ ),
+ call(
+ ["bash", "--noprofile", "--norc", "-ec", "tox"],
+ cwd=Path("/root/project"),
+ env=None,
+ stdout=ANY,
+ stderr=ANY,
+ ),
+ ],
+ execute_run.call_args_list,
+ )
diff --git a/lpcraft/config.py b/lpcraft/config.py
index e4011f3..92c868e 100644
--- a/lpcraft/config.py
+++ b/lpcraft/config.py
@@ -27,6 +27,7 @@ class Job(ModelConfigDefaults):
architectures: List[StrictStr]
run: Optional[StrictStr]
environment: Optional[Dict[str, Optional[str]]]
+ snaps: Optional[List[StrictStr]]
@pydantic.validator("architectures", pre=True)
def validate_architectures(
@@ -36,6 +37,14 @@ class Job(ModelConfigDefaults):
v = [v]
return v
+ @pydantic.validator("snaps", pre=True)
+ def validate_snaps(
+ cls, v: Optional[Union[StrictStr, List[StrictStr]]]
+ ) -> Optional[List[StrictStr]]:
+ if isinstance(v, str):
+ v = [v]
+ return v
+
def _expand_job_values(
values: Dict[StrictStr, Any]
diff --git a/lpcraft/tests/test_config.py b/lpcraft/tests/test_config.py
index 16649ad..536bcee 100644
--- a/lpcraft/tests/test_config.py
+++ b/lpcraft/tests/test_config.py
@@ -150,3 +150,58 @@ class TestConfig(TestCase):
self.assertEqual(
{"ACTIVE": "1", "SKIP": "0"}, config.jobs["test"][0].environment
)
+
+ def test_load_single_snap(self):
+ # A single snap can be written as a string, and is automatically
+ # wrapped in a list.
+ path = self.create_config(
+ dedent(
+ """
+ pipeline:
+ - test
+
+ jobs:
+ test:
+ series: focal
+ architectures: amd64
+ snaps: chromium
+ """
+ )
+ )
+ config = Config.load(path)
+ self.assertEqual(["chromium"], config.jobs["test"][0].snaps)
+
+ def test_load_multiple_snaps(self):
+ path = self.create_config(
+ dedent(
+ """
+ pipeline:
+ - test
+
+ jobs:
+ test:
+ series: focal
+ architectures: amd64
+ snaps: [chromium, firefox]
+ """
+ )
+ )
+ config = Config.load(path)
+ self.assertEqual(["chromium", "firefox"], config.jobs["test"][0].snaps)
+
+ def test_load_config_without_snaps(self):
+ path = self.create_config(
+ dedent(
+ """
+ pipeline:
+ - test
+
+ jobs:
+ test:
+ series: focal
+ architectures: amd64
+ """
+ )
+ )
+ config = Config.load(path)
+ self.assertEqual(None, config.jobs["test"][0].snaps)