Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(cli): properly capture store errors #4863

Merged
merged 2 commits into from
Jun 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -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
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(
sergiusens marked this conversation as resolved.
Show resolved Hide resolved
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]
)
)
Loading