Skip to content

Commit

Permalink
fix(cli): properly capture store errors
Browse files Browse the repository at this point in the history
When raised they show up as a Snapcraft internal error if not properly
handled.

This is using the same logic as in legacy.

Signed-off-by: Sergio Schvezov <sergio.schvezov@canonical.com>
  • Loading branch information
sergiusens committed Jun 19, 2024
1 parent 98bce59 commit f711ccf
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 1 deletion.
31 changes: 30 additions & 1 deletion snapcraft/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,15 @@
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
from overrides import override

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
Expand Down Expand Up @@ -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)
Expand Down
51 changes: 51 additions & 0 deletions tests/unit/test_application.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""Unit tests for application classes."""

import json
import os
import sys
from textwrap import dedent

import craft_cli
import craft_parts.plugins
import craft_store
import pytest
import yaml
from craft_application import util
Expand Down Expand Up @@ -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]
)
)

0 comments on commit f711ccf

Please sign in to comment.