diff --git a/snapcraft/meta/snap_yaml.py b/snapcraft/meta/snap_yaml.py index 7a59b56b89..6bab91fd79 100644 --- a/snapcraft/meta/snap_yaml.py +++ b/snapcraft/meta/snap_yaml.py @@ -118,6 +118,16 @@ def _validate_target_not_empty(cls, val): raise ValueError("value cannot be empty") return val + @validator("default_provider") + @classmethod + def _validate_default_provider(cls, default_provider): + if default_provider and "/" in default_provider: + raise ValueError( + "Specifying a snap channel in 'default_provider' is not supported: " + f"{default_provider}" + ) + return default_provider + @property def provider(self) -> Optional[str]: """Return the default content provider name.""" diff --git a/snapcraft/projects.py b/snapcraft/projects.py index 0e011b9ed8..b08fd443b2 100644 --- a/snapcraft/projects.py +++ b/snapcraft/projects.py @@ -374,6 +374,16 @@ class ContentPlug(ProjectModel): target: str default_provider: Optional[str] + @pydantic.validator("default_provider") + @classmethod + def _validate_default_provider(cls, default_provider): + if default_provider and "/" in default_provider: + raise ValueError( + "Specifying a snap channel in 'default_provider' is not supported: " + f"{default_provider}" + ) + return default_provider + MANDATORY_ADOPTABLE_FIELDS = ("version", "summary", "description") @@ -441,9 +451,18 @@ def _validate_plugs(cls, plugs): raise ValueError( f"ContentPlug '{plug_name}' must have a 'target' parameter." ) + if isinstance(plug, list): raise ValueError(f"Plug '{plug_name}' cannot be a list.") + if isinstance(plug, dict) and plug.get("default-provider"): + default_provider: str = plug.get("default-provider", "") + if "/" in default_provider: + raise ValueError( + "Specifying a snap channel in 'default_provider' is not supported: " + f"{default_provider}" + ) + if plug is None: empty_plugs.append(plug_name) diff --git a/snapcraft_legacy/internal/meta/plugs.py b/snapcraft_legacy/internal/meta/plugs.py index 9f716b9b1c..7a7845283f 100644 --- a/snapcraft_legacy/internal/meta/plugs.py +++ b/snapcraft_legacy/internal/meta/plugs.py @@ -149,6 +149,15 @@ def validate(self) -> None: message="`target` is required for content slot", ) + if self._default_provider and "/" in self._default_provider: + raise PlugValidationError( + plug_name=self.plug_name, + message=( + "Specifying a snap channel in 'default_provider' is not supported: " + f"{self._default_provider}" + ) + ) + @classmethod def from_dict(cls, *, plug_dict: Dict[str, str], plug_name: str) -> "ContentPlug": interface = plug_dict.get("interface") diff --git a/tests/legacy/unit/meta/test_plugs.py b/tests/legacy/unit/meta/test_plugs.py index 5db7dda980..c30fbbdab7 100644 --- a/tests/legacy/unit/meta/test_plugs.py +++ b/tests/legacy/unit/meta/test_plugs.py @@ -127,3 +127,20 @@ def test_basic_from_dict_no_default(self): plug = ContentPlug.from_dict(plug_dict=plug_dict, plug_name=plug_name) self.assertThat(plug.provider, Is(None)) + + def test_basic_default_provider_with_channel(self): + plug_dict = OrderedDict( + { + "interface": "content", + "content": "content", + "target": "target", + "default-provider": "gtk-common-themes:gtk-3-themes/edge", + } + ) + plug_name = "plug-test" + + plug = ContentPlug.from_dict( + plug_dict=plug_dict, + plug_name=plug_name, + ) + self.assertRaises(errors.PlugValidationError, plug.validate) diff --git a/tests/unit/meta/test_snap_yaml.py b/tests/unit/meta/test_snap_yaml.py index 13e57a2d69..1138bac1b4 100644 --- a/tests/unit/meta/test_snap_yaml.py +++ b/tests/unit/meta/test_snap_yaml.py @@ -699,6 +699,23 @@ def test_content_plug_provider(): assert plug.provider == "gtk-common-themes" +def test_content_plug_provider_with_channel(): + plug_dict = { + "interface": "content", + "content": "foo", + "target": "target", + "default-provider": "gtk-common-themes:gtk-3-themes/edge", + } + + error = ( + "Specifying a snap channel in 'default_provider' is not supported: " + "gtk-common-themes:gtk-3-themes/edge" + ) + + with pytest.raises(pydantic.ValidationError, match=error): + ContentPlug.unmarshal(plug_dict) + + def test_get_content_plugs(): yaml_data = textwrap.dedent( """\ diff --git a/tests/unit/test_projects.py b/tests/unit/test_projects.py index 2d13a8a6a9..6954faf5de 100644 --- a/tests/unit/test_projects.py +++ b/tests/unit/test_projects.py @@ -501,6 +501,24 @@ def test_project_get_content_snaps(self, project_yaml_data): project = Project.unmarshal(project_yaml_data(plugs=content_plug_data)) assert project.get_content_snaps() == ["test-provider"] + def test_project_default_provider_with_channel(self, project_yaml_data): + content_plug_data = { + "content-interface": { + "interface": "content", + "target": "test-target", + "content": "test-content", + "default-provider": "test-provider/edge", + } + } + + error = ( + "Specifying a snap channel in 'default_provider' is not supported: " + "test-provider/edge" + ) + + with pytest.raises(errors.ProjectValidationError, match=error): + Project.unmarshal(project_yaml_data(plugs=content_plug_data)) + @pytest.mark.parametrize("decl_type", ["symlink", "bind", "bind-file", "type"]) def test_project_layout(self, decl_type, project_yaml_data): project = Project.unmarshal(