87
87
88
88
if typing .TYPE_CHECKING :
89
89
# 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
91
91
92
92
_P = ParamSpec ("_P" )
93
93
_T = TypeVar ("_T" )
102
102
103
103
# Increment this PATCH version before using `charmcraft publish-lib` or reset
104
104
# to 0 if you are raising the major API version
105
- LIBPATCH = 11
105
+ LIBPATCH = 12
106
106
107
107
108
108
# Regex to locate 7-bit C1 ANSI sequences
@@ -268,6 +268,22 @@ class SnapState(Enum):
268
268
class SnapError (Error ):
269
269
"""Raised when there's an error running snap control commands."""
270
270
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
+
271
287
272
288
class SnapNotFoundError (Error ):
273
289
"""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:
340
356
optargs = optargs or []
341
357
args = ["snap" , command , self ._name , * optargs ]
342
358
try :
343
- return subprocess .check_output (args , text = True )
359
+ return subprocess .check_output (args , text = True , stderr = subprocess . PIPE )
344
360
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
348
363
349
364
def _snap_daemons (
350
365
self ,
@@ -371,7 +386,8 @@ def _snap_daemons(
371
386
try :
372
387
return subprocess .run (args , text = True , check = True , capture_output = True )
373
388
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
375
391
376
392
@typing .overload
377
393
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
477
493
try :
478
494
subprocess .run (args , text = True , check = True , capture_output = True )
479
495
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
481
498
482
499
def hold (self , duration : timedelta | None = None ) -> None :
483
500
"""Add a refresh hold to a snap.
@@ -506,11 +523,10 @@ def alias(self, application: str, alias: str | None = None) -> None:
506
523
alias = application
507
524
args = ["snap" , "alias" , f"{ self .name } .{ application } " , alias ]
508
525
try :
509
- subprocess .check_output (args , text = True )
526
+ subprocess .run (args , text = True , check = True , capture_output = True )
510
527
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
514
530
515
531
def restart (self , services : list [str ] | None = None , reload : bool = False ) -> None :
516
532
"""Restarts a snap's services.
@@ -1264,7 +1280,7 @@ def install_local(
1264
1280
if dangerous :
1265
1281
args .append ("--dangerous" )
1266
1282
try :
1267
- result = subprocess .check_output (args , text = True ).splitlines ()[- 1 ]
1283
+ result = subprocess .check_output (args , text = True , stderr = subprocess . PIPE ).splitlines ()[- 1 ]
1268
1284
snap_name , _ = result .split (" " , 1 )
1269
1285
snap_name = ansi_filter .sub ("" , snap_name )
1270
1286
@@ -1280,7 +1296,8 @@ def install_local(
1280
1296
)
1281
1297
raise SnapError (f"Failed to find snap { snap_name } in Snap cache" ) from e
1282
1298
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
1284
1301
1285
1302
1286
1303
def _system_set (config_item : str , value : str ) -> None :
@@ -1292,9 +1309,10 @@ def _system_set(config_item: str, value: str) -> None:
1292
1309
"""
1293
1310
args = ["snap" , "set" , "system" , f"{ config_item } ={ value } " ]
1294
1311
try :
1295
- subprocess .check_call (args , text = True )
1312
+ subprocess .run (args , text = True , check = True , capture_output = True )
1296
1313
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
1298
1316
1299
1317
1300
1318
def hold_refresh (days : int = 90 , forever : bool = False ) -> None :
0 commit comments