diff --git a/setup.cfg b/setup.cfg index 64e009835c..2bf3db8942 100644 --- a/setup.cfg +++ b/setup.cfg @@ -39,4 +39,4 @@ ignore = E203,E501 # D203 1 blank line required before class docstring (reason: pep257 default) # D213 Multi-line docstring summary should start at the second line (reason: pep257 default) ignore = D107, D203, D213 -ignore_decorators = overrides +ignore_decorators = override diff --git a/snapcraft/application.py b/snapcraft/application.py index e6f9d3e23d..8d1ab45741 100644 --- a/snapcraft/application.py +++ b/snapcraft/application.py @@ -27,6 +27,7 @@ import craft_application.commands as craft_app_commands import craft_cli import craft_parts +import craft_store from craft_application import Application, AppMetadata, util from craft_cli import emit from craft_parts.plugins.plugins import PluginType @@ -34,7 +35,7 @@ import snapcraft import snapcraft_legacy -from snapcraft import cli, commands, errors, models, services +from snapcraft import cli, commands, errors, models, services, store from snapcraft.commands import unimplemented from snapcraft.extensions import apply_extensions from snapcraft.models.project import SnapcraftBuildPlanner, apply_root_packages @@ -173,6 +174,34 @@ def app_config(self) -> dict[str, Any]: config["core24"] = self._known_core24 return config + @override + def run(self) -> int: + try: + return_code = super().run() + except craft_store.errors.NoKeyringError as err: + self._emit_error( + craft_cli.errors.CraftError( + f"craft-store error: {err}", + resolution=( + "Ensure the keyring is working or " + f"{store.constants.ENVIRONMENT_STORE_CREDENTIALS} " + "is correctly exported into the environment" + ), + docs_url="https://snapcraft.io/docs/snapcraft-authentication", + ) + ) + return_code = 1 + except craft_store.errors.CraftStoreError as err: + self._emit_error( + craft_cli.errors.CraftError( + f"craft-store error: {err}", resolution=err.resolution + ), + cause=err, + ) + return_code = 1 + + return return_code + @override def _setup_partitions(self, yaml_data: dict[str, Any]) -> list[str] | None: components = models.ComponentProject.unmarshal(yaml_data) diff --git a/tests/unit/test_application.py b/tests/unit/test_application.py index b801b79e59..1317cb64e5 100644 --- a/tests/unit/test_application.py +++ b/tests/unit/test_application.py @@ -14,6 +14,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . """Unit tests for application classes.""" + import json import os import sys @@ -21,6 +22,7 @@ import craft_cli import craft_parts.plugins +import craft_store import pytest import yaml from craft_application import util @@ -540,3 +542,52 @@ def test_known_core24(snapcraft_yaml, base, build_base, is_known_core24): app = application.create_app() assert app._known_core24 == is_known_core24 + + +@pytest.mark.parametrize( + ("message", "resolution", "expected_message"), + [ + ( + "error message", + "error resolution", + "error message\nRecommended resolution: error resolution", + ), + ("error message", None, "error message"), + ], +) +def test_store_error(mocker, capsys, message, resolution, expected_message): + mocker.patch( + "snapcraft.application.Application.run", + side_effect=craft_store.errors.CraftStoreError(message, resolution=resolution), + ) + + return_code = application.main() + + assert return_code == 1 + _, err = capsys.readouterr() + assert f"craft-store error: {expected_message}" in err + + +def test_store_key_error(mocker, capsys): + mocker.patch( + "snapcraft.application.Application.run", + side_effect=craft_store.errors.NoKeyringError(), + ) + + return_code = application.main() + + assert return_code == 1 + _, err = capsys.readouterr() + assert err.startswith( + # There is merit in showing the line as it would be printed out. + # If it is too long here it needs fixing at the source. + # pylint: disable=[line-too-long] + dedent( + """\ + craft-store error: No keyring found to store or retrieve credentials from. + Recommended resolution: Ensure the keyring is working or SNAPCRAFT_STORE_CREDENTIALS is correctly exported into the environment + For more information, check out: https://snapcraft.io/docs/snapcraft-authentication + """ + # pylint: enable=[line-too-long] + ) + )