11# This file is part of pycloudlib. See LICENSE file for license information.
22"""Base class for all other clouds to provide consistent set of functions."""
33
4+ import dataclasses
45import enum
56import getpass
67import io
2021)
2122from pycloudlib .instance import BaseInstance
2223from pycloudlib .key import KeyPair
24+ from pycloudlib .types import ImageInfo , ImageType
2325from pycloudlib .util import (
2426 get_timestamped_tag ,
2527 log_exception_list ,
2628)
2729
2830_RequiredValues = Optional [Sequence [Optional [Any ]]]
2931
30-
31- @enum .unique
32- class ImageType (enum .Enum ):
33- """Allowed image types when launching cloud images."""
34-
35- GENERIC = "generic"
36- MINIMAL = "minimal"
37- PRO = "Pro"
38- PRO_FIPS = "Pro FIPS"
39-
40-
4132class BaseCloud (ABC ):
4233 """Base Cloud Class."""
4334
@@ -58,7 +49,8 @@ def __init__(
5849 config_file: path to pycloudlib configuration file
5950 """
6051 self .created_instances : List [BaseInstance ] = []
61- self .created_images : List [str ] = []
52+ self .created_images : List [ImageInfo ] = []
53+ self .preserved_images : List [ImageInfo ] = [] # each dict will hold an id and name
6254
6355 self ._log = logging .getLogger ("{}.{}" .format (__name__ , self .__class__ .__name__ ))
6456 self .config = self ._check_and_get_config (config_file , required_values )
@@ -189,12 +181,13 @@ def launch(
189181 raise NotImplementedError
190182
191183 @abstractmethod
192- def snapshot (self , instance , clean = True , ** kwargs ):
184+ def snapshot (self , instance , * , clean = True , keep = False , ** kwargs ):
193185 """Snapshot an instance and generate an image from it.
194186
195187 Args:
196188 instance: Instance to snapshot
197189 clean: run instance clean method before taking snapshot
190+ keep: keep the snapshot after the cloud instance is cleaned up
198191
199192 Returns:
200193 An image id
@@ -216,11 +209,18 @@ def clean(self) -> List[Exception]:
216209 instance .delete ()
217210 except Exception as e :
218211 exceptions .append (e )
219- for image_id in self .created_images :
212+ for image_info in self .created_images :
220213 try :
221- self .delete_image (image_id )
214+ self .delete_image (image_id = image_info . image_id )
222215 except Exception as e :
223216 exceptions .append (e )
217+ for image_info in self .preserved_images :
218+ # noop - just log that we're not cleaning up these images
219+ self ._log .info (
220+ "Preserved image %s [id:%s] is NOT being cleaned up." ,
221+ image_info .image_name ,
222+ image_info .image_id ,
223+ )
224224 return exceptions
225225
226226 def list_keys (self ):
@@ -371,3 +371,70 @@ def _get_ssh_keys(
371371 private_key_path = private_key_path ,
372372 name = name ,
373373 )
374+
375+ def _store_snapshot_info (
376+ self ,
377+ snapshot_id : str ,
378+ snapshot_name : str ,
379+ keep_snapshot : bool ,
380+ ) -> ImageInfo :
381+ """
382+ Save the snapshot information for later cleanup depending on the keep_snapshot arg.
383+
384+ Will either save the snapshot information to created_images or preserved_images depending
385+ on the keep_snapshot arg. These lists are used by the BaseCloud's clean() method to
386+ cleanup the snapshots when the cloud instance is cleaned up. The snapshot information
387+ is also logged appropriately and in a consistent format.
388+
389+ :param snapshot_id: ID of the snapshot (this is used later to delete the snapshot)
390+ :param snapshot_name: Name of the snapshot (this is for user reference)
391+ :param keep_snapshot: Keep the snapshot after the cloud instance is cleaned up
392+
393+ :return: ImageInfo object with 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