1919)
2020from pycloudlib .instance import BaseInstance
2121from pycloudlib .key import KeyPair
22+ from pycloudlib .types import ImageInfo
2223from pycloudlib .util import (
2324 get_timestamped_tag ,
2425 log_exception_list ,
@@ -47,7 +48,8 @@ def __init__(
4748 config_file: path to pycloudlib configuration file
4849 """
4950 self .created_instances : List [BaseInstance ] = []
50- self .created_images : List [str ] = []
51+ self .created_images : List [ImageInfo ] = []
52+ self .preserved_images : List [ImageInfo ] = [] # each dict will hold an id and name
5153
5254 self ._log = logging .getLogger ("{}.{}" .format (__name__ , self .__class__ .__name__ ))
5355 self .config = self ._check_and_get_config (config_file , required_values )
@@ -177,12 +179,13 @@ def launch(
177179 raise NotImplementedError
178180
179181 @abstractmethod
180- def snapshot (self , instance , clean = True , ** kwargs ):
182+ def snapshot (self , instance , * , clean = True , keep = False , ** kwargs ):
181183 """Snapshot an instance and generate an image from it.
182184
183185 Args:
184186 instance: Instance to snapshot
185187 clean: run instance clean method before taking snapshot
188+ keep: keep the snapshot after the cloud instance is cleaned up
186189
187190 Returns:
188191 An image id
@@ -204,11 +207,18 @@ def clean(self) -> List[Exception]:
204207 instance .delete ()
205208 except Exception as e :
206209 exceptions .append (e )
207- for image_id in self .created_images :
210+ for image_info in self .created_images :
208211 try :
209- self .delete_image (image_id )
212+ self .delete_image (image_id = image_info . image_id )
210213 except Exception as e :
211214 exceptions .append (e )
215+ for image_info in self .preserved_images :
216+ # noop - just log that we're not cleaning up these images
217+ self ._log .info (
218+ "Preserved image %s [id:%s] is NOT being cleaned up." ,
219+ image_info .image_name ,
220+ image_info .image_id ,
221+ )
212222 return exceptions
213223
214224 def list_keys (self ):
@@ -359,3 +369,72 @@ def _get_ssh_keys(
359369 private_key_path = private_key_path ,
360370 name = name ,
361371 )
372+
373+ def _store_snapshot_info (
374+ self ,
375+ snapshot_id : str ,
376+ snapshot_name : str ,
377+ keep_snapshot : bool ,
378+ ) -> ImageInfo :
379+ """
380+ Save the snapshot information for later cleanup depending on the keep_snapshot argument.
381+
382+ This method saves the snapshot information to either `created_images` or `preserved_images`
383+ based on the value of `keep_snapshot`. These lists are used by the `BaseCloud`'s `clean()`
384+ method to manage snapshots during cleanup. The snapshot information is also logged in a
385+ consistent format so that individual clouds do NOT need to worry about logging.
386+
387+ Args:
388+ snapshot_id (str): ID of the snapshot (used later to delete the snapshot).
389+ snapshot_name (str): Name of the snapshot (for user reference).
390+ keep_snapshot (bool): Whether to keep the snapshot after the cloud instance is cleaned up.
391+
392+ Returns:
393+ ImageInfo: An ImageInfo object containing the snapshot information.
394+ """
395+ image_info = ImageInfo (
396+ image_id = snapshot_id ,
397+ image_name = snapshot_name ,
398+ )
399+ if not keep_snapshot :
400+ self .created_images .append (image_info )
401+ self ._log .info (
402+ "Created temporary snapshot %s" ,
403+ image_info ,
404+ )
405+ else :
406+ self .preserved_images .append (image_info )
407+ self ._log .info (
408+ "Created permanent snapshot %s" ,
409+ image_info ,
410+ )
411+ return image_info
412+
413+ def _record_image_deletion (self , image_id : str ):
414+ """
415+ Record the deletion of an image.
416+
417+ This method should be called after an image is successfully deleted.
418+ It will remove the image from the list of created_images or preserved_images
419+ so that the cloud does not attempt to re-clean it up later. It will also log
420+ the deletion of the image.
421+
422+ :param image_id: ID of the image that was deleted
423+ """
424+ if match := [i for i in self .created_images if i .image_id == image_id ]:
425+ deleted_image = match [0 ]
426+ self .created_images .remove (deleted_image )
427+ self ._log .debug (
428+ "Snapshot %s has been deleted. Will no longer need to be cleaned up later." ,
429+ deleted_image ,
430+ )
431+ elif match := [i for i in self .preserved_images if i .image_id == image_id ]:
432+ deleted_image = match [0 ]
433+ self .preserved_images .remove (deleted_image )
434+ self ._log .debug (
435+ "Snapshot %s has been deleted. This snapshot was taken with keep=True, "
436+ "but since it has been manually deleted, it will not be preserved." ,
437+ deleted_image ,
438+ )
439+ else :
440+ self ._log .debug ("Deleted image %s" , image_id )
0 commit comments