quantum-framework 0.18.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.
- quantum/__init__.py +1 -0
- quantum/cli/__init__.py +2 -0
- quantum/cli/admin.py +131 -0
- quantum/cli/check.py +223 -0
- quantum/cli/commands/__init__.py +15 -0
- quantum/cli/commands/build.py +414 -0
- quantum/cli/commands/dev.py +185 -0
- quantum/cli/commands/docs.py +329 -0
- quantum/cli/commands/lint.py +523 -0
- quantum/cli/commands/migrate.py +548 -0
- quantum/cli/commands/new.py +622 -0
- quantum/cli/commands/serve.py +190 -0
- quantum/cli/commands/test.py +193 -0
- quantum/cli/deploy.py +810 -0
- quantum/cli/hot_reload.py +951 -0
- quantum/cli/jobs.py +356 -0
- quantum/cli/mq.py +582 -0
- quantum/cli/pkg.py +390 -0
- quantum/cli/runner.py +565 -0
- quantum/cli/server_process.py +159 -0
- quantum/cli/utils.py +334 -0
- quantum/compiler/__init__.py +30 -0
- quantum/compiler/base_generator.py +367 -0
- quantum/compiler/cli.py +295 -0
- quantum/compiler/expression_transformer.py +444 -0
- quantum/compiler/javascript/__init__.py +10 -0
- quantum/compiler/javascript/generator.py +659 -0
- quantum/compiler/optimizer.py +270 -0
- quantum/compiler/python/__init__.py +10 -0
- quantum/compiler/python/generator.py +883 -0
- quantum/compiler/python/runtime.py +863 -0
- quantum/compiler/transpiler.py +330 -0
- quantum/core/__init__.py +3 -0
- quantum/core/ast_nodes.py +2619 -0
- quantum/core/config_env.py +54 -0
- quantum/core/docs_links.py +8 -0
- quantum/core/expression_diagnostics.py +87 -0
- quantum/core/expression_stdlib.py +208 -0
- quantum/core/expressions.py +677 -0
- quantum/core/features/agents/src/__init__.py +30 -0
- quantum/core/features/agents/src/ast_node.py +540 -0
- quantum/core/features/conditionals/src/__init__.py +8 -0
- quantum/core/features/conditionals/src/ast_node.py +68 -0
- quantum/core/features/data_import/src/__init__.py +0 -0
- quantum/core/features/data_import/src/ast_node.py +292 -0
- quantum/core/features/data_import/src/runtime.py +533 -0
- quantum/core/features/dump/src/__init__.py +12 -0
- quantum/core/features/dump/src/ast_node.py +106 -0
- quantum/core/features/dump/src/parser.py +61 -0
- quantum/core/features/dump/src/runtime.py +246 -0
- quantum/core/features/functions/src/__init__.py +8 -0
- quantum/core/features/functions/src/ast_node.py +149 -0
- quantum/core/features/game_engine_2d/src/__init__.py +19 -0
- quantum/core/features/game_engine_2d/src/ast_nodes.py +1719 -0
- quantum/core/features/game_engine_2d/src/parser.py +983 -0
- quantum/core/features/invocation/src/__init__.py +0 -0
- quantum/core/features/invocation/src/ast_node.py +147 -0
- quantum/core/features/invocation/src/runtime.py +332 -0
- quantum/core/features/knowledge_base/src/__init__.py +6 -0
- quantum/core/features/knowledge_base/src/ast_node.py +113 -0
- quantum/core/features/knowledge_base/src/parser.py +82 -0
- quantum/core/features/logging/src/__init__.py +12 -0
- quantum/core/features/logging/src/ast_node.py +111 -0
- quantum/core/features/logging/src/parser.py +50 -0
- quantum/core/features/logging/src/runtime.py +190 -0
- quantum/core/features/loops/src/__init__.py +8 -0
- quantum/core/features/loops/src/ast_node.py +60 -0
- quantum/core/features/query/src/__init__.py +0 -0
- quantum/core/features/query/src/query_validators.py +20 -0
- quantum/core/features/state_management/src/__init__.py +11 -0
- quantum/core/features/state_management/src/ast_node.py +121 -0
- quantum/core/features/terminal_engine/src/__init__.py +21 -0
- quantum/core/features/terminal_engine/src/ast_nodes.py +560 -0
- quantum/core/features/terminal_engine/src/parser.py +361 -0
- quantum/core/features/testing_engine/src/__init__.py +41 -0
- quantum/core/features/testing_engine/src/ast_nodes.py +1212 -0
- quantum/core/features/testing_engine/src/parser.py +604 -0
- quantum/core/features/theming/src/__init__.py +48 -0
- quantum/core/features/theming/src/ast_node.py +137 -0
- quantum/core/features/theming/src/presets.py +405 -0
- quantum/core/features/ui_engine/src/__init__.py +20 -0
- quantum/core/features/ui_engine/src/ast_nodes.py +1913 -0
- quantum/core/features/ui_engine/src/parser.py +1192 -0
- quantum/core/features/websocket/src/__init__.py +24 -0
- quantum/core/features/websocket/src/ast_node.py +247 -0
- quantum/core/guards.py +53 -0
- quantum/core/html_compat.py +299 -0
- quantum/core/parser.py +1551 -0
- quantum/core/parser_registry.py +213 -0
- quantum/core/parsers/__init__.py +76 -0
- quantum/core/parsers/ai/__init__.py +12 -0
- quantum/core/parsers/ai/agent_parser.py +106 -0
- quantum/core/parsers/ai/knowledge_parser.py +86 -0
- quantum/core/parsers/ai/llm_parser.py +78 -0
- quantum/core/parsers/ai/team_parser.py +88 -0
- quantum/core/parsers/base.py +322 -0
- quantum/core/parsers/composition/__init__.py +10 -0
- quantum/core/parsers/composition/import_parser.py +50 -0
- quantum/core/parsers/composition/slot_parser.py +48 -0
- quantum/core/parsers/control_flow/__init__.py +11 -0
- quantum/core/parsers/control_flow/if_parser.py +71 -0
- quantum/core/parsers/control_flow/loop_parser.py +105 -0
- quantum/core/parsers/control_flow/set_parser.py +93 -0
- quantum/core/parsers/data/__init__.py +12 -0
- quantum/core/parsers/data/data_parser.py +229 -0
- quantum/core/parsers/data/invoke_parser.py +133 -0
- quantum/core/parsers/data/query_parser.py +213 -0
- quantum/core/parsers/data/transaction_parser.py +86 -0
- quantum/core/parsers/events/__init__.py +9 -0
- quantum/core/parsers/events/dispatch_event_parser.py +44 -0
- quantum/core/parsers/forms/__init__.py +11 -0
- quantum/core/parsers/forms/action_parser.py +79 -0
- quantum/core/parsers/forms/flash_parser.py +34 -0
- quantum/core/parsers/forms/redirect_parser.py +33 -0
- quantum/core/parsers/functions/__init__.py +11 -0
- quantum/core/parsers/functions/function_parser.py +98 -0
- quantum/core/parsers/functions/param_parser.py +73 -0
- quantum/core/parsers/functions/return_parser.py +31 -0
- quantum/core/parsers/html/__init__.py +10 -0
- quantum/core/parsers/html/component_call_parser.py +130 -0
- quantum/core/parsers/html/html_parser.py +115 -0
- quantum/core/parsers/jobs/__init__.py +11 -0
- quantum/core/parsers/jobs/job_parser.py +71 -0
- quantum/core/parsers/jobs/schedule_parser.py +62 -0
- quantum/core/parsers/jobs/thread_parser.py +57 -0
- quantum/core/parsers/messaging/__init__.py +17 -0
- quantum/core/parsers/messaging/message_ack_parser.py +30 -0
- quantum/core/parsers/messaging/message_nack_parser.py +30 -0
- quantum/core/parsers/messaging/message_parser.py +114 -0
- quantum/core/parsers/messaging/queue_parser.py +61 -0
- quantum/core/parsers/messaging/websocket_parser.py +121 -0
- quantum/core/parsers/routing/__init__.py +9 -0
- quantum/core/parsers/routing/route_parser.py +41 -0
- quantum/core/parsers/scripting/__init__.py +12 -0
- quantum/core/parsers/scripting/pyclass_parser.py +62 -0
- quantum/core/parsers/scripting/pydecorator_parser.py +68 -0
- quantum/core/parsers/scripting/pyimport_parser.py +52 -0
- quantum/core/parsers/scripting/python_parser.py +49 -0
- quantum/core/parsers/services/__init__.py +12 -0
- quantum/core/parsers/services/dump_parser.py +52 -0
- quantum/core/parsers/services/file_parser.py +48 -0
- quantum/core/parsers/services/log_parser.py +46 -0
- quantum/core/parsers/services/mail_parser.py +65 -0
- quantum/core/tiers.py +164 -0
- quantum/core/xml_lines.py +81 -0
- quantum/packages/__init__.py +28 -0
- quantum/packages/manager.py +413 -0
- quantum/packages/manifest.py +351 -0
- quantum/packages/registry.py +399 -0
- quantum/packages/resolver.py +336 -0
- quantum/plugins/__init__.py +33 -0
- quantum/plugins/hooks.py +329 -0
- quantum/plugins/loader.py +479 -0
- quantum/plugins/manifest.py +336 -0
- quantum/plugins/registry.py +371 -0
- quantum/runtime/__init__.py +25 -0
- quantum/runtime/action_handler.py +532 -0
- quantum/runtime/adapters/__init__.py +88 -0
- quantum/runtime/adapters/memory_adapter.py +690 -0
- quantum/runtime/adapters/rabbitmq_adapter.py +715 -0
- quantum/runtime/adapters/redis_adapter.py +582 -0
- quantum/runtime/adapters/sqlite_adapter.py +414 -0
- quantum/runtime/agent_service.py +1133 -0
- quantum/runtime/ast_cache.py +506 -0
- quantum/runtime/auth_service.py +267 -0
- quantum/runtime/component.py +1048 -0
- quantum/runtime/component_composer.py +133 -0
- quantum/runtime/component_resolver.py +174 -0
- quantum/runtime/conditions.py +54 -0
- quantum/runtime/database_service.py +595 -0
- quantum/runtime/db_schema.py +100 -0
- quantum/runtime/dev_panel.py +178 -0
- quantum/runtime/email_service.py +162 -0
- quantum/runtime/error_handler.py +295 -0
- quantum/runtime/execution_context.py +283 -0
- quantum/runtime/executor_registry.py +178 -0
- quantum/runtime/executors/__init__.py +71 -0
- quantum/runtime/executors/ai/__init__.py +12 -0
- quantum/runtime/executors/ai/agent_executor.py +217 -0
- quantum/runtime/executors/ai/knowledge_executor.py +114 -0
- quantum/runtime/executors/ai/llm_executor.py +153 -0
- quantum/runtime/executors/ai/team_executor.py +171 -0
- quantum/runtime/executors/base.py +264 -0
- quantum/runtime/executors/control_flow/__init__.py +12 -0
- quantum/runtime/executors/control_flow/if_executor.py +98 -0
- quantum/runtime/executors/control_flow/loop_executor.py +308 -0
- quantum/runtime/executors/control_flow/redirect_executor.py +43 -0
- quantum/runtime/executors/control_flow/set_executor.py +464 -0
- quantum/runtime/executors/data/__init__.py +12 -0
- quantum/runtime/executors/data/data_executor.py +156 -0
- quantum/runtime/executors/data/invoke_executor.py +228 -0
- quantum/runtime/executors/data/query_executor.py +290 -0
- quantum/runtime/executors/data/transaction_executor.py +91 -0
- quantum/runtime/executors/jobs/__init__.py +11 -0
- quantum/runtime/executors/jobs/job_executor.py +190 -0
- quantum/runtime/executors/jobs/schedule_executor.py +132 -0
- quantum/runtime/executors/jobs/thread_executor.py +127 -0
- quantum/runtime/executors/messaging/__init__.py +17 -0
- quantum/runtime/executors/messaging/message_ack_executor.py +51 -0
- quantum/runtime/executors/messaging/message_executor.py +174 -0
- quantum/runtime/executors/messaging/queue_executor.py +103 -0
- quantum/runtime/executors/messaging/websocket_executor.py +197 -0
- quantum/runtime/executors/scripting/__init__.py +11 -0
- quantum/runtime/executors/scripting/pyclass_executor.py +90 -0
- quantum/runtime/executors/scripting/pyimport_executor.py +81 -0
- quantum/runtime/executors/scripting/python_executor.py +249 -0
- quantum/runtime/executors/services/__init__.py +12 -0
- quantum/runtime/executors/services/dump_executor.py +72 -0
- quantum/runtime/executors/services/file_executor.py +89 -0
- quantum/runtime/executors/services/log_executor.py +77 -0
- quantum/runtime/executors/services/mail_executor.py +81 -0
- quantum/runtime/expression_cache.py +498 -0
- quantum/runtime/file_upload_service.py +326 -0
- quantum/runtime/function_registry.py +118 -0
- quantum/runtime/game_builder.py +166 -0
- quantum/runtime/game_code_generator.py +2371 -0
- quantum/runtime/game_templates.py +2006 -0
- quantum/runtime/godot_code_generator.py +4681 -0
- quantum/runtime/godot_templates.py +1449 -0
- quantum/runtime/job_executor.py +1599 -0
- quantum/runtime/knowledge_service.py +546 -0
- quantum/runtime/llm_cache.py +100 -0
- quantum/runtime/llm_providers.py +704 -0
- quantum/runtime/llm_service.py +287 -0
- quantum/runtime/local_server.py +28 -0
- quantum/runtime/logging_setup.py +141 -0
- quantum/runtime/message_broker.py +364 -0
- quantum/runtime/message_queue_service.py +571 -0
- quantum/runtime/param_validation.py +218 -0
- quantum/runtime/pypy_compat.py +317 -0
- quantum/runtime/python_bridge.py +698 -0
- quantum/runtime/query_validators.py +304 -0
- quantum/runtime/renderer.py +734 -0
- quantum/runtime/service_container.py +470 -0
- quantum/runtime/table_params.py +163 -0
- quantum/runtime/terminal_builder.py +76 -0
- quantum/runtime/terminal_code_generator.py +607 -0
- quantum/runtime/terminal_templates.py +243 -0
- quantum/runtime/testing_builder.py +77 -0
- quantum/runtime/testing_code_generator.py +833 -0
- quantum/runtime/testing_templates.py +85 -0
- quantum/runtime/ui_builder.py +222 -0
- quantum/runtime/ui_console.py +405 -0
- quantum/runtime/ui_desktop.py +56 -0
- quantum/runtime/ui_form_rules.py +193 -0
- quantum/runtime/ui_html_adapter.py +2567 -0
- quantum/runtime/ui_html_templates.py +2106 -0
- quantum/runtime/ui_mobile_adapter.py +1832 -0
- quantum/runtime/ui_mobile_templates.py +1003 -0
- quantum/runtime/ui_page_renderer.py +392 -0
- quantum/runtime/ui_pager.py +73 -0
- quantum/runtime/ui_table_edit.py +139 -0
- quantum/runtime/ui_textual_adapter.py +1866 -0
- quantum/runtime/ui_textual_templates.py +45 -0
- quantum/runtime/ui_tokens.py +465 -0
- quantum/runtime/ui_validator.py +365 -0
- quantum/runtime/ui_view.py +221 -0
- quantum/runtime/validators.py +256 -0
- quantum/runtime/web_server.py +2131 -0
- quantum/runtime/websocket_adapter.py +501 -0
- quantum/runtime/websocket_service.py +585 -0
- quantum/runtime/websocket_transport.py +289 -0
- quantum/runtime/wsgi.py +101 -0
- quantum/services.py +109 -0
- quantum/utils/__init__.py +1 -0
- quantum_admin/__init__.py +4 -0
- quantum_admin/assets/quantum-admin-connectors.js +123 -0
- quantum_admin/assets/quantum-admin.css +2058 -0
- quantum_admin/assets/quantum-source-highlight.js +106 -0
- quantum_admin/backend/__init__.py +4 -0
- quantum_admin/backend/audit_service.py +346 -0
- quantum_admin/backend/auth_service.py +394 -0
- quantum_admin/backend/component_discovery.py +766 -0
- quantum_admin/backend/config_generator.py +586 -0
- quantum_admin/backend/connector_service.py +1016 -0
- quantum_admin/backend/crud.py +557 -0
- quantum_admin/backend/database.py +226 -0
- quantum_admin/backend/db_setup_service.py +193 -0
- quantum_admin/backend/deploy_service.py +698 -0
- quantum_admin/backend/docker_service.py +422 -0
- quantum_admin/backend/environment_service.py +418 -0
- quantum_admin/backend/git_service.py +370 -0
- quantum_admin/backend/health_service.py +390 -0
- quantum_admin/backend/job_service.py +347 -0
- quantum_admin/backend/log_service.py +331 -0
- quantum_admin/backend/main.py +14426 -0
- quantum_admin/backend/models.py +986 -0
- quantum_admin/backend/pipeline_service.py +812 -0
- quantum_admin/backend/resource_manager.py +1118 -0
- quantum_admin/backend/schemas.py +419 -0
- quantum_admin/backend/secret_manager.py +277 -0
- quantum_admin/backend/settings_service.py +488 -0
- quantum_admin/backend/template_service.py +710 -0
- quantum_admin/backend/test_execution_service.py +364 -0
- quantum_admin/backend/test_generator.py +782 -0
- quantum_admin/backend/webhook_service.py +454 -0
- quantum_admin/backend/websocket_manager.py +360 -0
- quantum_admin/components/admin/_layout/AdminShell.q +121 -0
- quantum_admin/components/admin/agents.q +107 -0
- quantum_admin/components/admin/app/[name].q +558 -0
- quantum_admin/components/admin/applications.q +160 -0
- quantum_admin/components/admin/component/[...path].q +205 -0
- quantum_admin/components/admin/components.q +59 -0
- quantum_admin/components/admin/connectors.q +283 -0
- quantum_admin/components/admin/dashboard.q +86 -0
- quantum_admin/components/admin/database.q +95 -0
- quantum_admin/components/admin/features.q +69 -0
- quantum_admin/components/admin/index.q +12 -0
- quantum_admin/components/admin/jobs.q +75 -0
- quantum_admin/components/admin/login.q +65 -0
- quantum_admin/components/admin/logout.q +11 -0
- quantum_admin/components/admin/settings.q +174 -0
- quantum_admin/components/admin/source.q +45 -0
- quantum_admin/components/admin/tests.q +73 -0
- quantum_admin/run.py +13 -0
- quantum_admin/services/__init__.py +16 -0
- quantum_admin/services/_base.py +84 -0
- quantum_admin/services/_component_test_generator.py +205 -0
- quantum_admin/services/_yaml_editor.py +82 -0
- quantum_admin/services/apps.py +330 -0
- quantum_admin/services/auth.py +61 -0
- quantum_admin/services/components.py +210 -0
- quantum_admin/services/connectors.py +164 -0
- quantum_admin/services/projects.py +189 -0
- quantum_admin/services/repository.py +223 -0
- quantum_admin/services/settings.py +150 -0
- quantum_admin/services/yaml_import.py +118 -0
- quantum_framework-0.18.0.dist-info/METADATA +281 -0
- quantum_framework-0.18.0.dist-info/RECORD +333 -0
- quantum_framework-0.18.0.dist-info/WHEEL +5 -0
- quantum_framework-0.18.0.dist-info/entry_points.txt +2 -0
- quantum_framework-0.18.0.dist-info/licenses/LICENSE +21 -0
- quantum_framework-0.18.0.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Environment Service for Quantum Admin
|
|
3
|
+
Handles deployment environment CRUD operations
|
|
4
|
+
"""
|
|
5
|
+
import json
|
|
6
|
+
import logging
|
|
7
|
+
from typing import Optional, List, Dict, Any
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
|
|
10
|
+
from sqlalchemy.orm import Session
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
from .models import Environment, Project
|
|
16
|
+
from .database import get_db, SessionLocal
|
|
17
|
+
except ImportError:
|
|
18
|
+
from models import Environment, Project
|
|
19
|
+
from database import get_db, SessionLocal
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class EnvironmentService:
|
|
23
|
+
"""Service for managing deployment environments"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, db: Session = None):
|
|
26
|
+
"""
|
|
27
|
+
Initialize environment service
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
db: SQLAlchemy session (optional, creates new if not provided)
|
|
31
|
+
"""
|
|
32
|
+
self._db = db
|
|
33
|
+
self._own_session = db is None
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def db(self) -> Session:
|
|
37
|
+
"""Get database session"""
|
|
38
|
+
if self._db is None:
|
|
39
|
+
self._db = SessionLocal()
|
|
40
|
+
return self._db
|
|
41
|
+
|
|
42
|
+
def close(self):
|
|
43
|
+
"""Close database session if we own it"""
|
|
44
|
+
if self._own_session and self._db:
|
|
45
|
+
self._db.close()
|
|
46
|
+
self._db = None
|
|
47
|
+
|
|
48
|
+
# =========================================================================
|
|
49
|
+
# CRUD Operations
|
|
50
|
+
# =========================================================================
|
|
51
|
+
|
|
52
|
+
def create_environment(self, project_id: int, data: Dict[str, Any]) -> Environment:
|
|
53
|
+
"""
|
|
54
|
+
Create a new environment
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
project_id: Project ID
|
|
58
|
+
data: Environment data dict
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
Created Environment object
|
|
62
|
+
"""
|
|
63
|
+
# Verify project exists
|
|
64
|
+
project = self.db.query(Project).filter(Project.id == project_id).first()
|
|
65
|
+
if not project:
|
|
66
|
+
raise ValueError(f"Project {project_id} not found")
|
|
67
|
+
|
|
68
|
+
# Determine order if not specified
|
|
69
|
+
if "order" not in data:
|
|
70
|
+
max_order = self.db.query(Environment).filter(
|
|
71
|
+
Environment.project_id == project_id
|
|
72
|
+
).count()
|
|
73
|
+
data["order"] = max_order + 1
|
|
74
|
+
|
|
75
|
+
# Handle env_vars
|
|
76
|
+
env_vars = data.pop("env_vars", None)
|
|
77
|
+
if env_vars and isinstance(env_vars, dict):
|
|
78
|
+
data["env_vars_json"] = json.dumps(env_vars)
|
|
79
|
+
|
|
80
|
+
env = Environment(
|
|
81
|
+
project_id=project_id,
|
|
82
|
+
name=data.get("name"),
|
|
83
|
+
display_name=data.get("display_name") or data.get("name", "").title(),
|
|
84
|
+
order=data.get("order", 1),
|
|
85
|
+
docker_host=data.get("docker_host"),
|
|
86
|
+
docker_registry=data.get("docker_registry"),
|
|
87
|
+
deploy_path=data.get("deploy_path"),
|
|
88
|
+
container_name=data.get("container_name"),
|
|
89
|
+
port=data.get("port"),
|
|
90
|
+
branch=data.get("branch", "main"),
|
|
91
|
+
auto_deploy=data.get("auto_deploy", False),
|
|
92
|
+
requires_approval=data.get("requires_approval", False),
|
|
93
|
+
health_url=data.get("health_url"),
|
|
94
|
+
health_timeout=data.get("health_timeout", 30),
|
|
95
|
+
env_vars_json=data.get("env_vars_json"),
|
|
96
|
+
is_active=data.get("is_active", True)
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
self.db.add(env)
|
|
100
|
+
self.db.commit()
|
|
101
|
+
self.db.refresh(env)
|
|
102
|
+
|
|
103
|
+
logger.info(f"Created environment: {env.name} for project {project_id}")
|
|
104
|
+
return env
|
|
105
|
+
|
|
106
|
+
def get_environment(self, env_id: int) -> Optional[Environment]:
|
|
107
|
+
"""
|
|
108
|
+
Get environment by ID
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
env_id: Environment ID
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
Environment or None
|
|
115
|
+
"""
|
|
116
|
+
return self.db.query(Environment).filter(Environment.id == env_id).first()
|
|
117
|
+
|
|
118
|
+
def get_environment_by_name(self, project_id: int, name: str) -> Optional[Environment]:
|
|
119
|
+
"""
|
|
120
|
+
Get environment by project and name
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
project_id: Project ID
|
|
124
|
+
name: Environment name (dev, staging, production)
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
Environment or None
|
|
128
|
+
"""
|
|
129
|
+
return self.db.query(Environment).filter(
|
|
130
|
+
Environment.project_id == project_id,
|
|
131
|
+
Environment.name == name
|
|
132
|
+
).first()
|
|
133
|
+
|
|
134
|
+
def list_environments(self, project_id: int, active_only: bool = True) -> List[Environment]:
|
|
135
|
+
"""
|
|
136
|
+
List all environments for a project
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
project_id: Project ID
|
|
140
|
+
active_only: Only return active environments
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
List of environments ordered by order field
|
|
144
|
+
"""
|
|
145
|
+
query = self.db.query(Environment).filter(Environment.project_id == project_id)
|
|
146
|
+
|
|
147
|
+
if active_only:
|
|
148
|
+
query = query.filter(Environment.is_active == True)
|
|
149
|
+
|
|
150
|
+
return query.order_by(Environment.order).all()
|
|
151
|
+
|
|
152
|
+
def update_environment(self, env_id: int, data: Dict[str, Any]) -> Optional[Environment]:
|
|
153
|
+
"""
|
|
154
|
+
Update an environment
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
env_id: Environment ID
|
|
158
|
+
data: Fields to update
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
Updated Environment or None
|
|
162
|
+
"""
|
|
163
|
+
env = self.get_environment(env_id)
|
|
164
|
+
if not env:
|
|
165
|
+
return None
|
|
166
|
+
|
|
167
|
+
# Handle env_vars specially
|
|
168
|
+
env_vars = data.pop("env_vars", None)
|
|
169
|
+
if env_vars is not None:
|
|
170
|
+
if isinstance(env_vars, dict):
|
|
171
|
+
data["env_vars_json"] = json.dumps(env_vars)
|
|
172
|
+
elif isinstance(env_vars, str):
|
|
173
|
+
data["env_vars_json"] = env_vars
|
|
174
|
+
|
|
175
|
+
# Update allowed fields
|
|
176
|
+
allowed_fields = {
|
|
177
|
+
"name", "display_name", "order", "docker_host", "docker_registry",
|
|
178
|
+
"deploy_path", "container_name", "port", "branch", "auto_deploy",
|
|
179
|
+
"requires_approval", "approved_by", "health_url", "health_timeout",
|
|
180
|
+
"env_vars_json", "is_active"
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
for key, value in data.items():
|
|
184
|
+
if key in allowed_fields:
|
|
185
|
+
setattr(env, key, value)
|
|
186
|
+
|
|
187
|
+
env.updated_at = datetime.utcnow()
|
|
188
|
+
self.db.commit()
|
|
189
|
+
self.db.refresh(env)
|
|
190
|
+
|
|
191
|
+
logger.info(f"Updated environment: {env.name}")
|
|
192
|
+
return env
|
|
193
|
+
|
|
194
|
+
def delete_environment(self, env_id: int) -> bool:
|
|
195
|
+
"""
|
|
196
|
+
Delete an environment
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
env_id: Environment ID
|
|
200
|
+
|
|
201
|
+
Returns:
|
|
202
|
+
True if deleted
|
|
203
|
+
"""
|
|
204
|
+
env = self.get_environment(env_id)
|
|
205
|
+
if not env:
|
|
206
|
+
return False
|
|
207
|
+
|
|
208
|
+
self.db.delete(env)
|
|
209
|
+
self.db.commit()
|
|
210
|
+
|
|
211
|
+
logger.info(f"Deleted environment: {env.name}")
|
|
212
|
+
return True
|
|
213
|
+
|
|
214
|
+
# =========================================================================
|
|
215
|
+
# Environment Operations
|
|
216
|
+
# =========================================================================
|
|
217
|
+
|
|
218
|
+
def get_env_vars(self, env_id: int) -> Dict[str, str]:
|
|
219
|
+
"""
|
|
220
|
+
Get environment variables for an environment
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
env_id: Environment ID
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
Dict of environment variables
|
|
227
|
+
"""
|
|
228
|
+
env = self.get_environment(env_id)
|
|
229
|
+
if not env or not env.env_vars_json:
|
|
230
|
+
return {}
|
|
231
|
+
|
|
232
|
+
try:
|
|
233
|
+
return json.loads(env.env_vars_json)
|
|
234
|
+
except json.JSONDecodeError:
|
|
235
|
+
return {}
|
|
236
|
+
|
|
237
|
+
def set_env_vars(self, env_id: int, env_vars: Dict[str, str]) -> bool:
|
|
238
|
+
"""
|
|
239
|
+
Set environment variables
|
|
240
|
+
|
|
241
|
+
Args:
|
|
242
|
+
env_id: Environment ID
|
|
243
|
+
env_vars: Dict of environment variables
|
|
244
|
+
|
|
245
|
+
Returns:
|
|
246
|
+
True if successful
|
|
247
|
+
"""
|
|
248
|
+
env = self.get_environment(env_id)
|
|
249
|
+
if not env:
|
|
250
|
+
return False
|
|
251
|
+
|
|
252
|
+
env.env_vars_json = json.dumps(env_vars)
|
|
253
|
+
env.updated_at = datetime.utcnow()
|
|
254
|
+
self.db.commit()
|
|
255
|
+
return True
|
|
256
|
+
|
|
257
|
+
def get_next_environment(self, env_id: int) -> Optional[Environment]:
|
|
258
|
+
"""
|
|
259
|
+
Get the next environment in the promotion chain
|
|
260
|
+
|
|
261
|
+
Args:
|
|
262
|
+
env_id: Current environment ID
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
Next environment or None if at the end
|
|
266
|
+
"""
|
|
267
|
+
current = self.get_environment(env_id)
|
|
268
|
+
if not current:
|
|
269
|
+
return None
|
|
270
|
+
|
|
271
|
+
return self.db.query(Environment).filter(
|
|
272
|
+
Environment.project_id == current.project_id,
|
|
273
|
+
Environment.order > current.order,
|
|
274
|
+
Environment.is_active == True
|
|
275
|
+
).order_by(Environment.order).first()
|
|
276
|
+
|
|
277
|
+
def find_environment_by_branch(self, project_id: int, branch: str) -> Optional[Environment]:
|
|
278
|
+
"""
|
|
279
|
+
Find an environment configured for auto-deploy on a specific branch
|
|
280
|
+
|
|
281
|
+
Args:
|
|
282
|
+
project_id: Project ID
|
|
283
|
+
branch: Git branch name
|
|
284
|
+
|
|
285
|
+
Returns:
|
|
286
|
+
Environment configured for auto-deploy on this branch, or None
|
|
287
|
+
"""
|
|
288
|
+
return self.db.query(Environment).filter(
|
|
289
|
+
Environment.project_id == project_id,
|
|
290
|
+
Environment.branch == branch,
|
|
291
|
+
Environment.auto_deploy == True,
|
|
292
|
+
Environment.is_active == True
|
|
293
|
+
).first()
|
|
294
|
+
|
|
295
|
+
def approve_environment(self, env_id: int, approved_by: str) -> bool:
|
|
296
|
+
"""
|
|
297
|
+
Record approval for an environment that requires it
|
|
298
|
+
|
|
299
|
+
Args:
|
|
300
|
+
env_id: Environment ID
|
|
301
|
+
approved_by: Username of approver
|
|
302
|
+
|
|
303
|
+
Returns:
|
|
304
|
+
True if successful
|
|
305
|
+
"""
|
|
306
|
+
env = self.get_environment(env_id)
|
|
307
|
+
if not env:
|
|
308
|
+
return False
|
|
309
|
+
|
|
310
|
+
env.approved_by = approved_by
|
|
311
|
+
env.updated_at = datetime.utcnow()
|
|
312
|
+
self.db.commit()
|
|
313
|
+
|
|
314
|
+
logger.info(f"Environment {env.name} approved by {approved_by}")
|
|
315
|
+
return True
|
|
316
|
+
|
|
317
|
+
async def test_connection(self, env_id: int) -> Dict[str, Any]:
|
|
318
|
+
"""
|
|
319
|
+
Test connection to an environment's Docker host
|
|
320
|
+
|
|
321
|
+
Args:
|
|
322
|
+
env_id: Environment ID
|
|
323
|
+
|
|
324
|
+
Returns:
|
|
325
|
+
Dict with success status and info/error
|
|
326
|
+
"""
|
|
327
|
+
env = self.get_environment(env_id)
|
|
328
|
+
if not env:
|
|
329
|
+
return {"success": False, "error": "Environment not found"}
|
|
330
|
+
|
|
331
|
+
if not env.docker_host:
|
|
332
|
+
return {"success": False, "error": "No Docker host configured"}
|
|
333
|
+
|
|
334
|
+
try:
|
|
335
|
+
# Import Docker service
|
|
336
|
+
try:
|
|
337
|
+
from .docker_service import DockerService
|
|
338
|
+
except ImportError:
|
|
339
|
+
from docker_service import DockerService
|
|
340
|
+
|
|
341
|
+
docker = DockerService(docker_host=env.docker_host)
|
|
342
|
+
info = docker.client.info()
|
|
343
|
+
|
|
344
|
+
return {
|
|
345
|
+
"success": True,
|
|
346
|
+
"docker_version": info.get("ServerVersion"),
|
|
347
|
+
"containers": info.get("Containers"),
|
|
348
|
+
"images": info.get("Images"),
|
|
349
|
+
"os": info.get("OperatingSystem")
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
except Exception as e:
|
|
353
|
+
logger.error(f"Failed to connect to environment {env.name}: {e}")
|
|
354
|
+
return {"success": False, "error": str(e)}
|
|
355
|
+
|
|
356
|
+
# =========================================================================
|
|
357
|
+
# Default Environments
|
|
358
|
+
# =========================================================================
|
|
359
|
+
|
|
360
|
+
def create_default_environments(self, project_id: int) -> List[Environment]:
|
|
361
|
+
"""
|
|
362
|
+
Create default environments for a new project
|
|
363
|
+
|
|
364
|
+
Args:
|
|
365
|
+
project_id: Project ID
|
|
366
|
+
|
|
367
|
+
Returns:
|
|
368
|
+
List of created environments
|
|
369
|
+
"""
|
|
370
|
+
defaults = [
|
|
371
|
+
{
|
|
372
|
+
"name": "development",
|
|
373
|
+
"display_name": "Development",
|
|
374
|
+
"order": 1,
|
|
375
|
+
"branch": "develop",
|
|
376
|
+
"auto_deploy": True,
|
|
377
|
+
"requires_approval": False
|
|
378
|
+
},
|
|
379
|
+
{
|
|
380
|
+
"name": "staging",
|
|
381
|
+
"display_name": "Staging",
|
|
382
|
+
"order": 2,
|
|
383
|
+
"branch": "staging",
|
|
384
|
+
"auto_deploy": True,
|
|
385
|
+
"requires_approval": False
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
"name": "production",
|
|
389
|
+
"display_name": "Production",
|
|
390
|
+
"order": 3,
|
|
391
|
+
"branch": "main",
|
|
392
|
+
"auto_deploy": False,
|
|
393
|
+
"requires_approval": True
|
|
394
|
+
}
|
|
395
|
+
]
|
|
396
|
+
|
|
397
|
+
environments = []
|
|
398
|
+
for env_data in defaults:
|
|
399
|
+
env = self.create_environment(project_id, env_data)
|
|
400
|
+
environments.append(env)
|
|
401
|
+
|
|
402
|
+
logger.info(f"Created default environments for project {project_id}")
|
|
403
|
+
return environments
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
# Singleton instance
|
|
407
|
+
_env_service = None
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
def get_environment_service(db: Session = None) -> EnvironmentService:
|
|
411
|
+
"""Get environment service instance"""
|
|
412
|
+
if db:
|
|
413
|
+
return EnvironmentService(db)
|
|
414
|
+
|
|
415
|
+
global _env_service
|
|
416
|
+
if _env_service is None:
|
|
417
|
+
_env_service = EnvironmentService()
|
|
418
|
+
return _env_service
|