daytona archival script

This commit is contained in:
marko-kraemer 2025-08-17 20:08:20 -07:00
parent ec988f0968
commit b665cc8653
4 changed files with 218 additions and 2 deletions

View File

@ -69,6 +69,7 @@ dependencies = [
"beautifulsoup4>=4.12.0", "beautifulsoup4>=4.12.0",
"cssutils>=2.9.0", "cssutils>=2.9.0",
"fastapi-sso>=0.9.0", "fastapi-sso>=0.9.0",
"daytona>=0.21.6",
] ]
[project.urls] [project.urls]

View File

@ -9,7 +9,7 @@ load_dotenv()
logger.debug("Initializing Daytona sandbox configuration") logger.debug("Initializing Daytona sandbox configuration")
daytona_config = DaytonaConfig( daytona_config = DaytonaConfig(
api_key=config.DAYTONA_API_KEY, api_key=config.DAYTONA_API_KEY,
api_url=config.DAYTONA_SERVER_URL, # Use api_url instead of server_url (deprecated) api_url=config.DAYTONA_SERVER_URL,
target=config.DAYTONA_TARGET, target=config.DAYTONA_TARGET,
) )

View File

@ -0,0 +1,187 @@
#!/usr/bin/env python3
"""
Simple script to archive all Daytona sandboxes with "STOPPED" state.
Usage:
python archive_stopped_sandboxes.py [--dry-run]
"""
import sys
import argparse
import json
import re
from datetime import datetime
from utils.config import config
try:
from daytona import Daytona
except ImportError:
print("Error: Daytona Python SDK not found. Please install it with: pip install daytona")
sys.exit(1)
def save_raw_list_as_json(raw_list, filename=None):
"""Save raw list output as JSON file."""
if filename is None:
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"raw_sandboxes_{timestamp}.json"
print(f"Saving raw list output to {filename}")
try:
with open(filename, 'w') as f:
json.dump(raw_list, f, indent=2, default=str)
print(f"✓ Successfully saved raw list to {filename}")
return filename
except Exception as e:
print(f"✗ Failed to save JSON file: {e}")
return None
def parse_sandbox_string(sandbox_str):
"""Parse sandbox string representation to extract ID and state."""
# Extract ID using regex
id_match = re.search(r"id='([^']+)'", sandbox_str)
sandbox_id = id_match.group(1) if id_match else None
# Extract state using regex
state_match = re.search(r"state=<SandboxState\.([^:]+):", sandbox_str)
state = state_match.group(1) if state_match else None
return sandbox_id, state
def get_stopped_sandboxes_from_json(json_file):
"""Extract STOPPED sandbox IDs from JSON file."""
try:
with open(json_file, 'r') as f:
sandboxes_data = json.load(f)
stopped_ids = []
for sandbox_str in sandboxes_data:
sandbox_id, state = parse_sandbox_string(sandbox_str)
if state == 'STOPPED' and sandbox_id:
stopped_ids.append(sandbox_id)
return stopped_ids
except Exception as e:
print(f"✗ Failed to parse JSON file: {e}")
return []
def archive_stopped_sandboxes(dry_run=False, save_json=False, json_filename=None, use_existing_json=None):
"""Archive all sandboxes in STOPPED state."""
# Initialize Daytona client using config
try:
daytona = Daytona()
print("✓ Connected to Daytona")
except Exception as e:
print(f"✗ Failed to connect to Daytona: {e}")
return False
stopped_sandbox_ids = []
if use_existing_json:
# Parse existing JSON file for STOPPED sandboxes
print(f"🔍 Parsing existing JSON file: {use_existing_json}")
stopped_sandbox_ids = get_stopped_sandboxes_from_json(use_existing_json)
print(f"✓ Found {len(stopped_sandbox_ids)} STOPPED sandboxes in JSON file")
# Log some sample IDs for verification
if stopped_sandbox_ids:
print(f"📋 Sample STOPPED sandbox IDs: {stopped_sandbox_ids[:5]}...")
else:
# Get all sandboxes from API
try:
sandboxes = daytona.list()
print(f"✓ Found {len(sandboxes)} total sandboxes")
# Print sandbox data for debugging
print("\nSandbox data structure:")
for i, sandbox in enumerate(sandboxes[:2]): # Show first 2 for debugging
print(f"Sandbox {i+1}: {sandbox}")
print(f" - ID: {getattr(sandbox, 'id', 'N/A')}")
print(f" - State: {getattr(sandbox, 'state', 'N/A')}")
print(f" - Name: {getattr(sandbox, 'name', 'N/A')}")
except Exception as e:
print(f"✗ Failed to list sandboxes: {e}")
return False
# Save raw list as JSON if requested
if save_json:
save_raw_list_as_json(sandboxes, json_filename)
# Filter for STOPPED sandboxes
stopped_sandboxes = [sb for sb in sandboxes if getattr(sb, 'state', None) == 'STOPPED']
stopped_sandbox_ids = [getattr(sb, 'id', 'unknown') for sb in stopped_sandboxes]
print(f"✓ Found {len(stopped_sandbox_ids)} sandboxes in STOPPED state")
if not stopped_sandbox_ids:
print("No sandboxes to archive")
return True
# Archive each stopped sandbox
success_count = 0
for sandbox_id in stopped_sandbox_ids:
try:
if dry_run:
print(f"[DRY RUN] Would archive: {sandbox_id}")
success_count += 1
else:
print(f"Archiving: {sandbox_id}")
# Get the sandbox object and archive it
sandbox = daytona.get(sandbox_id)
sandbox.archive()
print(f"✓ Archived: {sandbox_id}")
success_count += 1
except Exception as e:
print(f"✗ Failed to archive {sandbox_id}: {e}")
print(f"\nSummary: {success_count}/{len(stopped_sandbox_ids)} sandboxes processed")
return success_count == len(stopped_sandbox_ids)
def main():
parser = argparse.ArgumentParser(description="Archive stopped Daytona sandboxes and optionally save list as JSON")
parser.add_argument('--dry-run', action='store_true', help='Show what would be archived')
parser.add_argument('--save-json', action='store_true', help='Save sandboxes list as JSON file')
parser.add_argument('--json-file', type=str, help='Custom filename for JSON output (default: sandboxes_TIMESTAMP.json)')
parser.add_argument('--use-json', type=str, help='Use existing JSON file to get STOPPED sandbox IDs (e.g., raw_sandboxes_20250817_194448.json)')
parser.add_argument('--json-only', action='store_true', help='Only save JSON, skip archiving')
args = parser.parse_args()
print("Daytona API Key:", "✓ Configured" if config.DAYTONA_API_KEY else "✗ Missing")
print("Daytona API URL:", config.DAYTONA_SERVER_URL)
print("Daytona Target:", config.DAYTONA_TARGET)
print()
if args.dry_run:
print("=== DRY RUN MODE ===")
if args.json_only:
print("=== JSON ONLY MODE ===")
# Just save JSON without archiving
try:
from daytona import Daytona
daytona = Daytona()
sandboxes = daytona.list()
print(f"RAW SANDBOXES DATA: {sandboxes}")
save_raw_list_as_json(sandboxes, args.json_file)
sys.exit(0)
except Exception as e:
print(f"✗ Failed to save JSON: {e}")
sys.exit(1)
success = archive_stopped_sandboxes(
dry_run=args.dry_run,
save_json=args.save_json,
json_filename=args.json_file,
use_existing_json=args.use_json
)
sys.exit(0 if success else 1)
if __name__ == "__main__":
main()
#uv run python -m utils.scripts.archive_stopped_sandboxes --use-json raw_sandboxes_20250817_194448.json

