We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
LXD errors from Craft Providers not properly presented
### Bug Description

When there's a Craft Providers error related to running LXD, as a user we get a traceback

### To Reproduce

On a fresh system:

- install snapcraft
- run a core22 snapcraft project

You will be queried to install LXD, and that works well, but we are later presented with an error not being setup.

### Environment

Fresh Ubuntu 22.04.2 LTS

### snapcraft.yaml

```shell
name: foo
version: irrelevant
summary: reproducer
description: will only trigger if LXD has not been configured to work correctly
base: core22

parts:
    foo:
        plugin: nil
```


### Relevant log output

```shell
sergiusens@imladris:~/projects/snaps/darktable$ snapcraft 
LXD is required but not installed. Do you wish to install LXD and configure it with the defaults? [y/N]: y
[sudo] password for sergiusens: 
lxd 5.15-002fa0f from Canonical✓ installed
WARNING: There is 1 new warning. See 'snap warnings'.
Traceback (most recent call last):
  File "/snap/snapcraft/9362/bin/snapcraft", line 8, in <module>
    sys.exit(run())
  File "/snap/snapcraft/9362/lib/python3.8/site-packages/snapcraft/cli.py", line 255, in run
    _run_dispatcher(dispatcher, global_args)
  File "/snap/snapcraft/9362/lib/python3.8/site-packages/snapcraft/cli.py", line 228, in _run_dispatcher
    dispatcher.run()
  File "/snap/snapcraft/9362/lib/python3.8/site-packages/craft_cli/dispatcher.py", line 448, in run
    return self._loaded_command.run(self._parsed_command_args)
  File "/snap/snapcraft/9362/lib/python3.8/site-packages/snapcraft/commands/lifecycle.py", line 265, in run
    super().run(parsed_args)
  File "/snap/snapcraft/9362/lib/python3.8/site-packages/snapcraft/commands/lifecycle.py", line 138, in run
    parts_lifecycle.run(self.name, parsed_args)
  File "/snap/snapcraft/9362/lib/python3.8/site-packages/snapcraft/parts/lifecycle.py", line 216, in run
    _run_command(
  File "/snap/snapcraft/9362/lib/python3.8/site-packages/snapcraft/parts/lifecycle.py", line 262, in _run_command
    _run_in_provider(project, command_name, parsed_args)
  File "/snap/snapcraft/9362/lib/python3.8/site-packages/snapcraft/parts/lifecycle.py", line 487, in _run_in_provider
    providers.ensure_provider_is_available(provider)
  File "/snap/snapcraft/9362/lib/python3.8/site-packages/snapcraft/providers.py", line 148, in ensure_provider_is_available
    LXDProvider.ensure_provider_is_available()
  File "/snap/snapcraft/9362/lib/python3.8/site-packages/craft_providers/lxd/lxd_provider.py", line 69, in ensure_provider_is_available
    install()
  File "/snap/snapcraft/9362/lib/python3.8/site-packages/craft_providers/lxd/installer.py", line 71, in install
    raise errors.LXDInstallationError(
craft_providers.lxd.errors.LXDInstallationError: Failed to install LXD: user must be manually added to 'lxd' group before using LXD.
Visit https://linuxcontainers.org/lxd/getting-started-cli/ for instructions on installing and configuring LXD for your operating system.
```


### Additional context

