Skip to content

Commit e996996

Browse files
feat(models): add SnapInfo model for snapd response (#908)
1 parent ec0b4a4 commit e996996

File tree

7 files changed

+365
-95
lines changed

7 files changed

+365
-95
lines changed

craft_providers/actions/snap_installer.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
import subprocess
2828
import urllib.parse
2929
from http import HTTPStatus
30-
from typing import TYPE_CHECKING, Any, cast
30+
from typing import TYPE_CHECKING, cast
3131

3232
import pydantic
3333
import requests
@@ -40,6 +40,7 @@
4040
details_from_called_process_error,
4141
)
4242
from craft_providers.instance_config import InstanceConfiguration
43+
from craft_providers.models import SnapInfo
4344
from craft_providers.util import snap_cmd, temp_paths
4445

4546
if TYPE_CHECKING:
@@ -126,7 +127,7 @@ def _pack_host_snap(*, snap_name: str, output: pathlib.Path) -> None:
126127
)
127128

128129

129-
def get_host_snap_info(snap_name: str) -> dict[str, Any]:
130+
def get_host_snap_info(snap_name: str) -> SnapInfo:
130131
"""Get info about a snap installed on the host."""
131132
quoted_name = urllib.parse.quote(snap_name, safe="")
132133
url = f"http+unix://%2Frun%2Fsnapd.socket/v2/snaps/{quoted_name}"
@@ -137,7 +138,8 @@ def get_host_snap_info(snap_name: str) -> dict[str, Any]:
137138
brief="Unable to connect to snapd service."
138139
) from error
139140
snap_info.raise_for_status()
140-
return cast("dict[str, Any]", snap_info.json()["result"])
141+
result = snap_info.json()["result"]
142+
return SnapInfo.model_validate(result)
141143

142144

143145
def _get_target_snap_revision_from_snapd(
@@ -294,12 +296,15 @@ def _add_assertions_from_host(executor: Executor, snap_name: str) -> None:
294296
)
295297
snap_info = get_host_snap_info(snap_name)
296298

299+
if not snap_info.publisher:
300+
raise ProviderError("Can't get assertion for snap with no publisher info.")
301+
297302
try:
298303
with _get_assertions_file(
299304
snap_name=snap_name,
300-
snap_id=snap_info["id"],
301-
snap_revision=snap_info["revision"],
302-
snap_publisher_id=snap_info["publisher"]["id"],
305+
snap_id=snap_info.id,
306+
snap_revision=snap_info.revision,
307+
snap_publisher_id=snap_info.publisher.id,
303308
) as host_assert_path:
304309
executor.push_file(
305310
source=host_assert_path,
@@ -352,14 +357,14 @@ def inject_from_host(*, executor: Executor, snap_name: str, classic: bool) -> No
352357
)
353358

354359
host_snap_info = get_host_snap_info(snap_name)
355-
host_snap_base = host_snap_info.get("base", None)
360+
host_snap_base = host_snap_info.base
356361
if host_snap_base:
357362
logger.debug(
358363
"Installing base snap %r for %r from host", host_snap_base, snap_name
359364
)
360365
inject_from_host(executor=executor, snap_name=host_snap_base, classic=False)
361366

362-
host_revision = host_snap_info["revision"]
367+
host_revision = host_snap_info.revision
363368
target_revision = _get_snap_revision_ensuring_source(
364369
snap_name=snap_store_name,
365370
source=SNAP_SRC_HOST,

craft_providers/models/__init__.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#
2+
# Copyright 2026 Canonical Ltd.
3+
#
4+
# This program is free software; you can redistribute it and/or
5+
# modify it under the terms of the GNU Lesser General Public
6+
# License version 3 as published by the Free Software Foundation.
7+
#
8+
# This program is distributed in the hope that it will be useful,
9+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11+
# Lesser General Public License for more details.
12+
#
13+
# You should have received a copy of the GNU Lesser General Public License
14+
# along with this program; if not, write to the Free Software Foundation,
15+
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16+
#
17+
"""Models representing application states."""
18+
19+
from craft_providers.models.snaps import SnapInfo, SnapPublisher
20+
21+
__all__ = ["SnapInfo", "SnapPublisher"]

craft_providers/models/snaps.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#
2+
# Copyright 2026 Canonical Ltd.
3+
#
4+
# This program is free software; you can redistribute it and/or
5+
# modify it under the terms of the GNU Lesser General Public
6+
# License version 3 as published by the Free Software Foundation.
7+
#
8+
# This program is distributed in the hope that it will be useful,
9+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11+
# Lesser General Public License for more details.
12+
#
13+
# You should have received a copy of the GNU Lesser General Public License
14+
# along with this program; if not, write to the Free Software Foundation,
15+
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16+
#
17+
"""Pydantic models for snap metadata returned by snapd."""
18+
19+
import pydantic
20+
21+
22+
class SnapPublisher(pydantic.BaseModel, extra="ignore"):
23+
"""Publisher information returned by snapd."""
24+
25+
id: str
26+
27+
28+
class SnapInfo(pydantic.BaseModel, extra="ignore"):
29+
"""Information about an installed snap returned by snapd."""
30+
31+
id: str
32+
name: str | None = None
33+
type: str | None = None
34+
version: str | None = None
35+
channel: str | None = None
36+
confinement: str | None = None
37+
revision: str
38+
publisher: SnapPublisher | None = None
39+
base: str | None = None

tests/integration/conftest.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
import sys
2929
import tempfile
3030
from collections.abc import Callable, Generator
31-
from typing import cast
3231

3332
import pytest
3433
from craft_providers import lxd, multipass
@@ -61,7 +60,7 @@ def snap_exists(snap_name: str) -> bool:
6160

6261
def is_installed_dangerously(snap_name: str) -> bool:
6362
"""Returns true if a snap is installed dangerously."""
64-
return cast(str, get_host_snap_info(snap_name)["revision"]).startswith("x")
63+
return get_host_snap_info(snap_name).revision.startswith("x")
6564

6665

6766
@pytest.fixture

0 commit comments

Comments
 (0)