Skip to content

Commit a6d4b5a

Browse files
authored
Feat add script ae deletion (#301)
* feat: add ae script deletion * feat: add ae script deletion
1 parent 2b42227 commit a6d4b5a

File tree

1 file changed

+242
-0
lines changed

1 file changed

+242
-0
lines changed
Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
#!/usr/bin/env python3
2+
3+
# mypy: ignore-errors
4+
"""
5+
Script to delete all Agent Engine services from specified projects.
6+
7+
This script deletes all Agent Engine services from projects specified via environment variables.
8+
9+
Environment Variables:
10+
- PROJECT_IDS: Comma-separated list of project IDs (e.g., "proj1,proj2,proj3")
11+
- Alternative: Individual variables CICD_PROJECT_ID, E2E_PR_PROJECT_ID, E2E_ST_PROJECT_ID
12+
13+
Example usage:
14+
export PROJECT_IDS="my-project-1,my-project-2,my-project-3"
15+
python delete_agent_engines.py
16+
17+
Based on the cleanup logic from tests/cicd/test_e2e_deployment.py
18+
"""
19+
20+
import logging
21+
import os
22+
import sys
23+
import time
24+
25+
import vertexai
26+
from google.api_core import exceptions
27+
from vertexai import agent_engines
28+
29+
# Configure logging
30+
logging.basicConfig(
31+
level=logging.INFO,
32+
format="%(asctime)s - %(levelname)s - %(message)s",
33+
datefmt="%Y-%m-%d %H:%M:%S",
34+
)
35+
logger = logging.getLogger(__name__)
36+
37+
38+
# Project IDs to clean up - loaded from environment variables
39+
def get_project_ids() -> list[str]:
40+
"""Get project IDs from environment variables."""
41+
project_ids = []
42+
43+
# Try to get from comma-separated env var first
44+
env_projects = os.getenv("PROJECT_IDS")
45+
if env_projects:
46+
project_ids = [pid.strip() for pid in env_projects.split(",") if pid.strip()]
47+
else:
48+
# Fallback to individual env vars for backward compatibility
49+
for env_var in ["CICD_PROJECT_ID", "E2E_PR_PROJECT_ID", "E2E_ST_PROJECT_ID"]:
50+
project_id = os.getenv(env_var)
51+
if project_id:
52+
project_ids.append(project_id.strip())
53+
54+
if not project_ids:
55+
raise ValueError(
56+
"No project IDs found. Please set either:\n"
57+
"- PROJECT_IDS environment variable with comma-separated project IDs, or\n"
58+
"- Individual env vars: CICD_PROJECT_ID, E2E_PR_PROJECT_ID, E2E_ST_PROJECT_ID"
59+
)
60+
61+
return project_ids
62+
63+
64+
# Default region
65+
DEFAULT_REGION = "europe-west1"
66+
67+
# Rate limiting configuration
68+
MAX_RETRIES = 3
69+
RATE_LIMIT_DELAY = 60 # seconds to wait when hitting rate limits
70+
RETRY_DELAY = 5 # seconds to wait between retries
71+
72+
73+
def delete_single_agent_engine(engine, retry_count: int = 0) -> bool:
74+
"""
75+
Delete a single Agent Engine with retry logic and force deletion.
76+
77+
Args:
78+
engine: The AgentEngine instance to delete
79+
retry_count: Current retry attempt number
80+
81+
Returns:
82+
True if deleted successfully, False otherwise
83+
"""
84+
engine_name = engine.display_name or engine.resource_name
85+
86+
try:
87+
logger.info(f"🗑️ Deleting Agent Engine: {engine_name}")
88+
logger.info(f" Resource name: {engine.resource_name}")
89+
90+
# Try normal deletion first
91+
engine.delete()
92+
logger.info(f"✅ Successfully deleted Agent Engine: {engine_name}")
93+
return True
94+
95+
except exceptions.BadRequest as e:
96+
# Handle child resources error by using force deletion
97+
if "contains child resources" in str(e):
98+
logger.warning(
99+
f"⚠️ Agent Engine {engine_name} has child resources, attempting force deletion..."
100+
)
101+
try:
102+
# Force delete with child resources
103+
engine.delete(force=True)
104+
logger.info(
105+
f"✅ Force deleted Agent Engine with child resources: {engine_name}"
106+
)
107+
return True
108+
except Exception as force_e:
109+
logger.error(f"❌ Force deletion failed for {engine_name}: {force_e}")
110+
return False
111+
else:
112+
logger.error(f"❌ Bad request error for {engine_name}: {e}")
113+
return False
114+
115+
except exceptions.TooManyRequests as e:
116+
# Handle rate limiting
117+
if retry_count < MAX_RETRIES:
118+
logger.warning(
119+
f"⏱️ Rate limit hit for {engine_name}, waiting {RATE_LIMIT_DELAY} seconds before retry {retry_count + 1}/{MAX_RETRIES}..."
120+
)
121+
time.sleep(RATE_LIMIT_DELAY)
122+
return delete_single_agent_engine(engine, retry_count + 1)
123+
else:
124+
logger.error(f"❌ Rate limit exceeded max retries for {engine_name}: {e}")
125+
return False
126+
127+
except Exception as e:
128+
# Handle other errors with retry logic
129+
if retry_count < MAX_RETRIES:
130+
logger.warning(
131+
f"⏱️ Error deleting {engine_name}, retrying in {RETRY_DELAY} seconds... (attempt {retry_count + 1}/{MAX_RETRIES})"
132+
)
133+
time.sleep(RETRY_DELAY)
134+
return delete_single_agent_engine(engine, retry_count + 1)
135+
else:
136+
logger.error(
137+
f"❌ Failed to delete {engine_name} after {MAX_RETRIES} retries: {e}"
138+
)
139+
return False
140+
141+
142+
def delete_agent_engines_in_project(
143+
project_id: str, region: str = DEFAULT_REGION
144+
) -> tuple[int, int]:
145+
"""
146+
Delete all Agent Engine services in a specific project.
147+
148+
Args:
149+
project_id: The GCP project ID
150+
region: The GCP region (default: europe-west1)
151+
152+
Returns:
153+
Tuple of (successful_deletions, total_engines_found)
154+
"""
155+
logger.info(f"🔍 Checking for Agent Engine services in project {project_id}...")
156+
157+
try:
158+
# Initialize Vertex AI for this project
159+
vertexai.init(project=project_id, location=region)
160+
161+
# List all Agent Engine services in the project
162+
logger.info(f"📋 Listing all Agent Engine services in {project_id}...")
163+
engines = list(agent_engines.AgentEngine.list())
164+
165+
if not engines:
166+
logger.info(f"✅ No Agent Engine services found in {project_id}")
167+
return 0, 0
168+
169+
logger.info(f"🎯 Found {len(engines)} Agent Engine service(s) in {project_id}")
170+
171+
# Delete each engine with improved error handling
172+
deleted_count = 0
173+
for i, engine in enumerate(engines, 1):
174+
logger.info(f"📋 Processing engine {i}/{len(engines)} in {project_id}")
175+
176+
if delete_single_agent_engine(engine):
177+
deleted_count += 1
178+
179+
# Small delay between deletions to avoid overwhelming the API
180+
if i < len(engines): # Don't sleep after the last engine
181+
time.sleep(1)
182+
183+
logger.info(
184+
f"🎉 Deleted {deleted_count}/{len(engines)} Agent Engine service(s) in {project_id}"
185+
)
186+
return deleted_count, len(engines)
187+
188+
except Exception as e:
189+
logger.error(f"❌ Error processing project {project_id}: {e}")
190+
return 0, 0
191+
192+
193+
def main():
194+
"""Main function to delete Agent Engine services from all specified projects."""
195+
logger.info("🚀 Starting Agent Engine cleanup across multiple projects...")
196+
197+
try:
198+
project_ids = get_project_ids()
199+
logger.info(f"🎯 Target projects: {', '.join(project_ids)}")
200+
except ValueError as e:
201+
logger.error(f"❌ Configuration error: {e}")
202+
sys.exit(1)
203+
204+
total_deleted = 0
205+
total_found = 0
206+
failed_projects = []
207+
208+
for project_id in project_ids:
209+
try:
210+
deleted_count, found_count = delete_agent_engines_in_project(project_id)
211+
total_deleted += deleted_count
212+
total_found += found_count
213+
except Exception as e:
214+
logger.error(f"❌ Failed to process project {project_id}: {e}")
215+
failed_projects.append(project_id)
216+
217+
# Summary
218+
logger.info("\n" + "=" * 60)
219+
logger.info("📊 CLEANUP SUMMARY")
220+
logger.info("=" * 60)
221+
logger.info(f"🎯 Total Agent Engine services found: {total_found}")
222+
logger.info(f"✅ Total Agent Engine services deleted: {total_deleted}")
223+
logger.info(f"❌ Failed deletions: {total_found - total_deleted}")
224+
logger.info(
225+
f"📁 Projects processed: {len(project_ids) - len(failed_projects)}/{len(project_ids)}"
226+
)
227+
228+
if failed_projects:
229+
logger.warning(f"⚠️ Failed to process projects: {', '.join(failed_projects)}")
230+
sys.exit(1)
231+
elif total_found > total_deleted:
232+
logger.warning(
233+
f"⚠️ Some Agent Engine services could not be deleted ({total_found - total_deleted} failures)"
234+
)
235+
sys.exit(1)
236+
else:
237+
logger.info("🎉 All projects processed successfully!")
238+
sys.exit(0)
239+
240+
241+
if __name__ == "__main__":
242+
main()

0 commit comments

Comments
 (0)