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,70 @@ 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 arg.
381+
382+ Will either save the snapshot information to created_images or preserved_images depending
383+ on the keep_snapshot arg. These lists are used by the BaseCloud's clean() method to
384+ cleanup the snapshots when the cloud instance is cleaned up. The snapshot information
385+ is also logged appropriately and in a consistent format.
386+
387+ :param snapshot_id: ID of the snapshot (this is used later to delete the snapshot)
388+ :param snapshot_name: Name of the snapshot (this is for user reference)
389+ :param keep_snapshot: Keep the snapshot after the cloud instance is cleaned up
390+
391+ :return: ImageInfo object with the snapshot information
392+ """
393+ image_info = ImageInfo (
394+ image_id = snapshot_id ,
395+ image_name = snapshot_name ,
396+ )
397+ if not keep_snapshot :
398+ self .created_images .append (image_info )
399+ self ._log .info (
400+ "Created temporary snapshot %s" ,
401+ image_info ,
402+ )
403+ else :
404+ self .preserved_images .append (image_info )
405+ self ._log .info (
406+ "Created permanent snapshot %s" ,
407+ image_info ,
408+ )
409+ return image_info
410+
411+ def _record_image_deletion (self , image_id : str ):
412+ """
413+ Record the deletion of an image.
414+
415+ This method should be called after an image is successfully deleted.
416+ It will remove the image from the list of created_images or preserved_images
417+ so that the cloud does not attempt to re-clean it up later. It will also log
418+ the deletion of the image.
419+
420+ :param image_id: ID of the image that was deleted
421+ """
422+ if match := [i for i in self .created_images if i .image_id == image_id ]:
423+ deleted_image = match [0 ]
424+ self .created_images .remove (deleted_image )
425+ self ._log .debug (
426+ "Snapshot %s has been deleted. Will no longer need to be cleaned up later." ,
427+ deleted_image ,
428+ )
429+ elif match := [i for i in self .preserved_images if i .image_id == image_id ]:
430+ deleted_image = match [0 ]
431+ self .preserved_images .remove (deleted_image )
432+ self ._log .debug (
433+ "Snapshot %s has been deleted. This snapshot was taken with keep=True, "
434+ "but since it has been manually deleted, it will not be preserved." ,
435+ deleted_image ,
436+ )
437+ else :
438+ self ._log .debug ("Deleted image %s" , image_id )
0 commit comments