Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion snapcraft/meta/snap_yaml.py
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,9 @@ def get_metadata_from_project(
slots=project.slots,
hooks=project.hooks,
layout=project.layout,
system_usernames=project.system_usernames,
system_usernames=project.system_usernames.model_dump(exclude_none=True)
if project.system_usernames is not None
else None,
provenance=project.provenance,
links=links if links else None,
components=components,
Expand Down
40 changes: 36 additions & 4 deletions snapcraft/models/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -1258,6 +1258,29 @@ def from_architectures(
return platforms


class SystemUsernames(models.CraftBaseModel, extra="allow"):
"""System usernames the snap can use to run daemons.

See :external+snap:ref:`interfaces-system-usernames` in the snap documentation for
more information.
"""

snap_daemon: Annotated[
str | dict[str, Any] | None,
pydantic.Field(
default=None,
deprecated=(
"The 'snap_daemon' system username is deprecated. "
"See https://snapcraft.io/docs/system-usernames."
),
),
Comment on lines +1268 to +1276
] = None
Comment on lines +1268 to +1277
"""Deprecated system username.

Use ``snap_microk8s``, ``snap_aziotedge``, or ``snap_aziotdu`` instead.
"""


class Component(models.CraftBaseModel):
"""Snapcraft component definition."""

Expand Down Expand Up @@ -1791,16 +1814,13 @@ class Project(models.Project):
adopting part.
"""

system_usernames: dict[str, Any] | None = pydantic.Field(
system_usernames: SystemUsernames | None = pydantic.Field(
default=None,
description="The system usernames the snap can use to run daemons and services.",
examples=["{snap-daemon: shared}"],
)
"""The system usernames the snap can use to run daemons and services.

This is used to run daemons with the ``snap_daemon`` user defined by snapd.
Otherwise, this is an uncommon key.

See :external+snap:ref:`interfaces-system-usernames` in the snap documentation for
more information.
"""
Expand Down Expand Up @@ -1954,6 +1974,18 @@ def _validate_slots(cls, slots: dict[str, Any]) -> dict[str, Any]:

return slots

@pydantic.field_validator("system_usernames")
@classmethod
def _validate_system_usernames(
cls, system_usernames: SystemUsernames
) -> SystemUsernames:
Comment on lines +1980 to +1981
if system_usernames and system_usernames.snap_daemon is not None:
emit.warning(
"The 'snap_daemon' system username is deprecated. "
"See https://snapcraft.io/docs/system-usernames."
)
return system_usernames

@pydantic.model_validator(mode="after")
def _validate_adoptable_fields(self) -> Self:
for field in MANDATORY_ADOPTABLE_FIELDS:
Expand Down
33 changes: 32 additions & 1 deletion tests/unit/models/test_projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -1654,7 +1654,38 @@ def test_app_sockets_valid_socket_mode(self, socket_mode, socket_yaml_data):
)
def test_project_system_usernames_valid(self, system_username, project_yaml_data):
project = Project.unmarshal(project_yaml_data(system_usernames=system_username))
assert project.system_usernames == system_username
assert project.system_usernames is not None
for key, value in system_username.items():
assert getattr(project.system_usernames, key, None) == value

@pytest.mark.parametrize(
"system_username",
[
{"snap_daemon": {"scope": "shared"}},
{"snap_daemon": "shared"},
],
)
def test_project_system_usernames_snap_daemon_deprecated(
self, system_username, project_yaml_data, emitter
):
Project.unmarshal(project_yaml_data(system_usernames=system_username))
emitter.assert_warning(
"The 'snap_daemon' system username is deprecated. "
"See https://snapcraft.io/docs/system-usernames."
)

@pytest.mark.parametrize(
"system_username",
[
{"snap_microk8s": {"scope": "shared"}},
{"snap_aziotedge": "shared"},
],
)
def test_project_system_usernames_no_warning_for_others(
self, system_username, project_yaml_data, emitter
):
Project.unmarshal(project_yaml_data(system_usernames=system_username))
assert not any(i for i in emitter.interactions if i[0] == "warning")

@pytest.mark.parametrize(
"system_username",
Expand Down
Loading