PraisonAI 3.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- praisonai/__init__.py +54 -0
- praisonai/__main__.py +15 -0
- praisonai/acp/__init__.py +54 -0
- praisonai/acp/config.py +159 -0
- praisonai/acp/server.py +587 -0
- praisonai/acp/session.py +219 -0
- praisonai/adapters/__init__.py +50 -0
- praisonai/adapters/readers.py +395 -0
- praisonai/adapters/rerankers.py +315 -0
- praisonai/adapters/retrievers.py +394 -0
- praisonai/adapters/vector_stores.py +409 -0
- praisonai/agent_scheduler.py +337 -0
- praisonai/agents_generator.py +903 -0
- praisonai/api/call.py +292 -0
- praisonai/auto.py +1197 -0
- praisonai/capabilities/__init__.py +275 -0
- praisonai/capabilities/a2a.py +140 -0
- praisonai/capabilities/assistants.py +283 -0
- praisonai/capabilities/audio.py +320 -0
- praisonai/capabilities/batches.py +469 -0
- praisonai/capabilities/completions.py +336 -0
- praisonai/capabilities/container_files.py +155 -0
- praisonai/capabilities/containers.py +93 -0
- praisonai/capabilities/embeddings.py +158 -0
- praisonai/capabilities/files.py +467 -0
- praisonai/capabilities/fine_tuning.py +293 -0
- praisonai/capabilities/guardrails.py +182 -0
- praisonai/capabilities/images.py +330 -0
- praisonai/capabilities/mcp.py +190 -0
- praisonai/capabilities/messages.py +270 -0
- praisonai/capabilities/moderations.py +154 -0
- praisonai/capabilities/ocr.py +217 -0
- praisonai/capabilities/passthrough.py +204 -0
- praisonai/capabilities/rag.py +207 -0
- praisonai/capabilities/realtime.py +160 -0
- praisonai/capabilities/rerank.py +165 -0
- praisonai/capabilities/responses.py +266 -0
- praisonai/capabilities/search.py +109 -0
- praisonai/capabilities/skills.py +133 -0
- praisonai/capabilities/vector_store_files.py +334 -0
- praisonai/capabilities/vector_stores.py +304 -0
- praisonai/capabilities/videos.py +141 -0
- praisonai/chainlit_ui.py +304 -0
- praisonai/chat/__init__.py +106 -0
- praisonai/chat/app.py +125 -0
- praisonai/cli/__init__.py +26 -0
- praisonai/cli/app.py +213 -0
- praisonai/cli/commands/__init__.py +75 -0
- praisonai/cli/commands/acp.py +70 -0
- praisonai/cli/commands/completion.py +333 -0
- praisonai/cli/commands/config.py +166 -0
- praisonai/cli/commands/debug.py +142 -0
- praisonai/cli/commands/diag.py +55 -0
- praisonai/cli/commands/doctor.py +166 -0
- praisonai/cli/commands/environment.py +179 -0
- praisonai/cli/commands/lsp.py +112 -0
- praisonai/cli/commands/mcp.py +210 -0
- praisonai/cli/commands/profile.py +457 -0
- praisonai/cli/commands/run.py +228 -0
- praisonai/cli/commands/schedule.py +150 -0
- praisonai/cli/commands/serve.py +97 -0
- praisonai/cli/commands/session.py +212 -0
- praisonai/cli/commands/traces.py +145 -0
- praisonai/cli/commands/version.py +101 -0
- praisonai/cli/configuration/__init__.py +18 -0
- praisonai/cli/configuration/loader.py +353 -0
- praisonai/cli/configuration/paths.py +114 -0
- praisonai/cli/configuration/schema.py +164 -0
- praisonai/cli/features/__init__.py +268 -0
- praisonai/cli/features/acp.py +236 -0
- praisonai/cli/features/action_orchestrator.py +546 -0
- praisonai/cli/features/agent_scheduler.py +773 -0
- praisonai/cli/features/agent_tools.py +474 -0
- praisonai/cli/features/agents.py +375 -0
- praisonai/cli/features/at_mentions.py +471 -0
- praisonai/cli/features/auto_memory.py +182 -0
- praisonai/cli/features/autonomy_mode.py +490 -0
- praisonai/cli/features/background.py +356 -0
- praisonai/cli/features/base.py +168 -0
- praisonai/cli/features/capabilities.py +1326 -0
- praisonai/cli/features/checkpoints.py +338 -0
- praisonai/cli/features/code_intelligence.py +652 -0
- praisonai/cli/features/compaction.py +294 -0
- praisonai/cli/features/compare.py +534 -0
- praisonai/cli/features/cost_tracker.py +514 -0
- praisonai/cli/features/debug.py +810 -0
- praisonai/cli/features/deploy.py +517 -0
- praisonai/cli/features/diag.py +289 -0
- praisonai/cli/features/doctor/__init__.py +63 -0
- praisonai/cli/features/doctor/checks/__init__.py +24 -0
- praisonai/cli/features/doctor/checks/acp_checks.py +240 -0
- praisonai/cli/features/doctor/checks/config_checks.py +366 -0
- praisonai/cli/features/doctor/checks/db_checks.py +366 -0
- praisonai/cli/features/doctor/checks/env_checks.py +543 -0
- praisonai/cli/features/doctor/checks/lsp_checks.py +199 -0
- praisonai/cli/features/doctor/checks/mcp_checks.py +349 -0
- praisonai/cli/features/doctor/checks/memory_checks.py +268 -0
- praisonai/cli/features/doctor/checks/network_checks.py +251 -0
- praisonai/cli/features/doctor/checks/obs_checks.py +328 -0
- praisonai/cli/features/doctor/checks/performance_checks.py +235 -0
- praisonai/cli/features/doctor/checks/permissions_checks.py +259 -0
- praisonai/cli/features/doctor/checks/selftest_checks.py +322 -0
- praisonai/cli/features/doctor/checks/serve_checks.py +426 -0
- praisonai/cli/features/doctor/checks/skills_checks.py +231 -0
- praisonai/cli/features/doctor/checks/tools_checks.py +371 -0
- praisonai/cli/features/doctor/engine.py +266 -0
- praisonai/cli/features/doctor/formatters.py +310 -0
- praisonai/cli/features/doctor/handler.py +397 -0
- praisonai/cli/features/doctor/models.py +264 -0
- praisonai/cli/features/doctor/registry.py +239 -0
- praisonai/cli/features/endpoints.py +1019 -0
- praisonai/cli/features/eval.py +560 -0
- praisonai/cli/features/external_agents.py +231 -0
- praisonai/cli/features/fast_context.py +410 -0
- praisonai/cli/features/flow_display.py +566 -0
- praisonai/cli/features/git_integration.py +651 -0
- praisonai/cli/features/guardrail.py +171 -0
- praisonai/cli/features/handoff.py +185 -0
- praisonai/cli/features/hooks.py +583 -0
- praisonai/cli/features/image.py +384 -0
- praisonai/cli/features/interactive_runtime.py +585 -0
- praisonai/cli/features/interactive_tools.py +380 -0
- praisonai/cli/features/interactive_tui.py +603 -0
- praisonai/cli/features/jobs.py +632 -0
- praisonai/cli/features/knowledge.py +531 -0
- praisonai/cli/features/lite.py +244 -0
- praisonai/cli/features/lsp_cli.py +225 -0
- praisonai/cli/features/mcp.py +169 -0
- praisonai/cli/features/message_queue.py +587 -0
- praisonai/cli/features/metrics.py +211 -0
- praisonai/cli/features/n8n.py +673 -0
- praisonai/cli/features/observability.py +293 -0
- praisonai/cli/features/ollama.py +361 -0
- praisonai/cli/features/output_style.py +273 -0
- praisonai/cli/features/package.py +631 -0
- praisonai/cli/features/performance.py +308 -0
- praisonai/cli/features/persistence.py +636 -0
- praisonai/cli/features/profile.py +226 -0
- praisonai/cli/features/profiler/__init__.py +81 -0
- praisonai/cli/features/profiler/core.py +558 -0
- praisonai/cli/features/profiler/optimizations.py +652 -0
- praisonai/cli/features/profiler/suite.py +386 -0
- praisonai/cli/features/profiling.py +350 -0
- praisonai/cli/features/queue/__init__.py +73 -0
- praisonai/cli/features/queue/manager.py +395 -0
- praisonai/cli/features/queue/models.py +286 -0
- praisonai/cli/features/queue/persistence.py +564 -0
- praisonai/cli/features/queue/scheduler.py +484 -0
- praisonai/cli/features/queue/worker.py +372 -0
- praisonai/cli/features/recipe.py +1723 -0
- praisonai/cli/features/recipes.py +449 -0
- praisonai/cli/features/registry.py +229 -0
- praisonai/cli/features/repo_map.py +860 -0
- praisonai/cli/features/router.py +466 -0
- praisonai/cli/features/sandbox_executor.py +515 -0
- praisonai/cli/features/serve.py +829 -0
- praisonai/cli/features/session.py +222 -0
- praisonai/cli/features/skills.py +856 -0
- praisonai/cli/features/slash_commands.py +650 -0
- praisonai/cli/features/telemetry.py +179 -0
- praisonai/cli/features/templates.py +1384 -0
- praisonai/cli/features/thinking.py +305 -0
- praisonai/cli/features/todo.py +334 -0
- praisonai/cli/features/tools.py +680 -0
- praisonai/cli/features/tui/__init__.py +83 -0
- praisonai/cli/features/tui/app.py +580 -0
- praisonai/cli/features/tui/cli.py +566 -0
- praisonai/cli/features/tui/debug.py +511 -0
- praisonai/cli/features/tui/events.py +99 -0
- praisonai/cli/features/tui/mock_provider.py +328 -0
- praisonai/cli/features/tui/orchestrator.py +652 -0
- praisonai/cli/features/tui/screens/__init__.py +50 -0
- praisonai/cli/features/tui/screens/main.py +245 -0
- praisonai/cli/features/tui/screens/queue.py +174 -0
- praisonai/cli/features/tui/screens/session.py +124 -0
- praisonai/cli/features/tui/screens/settings.py +148 -0
- praisonai/cli/features/tui/widgets/__init__.py +56 -0
- praisonai/cli/features/tui/widgets/chat.py +261 -0
- praisonai/cli/features/tui/widgets/composer.py +224 -0
- praisonai/cli/features/tui/widgets/queue_panel.py +200 -0
- praisonai/cli/features/tui/widgets/status.py +167 -0
- praisonai/cli/features/tui/widgets/tool_panel.py +248 -0
- praisonai/cli/features/workflow.py +720 -0
- praisonai/cli/legacy.py +236 -0
- praisonai/cli/main.py +5559 -0
- praisonai/cli/schedule_cli.py +54 -0
- praisonai/cli/state/__init__.py +31 -0
- praisonai/cli/state/identifiers.py +161 -0
- praisonai/cli/state/sessions.py +313 -0
- praisonai/code/__init__.py +93 -0
- praisonai/code/agent_tools.py +344 -0
- praisonai/code/diff/__init__.py +21 -0
- praisonai/code/diff/diff_strategy.py +432 -0
- praisonai/code/tools/__init__.py +27 -0
- praisonai/code/tools/apply_diff.py +221 -0
- praisonai/code/tools/execute_command.py +275 -0
- praisonai/code/tools/list_files.py +274 -0
- praisonai/code/tools/read_file.py +206 -0
- praisonai/code/tools/search_replace.py +248 -0
- praisonai/code/tools/write_file.py +217 -0
- praisonai/code/utils/__init__.py +46 -0
- praisonai/code/utils/file_utils.py +307 -0
- praisonai/code/utils/ignore_utils.py +308 -0
- praisonai/code/utils/text_utils.py +276 -0
- praisonai/db/__init__.py +64 -0
- praisonai/db/adapter.py +531 -0
- praisonai/deploy/__init__.py +62 -0
- praisonai/deploy/api.py +231 -0
- praisonai/deploy/docker.py +454 -0
- praisonai/deploy/doctor.py +367 -0
- praisonai/deploy/main.py +327 -0
- praisonai/deploy/models.py +179 -0
- praisonai/deploy/providers/__init__.py +33 -0
- praisonai/deploy/providers/aws.py +331 -0
- praisonai/deploy/providers/azure.py +358 -0
- praisonai/deploy/providers/base.py +101 -0
- praisonai/deploy/providers/gcp.py +314 -0
- praisonai/deploy/schema.py +208 -0
- praisonai/deploy.py +185 -0
- praisonai/endpoints/__init__.py +53 -0
- praisonai/endpoints/a2u_server.py +410 -0
- praisonai/endpoints/discovery.py +165 -0
- praisonai/endpoints/providers/__init__.py +28 -0
- praisonai/endpoints/providers/a2a.py +253 -0
- praisonai/endpoints/providers/a2u.py +208 -0
- praisonai/endpoints/providers/agents_api.py +171 -0
- praisonai/endpoints/providers/base.py +231 -0
- praisonai/endpoints/providers/mcp.py +263 -0
- praisonai/endpoints/providers/recipe.py +206 -0
- praisonai/endpoints/providers/tools_mcp.py +150 -0
- praisonai/endpoints/registry.py +131 -0
- praisonai/endpoints/server.py +161 -0
- praisonai/inbuilt_tools/__init__.py +24 -0
- praisonai/inbuilt_tools/autogen_tools.py +117 -0
- praisonai/inc/__init__.py +2 -0
- praisonai/inc/config.py +96 -0
- praisonai/inc/models.py +155 -0
- praisonai/integrations/__init__.py +56 -0
- praisonai/integrations/base.py +303 -0
- praisonai/integrations/claude_code.py +270 -0
- praisonai/integrations/codex_cli.py +255 -0
- praisonai/integrations/cursor_cli.py +195 -0
- praisonai/integrations/gemini_cli.py +222 -0
- praisonai/jobs/__init__.py +67 -0
- praisonai/jobs/executor.py +425 -0
- praisonai/jobs/models.py +230 -0
- praisonai/jobs/router.py +314 -0
- praisonai/jobs/server.py +186 -0
- praisonai/jobs/store.py +203 -0
- praisonai/llm/__init__.py +66 -0
- praisonai/llm/registry.py +382 -0
- praisonai/mcp_server/__init__.py +152 -0
- praisonai/mcp_server/adapters/__init__.py +74 -0
- praisonai/mcp_server/adapters/agents.py +128 -0
- praisonai/mcp_server/adapters/capabilities.py +168 -0
- praisonai/mcp_server/adapters/cli_tools.py +568 -0
- praisonai/mcp_server/adapters/extended_capabilities.py +462 -0
- praisonai/mcp_server/adapters/knowledge.py +93 -0
- praisonai/mcp_server/adapters/memory.py +104 -0
- praisonai/mcp_server/adapters/prompts.py +306 -0
- praisonai/mcp_server/adapters/resources.py +124 -0
- praisonai/mcp_server/adapters/tools_bridge.py +280 -0
- praisonai/mcp_server/auth/__init__.py +48 -0
- praisonai/mcp_server/auth/api_key.py +291 -0
- praisonai/mcp_server/auth/oauth.py +460 -0
- praisonai/mcp_server/auth/oidc.py +289 -0
- praisonai/mcp_server/auth/scopes.py +260 -0
- praisonai/mcp_server/cli.py +852 -0
- praisonai/mcp_server/elicitation.py +445 -0
- praisonai/mcp_server/icons.py +302 -0
- praisonai/mcp_server/recipe_adapter.py +573 -0
- praisonai/mcp_server/recipe_cli.py +824 -0
- praisonai/mcp_server/registry.py +703 -0
- praisonai/mcp_server/sampling.py +422 -0
- praisonai/mcp_server/server.py +490 -0
- praisonai/mcp_server/tasks.py +443 -0
- praisonai/mcp_server/transports/__init__.py +18 -0
- praisonai/mcp_server/transports/http_stream.py +376 -0
- praisonai/mcp_server/transports/stdio.py +132 -0
- praisonai/persistence/__init__.py +84 -0
- praisonai/persistence/config.py +238 -0
- praisonai/persistence/conversation/__init__.py +25 -0
- praisonai/persistence/conversation/async_mysql.py +427 -0
- praisonai/persistence/conversation/async_postgres.py +410 -0
- praisonai/persistence/conversation/async_sqlite.py +371 -0
- praisonai/persistence/conversation/base.py +151 -0
- praisonai/persistence/conversation/json_store.py +250 -0
- praisonai/persistence/conversation/mysql.py +387 -0
- praisonai/persistence/conversation/postgres.py +401 -0
- praisonai/persistence/conversation/singlestore.py +240 -0
- praisonai/persistence/conversation/sqlite.py +341 -0
- praisonai/persistence/conversation/supabase.py +203 -0
- praisonai/persistence/conversation/surrealdb.py +287 -0
- praisonai/persistence/factory.py +301 -0
- praisonai/persistence/hooks/__init__.py +18 -0
- praisonai/persistence/hooks/agent_hooks.py +297 -0
- praisonai/persistence/knowledge/__init__.py +26 -0
- praisonai/persistence/knowledge/base.py +144 -0
- praisonai/persistence/knowledge/cassandra.py +232 -0
- praisonai/persistence/knowledge/chroma.py +295 -0
- praisonai/persistence/knowledge/clickhouse.py +242 -0
- praisonai/persistence/knowledge/cosmosdb_vector.py +438 -0
- praisonai/persistence/knowledge/couchbase.py +286 -0
- praisonai/persistence/knowledge/lancedb.py +216 -0
- praisonai/persistence/knowledge/langchain_adapter.py +291 -0
- praisonai/persistence/knowledge/lightrag_adapter.py +212 -0
- praisonai/persistence/knowledge/llamaindex_adapter.py +256 -0
- praisonai/persistence/knowledge/milvus.py +277 -0
- praisonai/persistence/knowledge/mongodb_vector.py +306 -0
- praisonai/persistence/knowledge/pgvector.py +335 -0
- praisonai/persistence/knowledge/pinecone.py +253 -0
- praisonai/persistence/knowledge/qdrant.py +301 -0
- praisonai/persistence/knowledge/redis_vector.py +291 -0
- praisonai/persistence/knowledge/singlestore_vector.py +299 -0
- praisonai/persistence/knowledge/surrealdb_vector.py +309 -0
- praisonai/persistence/knowledge/upstash_vector.py +266 -0
- praisonai/persistence/knowledge/weaviate.py +223 -0
- praisonai/persistence/migrations/__init__.py +10 -0
- praisonai/persistence/migrations/manager.py +251 -0
- praisonai/persistence/orchestrator.py +406 -0
- praisonai/persistence/state/__init__.py +21 -0
- praisonai/persistence/state/async_mongodb.py +200 -0
- praisonai/persistence/state/base.py +107 -0
- praisonai/persistence/state/dynamodb.py +226 -0
- praisonai/persistence/state/firestore.py +175 -0
- praisonai/persistence/state/gcs.py +155 -0
- praisonai/persistence/state/memory.py +245 -0
- praisonai/persistence/state/mongodb.py +158 -0
- praisonai/persistence/state/redis.py +190 -0
- praisonai/persistence/state/upstash.py +144 -0
- praisonai/persistence/tests/__init__.py +3 -0
- praisonai/persistence/tests/test_all_backends.py +633 -0
- praisonai/profiler.py +1214 -0
- praisonai/recipe/__init__.py +134 -0
- praisonai/recipe/bridge.py +278 -0
- praisonai/recipe/core.py +893 -0
- praisonai/recipe/exceptions.py +54 -0
- praisonai/recipe/history.py +402 -0
- praisonai/recipe/models.py +266 -0
- praisonai/recipe/operations.py +440 -0
- praisonai/recipe/policy.py +422 -0
- praisonai/recipe/registry.py +849 -0
- praisonai/recipe/runtime.py +214 -0
- praisonai/recipe/security.py +711 -0
- praisonai/recipe/serve.py +859 -0
- praisonai/recipe/server.py +613 -0
- praisonai/scheduler/__init__.py +45 -0
- praisonai/scheduler/agent_scheduler.py +552 -0
- praisonai/scheduler/base.py +124 -0
- praisonai/scheduler/daemon_manager.py +225 -0
- praisonai/scheduler/state_manager.py +155 -0
- praisonai/scheduler/yaml_loader.py +193 -0
- praisonai/scheduler.py +194 -0
- praisonai/setup/__init__.py +1 -0
- praisonai/setup/build.py +21 -0
- praisonai/setup/post_install.py +23 -0
- praisonai/setup/setup_conda_env.py +25 -0
- praisonai/setup.py +16 -0
- praisonai/templates/__init__.py +116 -0
- praisonai/templates/cache.py +364 -0
- praisonai/templates/dependency_checker.py +358 -0
- praisonai/templates/discovery.py +391 -0
- praisonai/templates/loader.py +564 -0
- praisonai/templates/registry.py +511 -0
- praisonai/templates/resolver.py +206 -0
- praisonai/templates/security.py +327 -0
- praisonai/templates/tool_override.py +498 -0
- praisonai/templates/tools_doctor.py +256 -0
- praisonai/test.py +105 -0
- praisonai/train.py +562 -0
- praisonai/train_vision.py +306 -0
- praisonai/ui/agents.py +824 -0
- praisonai/ui/callbacks.py +57 -0
- praisonai/ui/chainlit_compat.py +246 -0
- praisonai/ui/chat.py +532 -0
- praisonai/ui/code.py +717 -0
- praisonai/ui/colab.py +474 -0
- praisonai/ui/colab_chainlit.py +81 -0
- praisonai/ui/components/aicoder.py +284 -0
- praisonai/ui/context.py +283 -0
- praisonai/ui/database_config.py +56 -0
- praisonai/ui/db.py +294 -0
- praisonai/ui/realtime.py +488 -0
- praisonai/ui/realtimeclient/__init__.py +756 -0
- praisonai/ui/realtimeclient/tools.py +242 -0
- praisonai/ui/sql_alchemy.py +710 -0
- praisonai/upload_vision.py +140 -0
- praisonai/version.py +1 -0
- praisonai-3.0.0.dist-info/METADATA +3493 -0
- praisonai-3.0.0.dist-info/RECORD +393 -0
- praisonai-3.0.0.dist-info/WHEEL +5 -0
- praisonai-3.0.0.dist-info/entry_points.txt +4 -0
- praisonai-3.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Deploy configuration models using Pydantic.
|
|
3
|
+
"""
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from typing import Optional, Dict, List, Any
|
|
6
|
+
from pydantic import BaseModel, Field, field_validator
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class DeployType(str, Enum):
|
|
10
|
+
"""Deployment type enum."""
|
|
11
|
+
API = "api"
|
|
12
|
+
DOCKER = "docker"
|
|
13
|
+
CLOUD = "cloud"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class CloudProvider(str, Enum):
|
|
17
|
+
"""Cloud provider enum."""
|
|
18
|
+
AWS = "aws"
|
|
19
|
+
AZURE = "azure"
|
|
20
|
+
GCP = "gcp"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class APIConfig(BaseModel):
|
|
24
|
+
"""Configuration for API server deployment."""
|
|
25
|
+
host: str = Field(default="127.0.0.1", description="Server host")
|
|
26
|
+
port: int = Field(default=8005, description="Server port")
|
|
27
|
+
workers: int = Field(default=1, description="Number of worker processes")
|
|
28
|
+
cors_enabled: bool = Field(default=True, description="Enable CORS")
|
|
29
|
+
auth_enabled: bool = Field(default=False, description="Enable authentication")
|
|
30
|
+
auth_token: Optional[str] = Field(default=None, description="Authentication token")
|
|
31
|
+
reload: bool = Field(default=False, description="Enable auto-reload for development")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class DockerConfig(BaseModel):
|
|
35
|
+
"""Configuration for Docker deployment."""
|
|
36
|
+
image_name: str = Field(default="praisonai-app", description="Docker image name")
|
|
37
|
+
tag: str = Field(default="latest", description="Docker image tag")
|
|
38
|
+
base_image: str = Field(default="python:3.11-slim", description="Base Docker image")
|
|
39
|
+
expose: List[int] = Field(default=[8005], description="Ports to expose")
|
|
40
|
+
registry: Optional[str] = Field(default=None, description="Docker registry URL")
|
|
41
|
+
push: bool = Field(default=False, description="Push image to registry after build")
|
|
42
|
+
build_args: Optional[Dict[str, str]] = Field(default=None, description="Docker build arguments")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class CloudConfig(BaseModel):
|
|
46
|
+
"""Configuration for cloud deployment."""
|
|
47
|
+
provider: CloudProvider = Field(..., description="Cloud provider")
|
|
48
|
+
region: str = Field(..., description="Deployment region")
|
|
49
|
+
service_name: str = Field(..., description="Service/application name")
|
|
50
|
+
|
|
51
|
+
# Common cloud config
|
|
52
|
+
image: Optional[str] = Field(default=None, description="Container image URL")
|
|
53
|
+
cpu: Optional[str] = Field(default="256", description="CPU allocation")
|
|
54
|
+
memory: Optional[str] = Field(default="512", description="Memory allocation (MB)")
|
|
55
|
+
min_instances: int = Field(default=1, description="Minimum instances")
|
|
56
|
+
max_instances: int = Field(default=10, description="Maximum instances")
|
|
57
|
+
env_vars: Optional[Dict[str, str]] = Field(default=None, description="Environment variables")
|
|
58
|
+
|
|
59
|
+
# AWS-specific
|
|
60
|
+
cluster_name: Optional[str] = Field(default=None, description="ECS cluster name (AWS)")
|
|
61
|
+
task_definition: Optional[str] = Field(default=None, description="Task definition (AWS)")
|
|
62
|
+
|
|
63
|
+
# Azure-specific
|
|
64
|
+
resource_group: Optional[str] = Field(default=None, description="Resource group (Azure)")
|
|
65
|
+
subscription_id: Optional[str] = Field(default=None, description="Subscription ID (Azure)")
|
|
66
|
+
|
|
67
|
+
# GCP-specific
|
|
68
|
+
project_id: Optional[str] = Field(default=None, description="Project ID (GCP)")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class AgentConfig(BaseModel):
|
|
72
|
+
"""Configuration for an agent to deploy."""
|
|
73
|
+
name: str = Field(..., description="Agent name/identifier")
|
|
74
|
+
entrypoint: str = Field(..., description="Agent entrypoint file (e.g., agents.yaml)")
|
|
75
|
+
env: Dict[str, str] = Field(default_factory=dict, description="Environment variables")
|
|
76
|
+
secrets: Dict[str, str] = Field(default_factory=dict, description="Secret references")
|
|
77
|
+
ports: Optional[List[int]] = Field(default=None, description="Ports to expose")
|
|
78
|
+
resources: Optional[Dict[str, str]] = Field(default=None, description="Resource requirements")
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class DeployConfig(BaseModel):
|
|
82
|
+
"""Main deployment configuration."""
|
|
83
|
+
type: DeployType = Field(..., description="Deployment type")
|
|
84
|
+
api: Optional[APIConfig] = Field(default=None, description="API server configuration")
|
|
85
|
+
docker: Optional[DockerConfig] = Field(default=None, description="Docker configuration")
|
|
86
|
+
cloud: Optional[CloudConfig] = Field(default=None, description="Cloud configuration")
|
|
87
|
+
agents: Optional[List[AgentConfig]] = Field(default=None, description="Agent configurations")
|
|
88
|
+
|
|
89
|
+
@field_validator('api', 'docker', 'cloud')
|
|
90
|
+
@classmethod
|
|
91
|
+
def validate_config_for_type(cls, v, info):
|
|
92
|
+
"""Validate that required config is present for the deployment type."""
|
|
93
|
+
if info.data.get('type') == DeployType.API and info.field_name == 'api':
|
|
94
|
+
return v or APIConfig()
|
|
95
|
+
elif info.data.get('type') == DeployType.DOCKER and info.field_name == 'docker':
|
|
96
|
+
return v or DockerConfig()
|
|
97
|
+
elif info.data.get('type') == DeployType.CLOUD and info.field_name == 'cloud':
|
|
98
|
+
if v is None:
|
|
99
|
+
raise ValueError("cloud config required for cloud deployment type")
|
|
100
|
+
return v
|
|
101
|
+
return v
|
|
102
|
+
|
|
103
|
+
def model_post_init(self, __context):
|
|
104
|
+
"""Post-initialization validation."""
|
|
105
|
+
if self.type == DeployType.API and self.api is None:
|
|
106
|
+
self.api = APIConfig()
|
|
107
|
+
elif self.type == DeployType.DOCKER and self.docker is None:
|
|
108
|
+
self.docker = DockerConfig()
|
|
109
|
+
elif self.type == DeployType.CLOUD and self.cloud is None:
|
|
110
|
+
raise ValueError("cloud config required for cloud deployment type")
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class DeployResult(BaseModel):
|
|
114
|
+
"""Result of a deployment operation."""
|
|
115
|
+
success: bool = Field(..., description="Whether deployment succeeded")
|
|
116
|
+
message: str = Field(..., description="Result message")
|
|
117
|
+
url: Optional[str] = Field(default=None, description="Deployed service URL")
|
|
118
|
+
error: Optional[str] = Field(default=None, description="Error message if failed")
|
|
119
|
+
metadata: Dict[str, Any] = Field(default_factory=dict, description="Additional metadata")
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class ServiceState(str, Enum):
|
|
123
|
+
"""Service state enum."""
|
|
124
|
+
RUNNING = "running"
|
|
125
|
+
STOPPED = "stopped"
|
|
126
|
+
PENDING = "pending"
|
|
127
|
+
FAILED = "failed"
|
|
128
|
+
NOT_FOUND = "not_found"
|
|
129
|
+
UNKNOWN = "unknown"
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class DeployStatus(BaseModel):
|
|
133
|
+
"""Status of a deployed service."""
|
|
134
|
+
state: ServiceState = Field(..., description="Current service state")
|
|
135
|
+
url: Optional[str] = Field(default=None, description="Service URL/endpoint")
|
|
136
|
+
message: str = Field(default="", description="Status message")
|
|
137
|
+
|
|
138
|
+
# Resource identifiers
|
|
139
|
+
service_name: Optional[str] = Field(default=None, description="Service name")
|
|
140
|
+
provider: Optional[str] = Field(default=None, description="Provider (api/docker/aws/azure/gcp)")
|
|
141
|
+
region: Optional[str] = Field(default=None, description="Deployment region")
|
|
142
|
+
|
|
143
|
+
# Health info
|
|
144
|
+
healthy: bool = Field(default=False, description="Whether service is healthy")
|
|
145
|
+
instances_running: int = Field(default=0, description="Number of running instances")
|
|
146
|
+
instances_desired: int = Field(default=0, description="Desired number of instances")
|
|
147
|
+
|
|
148
|
+
# Timestamps
|
|
149
|
+
created_at: Optional[str] = Field(default=None, description="Creation timestamp")
|
|
150
|
+
updated_at: Optional[str] = Field(default=None, description="Last update timestamp")
|
|
151
|
+
|
|
152
|
+
# Provider-specific metadata
|
|
153
|
+
metadata: Dict[str, Any] = Field(default_factory=dict, description="Provider-specific metadata")
|
|
154
|
+
|
|
155
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
156
|
+
"""Convert to dictionary for JSON output."""
|
|
157
|
+
return {
|
|
158
|
+
"state": self.state.value,
|
|
159
|
+
"url": self.url,
|
|
160
|
+
"message": self.message,
|
|
161
|
+
"service_name": self.service_name,
|
|
162
|
+
"provider": self.provider,
|
|
163
|
+
"region": self.region,
|
|
164
|
+
"healthy": self.healthy,
|
|
165
|
+
"instances_running": self.instances_running,
|
|
166
|
+
"instances_desired": self.instances_desired,
|
|
167
|
+
"created_at": self.created_at,
|
|
168
|
+
"updated_at": self.updated_at,
|
|
169
|
+
"metadata": self.metadata
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class DestroyResult(BaseModel):
|
|
174
|
+
"""Result of a destroy operation."""
|
|
175
|
+
success: bool = Field(..., description="Whether destroy succeeded")
|
|
176
|
+
message: str = Field(..., description="Result message")
|
|
177
|
+
resources_deleted: List[str] = Field(default_factory=list, description="List of deleted resources")
|
|
178
|
+
error: Optional[str] = Field(default=None, description="Error message if failed")
|
|
179
|
+
metadata: Dict[str, Any] = Field(default_factory=dict, description="Additional metadata")
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Cloud provider adapters for deployment.
|
|
3
|
+
"""
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from .base import BaseProvider
|
|
8
|
+
from .aws import AWSProvider
|
|
9
|
+
from .azure import AzureProvider
|
|
10
|
+
from .gcp import GCPProvider
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def __getattr__(name):
|
|
14
|
+
"""Lazy load provider modules."""
|
|
15
|
+
if name == 'BaseProvider':
|
|
16
|
+
from .base import BaseProvider
|
|
17
|
+
return BaseProvider
|
|
18
|
+
elif name == 'AWSProvider':
|
|
19
|
+
from .aws import AWSProvider
|
|
20
|
+
return AWSProvider
|
|
21
|
+
elif name == 'AzureProvider':
|
|
22
|
+
from .azure import AzureProvider
|
|
23
|
+
return AzureProvider
|
|
24
|
+
elif name == 'GCPProvider':
|
|
25
|
+
from .gcp import GCPProvider
|
|
26
|
+
return GCPProvider
|
|
27
|
+
elif name == 'get_provider':
|
|
28
|
+
from .base import get_provider
|
|
29
|
+
return get_provider
|
|
30
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
__all__ = ['BaseProvider', 'AWSProvider', 'AzureProvider', 'GCPProvider', 'get_provider']
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AWS provider for cloud deployment (ECS Fargate).
|
|
3
|
+
"""
|
|
4
|
+
import subprocess
|
|
5
|
+
import json
|
|
6
|
+
from typing import Dict, Any
|
|
7
|
+
from .base import BaseProvider
|
|
8
|
+
from ..models import CloudConfig, DeployResult, DeployStatus, DestroyResult, ServiceState
|
|
9
|
+
from ..doctor import DoctorReport, DoctorCheckResult, check_aws_cli
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AWSProvider(BaseProvider):
|
|
13
|
+
"""AWS deployment provider using ECS Fargate."""
|
|
14
|
+
|
|
15
|
+
def doctor(self) -> DoctorReport:
|
|
16
|
+
"""Run AWS-specific health checks."""
|
|
17
|
+
checks = [check_aws_cli()]
|
|
18
|
+
|
|
19
|
+
# Check if region is configured
|
|
20
|
+
try:
|
|
21
|
+
result = subprocess.run(
|
|
22
|
+
['aws', 'configure', 'get', 'region'],
|
|
23
|
+
capture_output=True,
|
|
24
|
+
text=True,
|
|
25
|
+
timeout=5
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
if result.returncode == 0 and result.stdout.strip():
|
|
29
|
+
checks.append(DoctorCheckResult(
|
|
30
|
+
name="AWS Region",
|
|
31
|
+
passed=True,
|
|
32
|
+
message=f"Region configured: {result.stdout.strip()}"
|
|
33
|
+
))
|
|
34
|
+
else:
|
|
35
|
+
checks.append(DoctorCheckResult(
|
|
36
|
+
name="AWS Region",
|
|
37
|
+
passed=False,
|
|
38
|
+
message="No default region configured",
|
|
39
|
+
fix_suggestion="Run: aws configure set region us-east-1"
|
|
40
|
+
))
|
|
41
|
+
except Exception as e:
|
|
42
|
+
checks.append(DoctorCheckResult(
|
|
43
|
+
name="AWS Region",
|
|
44
|
+
passed=False,
|
|
45
|
+
message=f"Failed to check region: {e}",
|
|
46
|
+
fix_suggestion="Run: aws configure"
|
|
47
|
+
))
|
|
48
|
+
|
|
49
|
+
return DoctorReport(checks=checks)
|
|
50
|
+
|
|
51
|
+
def plan(self) -> Dict[str, Any]:
|
|
52
|
+
"""Generate AWS deployment plan."""
|
|
53
|
+
cluster_name = self.config.cluster_name or f"{self.config.service_name}-cluster"
|
|
54
|
+
|
|
55
|
+
plan = {
|
|
56
|
+
"provider": "aws",
|
|
57
|
+
"service_name": self.config.service_name,
|
|
58
|
+
"region": self.config.region,
|
|
59
|
+
"cluster_name": cluster_name,
|
|
60
|
+
"launch_type": "FARGATE",
|
|
61
|
+
"cpu": self.config.cpu,
|
|
62
|
+
"memory": self.config.memory,
|
|
63
|
+
"desired_count": self.config.min_instances,
|
|
64
|
+
"image": self.config.image or f"{self.config.service_name}:latest",
|
|
65
|
+
"steps": [
|
|
66
|
+
"1. Create ECS cluster (if not exists)",
|
|
67
|
+
"2. Create task definition",
|
|
68
|
+
"3. Create or update ECS service",
|
|
69
|
+
"4. Wait for service to stabilize"
|
|
70
|
+
]
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return plan
|
|
74
|
+
|
|
75
|
+
def deploy(self) -> DeployResult:
|
|
76
|
+
"""Deploy to AWS ECS Fargate."""
|
|
77
|
+
try:
|
|
78
|
+
cluster_name = self.config.cluster_name or f"{self.config.service_name}-cluster"
|
|
79
|
+
|
|
80
|
+
# Step 1: Create cluster if not exists
|
|
81
|
+
print(f"📦 Creating ECS cluster: {cluster_name}")
|
|
82
|
+
try:
|
|
83
|
+
subprocess.run(
|
|
84
|
+
['aws', 'ecs', 'create-cluster',
|
|
85
|
+
'--cluster-name', cluster_name,
|
|
86
|
+
'--region', self.config.region],
|
|
87
|
+
capture_output=True,
|
|
88
|
+
timeout=30
|
|
89
|
+
)
|
|
90
|
+
except Exception:
|
|
91
|
+
pass # Cluster may already exist
|
|
92
|
+
|
|
93
|
+
# Step 2: Register task definition
|
|
94
|
+
print(f"📝 Registering task definition")
|
|
95
|
+
|
|
96
|
+
task_def = {
|
|
97
|
+
"family": self.config.service_name,
|
|
98
|
+
"networkMode": "awsvpc",
|
|
99
|
+
"requiresCompatibilities": ["FARGATE"],
|
|
100
|
+
"cpu": self.config.cpu,
|
|
101
|
+
"memory": self.config.memory,
|
|
102
|
+
"containerDefinitions": [
|
|
103
|
+
{
|
|
104
|
+
"name": self.config.service_name,
|
|
105
|
+
"image": self.config.image or f"{self.config.service_name}:latest",
|
|
106
|
+
"portMappings": [
|
|
107
|
+
{
|
|
108
|
+
"containerPort": 8005,
|
|
109
|
+
"protocol": "tcp"
|
|
110
|
+
}
|
|
111
|
+
],
|
|
112
|
+
"environment": [
|
|
113
|
+
{"name": k, "value": v}
|
|
114
|
+
for k, v in (self.config.env_vars or {}).items()
|
|
115
|
+
]
|
|
116
|
+
}
|
|
117
|
+
]
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
result = subprocess.run(
|
|
121
|
+
['aws', 'ecs', 'register-task-definition',
|
|
122
|
+
'--cli-input-json', json.dumps(task_def),
|
|
123
|
+
'--region', self.config.region],
|
|
124
|
+
capture_output=True,
|
|
125
|
+
text=True,
|
|
126
|
+
timeout=30
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
if result.returncode != 0:
|
|
130
|
+
return DeployResult(
|
|
131
|
+
success=False,
|
|
132
|
+
message="Failed to register task definition",
|
|
133
|
+
error=result.stderr
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
# Step 3: Create or update service
|
|
137
|
+
print(f"🚀 Deploying service: {self.config.service_name}")
|
|
138
|
+
|
|
139
|
+
# Try to update first, create if doesn't exist
|
|
140
|
+
update_result = subprocess.run(
|
|
141
|
+
['aws', 'ecs', 'update-service',
|
|
142
|
+
'--cluster', cluster_name,
|
|
143
|
+
'--service', self.config.service_name,
|
|
144
|
+
'--task-definition', self.config.service_name,
|
|
145
|
+
'--desired-count', str(self.config.min_instances),
|
|
146
|
+
'--region', self.config.region],
|
|
147
|
+
capture_output=True,
|
|
148
|
+
text=True,
|
|
149
|
+
timeout=60
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
if update_result.returncode != 0:
|
|
153
|
+
# Service doesn't exist, create it
|
|
154
|
+
# Note: This is simplified - production would need VPC/subnet/security group config
|
|
155
|
+
return DeployResult(
|
|
156
|
+
success=False,
|
|
157
|
+
message="Service creation requires VPC configuration",
|
|
158
|
+
error="Please create the service manually with proper VPC/subnet/security group configuration, then use update"
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
return DeployResult(
|
|
162
|
+
success=True,
|
|
163
|
+
message=f"Service deployed successfully to AWS ECS",
|
|
164
|
+
url=f"https://console.aws.amazon.com/ecs/home?region={self.config.region}#/clusters/{cluster_name}/services/{self.config.service_name}",
|
|
165
|
+
metadata={
|
|
166
|
+
"cluster": cluster_name,
|
|
167
|
+
"service": self.config.service_name,
|
|
168
|
+
"region": self.config.region
|
|
169
|
+
}
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
except Exception as e:
|
|
173
|
+
return DeployResult(
|
|
174
|
+
success=False,
|
|
175
|
+
message="AWS deployment failed",
|
|
176
|
+
error=str(e)
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
def status(self) -> DeployStatus:
|
|
180
|
+
"""Get current AWS ECS service status."""
|
|
181
|
+
try:
|
|
182
|
+
cluster_name = self.config.cluster_name or f"{self.config.service_name}-cluster"
|
|
183
|
+
|
|
184
|
+
result = subprocess.run(
|
|
185
|
+
['aws', 'ecs', 'describe-services',
|
|
186
|
+
'--cluster', cluster_name,
|
|
187
|
+
'--services', self.config.service_name,
|
|
188
|
+
'--region', self.config.region,
|
|
189
|
+
'--output', 'json'],
|
|
190
|
+
capture_output=True,
|
|
191
|
+
text=True,
|
|
192
|
+
timeout=30
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
if result.returncode != 0:
|
|
196
|
+
return DeployStatus(
|
|
197
|
+
state=ServiceState.NOT_FOUND,
|
|
198
|
+
message=f"Service not found or error: {result.stderr}",
|
|
199
|
+
service_name=self.config.service_name,
|
|
200
|
+
provider="aws",
|
|
201
|
+
region=self.config.region
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
data = json.loads(result.stdout)
|
|
205
|
+
services = data.get('services', [])
|
|
206
|
+
|
|
207
|
+
if not services:
|
|
208
|
+
return DeployStatus(
|
|
209
|
+
state=ServiceState.NOT_FOUND,
|
|
210
|
+
message="Service not found",
|
|
211
|
+
service_name=self.config.service_name,
|
|
212
|
+
provider="aws",
|
|
213
|
+
region=self.config.region
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
service = services[0]
|
|
217
|
+
status_str = service.get('status', 'UNKNOWN')
|
|
218
|
+
running_count = service.get('runningCount', 0)
|
|
219
|
+
desired_count = service.get('desiredCount', 0)
|
|
220
|
+
|
|
221
|
+
# Map AWS status to ServiceState
|
|
222
|
+
if status_str == 'ACTIVE' and running_count > 0:
|
|
223
|
+
state = ServiceState.RUNNING
|
|
224
|
+
elif status_str == 'ACTIVE' and running_count == 0:
|
|
225
|
+
state = ServiceState.STOPPED
|
|
226
|
+
elif status_str == 'DRAINING':
|
|
227
|
+
state = ServiceState.PENDING
|
|
228
|
+
elif status_str == 'INACTIVE':
|
|
229
|
+
state = ServiceState.STOPPED
|
|
230
|
+
else:
|
|
231
|
+
state = ServiceState.UNKNOWN
|
|
232
|
+
|
|
233
|
+
return DeployStatus(
|
|
234
|
+
state=state,
|
|
235
|
+
url=f"https://console.aws.amazon.com/ecs/home?region={self.config.region}#/clusters/{cluster_name}/services/{self.config.service_name}",
|
|
236
|
+
message=f"Status: {status_str}",
|
|
237
|
+
service_name=self.config.service_name,
|
|
238
|
+
provider="aws",
|
|
239
|
+
region=self.config.region,
|
|
240
|
+
healthy=running_count >= desired_count and running_count > 0,
|
|
241
|
+
instances_running=running_count,
|
|
242
|
+
instances_desired=desired_count,
|
|
243
|
+
created_at=service.get('createdAt'),
|
|
244
|
+
metadata={
|
|
245
|
+
"cluster": cluster_name,
|
|
246
|
+
"task_definition": service.get('taskDefinition'),
|
|
247
|
+
"launch_type": service.get('launchType'),
|
|
248
|
+
"deployments": len(service.get('deployments', []))
|
|
249
|
+
}
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
except Exception as e:
|
|
253
|
+
return DeployStatus(
|
|
254
|
+
state=ServiceState.UNKNOWN,
|
|
255
|
+
message=f"Failed to get status: {e}",
|
|
256
|
+
service_name=self.config.service_name,
|
|
257
|
+
provider="aws",
|
|
258
|
+
region=self.config.region
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
def destroy(self, force: bool = False) -> DestroyResult:
|
|
262
|
+
"""Destroy AWS ECS service and related resources."""
|
|
263
|
+
try:
|
|
264
|
+
cluster_name = self.config.cluster_name or f"{self.config.service_name}-cluster"
|
|
265
|
+
deleted_resources = []
|
|
266
|
+
|
|
267
|
+
# Step 1: Update service desired count to 0
|
|
268
|
+
print(f"🛑 Stopping service: {self.config.service_name}")
|
|
269
|
+
subprocess.run(
|
|
270
|
+
['aws', 'ecs', 'update-service',
|
|
271
|
+
'--cluster', cluster_name,
|
|
272
|
+
'--service', self.config.service_name,
|
|
273
|
+
'--desired-count', '0',
|
|
274
|
+
'--region', self.config.region],
|
|
275
|
+
capture_output=True,
|
|
276
|
+
timeout=30
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
# Step 2: Delete the service
|
|
280
|
+
print(f"🗑️ Deleting service: {self.config.service_name}")
|
|
281
|
+
result = subprocess.run(
|
|
282
|
+
['aws', 'ecs', 'delete-service',
|
|
283
|
+
'--cluster', cluster_name,
|
|
284
|
+
'--service', self.config.service_name,
|
|
285
|
+
'--force' if force else '--no-force',
|
|
286
|
+
'--region', self.config.region],
|
|
287
|
+
capture_output=True,
|
|
288
|
+
text=True,
|
|
289
|
+
timeout=60
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
if result.returncode == 0:
|
|
293
|
+
deleted_resources.append(f"ecs-service:{self.config.service_name}")
|
|
294
|
+
else:
|
|
295
|
+
return DestroyResult(
|
|
296
|
+
success=False,
|
|
297
|
+
message="Failed to delete service",
|
|
298
|
+
error=result.stderr,
|
|
299
|
+
resources_deleted=deleted_resources
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
# Step 3: Optionally delete cluster if empty
|
|
303
|
+
if force:
|
|
304
|
+
print(f"🗑️ Deleting cluster: {cluster_name}")
|
|
305
|
+
cluster_result = subprocess.run(
|
|
306
|
+
['aws', 'ecs', 'delete-cluster',
|
|
307
|
+
'--cluster', cluster_name,
|
|
308
|
+
'--region', self.config.region],
|
|
309
|
+
capture_output=True,
|
|
310
|
+
text=True,
|
|
311
|
+
timeout=30
|
|
312
|
+
)
|
|
313
|
+
if cluster_result.returncode == 0:
|
|
314
|
+
deleted_resources.append(f"ecs-cluster:{cluster_name}")
|
|
315
|
+
|
|
316
|
+
return DestroyResult(
|
|
317
|
+
success=True,
|
|
318
|
+
message=f"Successfully destroyed AWS ECS service",
|
|
319
|
+
resources_deleted=deleted_resources,
|
|
320
|
+
metadata={
|
|
321
|
+
"cluster": cluster_name,
|
|
322
|
+
"region": self.config.region
|
|
323
|
+
}
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
except Exception as e:
|
|
327
|
+
return DestroyResult(
|
|
328
|
+
success=False,
|
|
329
|
+
message="Failed to destroy AWS resources",
|
|
330
|
+
error=str(e)
|
|
331
|
+
)
|