_No response_

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `snapcraft/cli.py`
Content:
```
1 # -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
2 #
3 # Copyright 2022 Canonical Ltd.
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License version 3 as
7 # published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 
17 """Command-line application entry point."""
18 
19 import argparse
20 import contextlib
21 import logging
22 import os
23 import sys
24 from typing import Any, Dict
25 
26 import craft_cli
27 import craft_store
28 from craft_cli import ArgumentParsingError, EmitterMode, ProvideHelpException, emit
29 
30 import snapcraft
31 import snapcraft_legacy
32 from snapcraft import __version__, errors, store, utils
33 from snapcraft.parts import plugins
34 from snapcraft_legacy.cli import legacy
35 
36 from . import commands
37 from .legacy_cli import _LIB_NAMES, _ORIGINAL_LIB_NAME_LOG_LEVEL, run_legacy
38 
39 COMMAND_GROUPS = [
40     craft_cli.CommandGroup(
41         "Lifecycle",
42         [
43             commands.CleanCommand,
44             commands.PullCommand,
45             commands.BuildCommand,
46             commands.StageCommand,
47             commands.PrimeCommand,
48             commands.PackCommand,
49             commands.RemoteBuildCommand,
50             commands.SnapCommand,  # hidden (legacy compatibility)
51             commands.PluginsCommand,
52             commands.ListPluginsCommand,
53             commands.TryCommand,
54         ],
55     ),
56     craft_cli.CommandGroup(
57         "Extensions",
58         [
59             commands.ListExtensionsCommand,
60             commands.ExtensionsCommand,  # hidden (alias to list-extensions)
61             commands.ExpandExtensionsCommand,
62         ],
63     ),
64     craft_cli.CommandGroup(
65         "Store Account",
66         [
67             commands.StoreLoginCommand,
68             commands.StoreExportLoginCommand,
69             commands.StoreLogoutCommand,
70             commands.StoreWhoAmICommand,
71         ],
72     ),
73     craft_cli.CommandGroup(
74         "Store Snap Names",
75         [
76             commands.StoreRegisterCommand,
77             commands.StoreNamesCommand,
78             commands.StoreLegacyListRegisteredCommand,
79             commands.StoreLegacyListCommand,
80             commands.StoreLegacyMetricsCommand,
81             commands.StoreLegacyUploadMetadataCommand,
82         ],
83     ),
84     craft_cli.CommandGroup(
85         "Store Snap Release Management",
86         [
87             commands.StoreReleaseCommand,
88             commands.StoreCloseCommand,
89             commands.StoreStatusCommand,
90             commands.StoreUploadCommand,
91             commands.StoreLegacyPushCommand,  # hidden (legacy for upload)
92             commands.StoreLegacyPromoteCommand,
93             commands.StoreListRevisionsCommand,
94             commands.StoreRevisionsCommand,  # hidden (alias to list-revisions)
95         ],
96     ),
97     craft_cli.CommandGroup(
98         "Store Snap Tracks",
99         [
100             commands.StoreListTracksCommand,
101             commands.StoreTracksCommand,  # hidden (alias to list-tracks)
102             commands.StoreLegacySetDefaultTrackCommand,
103         ],
104     ),
105     craft_cli.CommandGroup(
106         "Store Key Management",
107         [
108             commands.StoreLegacyCreateKeyCommand,
109             commands.StoreLegacyRegisterKeyCommand,
110             commands.StoreLegacySignBuildCommand,
111             commands.StoreLegacyListKeysCommand,
112         ],
113     ),
114     craft_cli.CommandGroup(
115         "Store Validation Sets",
116         [
117             commands.StoreEditValidationSetsCommand,
118             commands.StoreLegacyListValidationSetsCommand,
119             commands.StoreLegacyValidateCommand,
120             commands.StoreLegacyGatedCommand,
121         ],
122     ),
123     craft_cli.CommandGroup(
124         "Other",
125         [
126             commands.VersionCommand,
127             commands.LintCommand,
128             commands.InitCommand,
129         ],
130     ),
131 ]
132 
133 GLOBAL_ARGS = [
134     craft_cli.GlobalArgument(
135         "version", "flag", "-V", "--version", "Show the application version and exit"
136     ),
137     craft_cli.GlobalArgument("trace", "flag", "-t", "--trace", argparse.SUPPRESS),
138 ]
139 
140 
141 def get_verbosity() -> EmitterMode:
142     """Return the verbosity level to use.
143 
144     if SNAPCRAFT_ENABLE_DEVELOPER_DEBUG is set, the
145     default verbosity will be set to EmitterMode.DEBUG.
146 
147     If stdin is closed, the default verbosity will be
148     set to EmitterMode.VERBOSE.
149     """
150     verbosity = EmitterMode.BRIEF
151 
152     if not sys.stdin.isatty():
153         verbosity = EmitterMode.VERBOSE
154 
155     with contextlib.suppress(ValueError):
156         # Parse environment variable for backwards compatibility with launchpad
157         if utils.strtobool(os.getenv("SNAPCRAFT_ENABLE_DEVELOPER_DEBUG", "n").strip()):
158             verbosity = EmitterMode.DEBUG
159 
160     # if defined, use environmental variable SNAPCRAFT_VERBOSITY_LEVEL
161     verbosity_env = os.getenv("SNAPCRAFT_VERBOSITY_LEVEL")
162     if verbosity_env:
163         try:
164             verbosity = EmitterMode[verbosity_env.strip().upper()]
165         except KeyError:
166             values = utils.humanize_list(
167                 [e.name.lower() for e in EmitterMode], "and", sort=False
168             )
169             raise ArgumentParsingError(
170                 f"cannot parse verbosity level {verbosity_env!r} from environment "
171                 f"variable SNAPCRAFT_VERBOSITY_LEVEL (valid values are {values})"
172             ) from KeyError
173 
174     return verbosity
175 
176 
177 def get_dispatcher() -> craft_cli.Dispatcher:
178     """Return an instance of Dispatcher.
179 
180     Run all the checks and setup required to ensure the Dispatcher can run.
181     """
182     # Run the legacy implementation if inside a legacy managed environment.
183     if os.getenv("SNAPCRAFT_BUILD_ENVIRONMENT") == "managed-host":
184         snapcraft.ProjectOptions = snapcraft_legacy.ProjectOptions  # type: ignore
185         legacy.legacy_run()
186 
187     # set lib loggers to debug level so that all messages are sent to Emitter
188     for lib_name in _LIB_NAMES:
189         logger = logging.getLogger(lib_name)
190         _ORIGINAL_LIB_NAME_LOG_LEVEL[lib_name] = logger.level
191         logger.setLevel(logging.DEBUG)
192 
193     if utils.is_managed_mode():
194         log_filepath = utils.get_managed_environment_log_path()
195     else:
196         log_filepath = None
197 
198     emit.init(
199         mode=get_verbosity(),
200         appname="snapcraft",
201         greeting=f"Starting Snapcraft {__version__}",
202         log_filepath=log_filepath,
203     )
204 
205     return craft_cli.Dispatcher(
206         "snapcraft",
207         COMMAND_GROUPS,
208         summary="Package, distribute, and update snaps for Linux and IoT",
209         extra_global_args=GLOBAL_ARGS,
210         default_command=commands.PackCommand,
211     )
212 
213 
214 def _run_dispatcher(
215     dispatcher: craft_cli.Dispatcher, global_args: Dict[str, Any]
216 ) -> None:
217     if global_args.get("version"):
218         emit.message(f"snapcraft {__version__}")
219     else:
220         if global_args.get("trace"):
221             emit.message(
222                 "Options -t and --trace are deprecated, use --verbosity=debug instead."
223             )
224             emit.set_mode(EmitterMode.DEBUG)
225 
226         dispatcher.load_command(None)
227         dispatcher.run()
228     emit.ended_ok()
229 
230 
231 def _emit_error(error, cause=None):
232     """Emit the error in a centralized way so we can alter it consistently."""
233     # set the cause, if any
234     if cause is not None:
235         error.__cause__ = cause
236 
237     # Do not report the internal logpath if running inside instance
238     if utils.is_managed_mode():
239         error.logpath_report = False
240 
241     emit.error(error)
242 
243 
244 def run():  # noqa: C901
245     """Run the CLI."""
246     dispatcher = get_dispatcher()
247     retcode = 1
248 
249     try:
250         # Register our own plugins
251         global_args = dispatcher.pre_parse_args(sys.argv[1:])
252         plugins.register()
253 
254         _run_dispatcher(dispatcher, global_args)
255         retcode = 0
256     except ArgumentParsingError as err:
257         # TODO https://github.com/canonical/craft-cli/issues/78
258         with contextlib.suppress(KeyError, IndexError):
259             if (
260                 err.__context__ is not None
261                 and err.__context__.args[0]  # pylint: disable=no-member
262                 not in dispatcher.commands
263             ):
264                 run_legacy(err)
265         print(err, file=sys.stderr)  # to stderr, as argparse normally does
266         emit.ended_ok()
267         retcode = 1
268     except ProvideHelpException as err:
269         print(err, file=sys.stderr)  # to stderr, as argparse normally does
270         emit.ended_ok()
271         retcode = 0
272     except errors.LegacyFallback as err:
273         run_legacy(err)
274     except KeyboardInterrupt as err:
275         _emit_error(craft_cli.errors.CraftError("Interrupted."), cause=err)
276         retcode = 1
277     except craft_store.errors.NoKeyringError as err:
278         _emit_error(
279             craft_cli.errors.CraftError(
280                 f"craft-store error: {err}",
281                 resolution=(
282                     "Ensure the keyring is working or "
283                     f"{store.constants.ENVIRONMENT_STORE_CREDENTIALS} "
284                     "is correctly exported into the environment"
285                 ),
286                 docs_url="https://snapcraft.io/docs/snapcraft-authentication",
287             )
288         )
289         retcode = 1
290     except craft_store.errors.CraftStoreError as err:
291         _emit_error(craft_cli.errors.CraftError(f"craft-store error: {err}"))
292         retcode = 1
293     except errors.LinterError as err:
294         emit.error(craft_cli.errors.CraftError(f"linter error: {err}"))
295         retcode = err.exit_code
296     except errors.SnapcraftError as err:
297         _emit_error(err)
298         retcode = 1
299 
300     return retcode
301
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/snapcraft/cli.py b/snapcraft/cli.py
--- a/snapcraft/cli.py
+++ b/snapcraft/cli.py
@@ -1,6 +1,6 @@
 # -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
 #
-# Copyright 2022 Canonical Ltd.
+# Copyright 2022-2023 Canonical Ltd.
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License version 3 as
@@ -26,6 +26,7 @@
 import craft_cli
 import craft_store
 from craft_cli import ArgumentParsingError, EmitterMode, ProvideHelpException, emit
+from craft_providers import ProviderError
 
 import snapcraft
 import snapcraft_legacy
@@ -290,6 +291,9 @@
     except craft_store.errors.CraftStoreError as err:
         _emit_error(craft_cli.errors.CraftError(f"craft-store error: {err}"))
         retcode = 1
+    except ProviderError as err:
+        _emit_error(craft_cli.errors.CraftError(f"craft-providers error: {err}"))
+        retcode = 1
     except errors.LinterError as err:
         emit.error(craft_cli.errors.CraftError(f"linter error: {err}"))
         retcode = err.exit_code

{"golden_diff": "diff --git a/snapcraft/cli.py b/snapcraft/cli.py\n--- a/snapcraft/cli.py\n+++ b/snapcraft/cli.py\n@@ -1,6 +1,6 @@\n # -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-\n #\n-# Copyright 2022 Canonical Ltd.\n+# Copyright 2022-2023 Canonical Ltd.\n #\n # This program is free software: you can redistribute it and/or modify\n # it under the terms of the GNU General Public License version 3 as\n@@ -26,6 +26,7 @@\n import craft_cli\n import craft_store\n from craft_cli import ArgumentParsingError, EmitterMode, ProvideHelpException, emit\n+from craft_providers import ProviderError\n \n import snapcraft\n import snapcraft_legacy\n@@ -290,6 +291,9 @@\n     except craft_store.errors.CraftStoreError as err:\n         _emit_error(craft_cli.errors.CraftError(f\"craft-store error: {err}\"))\n         retcode = 1\n+    except ProviderError as err:\n+        _emit_error(craft_cli.errors.CraftError(f\"craft-providers error: {err}\"))\n+        retcode = 1\n     except errors.LinterError as err:\n         emit.error(craft_cli.errors.CraftError(f\"linter error: {err}\"))\n         retcode = err.exit_code\n", "issue": "LXD errors from Craft Providers not properly presented\n### Bug Description\n\nWhen there's a Craft Providers error related to running LXD, as a user we get a traceback\n\n### To Reproduce\n\nOn a fresh system:\r\n\r\n- install snapcraft\r\n- run a core22 snapcraft project\r\n\r\nYou will be queried to install LXD, and that works well, but we are later presented with an error not being setup.\n\n### Environment\n\nFresh Ubuntu 22.04.2 LTS\n\n### snapcraft.yaml\n\n```shell\nname: foo\r\nversion: irrelevant\r\nsummary: reproducer\r\ndescription: will only trigger if LXD has not been configured to work correctly\r\nbase: core22\r\n\r\nparts:\r\n    foo:\r\n        plugin: nil\n```\n\n\n### Relevant log output\n\n```shell\nsergiusens@imladris:~/projects/snaps/darktable$ snapcraft \r\nLXD is required but not installed. Do you wish to install LXD and configure it with the defaults? [y/N]: y\r\n[sudo] password for sergiusens: \r\nlxd 5.15-002fa0f from Canonical\u2713 installed\r\nWARNING: There is 1 new warning. See 'snap warnings'.\r\nTraceback (most recent call last):\r\n  File \"/snap/snapcraft/9362/bin/snapcraft\", line 8, in <module>\r\n    sys.exit(run())\r\n  File \"/snap/snapcraft/9362/lib/python3.8/site-packages/snapcraft/cli.py\", line 255, in run\r\n    _run_dispatcher(dispatcher, global_args)\r\n  File \"/snap/snapcraft/9362/lib/python3.8/site-packages/snapcraft/cli.py\", line 228, in _run_dispatcher\r\n    dispatcher.run()\r\n  File \"/snap/snapcraft/9362/lib/python3.8/site-packages/craft_cli/dispatcher.py\", line 448, in run\r\n    return self._loaded_command.run(self._parsed_command_args)\r\n  File \"/snap/snapcraft/9362/lib/python3.8/site-packages/snapcraft/commands/lifecycle.py\", line 265, in run\r\n    super().run(parsed_args)\r\n  File \"/snap/snapcraft/9362/lib/python3.8/site-packages/snapcraft/commands/lifecycle.py\", line 138, in run\r\n    parts_lifecycle.run(self.name, parsed_args)\r\n  File \"/snap/snapcraft/9362/lib/python3.8/site-packages/snapcraft/parts/lifecycle.py\", line 216, in run\r\n    _run_command(\r\n  File \"/snap/snapcraft/9362/lib/python3.8/site-packages/snapcraft/parts/lifecycle.py\", line 262, in _run_command\r\n    _run_in_provider(project, command_name, parsed_args)\r\n  File \"/snap/snapcraft/9362/lib/python3.8/site-packages/snapcraft/parts/lifecycle.py\", line 487, in _run_in_provider\r\n    providers.ensure_provider_is_available(provider)\r\n  File \"/snap/snapcraft/9362/lib/python3.8/site-packages/snapcraft/providers.py\", line 148, in ensure_provider_is_available\r\n    LXDProvider.ensure_provider_is_available()\r\n  File \"/snap/snapcraft/9362/lib/python3.8/site-packages/craft_providers/lxd/lxd_provider.py\", line 69, in ensure_provider_is_available\r\n    install()\r\n  File \"/snap/snapcraft/9362/lib/python3.8/site-packages/craft_providers/lxd/installer.py\", line 71, in install\r\n    raise errors.LXDInstallationError(\r\ncraft_providers.lxd.errors.LXDInstallationError: Failed to install LXD: user must be manually added to 'lxd' group before using LXD.\r\nVisit https://linuxcontainers.org/lxd/getting-started-cli/ for instructions on installing and configuring LXD for your operating system.\n```\n\n\n### Additional context\n\n_No response_\n", "before_files": [{"content": "# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-\n#\n# Copyright 2022 Canonical Ltd.\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License version 3 as\n# published by the Free Software Foundation.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\n\"\"\"Command-line application entry point.\"\"\"\n\nimport argparse\nimport contextlib\nimport logging\nimport os\nimport sys\nfrom typing import Any, Dict\n\nimport craft_cli\nimport craft_store\nfrom craft_cli import ArgumentParsingError, EmitterMode, ProvideHelpException, emit\n\nimport snapcraft\nimport snapcraft_legacy\nfrom snapcraft import __version__, errors, store, utils\nfrom snapcraft.parts import plugins\nfrom snapcraft_legacy.cli import legacy\n\nfrom . import commands\nfrom .legacy_cli import _LIB_NAMES, _ORIGINAL_LIB_NAME_LOG_LEVEL, run_legacy\n\nCOMMAND_GROUPS = [\n    craft_cli.CommandGroup(\n        \"Lifecycle\",\n        [\n            commands.CleanCommand,\n            commands.PullCommand,\n            commands.BuildCommand,\n            commands.StageCommand,\n            commands.PrimeCommand,\n            commands.PackCommand,\n            commands.RemoteBuildCommand,\n            commands.SnapCommand,  # hidden (legacy compatibility)\n            commands.PluginsCommand,\n            commands.ListPluginsCommand,\n            commands.TryCommand,\n        ],\n    ),\n    craft_cli.CommandGroup(\n        \"Extensions\",\n        [\n            commands.ListExtensionsCommand,\n            commands.ExtensionsCommand,  # hidden (alias to list-extensions)\n            commands.ExpandExtensionsCommand,\n        ],\n    ),\n    craft_cli.CommandGroup(\n        \"Store Account\",\n        [\n            commands.StoreLoginCommand,\n            commands.StoreExportLoginCommand,\n            commands.StoreLogoutCommand,\n            commands.StoreWhoAmICommand,\n        ],\n    ),\n    craft_cli.CommandGroup(\n        \"Store Snap Names\",\n        [\n            commands.StoreRegisterCommand,\n            commands.StoreNamesCommand,\n            commands.StoreLegacyListRegisteredCommand,\n            commands.StoreLegacyListCommand,\n            commands.StoreLegacyMetricsCommand,\n            commands.StoreLegacyUploadMetadataCommand,\n        ],\n    ),\n    craft_cli.CommandGroup(\n        \"Store Snap Release Management\",\n        [\n            commands.StoreReleaseCommand,\n            commands.StoreCloseCommand,\n            commands.StoreStatusCommand,\n            commands.StoreUploadCommand,\n            commands.StoreLegacyPushCommand,  # hidden (legacy for upload)\n            commands.StoreLegacyPromoteCommand,\n            commands.StoreListRevisionsCommand,\n            commands.StoreRevisionsCommand,  # hidden (alias to list-revisions)\n        ],\n    ),\n    craft_cli.CommandGroup(\n        \"Store Snap Tracks\",\n        [\n            commands.StoreListTracksCommand,\n            commands.StoreTracksCommand,  # hidden (alias to list-tracks)\n            commands.StoreLegacySetDefaultTrackCommand,\n        ],\n    ),\n    craft_cli.CommandGroup(\n        \"Store Key Management\",\n        [\n            commands.StoreLegacyCreateKeyCommand,\n            commands.StoreLegacyRegisterKeyCommand,\n            commands.StoreLegacySignBuildCommand,\n            commands.StoreLegacyListKeysCommand,\n        ],\n    ),\n    craft_cli.CommandGroup(\n        \"Store Validation Sets\",\n        [\n            commands.StoreEditValidationSetsCommand,\n            commands.StoreLegacyListValidationSetsCommand,\n            commands.StoreLegacyValidateCommand,\n            commands.StoreLegacyGatedCommand,\n        ],\n    ),\n    craft_cli.CommandGroup(\n        \"Other\",\n        [\n            commands.VersionCommand,\n            commands.LintCommand,\n            commands.InitCommand,\n        ],\n    ),\n]\n\nGLOBAL_ARGS = [\n    craft_cli.GlobalArgument(\n        \"version\", \"flag\", \"-V\", \"--version\", \"Show the application version and exit\"\n    ),\n    craft_cli.GlobalArgument(\"trace\", \"flag\", \"-t\", \"--trace\", argparse.SUPPRESS),\n]\n\n\ndef get_verbosity() -> EmitterMode:\n    \"\"\"Return the verbosity level to use.\n\n    if SNAPCRAFT_ENABLE_DEVELOPER_DEBUG is set, the\n    default verbosity will be set to EmitterMode.DEBUG.\n\n    If stdin is closed, the default verbosity will be\n    set to EmitterMode.VERBOSE.\n    \"\"\"\n    verbosity = EmitterMode.BRIEF\n\n    if not sys.stdin.isatty():\n        verbosity = EmitterMode.VERBOSE\n\n    with contextlib.suppress(ValueError):\n        # Parse environment variable for backwards compatibility with launchpad\n        if utils.strtobool(os.getenv(\"SNAPCRAFT_ENABLE_DEVELOPER_DEBUG\", \"n\").strip()):\n            verbosity = EmitterMode.DEBUG\n\n    # if defined, use environmental variable SNAPCRAFT_VERBOSITY_LEVEL\n    verbosity_env = os.getenv(\"SNAPCRAFT_VERBOSITY_LEVEL\")\n    if verbosity_env:\n        try:\n            verbosity = EmitterMode[verbosity_env.strip().upper()]\n        except KeyError:\n            values = utils.humanize_list(\n                [e.name.lower() for e in EmitterMode], \"and\", sort=False\n            )\n            raise ArgumentParsingError(\n                f\"cannot parse verbosity level {verbosity_env!r} from environment \"\n                f\"variable SNAPCRAFT_VERBOSITY_LEVEL (valid values are {values})\"\n            ) from KeyError\n\n    return verbosity\n\n\ndef get_dispatcher() -> craft_cli.Dispatcher:\n    \"\"\"Return an instance of Dispatcher.\n\n    Run all the checks and setup required to ensure the Dispatcher can run.\n    \"\"\"\n    # Run the legacy implementation if inside a legacy managed environment.\n    if os.getenv(\"SNAPCRAFT_BUILD_ENVIRONMENT\") == \"managed-host\":\n        snapcraft.ProjectOptions = snapcraft_legacy.ProjectOptions  # type: ignore\n        legacy.legacy_run()\n\n    # set lib loggers to debug level so that all messages are sent to Emitter\n    for lib_name in _LIB_NAMES:\n        logger = logging.getLogger(lib_name)\n        _ORIGINAL_LIB_NAME_LOG_LEVEL[lib_name] = logger.level\n        logger.setLevel(logging.DEBUG)\n\n    if utils.is_managed_mode():\n        log_filepath = utils.get_managed_environment_log_path()\n    else:\n        log_filepath = None\n\n    emit.init(\n        mode=get_verbosity(),\n        appname=\"snapcraft\",\n        greeting=f\"Starting Snapcraft {__version__}\",\n        log_filepath=log_filepath,\n    )\n\n    return craft_cli.Dispatcher(\n        \"snapcraft\",\n        COMMAND_GROUPS,\n        summary=\"Package, distribute, and update snaps for Linux and IoT\",\n        extra_global_args=GLOBAL_ARGS,\n        default_command=commands.PackCommand,\n    )\n\n\ndef _run_dispatcher(\n    dispatcher: craft_cli.Dispatcher, global_args: Dict[str, Any]\n) -> None:\n    if global_args.get(\"version\"):\n        emit.message(f\"snapcraft {__version__}\")\n    else:\n        if global_args.get(\"trace\"):\n            emit.message(\n                \"Options -t and --trace are deprecated, use --verbosity=debug instead.\"\n            )\n            emit.set_mode(EmitterMode.DEBUG)\n\n        dispatcher.load_command(None)\n        dispatcher.run()\n    emit.ended_ok()\n\n\ndef _emit_error(error, cause=None):\n    \"\"\"Emit the error in a centralized way so we can alter it consistently.\"\"\"\n    # set the cause, if any\n    if cause is not None:\n        error.__cause__ = cause\n\n    # Do not report the internal logpath if running inside instance\n    if utils.is_managed_mode():\n        error.logpath_report = False\n\n    emit.error(error)\n\n\ndef run():  # noqa: C901\n    \"\"\"Run the CLI.\"\"\"\n    dispatcher = get_dispatcher()\n    retcode = 1\n\n    try:\n        # Register our own plugins\n        global_args = dispatcher.pre_parse_args(sys.argv[1:])\n        plugins.register()\n\n        _run_dispatcher(dispatcher, global_args)\n        retcode = 0\n    except ArgumentParsingError as err:\n        # TODO https://github.com/canonical/craft-cli/issues/78\n        with contextlib.suppress(KeyError, IndexError):\n            if (\n                err.__context__ is not None\n                and err.__context__.args[0]  # pylint: disable=no-member\n                not in dispatcher.commands\n            ):\n                run_legacy(err)\n        print(err, file=sys.stderr)  # to stderr, as argparse normally does\n        emit.ended_ok()\n        retcode = 1\n    except ProvideHelpException as err:\n        print(err, file=sys.stderr)  # to stderr, as argparse normally does\n        emit.ended_ok()\n        retcode = 0\n    except errors.LegacyFallback as err:\n        run_legacy(err)\n    except KeyboardInterrupt as err:\n        _emit_error(craft_cli.errors.CraftError(\"Interrupted.\"), cause=err)\n        retcode = 1\n    except craft_store.errors.NoKeyringError as err:\n        _emit_error(\n            craft_cli.errors.CraftError(\n                f\"craft-store error: {err}\",\n                resolution=(\n                    \"Ensure the keyring is working or \"\n                    f\"{store.constants.ENVIRONMENT_STORE_CREDENTIALS} \"\n                    \"is correctly exported into the environment\"\n                ),\n                docs_url=\"https://snapcraft.io/docs/snapcraft-authentication\",\n            )\n        )\n        retcode = 1\n    except craft_store.errors.CraftStoreError as err:\n        _emit_error(craft_cli.errors.CraftError(f\"craft-store error: {err}\"))\n        retcode = 1\n    except errors.LinterError as err:\n        emit.error(craft_cli.errors.CraftError(f\"linter error: {err}\"))\n        retcode = err.exit_code\n    except errors.SnapcraftError as err:\n        _emit_error(err)\n        retcode = 1\n\n    return retcode\n", "path": "snapcraft/cli.py"}], "after_files": [{"content": "# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-\n#\n# Copyright 2022-2023 Canonical Ltd.\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License version 3 as\n# published by the Free Software Foundation.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\n\"\"\"Command-line application entry point.\"\"\"\n\nimport argparse\nimport contextlib\nimport logging\nimport os\nimport sys\nfrom typing import Any, Dict\n\nimport craft_cli\nimport craft_store\nfrom craft_cli import ArgumentParsingError, EmitterMode, ProvideHelpException, emit\nfrom craft_providers import ProviderError\n\nimport snapcraft\nimport snapcraft_legacy\nfrom snapcraft import __version__, errors, store, utils\nfrom snapcraft.parts import plugins\nfrom snapcraft_legacy.cli import legacy\n\nfrom . import commands\nfrom .legacy_cli import _LIB_NAMES, _ORIGINAL_LIB_NAME_LOG_LEVEL, run_legacy\n\nCOMMAND_GROUPS = [\n    craft_cli.CommandGroup(\n        \"Lifecycle\",\n        [\n            commands.CleanCommand,\n            commands.PullCommand,\n            commands.BuildCommand,\n            commands.StageCommand,\n            commands.PrimeCommand,\n            commands.PackCommand,\n            commands.RemoteBuildCommand,\n            commands.SnapCommand,  # hidden (legacy compatibility)\n            commands.PluginsCommand,\n            commands.ListPluginsCommand,\n            commands.TryCommand,\n        ],\n    ),\n    craft_cli.CommandGroup(\n        \"Extensions\",\n        [\n            commands.ListExtensionsCommand,\n            commands.ExtensionsCommand,  # hidden (alias to list-extensions)\n            commands.ExpandExtensionsCommand,\n        ],\n    ),\n    craft_cli.CommandGroup(\n        \"Store Account\",\n        [\n            commands.StoreLoginCommand,\n            commands.StoreExportLoginCommand,\n            commands.StoreLogoutCommand,\n            commands.StoreWhoAmICommand,\n        ],\n    ),\n    craft_cli.CommandGroup(\n        \"Store Snap Names\",\n        [\n            commands.StoreRegisterCommand,\n            commands.StoreNamesCommand,\n            commands.StoreLegacyListRegisteredCommand,\n            commands.StoreLegacyListCommand,\n            commands.StoreLegacyMetricsCommand,\n            commands.StoreLegacyUploadMetadataCommand,\n        ],\n    ),\n    craft_cli.CommandGroup(\n        \"Store Snap Release Management\",\n        [\n            commands.StoreReleaseCommand,\n            commands.StoreCloseCommand,\n            commands.StoreStatusCommand,\n            commands.StoreUploadCommand,\n            commands.StoreLegacyPushCommand,  # hidden (legacy for upload)\n            commands.StoreLegacyPromoteCommand,\n            commands.StoreListRevisionsCommand,\n            commands.StoreRevisionsCommand,  # hidden (alias to list-revisions)\n        ],\n    ),\n    craft_cli.CommandGroup(\n        \"Store Snap Tracks\",\n        [\n            commands.StoreListTracksCommand,\n            commands.StoreTracksCommand,  # hidden (alias to list-tracks)\n            commands.StoreLegacySetDefaultTrackCommand,\n        ],\n    ),\n    craft_cli.CommandGroup(\n        \"Store Key Management\",\n        [\n            commands.StoreLegacyCreateKeyCommand,\n            commands.StoreLegacyRegisterKeyCommand,\n            commands.StoreLegacySignBuildCommand,\n            commands.StoreLegacyListKeysCommand,\n        ],\n    ),\n    craft_cli.CommandGroup(\n        \"Store Validation Sets\",\n        [\n            commands.StoreEditValidationSetsCommand,\n            commands.StoreLegacyListValidationSetsCommand,\n            commands.StoreLegacyValidateCommand,\n            commands.StoreLegacyGatedCommand,\n        ],\n    ),\n    craft_cli.CommandGroup(\n        \"Other\",\n        [\n            commands.VersionCommand,\n            commands.LintCommand,\n            commands.InitCommand,\n        ],\n    ),\n]\n\nGLOBAL_ARGS = [\n    craft_cli.GlobalArgument(\n        \"version\", \"flag\", \"-V\", \"--version\", \"Show the application version and exit\"\n    ),\n    craft_cli.GlobalArgument(\"trace\", \"flag\", \"-t\", \"--trace\", argparse.SUPPRESS),\n]\n\n\ndef get_verbosity() -> EmitterMode:\n    \"\"\"Return the verbosity level to use.\n\n    if SNAPCRAFT_ENABLE_DEVELOPER_DEBUG is set, the\n    default verbosity will be set to EmitterMode.DEBUG.\n\n    If stdin is closed, the default verbosity will be\n    set to EmitterMode.VERBOSE.\n    \"\"\"\n    verbosity = EmitterMode.BRIEF\n\n    if not sys.stdin.isatty():\n        verbosity = EmitterMode.VERBOSE\n\n    with contextlib.suppress(ValueError):\n        # Parse environment variable for backwards compatibility with launchpad\n        if utils.strtobool(os.getenv(\"SNAPCRAFT_ENABLE_DEVELOPER_DEBUG\", \"n\").strip()):\n            verbosity = EmitterMode.DEBUG\n\n    # if defined, use environmental variable SNAPCRAFT_VERBOSITY_LEVEL\n    verbosity_env = os.getenv(\"SNAPCRAFT_VERBOSITY_LEVEL\")\n    if verbosity_env:\n        try:\n            verbosity = EmitterMode[verbosity_env.strip().upper()]\n        except KeyError:\n            values = utils.humanize_list(\n                [e.name.lower() for e in EmitterMode], \"and\", sort=False\n            )\n            raise ArgumentParsingError(\n                f\"cannot parse verbosity level {verbosity_env!r} from environment \"\n                f\"variable SNAPCRAFT_VERBOSITY_LEVEL (valid values are {values})\"\n            ) from KeyError\n\n    return verbosity\n\n\ndef get_dispatcher() -> craft_cli.Dispatcher:\n    \"\"\"Return an instance of Dispatcher.\n\n    Run all the checks and setup required to ensure the Dispatcher can run.\n    \"\"\"\n    # Run the legacy implementation if inside a legacy managed environment.\n    if os.getenv(\"SNAPCRAFT_BUILD_ENVIRONMENT\") == \"managed-host\":\n        snapcraft.ProjectOptions = snapcraft_legacy.ProjectOptions  # type: ignore\n        legacy.legacy_run()\n\n    # set lib loggers to debug level so that all messages are sent to Emitter\n    for lib_name in _LIB_NAMES:\n        logger = logging.getLogger(lib_name)\n        _ORIGINAL_LIB_NAME_LOG_LEVEL[lib_name] = logger.level\n        logger.setLevel(logging.DEBUG)\n\n    if utils.is_managed_mode():\n        log_filepath = utils.get_managed_environment_log_path()\n    else:\n        log_filepath = None\n\n    emit.init(\n        mode=get_verbosity(),\n        appname=\"snapcraft\",\n        greeting=f\"Starting Snapcraft {__version__}\",\n        log_filepath=log_filepath,\n    )\n\n    return craft_cli.Dispatcher(\n        \"snapcraft\",\n        COMMAND_GROUPS,\n        summary=\"Package, distribute, and update snaps for Linux and IoT\",\n        extra_global_args=GLOBAL_ARGS,\n        default_command=commands.PackCommand,\n    )\n\n\ndef _run_dispatcher(\n    dispatcher: craft_cli.Dispatcher, global_args: Dict[str, Any]\n) -> None:\n    if global_args.get(\"version\"):\n        emit.message(f\"snapcraft {__version__}\")\n    else:\n        if global_args.get(\"trace\"):\n            emit.message(\n                \"Options -t and --trace are deprecated, use --verbosity=debug instead.\"\n            )\n            emit.set_mode(EmitterMode.DEBUG)\n\n        dispatcher.load_command(None)\n        dispatcher.run()\n    emit.ended_ok()\n\n\ndef _emit_error(error, cause=None):\n    \"\"\"Emit the error in a centralized way so we can alter it consistently.\"\"\"\n    # set the cause, if any\n    if cause is not None:\n        error.__cause__ = cause\n\n    # Do not report the internal logpath if running inside instance\n    if utils.is_managed_mode():\n        error.logpath_report = False\n\n    emit.error(error)\n\n\ndef run():  # noqa: C901\n    \"\"\"Run the CLI.\"\"\"\n    dispatcher = get_dispatcher()\n    retcode = 1\n\n    try:\n        # Register our own plugins\n        global_args = dispatcher.pre_parse_args(sys.argv[1:])\n        plugins.register()\n\n        _run_dispatcher(dispatcher, global_args)\n        retcode = 0\n    except ArgumentParsingError as err:\n        # TODO https://github.com/canonical/craft-cli/issues/78\n        with contextlib.suppress(KeyError, IndexError):\n            if (\n                err.__context__ is not None\n                and err.__context__.args[0]  # pylint: disable=no-member\n                not in dispatcher.commands\n            ):\n                run_legacy(err)\n        print(err, file=sys.stderr)  # to stderr, as argparse normally does\n        emit.ended_ok()\n        retcode = 1\n    except ProvideHelpException as err:\n        print(err, file=sys.stderr)  # to stderr, as argparse normally does\n        emit.ended_ok()\n        retcode = 0\n    except errors.LegacyFallback as err:\n        run_legacy(err)\n    except KeyboardInterrupt as err:\n        _emit_error(craft_cli.errors.CraftError(\"Interrupted.\"), cause=err)\n        retcode = 1\n    except craft_store.errors.NoKeyringError as err:\n        _emit_error(\n            craft_cli.errors.CraftError(\n                f\"craft-store error: {err}\",\n                resolution=(\n                    \"Ensure the keyring is working or \"\n                    f\"{store.constants.ENVIRONMENT_STORE_CREDENTIALS} \"\n                    \"is correctly exported into the environment\"\n                ),\n                docs_url=\"https://snapcraft.io/docs/snapcraft-authentication\",\n            )\n        )\n        retcode = 1\n    except craft_store.errors.CraftStoreError as err:\n        _emit_error(craft_cli.errors.CraftError(f\"craft-store error: {err}\"))\n        retcode = 1\n    except ProviderError as err:\n        _emit_error(craft_cli.errors.CraftError(f\"craft-providers error: {err}\"))\n        retcode = 1\n    except errors.LinterError as err:\n        emit.error(craft_cli.errors.CraftError(f\"linter error: {err}\"))\n        retcode = err.exit_code\n    except errors.SnapcraftError as err:\n        _emit_error(err)\n        retcode = 1\n\n    return retcode\n", "path": "snapcraft/cli.py"}]}