diff --git a/snapcraft/models/__init__.py b/snapcraft/models/__init__.py index b61840fa6d..c12016e948 100644 --- a/snapcraft/models/__init__.py +++ b/snapcraft/models/__init__.py @@ -15,7 +15,13 @@ # along with this program. If not, see . """Data models for snapcraft.""" -from .assertions import Assertion, RegistryAssertion +from .assertions import ( + Assertion, + EditableAssertion, + EditableRegistryAssertion, + Registry, + RegistryAssertion, +) from .manifest import Manifest from .project import ( MANDATORY_ADOPTABLE_FIELDS, @@ -43,12 +49,15 @@ "Component", "ComponentProject", "ContentPlug", + "EditableAssertion", + "EditableRegistryAssertion", "GrammarAwareProject", "Hook", "Lint", "Manifest", "Platform", "Project", + "Registry", "RegistryAssertion", "SnapcraftBuildPlanner", "Socket", diff --git a/snapcraft/models/assertions.py b/snapcraft/models/assertions.py index 8f1364173c..d557ff9092 100644 --- a/snapcraft/models/assertions.py +++ b/snapcraft/models/assertions.py @@ -14,29 +14,82 @@ # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . -"""Models for assertion sets.""" +"""Assertion models.""" -from typing import Any, Literal +from typing import Literal +import pydantic from craft_application import models +from typing_extensions import Self -class RegistryAssertion(models.CraftBaseModel): - """Data model for a registry assertion.""" +class Registry(models.CraftBaseModel): + """Access and data definitions for a specific facet of a snap or system.""" + + request: str | None = None + """Optional dot-separated path to access the field.""" + + storage: str + """Dot-separated storage path.""" + + access: Literal["read", "write", "read-write"] | None = None + """Access permissions for the field.""" + + content: list[Self] | None = None + """Optional nested rules.""" + + +class Rules(models.CraftBaseModel): + """A list of registries for a particular view.""" + + rules: list[Registry] + + +class EditableRegistryAssertion(models.CraftBaseModel): + """Subset of a registries assertion that can be edited by the user.""" account_id: str - authority_id: str - body: dict[str, Any] | str | None = None - body_length: str | None = None + """Issuer of the registry assertion and owner of the signing key.""" + name: str - revision: int = 0 - sign_key_sha3_384: str | None = None summary: str | None = None - timestamp: str + revision: int | None = 0 + + views: dict[str, Rules] + """A map of logical views of how the storage is accessed.""" + + body: str | None = None + """A JSON schema that defines the storage structure.""" + + +class RegistryAssertion(EditableRegistryAssertion): + """A full registries assertion containing editable and non-editable fields.""" + type: Literal["registry"] - views: dict[str, Any] + + authority_id: str + """Issuer of the registry assertion and owner of the signing key.""" + + timestamp: str + """Timestamp of when the assertion was issued.""" + + body_length: str | None = None + """Length of the body field.""" + + sign_key_sha3_384: str | None = None + """Signing key ID.""" + + +class RegistriesList(models.CraftBaseModel): + """A list of registry assertions.""" + + registry_list: list[RegistryAssertion] = pydantic.Field(default_factory=list) # this will be a union for validation sets and registries once # validation sets are migrated from the legacy codebase Assertion = RegistryAssertion + +# this will be a union for editable validation sets and editable registries once +# validation sets are migrated from the legacy codebase +EditableAssertion = EditableRegistryAssertion diff --git a/tests/unit/models/test_assertions.py b/tests/unit/models/test_assertions.py index d2653dcd71..a6fed23d95 100644 --- a/tests/unit/models/test_assertions.py +++ b/tests/unit/models/test_assertions.py @@ -17,11 +17,92 @@ """Tests for Assertion models.""" +from snapcraft.models import EditableRegistryAssertion, Registry, RegistryAssertion -def test_assertion_defaults(fake_registry_assertion_data, check): + +def test_registry_defaults(check): + """Test default values of the Registry model.""" + registry = Registry.unmarshal({"storage": "test-storage"}) + + check.is_none(registry.request) + check.is_none(registry.access) + check.is_none(registry.content) + + +def test_registry_nested(check): + """Test that nested registries are supported.""" + registry = Registry.unmarshal( + { + "request": "test-request", + "storage": "test-storage", + "access": "read", + "content": [ + { + "request": "nested-request", + "storage": "nested-storage", + "access": "write", + } + ], + } + ) + + check.equal(registry.request, "test-request") + check.equal(registry.storage, "test-storage") + check.equal(registry.access, "read") + check.equal( + registry.content, + [Registry(request="nested-request", storage="nested-storage", access="write")], + ) + + +def test_editable_registry_assertion_defaults(check): + """Test default values of the EditableRegistryAssertion model.""" + assertion = EditableRegistryAssertion.unmarshal( + { + "account_id": "test-account-id", + "name": "test-registry", + "views": { + "wifi-setup": { + "rules": [ + { + "storage": "wifi.ssids", + } + ] + } + }, + } + ) + + check.is_none(assertion.summary) + check.equal(assertion.revision, 0) + check.is_none(assertion.body) + + +def test_registry_assertion_defaults(check): """Test default values of the RegistryAssertion model.""" - check.equal(fake_registry_assertion_data.body, None) - check.equal(fake_registry_assertion_data.body_length, None) - check.equal(fake_registry_assertion_data.sign_key_sha3_384, None) - check.equal(fake_registry_assertion_data.summary, None) - check.equal(fake_registry_assertion_data.revision, 0) + assertion = RegistryAssertion.unmarshal( + { + "account_id": "test-account-id", + "authority_id": "test-authority-id", + "name": "test-registry", + "timestamp": "2024-01-01T10:20:30Z", + "type": "registry", + "views": { + "wifi-setup": { + "rules": [ + { + "access": "read-write", + "request": "ssids", + "storage": "wifi.ssids", + } + ] + } + }, + } + ) + + check.is_none(assertion.body) + check.is_none(assertion.body_length) + check.is_none(assertion.sign_key_sha3_384) + check.is_none(assertion.summary) + check.equal(assertion.revision, 0)