From a659a2ba97a0a4bee75f6af871f2ccfc7cc5a35d Mon Sep 17 00:00:00 2001 From: Tiago Nobrega Date: Mon, 22 Apr 2024 11:11:49 -0300 Subject: [PATCH] fix: install gpg and dirmngr in core24 builds This commit mimicks the behavior for core22<= builds where gpg and dirmngr are installed at runtime for projects with package-repositories. The trigger for this commit is the fact that the environment where Snapcraft builds in Launchpad do _not_ have the 'dirmngr' package installed. Unfortunately the strategy of bundling 'gpg' and 'dirmngr' as stage-packages in Snapcraft's own snap didn't work because gpg has the expected path to the 'dirmngr' executable hardcoded (always trying to call '/usr/bin/dirmngr'). Fixes #4740 --- snapcraft/services/lifecycle.py | 9 ++++++++ .../core24/package-repositories/task.yaml | 7 ++++++ tests/unit/services/test_lifecycle.py | 23 +++++++++++++++++++ 3 files changed, 39 insertions(+) diff --git a/snapcraft/services/lifecycle.py b/snapcraft/services/lifecycle.py index 715e7610292..d652412a84a 100644 --- a/snapcraft/services/lifecycle.py +++ b/snapcraft/services/lifecycle.py @@ -25,6 +25,7 @@ from craft_application import AppMetadata, LifecycleService, ServiceFactory from craft_application.models import BuildInfo from craft_parts import ProjectInfo, StepInfo +from craft_parts.packages import Repository as Repo from overrides import overrides from snapcraft import __version__, errors, models, os_release, parts, utils @@ -60,6 +61,14 @@ def __init__( # noqa: PLR0913 (too many arguments) def setup(self) -> None: project = cast(models.Project, self._project) + if project.package_repositories: + # Note: we unfortunately need to handle missing gpg/dirmngr binaries + # ourselves here, as this situation happens in Launchpad (where + # builds are executed destructively). + required_packages = ["gpg", "dirmngr"] + if any(p for p in required_packages if not Repo.is_package_installed(p)): + Repo.install_packages(required_packages, refresh_package_cache=False) + # Have the lifecycle install the base snap, and look into it when # determining the package cutoff. self._manager_kwargs.update( diff --git a/tests/spread/core24/package-repositories/task.yaml b/tests/spread/core24/package-repositories/task.yaml index 74f0c28d41f..96db7835e5a 100644 --- a/tests/spread/core24/package-repositories/task.yaml +++ b/tests/spread/core24/package-repositories/task.yaml @@ -10,7 +10,14 @@ environment: SNAP/test_foreign_armhf: test-foreign-armhf SNAP/test_foreign_i386: test-foreign-i386 +prepare: | + # Remove the currently installed "gpg" and "dirmngr" packages to ensure that + # Snapcraft itself is installing them when necessary. + sudo dpkg --remove --force-depends gpg dirmngr + restore: | + sudo apt install -y gpg dirmngr + cd "$SNAP" rm -f ./*.snap snapcraft clean diff --git a/tests/unit/services/test_lifecycle.py b/tests/unit/services/test_lifecycle.py index 96e33f8ccd1..f5081983be9 100644 --- a/tests/unit/services/test_lifecycle.py +++ b/tests/unit/services/test_lifecycle.py @@ -23,6 +23,7 @@ import pytest import pytest_subprocess +from craft_parts.packages import Repository from snapcraft import __version__, models, os_release, utils @@ -198,3 +199,25 @@ def test_lifecycle_prime_dirs(lifecycle_service): lifecycle_service.setup() assert lifecycle_service.prime_dirs == {None: lifecycle_service._work_dir / "prime"} + + +@pytest.fixture() +def extra_project_params(extra_project_params): + """Add package-repositories configuration to the default project.""" + extra_project_params["package_repositories"] = [{"type": "apt", "ppa": "test/ppa"}] + return extra_project_params + + +@pytest.mark.usefixtures("default_project") +def test_lifecycle_installs_gpg_dirmngr(lifecycle_service, mocker): + mock_is_installed = mocker.patch.object( + Repository, "is_package_installed", return_value=False + ) + mock_install = mocker.patch.object(Repository, "install_packages") + + lifecycle_service.setup() + + assert mock_is_installed.called + mock_install.assert_called_once_with( + ["gpg", "dirmngr"], refresh_package_cache=False + )