Skip to content

Commit d2bd3d2

Browse files
committed
Add an example for auto delete
1 parent ebf9b44 commit d2bd3d2

1 file changed

Lines changed: 311 additions & 0 deletions

File tree

Lines changed: 311 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,311 @@
1+
#!/usr/bin/env python3
2+
"""Create sandboxes with auto-delete lifecycle settings and wait for them to be deleted"""
3+
4+
import os
5+
import time
6+
from collections import defaultdict
7+
from datetime import datetime
8+
9+
from koyeb import Sandbox
10+
from koyeb.sandbox.utils import get_api_client
11+
12+
13+
class TimingTracker:
14+
"""Track timing information for operations"""
15+
16+
def __init__(self):
17+
self.operations = []
18+
self.categories = defaultdict(list)
19+
20+
def record(self, name, duration, category="general"):
21+
"""Record an operation's timing"""
22+
self.operations.append(
23+
{
24+
"name": name,
25+
"duration": duration,
26+
"category": category,
27+
"timestamp": datetime.now(),
28+
}
29+
)
30+
self.categories[category].append(duration)
31+
32+
def get_total_time(self):
33+
"""Get total time for all operations"""
34+
return sum(op["duration"] for op in self.operations)
35+
36+
def print_recap(self):
37+
"""Print a detailed recap of all timings"""
38+
print("\n" + "=" * 70)
39+
print(" TIMING SUMMARY")
40+
print("=" * 70)
41+
42+
if not self.operations:
43+
print("No operations recorded")
44+
return
45+
46+
total_time = self.get_total_time()
47+
48+
print()
49+
for op in self.operations:
50+
percentage = (op["duration"] / total_time * 100) if total_time > 0 else 0
51+
bar_length = int(percentage / 2)
52+
bar = "█" * bar_length
53+
54+
print(
55+
f" {op['name']:<40} {op['duration']:6.2f}s {percentage:5.1f}% {bar}"
56+
)
57+
58+
print()
59+
print("-" * 70)
60+
print(f" {'TOTAL':<40} {total_time:6.2f}s 100.0%")
61+
print("=" * 70)
62+
63+
64+
def get_service_lifecycle(api_token: str, service_id: str):
65+
"""Fetch and return the service lifecycle settings from the API"""
66+
_, services_api, _, _, _ = get_api_client(api_token)
67+
service_response = services_api.get_service(service_id)
68+
return service_response.service.life_cycle
69+
70+
71+
def service_exists(api_token: str, service_id: str) -> bool:
72+
"""Check if a service still exists"""
73+
try:
74+
_, services_api, _, _, _ = get_api_client(api_token)
75+
services_api.get_service(service_id)
76+
return True
77+
except Exception:
78+
return False
79+
80+
81+
def wait_for_deletion(api_token: str, service_id: str, name: str, timeout: int = 600) -> float:
82+
"""Wait for a service to be deleted. Returns time waited."""
83+
start = time.time()
84+
print(f" Waiting for {name} to be auto-deleted...")
85+
86+
while time.time() - start < timeout:
87+
if not service_exists(api_token, service_id):
88+
duration = time.time() - start
89+
print(f" ✓ {name} was auto-deleted after {duration:.1f}s")
90+
return duration
91+
time.sleep(5)
92+
elapsed = time.time() - start
93+
print(f" ... still waiting ({elapsed:.0f}s elapsed)")
94+
95+
print(f" ✗ Timeout waiting for {name} to be deleted")
96+
return time.time() - start
97+
98+
99+
def main():
100+
tracker = TimingTracker()
101+
102+
print("=" * 70)
103+
print(" AUTO-DELETE SANDBOX DEMO")
104+
print("=" * 70)
105+
print()
106+
print("This example creates two sandboxes with different auto-delete configs:")
107+
print(" 1. delete_after_delay (delete 60s after creation)")
108+
print(" 2. idle_timeout + delete_after_inactivity_delay (sleep after idle, then delete)")
109+
print()
110+
111+
api_token = os.getenv("KOYEB_API_TOKEN")
112+
if not api_token:
113+
print("Error: KOYEB_API_TOKEN not set")
114+
return
115+
116+
sandbox1 = None
117+
sandbox2 = None
118+
119+
try:
120+
# =====================================================================
121+
# Sandbox 1: delete_after_delay (delete after creation)
122+
# =====================================================================
123+
print("-" * 70)
124+
print(" SANDBOX 1: delete_after_delay (delete 60s after creation)")
125+
print("-" * 70)
126+
print()
127+
128+
delete_after_delay_1 = 60 # Delete 60s after creation
129+
130+
print(" → Creating sandbox 1...")
131+
print(f" - delete_after_delay: {delete_after_delay_1}s (delete after creation)")
132+
print(f" → Expected deletion: ~{delete_after_delay_1}s after creation")
133+
134+
create_start = time.time()
135+
sandbox1 = Sandbox.create(
136+
image="koyeb/sandbox",
137+
name="auto-delete-test-1",
138+
wait_ready=True,
139+
api_token=api_token,
140+
region="fra",
141+
delete_after_delay=delete_after_delay_1,
142+
)
143+
create_duration = time.time() - create_start
144+
tracker.record("Sandbox 1 creation", create_duration, "setup")
145+
print(f" ✓ Created in {create_duration:.1f}s")
146+
print(f" Service ID: {sandbox1.service_id}")
147+
148+
# Verify lifecycle settings
149+
lifecycle1 = get_service_lifecycle(api_token, sandbox1.service_id)
150+
if lifecycle1:
151+
print(f" Lifecycle: delete_after_sleep={lifecycle1.delete_after_sleep}s, "
152+
f"delete_after_create={lifecycle1.delete_after_create}s")
153+
154+
# Quick health check
155+
print(" → Verifying sandbox 1 is healthy...")
156+
assert sandbox1.is_healthy(), "Sandbox 1 should be healthy"
157+
result = sandbox1.exec("echo 'Sandbox 1 ready'")
158+
print(f" ✓ {result.stdout.strip()}")
159+
print()
160+
161+
# =====================================================================
162+
# Sandbox 2: idle_timeout + delete_after_inactivity_delay
163+
# =====================================================================
164+
print("-" * 70)
165+
print(" SANDBOX 2: idle_timeout + delete_after_inactivity_delay")
166+
print("-" * 70)
167+
print()
168+
169+
idle_timeout_2 = 60 # Sleep after 60s of inactivity
170+
delete_after_inactivity_2 = 60 # Delete 60s after sleep
171+
172+
print(" → Creating sandbox 2...")
173+
print(f" - idle_timeout: {idle_timeout_2}s (sleep after idle)")
174+
print(f" - delete_after_inactivity_delay: {delete_after_inactivity_2}s (delete after sleep)")
175+
print(f" → Expected total time to deletion: ~{idle_timeout_2 + delete_after_inactivity_2}s")
176+
177+
create_start = time.time()
178+
sandbox2 = Sandbox.create(
179+
image="koyeb/sandbox",
180+
name="auto-delete-test-2",
181+
wait_ready=True,
182+
api_token=api_token,
183+
region="fra",
184+
idle_timeout=idle_timeout_2,
185+
delete_after_inactivity_delay=delete_after_inactivity_2,
186+
)
187+
create_duration = time.time() - create_start
188+
tracker.record("Sandbox 2 creation", create_duration, "setup")
189+
print(f" ✓ Created in {create_duration:.1f}s")
190+
print(f" Service ID: {sandbox2.service_id}")
191+
192+
# Verify lifecycle settings
193+
lifecycle2 = get_service_lifecycle(api_token, sandbox2.service_id)
194+
if lifecycle2:
195+
print(f" Lifecycle: delete_after_sleep={lifecycle2.delete_after_sleep}s, "
196+
f"delete_after_create={lifecycle2.delete_after_create}s")
197+
198+
# Quick health check
199+
print(" → Verifying sandbox 2 is healthy...")
200+
assert sandbox2.is_healthy(), "Sandbox 2 should be healthy"
201+
result = sandbox2.exec("echo 'Sandbox 2 ready'")
202+
print(f" ✓ {result.stdout.strip()}")
203+
print()
204+
205+
# =====================================================================
206+
# Wait for auto-deletion
207+
# =====================================================================
208+
print("-" * 70)
209+
print(" WAITING FOR AUTO-DELETION")
210+
print("-" * 70)
211+
print()
212+
print(" Waiting for both sandboxes to be auto-deleted...")
213+
print(f" Sandbox 1: should delete ~{delete_after_delay_1}s after creation")
214+
print(f" Sandbox 2: should sleep after {idle_timeout_2}s, then delete after {delete_after_inactivity_2}s more")
215+
print()
216+
217+
# Track which sandboxes are still alive
218+
sandbox1_deleted = False
219+
sandbox2_deleted = False
220+
sandbox1_delete_time = None
221+
sandbox2_delete_time = None
222+
223+
wait_start = time.time()
224+
max_wait = 600 # 10 minutes max
225+
226+
while time.time() - wait_start < max_wait:
227+
elapsed = time.time() - wait_start
228+
229+
# Check sandbox 1
230+
if not sandbox1_deleted and not service_exists(api_token, sandbox1.service_id):
231+
sandbox1_deleted = True
232+
sandbox1_delete_time = elapsed
233+
print(f" ✓ Sandbox 1 auto-deleted at {elapsed:.1f}s")
234+
tracker.record("Sandbox 1 auto-deletion wait", elapsed, "auto-delete")
235+
236+
# Check sandbox 2
237+
if not sandbox2_deleted and not service_exists(api_token, sandbox2.service_id):
238+
sandbox2_deleted = True
239+
sandbox2_delete_time = elapsed
240+
print(f" ✓ Sandbox 2 auto-deleted at {elapsed:.1f}s")
241+
tracker.record("Sandbox 2 auto-deletion wait", elapsed, "auto-delete")
242+
243+
# Both deleted?
244+
if sandbox1_deleted and sandbox2_deleted:
245+
print()
246+
print(" ✓ Both sandboxes have been auto-deleted!")
247+
break
248+
249+
# Status update every 30s
250+
if int(elapsed) % 30 == 0 and int(elapsed) > 0:
251+
status1 = "deleted" if sandbox1_deleted else "alive"
252+
status2 = "deleted" if sandbox2_deleted else "alive"
253+
print(f" ... {elapsed:.0f}s elapsed (sandbox1: {status1}, sandbox2: {status2})")
254+
255+
time.sleep(5)
256+
else:
257+
print()
258+
print(" ✗ Timeout waiting for sandboxes to be deleted")
259+
if not sandbox1_deleted:
260+
print(" - Sandbox 1 was not deleted")
261+
if not sandbox2_deleted:
262+
print(" - Sandbox 2 was not deleted")
263+
264+
# Clear sandbox references so finally block doesn't try to delete them
265+
if sandbox1_deleted:
266+
sandbox1 = None
267+
if sandbox2_deleted:
268+
sandbox2 = None
269+
270+
print()
271+
print("-" * 70)
272+
print(" RESULTS")
273+
print("-" * 70)
274+
print()
275+
if sandbox1_delete_time:
276+
print(f" Sandbox 1 (delete_after_delay): deleted after {sandbox1_delete_time:.1f}s")
277+
print(f" Expected: ~{delete_after_delay_1}s after creation")
278+
if sandbox2_delete_time:
279+
print(f" Sandbox 2 (idle_timeout + delete_after_inactivity): deleted after {sandbox2_delete_time:.1f}s")
280+
print(f" Expected: ~{idle_timeout_2 + delete_after_inactivity_2}s (idle + delete delay)")
281+
282+
except Exception as e:
283+
print(f"\n✗ Error occurred: {e}")
284+
import traceback
285+
traceback.print_exc()
286+
287+
finally:
288+
# Clean up any sandboxes that weren't auto-deleted
289+
if sandbox1:
290+
print()
291+
print(" → Manually deleting sandbox 1 (wasn't auto-deleted)...")
292+
delete_start = time.time()
293+
sandbox1.delete()
294+
tracker.record("Sandbox 1 manual deletion", time.time() - delete_start, "cleanup")
295+
296+
if sandbox2:
297+
print()
298+
print(" → Manually deleting sandbox 2 (wasn't auto-deleted)...")
299+
delete_start = time.time()
300+
sandbox2.delete()
301+
tracker.record("Sandbox 2 manual deletion", time.time() - delete_start, "cleanup")
302+
303+
print()
304+
print("✓ Demo completed")
305+
306+
# Print detailed recap
307+
tracker.print_recap()
308+
309+
310+
if __name__ == "__main__":
311+
main()

0 commit comments

Comments
 (0)