View File

@ -1,5 +1,5 @@
version = 1 version = 1
revision = 2 revision = 3
requires-python = ">=3.11" requires-python = ">=3.11"
resolution-markers = [ resolution-markers = [
"python_full_version >= '3.12'", "python_full_version >= '3.12'",
@ -539,6 +539,32 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/a7/ec/bb273b7208c606890dc36540fe667d06ce840a6f62f9fae7e658fcdc90fb/cssutils-2.11.1-py3-none-any.whl", hash = "sha256:a67bfdfdff4f3867fab43698ec4897c1a828eca5973f4073321b3bccaf1199b1", size = 385747, upload-time = "2024-06-04T15:51:37.499Z" }, { url = "https://files.pythonhosted.org/packages/a7/ec/bb273b7208c606890dc36540fe667d06ce840a6f62f9fae7e658fcdc90fb/cssutils-2.11.1-py3-none-any.whl", hash = "sha256:a67bfdfdff4f3867fab43698ec4897c1a828eca5973f4073321b3bccaf1199b1", size = 385747, upload-time = "2024-06-04T15:51:37.499Z" },
] ]
[[package]]
name = "daytona"
version = "0.21.6"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "aioboto3" },
{ name = "aiofiles" },
{ name = "aiohttp" },
{ name = "aiohttp-retry" },
{ name = "boto3" },
{ name = "daytona-api-client" },
{ name = "daytona-api-client-async" },
{ name = "deprecated" },
{ name = "environs" },
{ name = "httpx" },
{ name = "marshmallow" },
{ name = "pydantic" },
{ name = "python-dateutil" },
{ name = "toml" },
{ name = "urllib3" },
]
sdist = { url = "https://files.pythonhosted.org/packages/88/61/045b9a3f28490a69941a3c680d1042a93f3fd52a2a7f8aa4ba1d310829bd/daytona-0.21.6.tar.gz", hash = "sha256:0551db851d4992d08d92f2c3bc1dab5834f8df12b7a21f95a897643410aa04da", size = 87564, upload-time = "2025-07-02T20:34:14.176Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/54/f5/a4d773564ffb2f1af19e8c606d6d80454b298edd8f5b44322db37e5c1b85/daytona-0.21.6-py3-none-any.whl", hash = "sha256:6427e5aa08482c92e1d4e889109b266246806d662e1085da709962bef241ed40", size = 105876, upload-time = "2025-07-02T20:34:11.367Z" },
]
[[package]] [[package]]
name = "daytona-api-client" name = "daytona-api-client"
version = "0.21.0" version = "0.21.0"
@ -2586,6 +2612,7 @@ dependencies = [
{ name = "croniter" }, { name = "croniter" },
{ name = "cryptography" }, { name = "cryptography" },
{ name = "cssutils" }, { name = "cssutils" },
{ name = "daytona" },
{ name = "daytona-api-client" }, { name = "daytona-api-client" },
{ name = "daytona-api-client-async" }, { name = "daytona-api-client-async" },
{ name = "daytona-sdk" }, { name = "daytona-sdk" },
@ -2653,6 +2680,7 @@ requires-dist = [
{ name = "croniter", specifier = ">=1.4.0" }, { name = "croniter", specifier = ">=1.4.0" },
{ name = "cryptography", specifier = ">=41.0.0" }, { name = "cryptography", specifier = ">=41.0.0" },
{ name = "cssutils", specifier = ">=2.9.0" }, { name = "cssutils", specifier = ">=2.9.0" },
{ name = "daytona", specifier = ">=0.21.6" },
{ name = "daytona-api-client", specifier = "==0.21.0" }, { name = "daytona-api-client", specifier = "==0.21.0" },
{ name = "daytona-api-client-async", specifier = "==0.21.0" }, { name = "daytona-api-client-async", specifier = "==0.21.0" },
{ name = "daytona-sdk", specifier = "==0.21.0" }, { name = "daytona-sdk", specifier = "==0.21.0" },