19
19
)
20
20
from pycloudlib .instance import BaseInstance
21
21
from pycloudlib .key import KeyPair
22
+ from pycloudlib .types import ImageInfo
22
23
from pycloudlib .util import (
23
24
get_timestamped_tag ,
24
25
log_exception_list ,
@@ -47,7 +48,8 @@ def __init__(
47
48
config_file: path to pycloudlib configuration file
48
49
"""
49
50
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
51
53
52
54
self ._log = logging .getLogger ("{}.{}" .format (__name__ , self .__class__ .__name__ ))
53
55
self .config = self ._check_and_get_config (config_file , required_values )
@@ -177,12 +179,13 @@ def launch(
177
179
raise NotImplementedError
178
180
179
181
@abstractmethod
180
- def snapshot (self , instance , clean = True , ** kwargs ):
182
+ def snapshot (self , instance , * , clean = True , keep = False , ** kwargs ):
181
183
"""Snapshot an instance and generate an image from it.
182
184
183
185
Args:
184
186
instance: Instance to snapshot
185
187
clean: run instance clean method before taking snapshot
188
+ keep: keep the snapshot after the cloud instance is cleaned up
186
189
187
190
Returns:
188
191
An image id
@@ -204,11 +207,18 @@ def clean(self) -> List[Exception]:
204
207
instance .delete ()
205
208
except Exception as e :
206
209
exceptions .append (e )
207
- for image_id in self .created_images :
210
+ for image_info in self .created_images :
208
211
try :
209
- self .delete_image (image_id )
212
+ self .delete_image (image_id = image_info . image_id )
210
213
except Exception as e :
211
214
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
+ )
212
222
return exceptions
213
223
214
224
def list_keys (self ):
@@ -359,3 +369,70 @@ def _get_ssh_keys(
359
369
private_key_path = private_key_path ,
360
370
name = name ,
361
371
)
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