From b6ce7100b1f440554bae3b20da95f4d58f518400 Mon Sep 17 00:00:00 2001 From: Alex Lowe Date: Wed, 2 Oct 2024 17:23:19 -0400 Subject: [PATCH 1/8] build(deps): switch to requests-unixsocket2 --- setup.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/setup.py b/setup.py index b893e30c37..2d4a2377da 100755 --- a/setup.py +++ b/setup.py @@ -121,7 +121,7 @@ def recursive_data_files(directory, install_directory): "pyyaml", "raven", "requests-toolbelt", - "requests-unixsocket", + "requests-unixsocket2", "requests", # pin setuptools<66 (CRAFT-1598) "setuptools<66", @@ -131,7 +131,6 @@ def recursive_data_files(directory, install_directory): "toml", "tinydb", "typing-extensions", - "urllib3<2", # requests-unixsocket does not yet work with urllib3 v2.0+ ] try: From 0769811adc5adac134191a3e77dd56423707047b Mon Sep 17 00:00:00 2001 From: Alex Lowe Date: Wed, 2 Oct 2024 17:24:11 -0400 Subject: [PATCH 2/8] build(deps): update dependencies --- requirements-devel.txt | 12 ++++++------ requirements.txt | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/requirements-devel.txt b/requirements-devel.txt index 2163371a67..92e9651bd0 100644 --- a/requirements-devel.txt +++ b/requirements-devel.txt @@ -13,9 +13,9 @@ colorama==0.4.6 coverage==7.2.5 craft-archives==1.1.3 craft-cli==1.2.0 -craft-grammar==1.1.1 -craft-parts==1.19.7 -craft-providers==1.20.2 +craft-grammar==1.1.2 +craft-parts==1.19.8 +craft-providers==1.20.4 craft-store==2.5.0 cryptography==40.0.2 Deprecated==1.2.14 @@ -93,7 +93,7 @@ PyYAML==6.0.2 raven==6.10.0 requests==2.30.0 requests-toolbelt==1.0.0 -requests-unixsocket==0.3.0 +requests-unixsocket2==0.4.2 ruff==0.0.220 SecretStorage==3.3.3 simplejson==3.19.2 @@ -116,7 +116,7 @@ types-setuptools==67.7.0.2 types-tabulate==0.9.0.20240106 types-urllib3==1.26.25.14 typing_extensions==4.5.0 -urllib3==1.26.19 +urllib3==2.2.3 venusian==3.0.0 virtualenv==20.23.0 wadllib==1.3.6 @@ -125,6 +125,6 @@ wrapt==1.15.0 ws4py==0.5.1 zope.deprecation==5.0 zope.interface==6.0 -python-apt @ https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/python-apt/2.0.1ubuntu0.20.04.1/python-apt_2.0.1ubuntu0.20.04.1.tar.xz; sys.platform == "linux" +python-apt @ https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/python-apt/2.0.1ubuntu0.20.04.1/python-apt_2.0.1ubuntu0.20.04.1.tar.xz ; sys.platform == "linux" setuptools<66 pyinstaller==4.10; sys.platform == "win32" diff --git a/requirements.txt b/requirements.txt index 82f60f1f0b..4db1d8a064 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,9 +7,9 @@ charset-normalizer==3.1.0 click==8.1.7 craft-archives==1.1.3 craft-cli==1.2.0 -craft-grammar==1.1.1 -craft-parts==1.19.7 -craft-providers==1.20.2 +craft-grammar==1.1.2 +craft-parts==1.19.8 +craft-providers==1.20.4 craft-store==2.5.0 cryptography==40.0.2 Deprecated==1.2.14 @@ -55,7 +55,7 @@ PyYAML==6.0.2 raven==6.10.0 requests==2.30.0 requests-toolbelt==1.0.0 -requests-unixsocket==0.3.0 +requests-unixsocket2==0.4.2 SecretStorage==3.3.3 simplejson==3.19.2 six==1.16.0 @@ -66,9 +66,9 @@ toml==0.10.2 types-Deprecated==1.2.9.20240311 types-PyYAML==6.0.12.20240808 typing_extensions==4.5.0 -urllib3==1.26.19 +urllib3==2.2.3 wadllib==1.3.6 wrapt==1.15.0 ws4py==0.5.1 zipp==3.15.0 -python-apt @ https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/python-apt/2.0.1ubuntu0.20.04.1/python-apt_2.0.1ubuntu0.20.04.1.tar.xz; sys.platform == "linux" +python-apt @ https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/python-apt/2.0.1ubuntu0.20.04.1/python-apt_2.0.1ubuntu0.20.04.1.tar.xz ; sys.platform == "linux" From 8fec6cae182e1a1f78b0e16d902ea62a313237d4 Mon Sep 17 00:00:00 2001 From: Alex Lowe Date: Wed, 2 Oct 2024 17:27:17 -0400 Subject: [PATCH 3/8] build(deps): update requests for security issue --- requirements-devel.txt | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements-devel.txt b/requirements-devel.txt index 92e9651bd0..5070baab70 100644 --- a/requirements-devel.txt +++ b/requirements-devel.txt @@ -91,7 +91,7 @@ pytz==2023.3 pyxdg==0.28 PyYAML==6.0.2 raven==6.10.0 -requests==2.30.0 +requests==2.32.3 requests-toolbelt==1.0.0 requests-unixsocket2==0.4.2 ruff==0.0.220 diff --git a/requirements.txt b/requirements.txt index 4db1d8a064..f7483945d5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -53,7 +53,7 @@ pytz==2023.3 pyxdg==0.28 PyYAML==6.0.2 raven==6.10.0 -requests==2.30.0 +requests==2.32.3 requests-toolbelt==1.0.0 requests-unixsocket2==0.4.2 SecretStorage==3.3.3 From e9f488239d6a5bfda5afc102693733b32f81bb57 Mon Sep 17 00:00:00 2001 From: Alex Lowe Date: Wed, 2 Oct 2024 17:42:59 -0400 Subject: [PATCH 4/8] build(deps): update pylxd --- requirements-devel.txt | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements-devel.txt b/requirements-devel.txt index 5070baab70..98ba127424 100644 --- a/requirements-devel.txt +++ b/requirements-devel.txt @@ -74,7 +74,7 @@ pyftpdlib==1.5.10 pylint==2.17.7 pylint-fixme-info==1.0.4 pylint-pytest==1.1.8 -pylxd==2.3.4 +pylxd==2.3.5 pymacaroons==0.13.0 PyNaCl==1.5.0 pyparsing==3.0.9 diff --git a/requirements.txt b/requirements.txt index f7483945d5..9cc2d2172a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -42,7 +42,7 @@ pycparser==2.21 pydantic==1.10.7 pydantic-yaml==0.11.2 pyelftools==0.29 -pylxd==2.3.4 +pylxd==2.3.5 pymacaroons==0.13.0 PyNaCl==1.5.0 pyparsing==3.0.9 From bf9a203df2e186920625a84b858d4364fac7ff67 Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Thu, 3 Oct 2024 10:37:03 -0500 Subject: [PATCH 5/8] tests: pin deps in electron-builder test Signed-off-by: Callahan Kovacs --- .../electron-builder-hello-world/package.json | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/spread/electron-builder/no-template/electron-builder-hello-world/package.json b/tests/spread/electron-builder/no-template/electron-builder-hello-world/package.json index 83884ea31d..216ae8f342 100644 --- a/tests/spread/electron-builder/no-template/electron-builder-hello-world/package.json +++ b/tests/spread/electron-builder/no-template/electron-builder-hello-world/package.json @@ -17,9 +17,12 @@ ], "author": "GitHub", "license": "CC0-1.0", + "resolutions": { + "minimatch": "9.0.5" + }, "devDependencies": { - "electron": "latest", - "electron-builder": "latest", - "electron-installer-snap": "latest" + "electron": "31.3.1", + "electron-builder": "25.0.1", + "electron-installer-snap": "5.2.0" } } From 702ed0b11465b0acd6dd68a9f5ee45fae938046b Mon Sep 17 00:00:00 2001 From: Alex Lowe Date: Thu, 3 Oct 2024 14:41:51 -0400 Subject: [PATCH 6/8] fix(tests/legacy): don't send bad data --- tests/legacy/fake_servers/snapd.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/legacy/fake_servers/snapd.py b/tests/legacy/fake_servers/snapd.py index 4616b28a32..be5c52a01f 100644 --- a/tests/legacy/fake_servers/snapd.py +++ b/tests/legacy/fake_servers/snapd.py @@ -53,7 +53,6 @@ def _handle_snaps(self): def _handle_snap_file(self, parsed_url): self.send_response(200) - self.send_header("Content-Length", len(parsed_url)) self.send_header("Content-type", "text/plain") self.end_headers() self.wfile.write(parsed_url.encode()) From 1a89a70a210acb32e5fad44aabc81eaa5951014e Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Thu, 3 Oct 2024 07:55:42 -0500 Subject: [PATCH 7/8] refactor: rename snap_name Rename legacy variables to match snapd's definition: snap_name->snap_instance_name (possibly aliased) snap_store_name->snap_name (unaliased) Signed-off-by: Callahan Kovacs --- .../internal/build_providers/_snap.py | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/snapcraft_legacy/internal/build_providers/_snap.py b/snapcraft_legacy/internal/build_providers/_snap.py index db4b1cf101..bd85107353 100644 --- a/snapcraft_legacy/internal/build_providers/_snap.py +++ b/snapcraft_legacy/internal/build_providers/_snap.py @@ -58,9 +58,10 @@ def __init__( latest_revision: Optional[str], inject_from_host: bool = True ) -> None: - self.snap_name = snap_name - # the local snap name may have a suffix if it was installed with `--name` - self.snap_store_name = snap_name.split("_")[0] + # name of the snap instance, which may have an alias + self.snap_instance_name = snap_name + # name of the snap (no alias) + self.snap_name = snap_name.split("_")[0] self._remote_snap_dir = remote_snap_dir self._inject_from_host = inject_from_host @@ -74,7 +75,7 @@ def __init__( def _get_snap_repo(self): if self.__repo is None: - self.__repo = repo.snaps.SnapPackage(self.snap_name) + self.__repo = repo.snaps.SnapPackage(self.snap_instance_name) return self.__repo def get_op(self) -> _SnapOp: @@ -125,7 +126,7 @@ def get_op(self) -> _SnapOp: # This is a programmatic error raise RuntimeError( "Unhandled scenario for {!r} (host installed: {}, latest_revision {})".format( - self.snap_name, is_installed, self._latest_revision + self.snap_instance_name, is_installed, self._latest_revision ) ) @@ -136,9 +137,9 @@ def push_host_snap(self, *, file_pusher: Callable[..., None]) -> None: # TODO not being able to lock down on a snap revision can lead to races. host_snap_repo = self._get_snap_repo() with tempfile.TemporaryDirectory() as temp_dir: - snap_file_path = os.path.join(temp_dir, "{}.snap".format(self.snap_name)) + snap_file_path = os.path.join(temp_dir, "{}.snap".format(self.snap_instance_name)) assertion_file_path = os.path.join( - temp_dir, "{}.assert".format(self.snap_name) + temp_dir, "{}.assert".format(self.snap_instance_name) ) host_snap_repo.local_download( snap_path=snap_file_path, assertion_path=assertion_file_path @@ -171,7 +172,7 @@ def _set_data(self) -> None: switch_cmd = [ "snap", "switch", - self.snap_name, + self.snap_instance_name, "--channel", snap_channel, ] @@ -198,9 +199,9 @@ def _set_data(self) -> None: elif op == _SnapOp.INSTALL or op == _SnapOp.REFRESH: install_cmd = ["snap", op.name.lower()] - snap_channel = _get_snap_channel(self.snap_store_name) + snap_channel = _get_snap_channel(self.snap_name) - store_snap_info = storeapi.SnapAPI().get_info(self.snap_store_name) + store_snap_info = storeapi.SnapAPI().get_info(self.snap_name) snap_channel_map = store_snap_info.get_channel_mapping( risk=snap_channel.risk, track=snap_channel.track ) @@ -208,7 +209,7 @@ def _set_data(self) -> None: if snap_channel_map.confinement == "classic": install_cmd.append("--classic") install_cmd.extend(["--channel", snap_channel_map.channel_details.name]) - install_cmd.append(self.snap_store_name) + install_cmd.append(self.snap_name) self.__install_cmd = install_cmd self.__switch_cmd = switch_cmd @@ -224,7 +225,7 @@ def get_revision(self) -> str: # Shouldn't happen. raise RuntimeError( "Unhandled scenario for {!r} (revision {})".format( - self.snap_name, self.__revision + self.snap_instance_name, self.__revision ) ) @@ -238,7 +239,7 @@ def get_snap_install_cmd(self) -> List[str]: if self.__install_cmd is None: raise RuntimeError( "Unhandled scenario for {!r} (install_cmd {})".format( - self.snap_name, self.__install_cmd + self.snap_instance_name, self.__install_cmd ) ) @@ -259,7 +260,7 @@ def get_assertion_ack_cmd(self) -> List[str]: if self.__assertion_ack_cmd is None: raise RuntimeError( "Unhandled scenario for {!r} (assertion_ack_cmd {})".format( - self.snap_name, self.__assertion_ack_cmd + self.snap_instance_name, self.__assertion_ack_cmd ) ) @@ -379,7 +380,7 @@ def apply(self) -> None: return # Allow using snapd from the snapd snap to leverage newer snapd features. - if any(s.snap_name == "snapd" for s in self._snaps): + if any(s.snap_instance_name == "snapd" for s in self._snaps): self._enable_snapd_snap() # Disable refreshes so they do not interfere with installation ops. @@ -396,6 +397,6 @@ def apply(self) -> None: self._runner(snap.get_snap_install_cmd()) if snap.get_channel_switch_cmd() is not None: self._runner(snap.get_channel_switch_cmd()) - self._record_revision(snap.snap_store_name, snap.get_revision()) + self._record_revision(snap.snap_name, snap.get_revision()) _save_registry(self._registry_data, self._registry_filepath) From 119b92a5fd2df06ccd53831aa7965a8ecca3f580 Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Thu, 3 Oct 2024 08:51:30 -0500 Subject: [PATCH 8/8] fix(snapcraft_legacy): get assertions for aliased snaps Signed-off-by: Callahan Kovacs --- .../internal/build_providers/_snap.py | 6 +- snapcraft_legacy/internal/repo/snaps.py | 8 +- .../legacy/unit/build_providers/test_snap.py | 118 ++++++++++++++++++ tests/legacy/unit/repo/test_snaps.py | 42 +++++++ 4 files changed, 168 insertions(+), 6 deletions(-) diff --git a/snapcraft_legacy/internal/build_providers/_snap.py b/snapcraft_legacy/internal/build_providers/_snap.py index bd85107353..60191cd888 100644 --- a/snapcraft_legacy/internal/build_providers/_snap.py +++ b/snapcraft_legacy/internal/build_providers/_snap.py @@ -170,11 +170,7 @@ def _set_data(self) -> None: if not snap_revision.startswith("x") and snap_channel: switch_cmd = [ - "snap", - "switch", - self.snap_instance_name, - "--channel", - snap_channel, + "snap", "switch", self.snap_name, "--channel", snap_channel ] if snap_revision.startswith("x"): diff --git a/snapcraft_legacy/internal/repo/snaps.py b/snapcraft_legacy/internal/repo/snaps.py index 75e286c5fc..0a779b0550 100644 --- a/snapcraft_legacy/internal/repo/snaps.py +++ b/snapcraft_legacy/internal/repo/snaps.py @@ -179,7 +179,13 @@ def local_download(self, *, snap_path: str, assertion_path: str) -> None: # We write an empty assertions file for dangerous installs to # have a consistent interface. if self.has_assertions(): - assertions.append(["snap-declaration", "snap-name={}".format(self.name)]) + assertions.append( + [ + "snap-declaration", + # use the snap name without any alias + f"snap-name={self.name.partition('_')[0]}" + ] + ) assertions.append( [ "snap-revision", diff --git a/tests/legacy/unit/build_providers/test_snap.py b/tests/legacy/unit/build_providers/test_snap.py index 40bff35b19..762ad15c88 100644 --- a/tests/legacy/unit/build_providers/test_snap.py +++ b/tests/legacy/unit/build_providers/test_snap.py @@ -162,6 +162,124 @@ def test_snapcraft_installed_on_host_from_store(self): ), ) + def test_snapcraft_installed_on_host_aliased_from_store(self): + self.fake_snapd.snaps_result = [ + { + "name": "snapd", + "confinement": "strict", + "id": "2kkitQ", + "channel": "edge", + "revision": "1", + "tracking-channel": "latest/edge", + }, + { + "name": "core18", + "confinement": "strict", + "id": "2kkibb", + "channel": "stable", + "revision": "123", + "tracking-channel": "latest/beta", + }, + { + "name": "snapcraft_alias", + "confinement": "classic", + "id": "3lljuR", + "channel": "edge", + "revision": "345", + "tracking-channel": "latest/candidate", + }, + ] + self.get_assertion_mock.side_effect = [ + b"fake-assertion-account-store", + b"fake-assertion-declaration-snapd", + b"fake-assertion-revision-snapd-1", + b"fake-assertion-account-store", + b"fake-assertion-declaration-core18", + b"fake-assertion-revision-core18-123", + b"fake-assertion-account-store", + b"fake-assertion-declaration-snapcraft", + b"fake-assertion-revision-snapcraft-345", + ] + + snap_injector = SnapInjector( + registry_filepath=self.registry_filepath, + runner=self.provider._run, + file_pusher=self.provider._push_file, + ) + snap_injector.add("snapd") + snap_injector.add("core18") + snap_injector.add("snapcraft_alias") + snap_injector.apply() + + get_assertion_calls = [ + call( + [ + "account-key", + "public-key-sha3-384=BWDEoaqyr25nF5SNCvEv2v7QnM9QsfCc0PBMYD_i2NGSQ32EF2d4D0hqUel3m8ul", + ] + ), + call(["snap-declaration", "snap-name=snapd"]), + call(["snap-revision", "snap-revision=1", "snap-id=2kkitQ"]), + call( + [ + "account-key", + "public-key-sha3-384=BWDEoaqyr25nF5SNCvEv2v7QnM9QsfCc0PBMYD_i2NGSQ32EF2d4D0hqUel3m8ul", + ] + ), + call(["snap-declaration", "snap-name=core18"]), + call(["snap-revision", "snap-revision=123", "snap-id=2kkibb"]), + call( + [ + "account-key", + "public-key-sha3-384=BWDEoaqyr25nF5SNCvEv2v7QnM9QsfCc0PBMYD_i2NGSQ32EF2d4D0hqUel3m8ul", + ] + ), + call(["snap-declaration", "snap-name=snapcraft"]), + call(["snap-revision", "snap-revision=345", "snap-id=3lljuR"]), + ] + self.get_assertion_mock.assert_has_calls(get_assertion_calls) + self.provider.run_mock.assert_has_calls( + [ + call(["snap", "set", "system", "experimental.snapd-snap=true"]), + call(["snap", "set", "system", ANY]), + call(["snap", "watch", "--last=auto-refresh?"]), + call(["snap", "ack", "/var/tmp/snapd.assert"]), + call(["snap", "install", "/var/tmp/snapd.snap"]), + call(["snap", "switch", "snapd", "--channel", "latest/edge"]), + call(["snap", "ack", "/var/tmp/core18.assert"]), + call(["snap", "install", "/var/tmp/core18.snap"]), + call(["snap", "switch", "core18", "--channel", "latest/beta"]), + call(["snap", "ack", "/var/tmp/snapcraft_alias.assert"]), + call(["snap", "install", "--classic", "/var/tmp/snapcraft_alias.snap"]), + call(["snap", "switch", "snapcraft", "--channel", "latest/candidate"]), + ] + ) + self.provider.push_file_mock.assert_has_calls( + [ + call(source=ANY, destination="/var/tmp/snapd.snap"), + call(source=ANY, destination="/var/tmp/snapd.assert"), + call(source=ANY, destination="/var/tmp/core18.snap"), + call(source=ANY, destination="/var/tmp/core18.assert"), + call(source=ANY, destination="/var/tmp/snapcraft_alias.snap"), + call(source=ANY, destination="/var/tmp/snapcraft_alias.assert"), + ] + ) + self.assertThat( + self.registry_filepath, + FileContains( + dedent( + """\ + core18: + - revision: '123' + snapcraft: + - revision: '345' + snapd: + - revision: '1' + """ + ) + ), + ) + def test_snapcraft_installed_on_host_from_store_but_injection_disabled(self): self.useFixture(fixture_setup.FakeStore()) diff --git a/tests/legacy/unit/repo/test_snaps.py b/tests/legacy/unit/repo/test_snaps.py index bf7f8ffcb6..13d813dbc3 100644 --- a/tests/legacy/unit/repo/test_snaps.py +++ b/tests/legacy/unit/repo/test_snaps.py @@ -348,6 +348,48 @@ def test_download_from_host(self): ] ) + def test_download_from_host_alias(self): + """Download an aliased snap from the host.""" + fake_get_assertion = fixtures.MockPatch( + "snapcraft_legacy.internal.repo.snaps.get_assertion", + return_value=b"foo-assert", + ) + self.useFixture(fake_get_assertion) + + self.fake_snapd.snaps_result = [ + { + "id": "fake-snap-id", + "name": "fake-snap_alias", + "channel": "stable", + "revision": "10", + } + ] + + snap_pkg = snaps.SnapPackage("fake-snap_alias/strict/stable") + snap_pkg.local_download( + snap_path="fake-snap.snap", assertion_path="fake-snap.assert" + ) + + self.assertThat("fake-snap.snap", FileExists()) + self.assertThat( + "fake-snap.assert", FileContains("foo-assert\nfoo-assert\nfoo-assert\n") + ) + fake_get_assertion.mock.assert_has_calls( + [ + mock.call( + [ + "account-key", + "public-key-sha3-384=BWDEoaqyr25nF5SNCvEv2v7QnM9QsfCc0PBMYD_i2NGSQ32EF2d4D0hqUel3m8ul", + ] + ), + # uses the non-aliased name + mock.call(["snap-declaration", "snap-name=fake-snap"]), + mock.call( + ["snap-revision", "snap-revision=10", "snap-id=fake-snap-id"] + ), + ] + ) + def test_download_from_host_dangerous(self): fake_get_assertion = fixtures.MockPatch( "snapcraft_legacy.internal.repo.snaps.get_assertion",