3434from runloop_api_client import AsyncRunloopSDK
3535from runloop_api_client .lib .polling import PollingConfig
3636from runloop_api_client .sdk .async_devbox import AsyncDevbox
37- from runloop_api_client .sdk .async_snapshot import AsyncSnapshot
3837
3938from ._harness import run_as_cli , unique_name , wrap_recipe
4039from .example_types import ExampleCheck , RecipeOutput , RecipeContext
@@ -55,45 +54,6 @@ async def recipe(ctx: RecipeContext) -> RecipeOutput:
5554
5655 resources_created : list [str ] = []
5756
58- source_devbox : AsyncDevbox | None = None
59- clone_a : AsyncDevbox | None = None
60- clone_b : AsyncDevbox | None = None
61- snapshot : AsyncSnapshot | None = None
62- local_file_path : Path | None = None
63-
64- source_needs_cleanup = False
65- clone_a_needs_cleanup = False
66- clone_b_needs_cleanup = False
67- snapshot_needs_cleanup = False
68-
69- async def cleanup_source () -> None :
70- if source_needs_cleanup and source_devbox is not None :
71- await source_devbox .shutdown ()
72-
73- async def cleanup_clone_a () -> None :
74- if clone_a_needs_cleanup and clone_a is not None :
75- await clone_a .shutdown ()
76-
77- async def cleanup_clone_b () -> None :
78- if clone_b_needs_cleanup and clone_b is not None :
79- await clone_b .shutdown ()
80-
81- async def cleanup_snapshot () -> None :
82- if snapshot_needs_cleanup and snapshot is not None :
83- await snapshot .delete ()
84-
85- def cleanup_local_file () -> None :
86- if local_file_path is not None :
87- local_file_path .unlink (missing_ok = True )
88-
89- # Cleanup runs in LIFO order, so register these handlers up front in reverse
90- # dependency order: clones, then source devbox, then snapshot, then local file.
91- cleanup .add ("local-file:snapshot-demo" , cleanup_local_file )
92- cleanup .add ("snapshot:baseline" , cleanup_snapshot )
93- cleanup .add ("devbox:source" , cleanup_source )
94- cleanup .add ("devbox:clone-a" , cleanup_clone_a )
95- cleanup .add ("devbox:clone-b" , cleanup_clone_b )
96-
9757 uploaded_contents = "uploaded-from-local-file"
9858 baseline_contents = "baseline-after-upload-and-mutation"
9959 source_contents = "source-devbox-after-isolated-mutation"
@@ -103,23 +63,23 @@ def cleanup_local_file() -> None:
10363 with tempfile .NamedTemporaryFile (mode = "w" , delete = False , suffix = ".txt" ) as tmp_file :
10464 tmp_file .write (uploaded_contents )
10565 local_file_path = Path (tmp_file .name )
66+ cleanup .add ("local-file:snapshot-demo" , lambda : local_file_path .unlink (missing_ok = True ))
10667
10768 source_devbox = await sdk .devbox .create (
10869 name = unique_name ("snapshot-source" ),
10970 launch_parameters = {
11071 "resource_size_request" : "X_SMALL" ,
11172 },
11273 )
113- source_needs_cleanup = True
74+ cleanup . add ( f"devbox: { source_devbox . id } " , source_devbox . shutdown )
11475 resources_created .append (f"devbox:{ source_devbox .id } " )
11576
11677 await source_devbox .file .upload (path = FILE_PATH , file = local_file_path )
11778 uploaded_readback = await read_file_contents (source_devbox )
11879
11980 await source_devbox .file .write (file_path = FILE_PATH , contents = baseline_contents )
12081
121- suspend_response = await source_devbox .suspend ()
122- suspended_info = suspend_response
82+ suspended_info = await source_devbox .suspend ()
12383 if suspended_info .status != "suspended" :
12484 suspended_info = await source_devbox .await_suspended (polling_config = POLLING_CONFIG )
12585
@@ -131,7 +91,7 @@ def cleanup_local_file() -> None:
13191 commit_message = "Capture the shared baseline after suspend and resume." ,
13292 polling_config = POLLING_CONFIG ,
13393 )
134- snapshot_needs_cleanup = True
94+ cleanup . add ( f"snapshot: { snapshot . id } " , snapshot . delete )
13595 resources_created .append (f"snapshot:{ snapshot .id } " )
13696
13797 clone_a = await snapshot .create_devbox (
@@ -140,17 +100,19 @@ def cleanup_local_file() -> None:
140100 "resource_size_request" : "X_SMALL" ,
141101 },
142102 )
143- clone_a_needs_cleanup = True
103+ cleanup . add ( f"devbox: { clone_a . id } " , clone_a . shutdown )
144104 resources_created .append (f"devbox:{ clone_a .id } " )
145105
106+ # clone_a used snapshot.create_devbox(); clone_b uses sdk.devbox.create_from_snapshot()
107+ # to demonstrate both entry points for restoring a devbox from a snapshot.
146108 clone_b = await sdk .devbox .create_from_snapshot (
147109 snapshot .id ,
148110 name = unique_name ("snapshot-clone-b" ),
149111 launch_parameters = {
150112 "resource_size_request" : "X_SMALL" ,
151113 },
152114 )
153- clone_b_needs_cleanup = True
115+ cleanup . add ( f"devbox: { clone_b . id } " , clone_b . shutdown )
154116 resources_created .append (f"devbox:{ clone_b .id } " )
155117
156118 clone_a_baseline_readback = await read_file_contents (clone_a )
@@ -164,18 +126,6 @@ def cleanup_local_file() -> None:
164126 clone_a_isolated_readback = await read_file_contents (clone_a )
165127 clone_b_isolated_readback = await read_file_contents (clone_b )
166128
167- await clone_b .shutdown ()
168- clone_b_needs_cleanup = False
169-
170- await clone_a .shutdown ()
171- clone_a_needs_cleanup = False
172-
173- await source_devbox .shutdown ()
174- source_needs_cleanup = False
175-
176- await snapshot .delete ()
177- snapshot_needs_cleanup = False
178-
179129 return RecipeOutput (
180130 resources_created = resources_created ,
181131 checks = [
@@ -229,11 +179,6 @@ def cleanup_local_file() -> None:
229179 ),
230180 details = (f"values={ [source_isolated_readback , clone_a_isolated_readback , clone_b_isolated_readback ]} " ),
231181 ),
232- ExampleCheck (
233- name = "snapshot can be deleted after the demo finishes" ,
234- passed = not snapshot_needs_cleanup ,
235- details = f"deleted={ not snapshot_needs_cleanup } " ,
236- ),
237182 ],
238183 )
239184
0 commit comments