8787
8888if typing .TYPE_CHECKING :
8989 # avoid typing_extensions import at runtime
90- from typing_extensions import NotRequired , ParamSpec , Required , TypeAlias , Unpack
90+ from typing_extensions import NotRequired , ParamSpec , Required , Self , TypeAlias , Unpack
9191
9292 _P = ParamSpec ("_P" )
9393 _T = TypeVar ("_T" )
102102
103103# Increment this PATCH version before using `charmcraft publish-lib` or reset
104104# to 0 if you are raising the major API version
105- LIBPATCH = 11
105+ LIBPATCH = 12
106106
107107
108108# Regex to locate 7-bit C1 ANSI sequences
@@ -268,6 +268,22 @@ class SnapState(Enum):
268268class SnapError (Error ):
269269 """Raised when there's an error running snap control commands."""
270270
271+ @classmethod
272+ def _from_called_process_error (cls , msg : str , error : CalledProcessError ) -> Self :
273+ lines = [msg ]
274+ if error .stdout :
275+ lines .extend (['Stdout:' , error .stdout ])
276+ if error .stderr :
277+ lines .extend (['Stderr:' , error .stderr ])
278+ try :
279+ cmd = ['journalctl' , '--unit' , 'snapd' , '--lines' , '20' ]
280+ logs = subprocess .check_output (cmd , text = True )
281+ except Exception as e :
282+ lines .extend (['Error fetching logs:' , str (e )])
283+ else :
284+ lines .extend (['Latest logs:' , logs ])
285+ return cls ('\n ' .join (lines ))
286+
271287
272288class SnapNotFoundError (Error ):
273289 """Raised when a requested snap is not known to the system."""
@@ -340,11 +356,10 @@ def _snap(self, command: str, optargs: Iterable[str] | None = None) -> str:
340356 optargs = optargs or []
341357 args = ["snap" , command , self ._name , * optargs ]
342358 try :
343- return subprocess .check_output (args , text = True )
359+ return subprocess .check_output (args , text = True , stderr = subprocess . PIPE )
344360 except CalledProcessError as e :
345- raise SnapError (
346- f"Snap: { self ._name !r} ; command { args !r} failed with output = { e .output !r} "
347- ) from e
361+ msg = f'Snap: { self ._name !r} -- command { args !r} failed!'
362+ raise SnapError ._from_called_process_error (msg = msg , error = e ) from e
348363
349364 def _snap_daemons (
350365 self ,
@@ -371,7 +386,8 @@ def _snap_daemons(
371386 try :
372387 return subprocess .run (args , text = True , check = True , capture_output = True )
373388 except CalledProcessError as e :
374- raise SnapError (f"Could not { args } for snap [{ self ._name } ]: { e .stderr } " ) from e
389+ msg = f'Snap: { self ._name !r} -- command { args !r} failed!'
390+ raise SnapError ._from_called_process_error (msg = msg , error = e ) from e
375391
376392 @typing .overload
377393 def get (self , key : None | Literal ["" ], * , typed : Literal [False ] = False ) -> NoReturn : ...
@@ -477,7 +493,8 @@ def connect(self, plug: str, service: str | None = None, slot: str | None = None
477493 try :
478494 subprocess .run (args , text = True , check = True , capture_output = True )
479495 except CalledProcessError as e :
480- raise SnapError (f"Could not { args } for snap [{ self ._name } ]: { e .stderr } " ) from e
496+ msg = f'Snap: { self ._name !r} -- command { args !r} failed!'
497+ raise SnapError ._from_called_process_error (msg = msg , error = e ) from e
481498
482499 def hold (self , duration : timedelta | None = None ) -> None :
483500 """Add a refresh hold to a snap.
@@ -506,11 +523,10 @@ def alias(self, application: str, alias: str | None = None) -> None:
506523 alias = application
507524 args = ["snap" , "alias" , f"{ self .name } .{ application } " , alias ]
508525 try :
509- subprocess .check_output (args , text = True )
526+ subprocess .run (args , text = True , check = True , capture_output = True )
510527 except CalledProcessError as e :
511- raise SnapError (
512- f"Snap: { self ._name !r} ; command { args !r} failed with output = { e .output !r} "
513- ) from e
528+ msg = f'Snap: { self ._name !r} -- command { args !r} failed!'
529+ raise SnapError ._from_called_process_error (msg = msg , error = e ) from e
514530
515531 def restart (self , services : list [str ] | None = None , reload : bool = False ) -> None :
516532 """Restarts a snap's services.
@@ -1264,7 +1280,7 @@ def install_local(
12641280 if dangerous :
12651281 args .append ("--dangerous" )
12661282 try :
1267- result = subprocess .check_output (args , text = True ).splitlines ()[- 1 ]
1283+ result = subprocess .check_output (args , text = True , stderr = subprocess . PIPE ).splitlines ()[- 1 ]
12681284 snap_name , _ = result .split (" " , 1 )
12691285 snap_name = ansi_filter .sub ("" , snap_name )
12701286
@@ -1280,7 +1296,8 @@ def install_local(
12801296 )
12811297 raise SnapError (f"Failed to find snap { snap_name } in Snap cache" ) from e
12821298 except CalledProcessError as e :
1283- raise SnapError (f"Could not install snap { filename } : { e .output } " ) from e
1299+ msg = f'Cound not install snap { filename } !'
1300+ raise SnapError ._from_called_process_error (msg = msg , error = e ) from e
12841301
12851302
12861303def _system_set (config_item : str , value : str ) -> None :
@@ -1292,9 +1309,10 @@ def _system_set(config_item: str, value: str) -> None:
12921309 """
12931310 args = ["snap" , "set" , "system" , f"{ config_item } ={ value } " ]
12941311 try :
1295- subprocess .check_call (args , text = True )
1312+ subprocess .run (args , text = True , check = True , capture_output = True )
12961313 except CalledProcessError as e :
1297- raise SnapError (f"Failed setting system config '{ config_item } ' to '{ value } '" ) from e
1314+ msg = f"Failed setting system config '{ config_item } ' to '{ value } '"
1315+ raise SnapError ._from_called_process_error (msg = msg , error = e ) from e
12981316
12991317
13001318def hold_refresh (days : int = 90 , forever : bool = False ) -> None :
0 commit comments