mirror of https://github.com/kortix-ai/suna.git
daytona archival script
This commit is contained in:
parent
ec988f0968
commit
b665cc8653
|
@ -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]
|
||||||
|
|
|
@ -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,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
@ -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" },
|
||||||
|
|
Loading…
Reference in New Issue