@@ -41,10 +41,32 @@ class ImageType(enum.Enum):
4141
4242@dataclasses .dataclass
4343class ImageInfo :
44- """Dataclass to hold image information ."""
44+ """Dataclass that represents an image on any given cloud ."""
4545
46- id : str
47- name : str
46+ image_id : str
47+ image_name : str
48+
49+ def __str__ (self ):
50+ """Return a human readable string representation of the image."""
51+ return f"{ self .image_name } [id: { self .image_id } ]"
52+
53+ def __repr__ (self ):
54+ """Return a string representation of the image."""
55+ return f"ImageInfo(id={ self .image_id } , name={ self .image_name } )"
56+
57+ def __eq__ (self , other ):
58+ """
59+ Check if two ImageInfo objects represent the same image.
60+
61+ Only the id is used for comparison since this should be the unique identifier for an image.
62+ """
63+ if not isinstance (other , ImageInfo ):
64+ return False
65+ return self .image_id == other .image_id
66+
67+ def __dict__ (self ):
68+ """Return a dictionary representation of the image."""
69+ return {"image_id" : self .image_id , "image_name" : self .image_name }
4870
4971
5072class BaseCloud (ABC ):
@@ -225,15 +247,15 @@ def clean(self) -> List[Exception]:
225247 exceptions .append (e )
226248 for image_info in self .created_images :
227249 try :
228- self .delete_image (image_id = image_info .id )
250+ self .delete_image (image_id = image_info .image_id )
229251 except Exception as e :
230252 exceptions .append (e )
231253 for image_info in self .preserved_images :
232254 # noop - just log that we're not cleaning up these images
233255 self ._log .info (
234256 "Preserved image %s [id:%s] is NOT being cleaned up." ,
235- image_info .name ,
236- image_info .id ,
257+ image_info .image_name ,
258+ image_info .image_id ,
237259 )
238260 return exceptions
239261
@@ -329,6 +351,18 @@ def _validate_tag(tag: str):
329351 raise InvalidTagNameError (tag = tag , rules_failed = rules_failed )
330352
331353 def _get_ssh_keys (self ) -> KeyPair :
354+ """
355+ Get the ssh key pair to use for the cloud instance.
356+
357+ If no key pair is provided in the config file, the default key pair
358+ will be used. The default key pair is the id_rsa or id_ed25519 key
359+ in the user's .ssh directory.
360+
361+ :raises PycloudlibError: if no public key path is provided and no default key is found
362+ :raises PycloudlibError: if the public key path provided in the config does not exist
363+
364+ :return: KeyPair object with the public and private key paths
365+ """
332366 user = getpass .getuser ()
333367 # check if id_rsa or id_ed25519 keys exist in the user's .ssh directory
334368 possible_default_keys = [
@@ -380,21 +414,48 @@ def _store_snapshot_info(
380414 :return: ImageInfo object with the snapshot information
381415 """
382416 image_info = ImageInfo (
383- id = snapshot_id ,
384- name = snapshot_name ,
417+ image_id = snapshot_id ,
418+ image_name = snapshot_name ,
385419 )
386420 if not keep_snapshot :
387421 self .created_images .append (image_info )
388422 self ._log .info (
389- "Created temporary snapshot %s [id:%s]" ,
390- image_info .name ,
391- image_info .id ,
423+ "Created temporary snapshot %s" ,
424+ image_info ,
392425 )
393426 else :
394427 self .preserved_images .append (image_info )
395428 self ._log .info (
396- "Created permanent snapshot %s [id:%s]" ,
397- image_info .name ,
398- image_info .id ,
429+ "Created permanent snapshot %s" ,
430+ image_info ,
399431 )
400432 return image_info
433+
434+ def _record_image_deletion (self , image_id : str ):
435+ """
436+ Record the deletion of an image.
437+
438+ This method should be called after an image is successfully deleted.
439+ It will remove the image from the list of created_images or preserved_images
440+ so that the cloud does not attempt to re-clean it up later. It will also log
441+ the deletion of the image.
442+
443+ :param image_id: ID of the image that was deleted
444+ """
445+ if match := [i for i in self .created_images if i .image_id == image_id ]:
446+ deleted_image = match [0 ]
447+ self .created_images .remove (deleted_image )
448+ self ._log .debug (
449+ "Snapshot %s has been deleted. Will no longer need to be cleaned up later." ,
450+ deleted_image ,
451+ )
452+ elif match := [i for i in self .preserved_images if i .image_id == image_id ]:
453+ deleted_image = match [0 ]
454+ self .preserved_images .remove (deleted_image )
455+ self ._log .debug (
456+ "Snapshot %s has been deleted. This snapshot was taken with keep=True, "
457+ "but since it has been manually deleted, it will not be preserved." ,
458+ deleted_image ,
459+ )
460+ else :
461+ self ._log .debug ("Deleted image %s" , image_id )
0 commit comments