diff --git a/snapcraft/commands/validation_sets.py b/snapcraft/commands/validation_sets.py index 50f1ef97ed..f3964d8ad0 100644 --- a/snapcraft/commands/validation_sets.py +++ b/snapcraft/commands/validation_sets.py @@ -48,6 +48,12 @@ # the provided name. # presence: [required|optional|invalid] # Optional, defaults to required. # revision: # The revision of the snap. Optional. + # components: # Constraints to apply to the snap's components. Optional. + # : [required|optional|invalid] # Short form for specifying the component's presence. + # : # Long form that allows specifying the component's revision. + # presence: [required|optional|invalid] # Presence of the component. Required. + # revision: # The revision of the component, required if the snap's revision is given. + # Otherwise, not allowed. """ ) diff --git a/snapcraft_legacy/storeapi/v2/validation_sets.py b/snapcraft_legacy/storeapi/v2/validation_sets.py index 3dc7b68c45..4df9981cdf 100644 --- a/snapcraft_legacy/storeapi/v2/validation_sets.py +++ b/snapcraft_legacy/storeapi/v2/validation_sets.py @@ -56,6 +56,14 @@ def _to_string( return data +class Component(models.CraftBaseModel): + """Represent a Component in a Validation Set.""" + + presence: Literal["required", "optional", "invalid"] + """Component presence""" + + revision: int | None = None + """Component revision""" class Snap(models.CraftBaseModel): """Represent a Snap in a Validation Set.""" @@ -72,6 +80,8 @@ class Snap(models.CraftBaseModel): revision: int | None = None """Snap revision""" + components: dict[str, str | Component] | None = None + """Snap components""" class EditableBuildAssertion(models.CraftBaseModel): """Subset of a build assertion that can be edited by the user. diff --git a/tests/legacy/unit/store/v2/test_validation_sets.py b/tests/legacy/unit/store/v2/test_validation_sets.py index 2820efab48..81fa911c6f 100644 --- a/tests/legacy/unit/store/v2/test_validation_sets.py +++ b/tests/legacy/unit/store/v2/test_validation_sets.py @@ -26,6 +26,13 @@ def fake_snap_data(): "id": "snap-id", "presence": "required", "revision": 42, + "components": { + "component-with-revision": { + "presence": "required", + "revision": 10, + }, + "component-without-revision": "invalid", + }, } @@ -125,6 +132,13 @@ def test_editable_build_assertion_marshal_as_str(fake_editable_build_assertion): "name": "snap-name", "presence": "required", "revision": "42", + "components": { + "component-with-revision": { + "presence": "required", + "revision": "10", + }, + "component-without-revision": "invalid", + }, }, ], } @@ -147,6 +161,13 @@ def test_build_assertion_marshal_as_str(fake_build_assertion): "name": "snap-name", "presence": "required", "revision": "42", + "components": { + "component-with-revision": { + "presence": "required", + "revision": "10", + }, + "component-without-revision": "invalid", + }, }, ], "timestamp": "2020-10-29T16:36:56Z", diff --git a/tests/unit/commands/test_validation_sets.py b/tests/unit/commands/test_validation_sets.py index 1e69a278d1..0deefb31ee 100644 --- a/tests/unit/commands/test_validation_sets.py +++ b/tests/unit/commands/test_validation_sets.py @@ -54,6 +54,28 @@ "id": "XXSnapIDForXSnapName2XXXXXXXXXXX", "name": "snap-name-2", }, + { + "id": "XXSnapIDForXSnapName3XXXXXXXXXXX", + "name": "snap-name-3", + "presence": "optional", + "revision": "1", + "components": { + "comp-name-1": { + "presence": "required", + "revision": "11", + }, + }, + }, + { + "id": "XXSnapIDForXSnapName4XXXXXXXXXXX", + "name": "snap-name-4", + "presence": "optional", + "components": { + "comp-name-1": "required", + "comp-name-2": "optional", + "comp-name-3": "invalid", + }, + }, ], "timestamp": "2020-10-29T16:36:56Z", "type": "validation-set", @@ -125,6 +147,28 @@ def edit_return_value(): "revision": "10", }, {"id": "XXSnapIDForXSnapName2XXXXXXXXXXX", "name": "snap-name-2"}, + { + "id": "XXSnapIDForXSnapName3XXXXXXXXXXX", + "name": "snap-name-3", + "presence": "optional", + "revision": "1", + "components": { + "comp-name-1": { + "presence": "required", + "revision": "11", + }, + }, + }, + { + "id": "XXSnapIDForXSnapName4XXXXXXXXXXX", + "name": "snap-name-4", + "presence": "optional", + "components": { + "comp-name-1": "required", + "comp-name-2": "optional", + "comp-name-3": "invalid", + }, + }, ], } ) @@ -157,6 +201,28 @@ def fake_edit_validation_sets(mocker, edit_return_value): "revision": 10, }, {"name": "snap-name-2", "id": "XXSnapIDForXSnapName2XXXXXXXXXXX"}, + { + "id": "XXSnapIDForXSnapName3XXXXXXXXXXX", + "name": "snap-name-3", + "presence": "optional", + "revision": 1, + "components": { + "comp-name-1": { + "presence": "required", + "revision": 11, + }, + }, + }, + { + "id": "XXSnapIDForXSnapName4XXXXXXXXXXX", + "name": "snap-name-4", + "presence": "optional", + "components": { + "comp-name-1": "required", + "comp-name-2": "optional", + "comp-name-3": "invalid", + }, + }, ], } ) @@ -165,7 +231,9 @@ def fake_edit_validation_sets(mocker, edit_return_value): '{{"account-id": "AccountIDXXXOfTheRequestingUserX", "name": "certification-x1", ' '"revision": "222", "sequence": "9", "snaps": [{{"name": "snap-name-1", "id": ' '"XXSnapIDForXSnapName1XXXXXXXXXXX", "presence": "optional", "revision": "10"}}, ' - '{{"name": "snap-name-2", "id": "XXSnapIDForXSnapName2XXXXXXXXXXX"}}], ' + '{{"name": "snap-name-2", "id": "XXSnapIDForXSnapName2XXXXXXXXXXX"}}, ' + '{{"name": "snap-name-3", "id": "XXSnapIDForXSnapName3XXXXXXXXXXX", "presence": "optional", "revision": "1", "components": {{"comp-name-1": {{"presence": "required", "revision": "11"}}}}}}, ' + '{{"name": "snap-name-4", "id": "XXSnapIDForXSnapName4XXXXXXXXXXX", "presence": "optional", "components": {{"comp-name-1": "required", "comp-name-2": "optional", "comp-name-3": "invalid"}}}}], ' '"authority-id": "AccountIDXXXOfTheRequestingUserX", "series": "16", "timestamp": ' '"2020-10-29T16:36:56Z", "type": "validation-set"}}\n\nSIGNED{key_name}' )