claude-mpm 4.3.22__py3-none-any.whl → 4.4.3__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.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/WORKFLOW.md +2 -14
- claude_mpm/cli/commands/configure.py +2 -29
- claude_mpm/cli/commands/doctor.py +2 -2
- claude_mpm/cli/commands/mpm_init.py +3 -3
- claude_mpm/cli/parsers/configure_parser.py +4 -15
- claude_mpm/core/framework/__init__.py +38 -0
- claude_mpm/core/framework/formatters/__init__.py +11 -0
- claude_mpm/core/framework/formatters/capability_generator.py +356 -0
- claude_mpm/core/framework/formatters/content_formatter.py +283 -0
- claude_mpm/core/framework/formatters/context_generator.py +180 -0
- claude_mpm/core/framework/loaders/__init__.py +13 -0
- claude_mpm/core/framework/loaders/agent_loader.py +202 -0
- claude_mpm/core/framework/loaders/file_loader.py +213 -0
- claude_mpm/core/framework/loaders/instruction_loader.py +151 -0
- claude_mpm/core/framework/loaders/packaged_loader.py +208 -0
- claude_mpm/core/framework/processors/__init__.py +11 -0
- claude_mpm/core/framework/processors/memory_processor.py +222 -0
- claude_mpm/core/framework/processors/metadata_processor.py +146 -0
- claude_mpm/core/framework/processors/template_processor.py +238 -0
- claude_mpm/core/framework_loader.py +277 -1798
- claude_mpm/hooks/__init__.py +9 -1
- claude_mpm/hooks/kuzu_memory_hook.py +352 -0
- claude_mpm/hooks/memory_integration_hook.py +1 -1
- claude_mpm/services/agents/memory/content_manager.py +5 -2
- claude_mpm/services/agents/memory/memory_file_service.py +1 -0
- claude_mpm/services/agents/memory/memory_limits_service.py +1 -0
- claude_mpm/services/core/path_resolver.py +1 -0
- claude_mpm/services/diagnostics/diagnostic_runner.py +1 -0
- claude_mpm/services/mcp_config_manager.py +67 -4
- claude_mpm/services/mcp_gateway/core/process_pool.py +281 -0
- claude_mpm/services/mcp_gateway/core/startup_verification.py +2 -2
- claude_mpm/services/mcp_gateway/main.py +3 -13
- claude_mpm/services/mcp_gateway/server/stdio_server.py +4 -10
- claude_mpm/services/mcp_gateway/tools/__init__.py +13 -2
- claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +36 -6
- claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +542 -0
- claude_mpm/services/shared/__init__.py +2 -1
- claude_mpm/services/shared/service_factory.py +8 -5
- claude_mpm/services/unified/__init__.py +65 -0
- claude_mpm/services/unified/analyzer_strategies/__init__.py +44 -0
- claude_mpm/services/unified/analyzer_strategies/code_analyzer.py +473 -0
- claude_mpm/services/unified/analyzer_strategies/dependency_analyzer.py +643 -0
- claude_mpm/services/unified/analyzer_strategies/performance_analyzer.py +804 -0
- claude_mpm/services/unified/analyzer_strategies/security_analyzer.py +661 -0
- claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +696 -0
- claude_mpm/services/unified/config_strategies/__init__.py +190 -0
- claude_mpm/services/unified/config_strategies/config_schema.py +689 -0
- claude_mpm/services/unified/config_strategies/context_strategy.py +748 -0
- claude_mpm/services/unified/config_strategies/error_handling_strategy.py +999 -0
- claude_mpm/services/unified/config_strategies/file_loader_strategy.py +871 -0
- claude_mpm/services/unified/config_strategies/unified_config_service.py +802 -0
- claude_mpm/services/unified/config_strategies/validation_strategy.py +1105 -0
- claude_mpm/services/unified/deployment_strategies/__init__.py +97 -0
- claude_mpm/services/unified/deployment_strategies/base.py +557 -0
- claude_mpm/services/unified/deployment_strategies/cloud_strategies.py +486 -0
- claude_mpm/services/unified/deployment_strategies/local.py +594 -0
- claude_mpm/services/unified/deployment_strategies/utils.py +672 -0
- claude_mpm/services/unified/deployment_strategies/vercel.py +471 -0
- claude_mpm/services/unified/interfaces.py +499 -0
- claude_mpm/services/unified/migration.py +532 -0
- claude_mpm/services/unified/strategies.py +551 -0
- claude_mpm/services/unified/unified_analyzer.py +534 -0
- claude_mpm/services/unified/unified_config.py +688 -0
- claude_mpm/services/unified/unified_deployment.py +470 -0
- {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.3.dist-info}/METADATA +15 -15
- {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.3.dist-info}/RECORD +71 -32
- claude_mpm/cli/commands/configure_tui.py +0 -1927
- claude_mpm/services/mcp_gateway/tools/ticket_tools.py +0 -645
- claude_mpm/services/mcp_gateway/tools/unified_ticket_tool.py +0 -602
- {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.3.dist-info}/WHEEL +0 -0
- {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.3.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.3.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.3.22.dist-info → claude_mpm-4.4.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,97 @@
|
|
1
|
+
"""
|
2
|
+
Unified Deployment Strategies
|
3
|
+
=============================
|
4
|
+
|
5
|
+
This module consolidates 45+ deployment services into a unified strategy-based architecture.
|
6
|
+
Reduces ~17,938 LOC of duplicated deployment code to ~6,000 LOC through strategic pattern
|
7
|
+
application and code reuse.
|
8
|
+
|
9
|
+
Deployment Strategies:
|
10
|
+
- LocalDeploymentStrategy: Deploy to local filesystem/project
|
11
|
+
- VercelDeploymentStrategy: Deploy to Vercel platform
|
12
|
+
- RailwayDeploymentStrategy: Deploy to Railway platform
|
13
|
+
- AWSDeploymentStrategy: Deploy to AWS (Lambda, EC2, ECS)
|
14
|
+
- DockerDeploymentStrategy: Docker container deployments
|
15
|
+
- GitDeploymentStrategy: Git-based deployments (GitHub, GitLab)
|
16
|
+
|
17
|
+
Consolidates services from:
|
18
|
+
- agent_deployment.py (887 LOC)
|
19
|
+
- multi_source_deployment_service.py (1,055 LOC)
|
20
|
+
- agent_template_builder.py (1,134 LOC)
|
21
|
+
- 8+ pipeline services with duplicated patterns
|
22
|
+
- 30+ other deployment-related services
|
23
|
+
|
24
|
+
Design Principles:
|
25
|
+
1. Strategy pattern for pluggable deployment types
|
26
|
+
2. Common utilities for shared functionality
|
27
|
+
3. Health checks and rollback capabilities
|
28
|
+
4. Validation and pre-flight checks
|
29
|
+
5. Metrics and monitoring integration
|
30
|
+
"""
|
31
|
+
|
32
|
+
from .base import DeploymentStrategy, DeploymentContext, DeploymentResult
|
33
|
+
from .local import LocalDeploymentStrategy
|
34
|
+
from .vercel import VercelDeploymentStrategy
|
35
|
+
from .cloud_strategies import (
|
36
|
+
RailwayDeploymentStrategy,
|
37
|
+
AWSDeploymentStrategy,
|
38
|
+
DockerDeploymentStrategy,
|
39
|
+
GitDeploymentStrategy,
|
40
|
+
)
|
41
|
+
from .utils import (
|
42
|
+
validate_deployment_config,
|
43
|
+
prepare_deployment_artifact,
|
44
|
+
verify_deployment_health,
|
45
|
+
rollback_deployment,
|
46
|
+
)
|
47
|
+
|
48
|
+
__all__ = [
|
49
|
+
# Base classes
|
50
|
+
"DeploymentStrategy",
|
51
|
+
"DeploymentContext",
|
52
|
+
"DeploymentResult",
|
53
|
+
# Strategy implementations
|
54
|
+
"LocalDeploymentStrategy",
|
55
|
+
"VercelDeploymentStrategy",
|
56
|
+
"RailwayDeploymentStrategy",
|
57
|
+
"AWSDeploymentStrategy",
|
58
|
+
"DockerDeploymentStrategy",
|
59
|
+
"GitDeploymentStrategy",
|
60
|
+
# Utilities
|
61
|
+
"validate_deployment_config",
|
62
|
+
"prepare_deployment_artifact",
|
63
|
+
"verify_deployment_health",
|
64
|
+
"rollback_deployment",
|
65
|
+
]
|
66
|
+
|
67
|
+
# Strategy registry for automatic discovery
|
68
|
+
DEPLOYMENT_STRATEGIES = {
|
69
|
+
"local": LocalDeploymentStrategy,
|
70
|
+
"vercel": VercelDeploymentStrategy,
|
71
|
+
"railway": RailwayDeploymentStrategy,
|
72
|
+
"aws": AWSDeploymentStrategy,
|
73
|
+
"docker": DockerDeploymentStrategy,
|
74
|
+
"git": GitDeploymentStrategy,
|
75
|
+
}
|
76
|
+
|
77
|
+
|
78
|
+
def get_deployment_strategy(deployment_type: str) -> type[DeploymentStrategy]:
|
79
|
+
"""
|
80
|
+
Get deployment strategy class by type.
|
81
|
+
|
82
|
+
Args:
|
83
|
+
deployment_type: Type of deployment (local, vercel, aws, etc.)
|
84
|
+
|
85
|
+
Returns:
|
86
|
+
DeploymentStrategy class
|
87
|
+
|
88
|
+
Raises:
|
89
|
+
ValueError: If deployment type not supported
|
90
|
+
"""
|
91
|
+
strategy_class = DEPLOYMENT_STRATEGIES.get(deployment_type.lower())
|
92
|
+
if not strategy_class:
|
93
|
+
raise ValueError(
|
94
|
+
f"Unsupported deployment type: {deployment_type}. "
|
95
|
+
f"Supported types: {', '.join(DEPLOYMENT_STRATEGIES.keys())}"
|
96
|
+
)
|
97
|
+
return strategy_class
|
@@ -0,0 +1,557 @@
|
|
1
|
+
"""
|
2
|
+
Base Deployment Strategy
|
3
|
+
========================
|
4
|
+
|
5
|
+
Enhanced base class for all deployment strategies, extending the generic
|
6
|
+
DeploymentStrategy from the unified strategies module.
|
7
|
+
|
8
|
+
This module provides:
|
9
|
+
- Abstract base class with common deployment operations
|
10
|
+
- Deployment context and result data structures
|
11
|
+
- Health check and rollback interfaces
|
12
|
+
- Validation and pre-flight check framework
|
13
|
+
- Metrics collection hooks
|
14
|
+
"""
|
15
|
+
|
16
|
+
from abc import abstractmethod
|
17
|
+
from dataclasses import dataclass, field
|
18
|
+
from datetime import datetime
|
19
|
+
from enum import Enum
|
20
|
+
from pathlib import Path
|
21
|
+
from typing import Any, Dict, List, Optional, Union
|
22
|
+
|
23
|
+
from claude_mpm.core.logging_utils import get_logger
|
24
|
+
from claude_mpm.services.unified.strategies import (
|
25
|
+
DeploymentStrategy as BaseDeploymentStrategy,
|
26
|
+
StrategyContext,
|
27
|
+
StrategyMetadata,
|
28
|
+
StrategyPriority,
|
29
|
+
)
|
30
|
+
|
31
|
+
|
32
|
+
class DeploymentStatus(Enum):
|
33
|
+
"""Deployment status enumeration."""
|
34
|
+
PENDING = "pending"
|
35
|
+
VALIDATING = "validating"
|
36
|
+
PREPARING = "preparing"
|
37
|
+
DEPLOYING = "deploying"
|
38
|
+
VERIFYING = "verifying"
|
39
|
+
COMPLETED = "completed"
|
40
|
+
FAILED = "failed"
|
41
|
+
ROLLED_BACK = "rolled_back"
|
42
|
+
|
43
|
+
|
44
|
+
class DeploymentType(Enum):
|
45
|
+
"""Types of deployments."""
|
46
|
+
AGENT = "agent"
|
47
|
+
CONFIG = "config"
|
48
|
+
RESOURCE = "resource"
|
49
|
+
TEMPLATE = "template"
|
50
|
+
APPLICATION = "application"
|
51
|
+
SERVICE = "service"
|
52
|
+
INFRASTRUCTURE = "infrastructure"
|
53
|
+
|
54
|
+
|
55
|
+
@dataclass
|
56
|
+
class DeploymentContext:
|
57
|
+
"""
|
58
|
+
Enhanced context for deployment operations.
|
59
|
+
|
60
|
+
Consolidates context patterns from:
|
61
|
+
- agent_deployment.py
|
62
|
+
- multi_source_deployment_service.py
|
63
|
+
- deployment_config_loader.py
|
64
|
+
"""
|
65
|
+
# Core deployment info
|
66
|
+
source: Union[str, Path]
|
67
|
+
target: Union[str, Path]
|
68
|
+
deployment_type: DeploymentType
|
69
|
+
|
70
|
+
# Configuration
|
71
|
+
config: Dict[str, Any] = field(default_factory=dict)
|
72
|
+
environment: Dict[str, str] = field(default_factory=dict)
|
73
|
+
|
74
|
+
# Options
|
75
|
+
force: bool = False
|
76
|
+
dry_run: bool = False
|
77
|
+
validate_only: bool = False
|
78
|
+
backup_enabled: bool = True
|
79
|
+
|
80
|
+
# Versioning
|
81
|
+
version: Optional[str] = None
|
82
|
+
previous_version: Optional[str] = None
|
83
|
+
|
84
|
+
# Metadata
|
85
|
+
tags: List[str] = field(default_factory=list)
|
86
|
+
labels: Dict[str, str] = field(default_factory=dict)
|
87
|
+
annotations: Dict[str, Any] = field(default_factory=dict)
|
88
|
+
|
89
|
+
def to_strategy_context(self) -> StrategyContext:
|
90
|
+
"""Convert to generic strategy context."""
|
91
|
+
return StrategyContext(
|
92
|
+
target_type=self.deployment_type.value,
|
93
|
+
operation="deploy",
|
94
|
+
parameters={
|
95
|
+
"source": str(self.source),
|
96
|
+
"target": str(self.target),
|
97
|
+
"config": self.config,
|
98
|
+
"force": self.force,
|
99
|
+
"dry_run": self.dry_run,
|
100
|
+
},
|
101
|
+
constraints=self.tags,
|
102
|
+
preferences={"environment": self.environment},
|
103
|
+
)
|
104
|
+
|
105
|
+
|
106
|
+
@dataclass
|
107
|
+
class DeploymentResult:
|
108
|
+
"""
|
109
|
+
Result of deployment operation.
|
110
|
+
|
111
|
+
Consolidates result patterns from multiple deployment services.
|
112
|
+
"""
|
113
|
+
# Core result
|
114
|
+
success: bool
|
115
|
+
status: DeploymentStatus
|
116
|
+
message: str = ""
|
117
|
+
|
118
|
+
# Deployment details
|
119
|
+
deployment_id: Optional[str] = None
|
120
|
+
deployed_path: Optional[Path] = None
|
121
|
+
deployment_url: Optional[str] = None
|
122
|
+
|
123
|
+
# Versioning
|
124
|
+
version: Optional[str] = None
|
125
|
+
previous_version: Optional[str] = None
|
126
|
+
|
127
|
+
# Timing
|
128
|
+
started_at: Optional[datetime] = None
|
129
|
+
completed_at: Optional[datetime] = None
|
130
|
+
duration_seconds: Optional[float] = None
|
131
|
+
|
132
|
+
# Artifacts
|
133
|
+
artifacts: List[Path] = field(default_factory=list)
|
134
|
+
logs: List[str] = field(default_factory=list)
|
135
|
+
|
136
|
+
# Rollback info
|
137
|
+
rollback_available: bool = False
|
138
|
+
rollback_info: Dict[str, Any] = field(default_factory=dict)
|
139
|
+
|
140
|
+
# Metrics
|
141
|
+
metrics: Dict[str, Any] = field(default_factory=dict)
|
142
|
+
|
143
|
+
# Errors and warnings
|
144
|
+
errors: List[str] = field(default_factory=list)
|
145
|
+
warnings: List[str] = field(default_factory=list)
|
146
|
+
|
147
|
+
def to_dict(self) -> Dict[str, Any]:
|
148
|
+
"""Convert to dictionary for serialization."""
|
149
|
+
return {
|
150
|
+
"success": self.success,
|
151
|
+
"status": self.status.value,
|
152
|
+
"message": self.message,
|
153
|
+
"deployment_id": self.deployment_id,
|
154
|
+
"deployed_path": str(self.deployed_path) if self.deployed_path else None,
|
155
|
+
"deployment_url": self.deployment_url,
|
156
|
+
"version": self.version,
|
157
|
+
"previous_version": self.previous_version,
|
158
|
+
"started_at": self.started_at.isoformat() if self.started_at else None,
|
159
|
+
"completed_at": self.completed_at.isoformat() if self.completed_at else None,
|
160
|
+
"duration_seconds": self.duration_seconds,
|
161
|
+
"artifacts": [str(a) for a in self.artifacts],
|
162
|
+
"logs": self.logs,
|
163
|
+
"rollback_available": self.rollback_available,
|
164
|
+
"rollback_info": self.rollback_info,
|
165
|
+
"metrics": self.metrics,
|
166
|
+
"errors": self.errors,
|
167
|
+
"warnings": self.warnings,
|
168
|
+
}
|
169
|
+
|
170
|
+
|
171
|
+
class DeploymentStrategy(BaseDeploymentStrategy):
|
172
|
+
"""
|
173
|
+
Enhanced base class for deployment strategies.
|
174
|
+
|
175
|
+
This class consolidates common deployment patterns from 45+ deployment
|
176
|
+
services into a single, reusable base class.
|
177
|
+
|
178
|
+
Subclasses should implement:
|
179
|
+
- validate(): Validate deployment configuration
|
180
|
+
- prepare(): Prepare deployment artifacts
|
181
|
+
- execute(): Execute the actual deployment
|
182
|
+
- verify(): Verify deployment success
|
183
|
+
- rollback(): Rollback on failure
|
184
|
+
- get_health_status(): Check deployment health
|
185
|
+
"""
|
186
|
+
|
187
|
+
def __init__(self, metadata: Optional[StrategyMetadata] = None):
|
188
|
+
"""
|
189
|
+
Initialize deployment strategy.
|
190
|
+
|
191
|
+
Args:
|
192
|
+
metadata: Strategy metadata
|
193
|
+
"""
|
194
|
+
super().__init__(metadata or self._create_metadata())
|
195
|
+
self._logger = get_logger(f"{__name__}.{self.__class__.__name__}")
|
196
|
+
self._current_deployment: Optional[DeploymentContext] = None
|
197
|
+
self._deployment_history: List[DeploymentResult] = []
|
198
|
+
|
199
|
+
def _create_metadata(self) -> StrategyMetadata:
|
200
|
+
"""Create default metadata for strategy."""
|
201
|
+
return StrategyMetadata(
|
202
|
+
name=self.__class__.__name__,
|
203
|
+
description=self.__doc__ or "Deployment strategy",
|
204
|
+
priority=StrategyPriority.NORMAL,
|
205
|
+
supported_types=["*"],
|
206
|
+
supported_operations=["deploy", "rollback", "verify"],
|
207
|
+
)
|
208
|
+
|
209
|
+
# Override base class method
|
210
|
+
def deploy(
|
211
|
+
self,
|
212
|
+
source: Union[str, Path],
|
213
|
+
target: Union[str, Path],
|
214
|
+
config: Optional[Dict[str, Any]] = None,
|
215
|
+
) -> Dict[str, Any]:
|
216
|
+
"""
|
217
|
+
Execute deployment (implements base class abstract method).
|
218
|
+
|
219
|
+
Args:
|
220
|
+
source: Deployment source
|
221
|
+
target: Deployment target
|
222
|
+
config: Deployment configuration
|
223
|
+
|
224
|
+
Returns:
|
225
|
+
Deployment result dictionary
|
226
|
+
"""
|
227
|
+
context = DeploymentContext(
|
228
|
+
source=Path(source),
|
229
|
+
target=Path(target),
|
230
|
+
deployment_type=self._detect_deployment_type(source, config),
|
231
|
+
config=config or {},
|
232
|
+
)
|
233
|
+
result = self.deploy_with_context(context)
|
234
|
+
return result.to_dict()
|
235
|
+
|
236
|
+
def deploy_with_context(self, context: DeploymentContext) -> DeploymentResult:
|
237
|
+
"""
|
238
|
+
Execute deployment with full context.
|
239
|
+
|
240
|
+
This is the main entry point for deployment operations.
|
241
|
+
|
242
|
+
Args:
|
243
|
+
context: Deployment context
|
244
|
+
|
245
|
+
Returns:
|
246
|
+
Deployment result
|
247
|
+
"""
|
248
|
+
self._current_deployment = context
|
249
|
+
result = DeploymentResult(
|
250
|
+
success=False,
|
251
|
+
status=DeploymentStatus.PENDING,
|
252
|
+
started_at=datetime.now(),
|
253
|
+
)
|
254
|
+
|
255
|
+
try:
|
256
|
+
# Validation phase
|
257
|
+
result.status = DeploymentStatus.VALIDATING
|
258
|
+
self._logger.info(f"Validating deployment: {context.source} -> {context.target}")
|
259
|
+
|
260
|
+
validation_errors = self.validate(context)
|
261
|
+
if validation_errors:
|
262
|
+
result.errors = validation_errors
|
263
|
+
result.message = f"Validation failed: {'; '.join(validation_errors)}"
|
264
|
+
result.status = DeploymentStatus.FAILED
|
265
|
+
return result
|
266
|
+
|
267
|
+
if context.validate_only:
|
268
|
+
result.success = True
|
269
|
+
result.status = DeploymentStatus.COMPLETED
|
270
|
+
result.message = "Validation successful"
|
271
|
+
return result
|
272
|
+
|
273
|
+
# Preparation phase
|
274
|
+
result.status = DeploymentStatus.PREPARING
|
275
|
+
self._logger.info("Preparing deployment artifacts")
|
276
|
+
|
277
|
+
artifacts = self.prepare(context)
|
278
|
+
result.artifacts = artifacts
|
279
|
+
|
280
|
+
# Execution phase
|
281
|
+
if not context.dry_run:
|
282
|
+
result.status = DeploymentStatus.DEPLOYING
|
283
|
+
self._logger.info("Executing deployment")
|
284
|
+
|
285
|
+
deployment_info = self.execute(context, artifacts)
|
286
|
+
result.deployment_id = deployment_info.get("deployment_id")
|
287
|
+
result.deployed_path = deployment_info.get("deployed_path")
|
288
|
+
result.deployment_url = deployment_info.get("deployment_url")
|
289
|
+
result.version = context.version
|
290
|
+
result.previous_version = context.previous_version
|
291
|
+
|
292
|
+
# Verification phase
|
293
|
+
result.status = DeploymentStatus.VERIFYING
|
294
|
+
self._logger.info("Verifying deployment")
|
295
|
+
|
296
|
+
if self.verify(context, deployment_info):
|
297
|
+
result.success = True
|
298
|
+
result.status = DeploymentStatus.COMPLETED
|
299
|
+
result.message = "Deployment successful"
|
300
|
+
|
301
|
+
# Prepare rollback info
|
302
|
+
result.rollback_available = True
|
303
|
+
result.rollback_info = self.prepare_rollback(deployment_info)
|
304
|
+
else:
|
305
|
+
raise Exception("Deployment verification failed")
|
306
|
+
else:
|
307
|
+
result.success = True
|
308
|
+
result.status = DeploymentStatus.COMPLETED
|
309
|
+
result.message = "Dry run completed successfully"
|
310
|
+
|
311
|
+
except Exception as e:
|
312
|
+
self._logger.error(f"Deployment failed: {str(e)}")
|
313
|
+
result.status = DeploymentStatus.FAILED
|
314
|
+
result.message = str(e)
|
315
|
+
result.errors.append(str(e))
|
316
|
+
|
317
|
+
# Attempt rollback if not dry run
|
318
|
+
if not context.dry_run and context.backup_enabled:
|
319
|
+
self._logger.info("Attempting rollback")
|
320
|
+
try:
|
321
|
+
self.rollback(context, result)
|
322
|
+
result.status = DeploymentStatus.ROLLED_BACK
|
323
|
+
result.message += " (rolled back)"
|
324
|
+
except Exception as rollback_error:
|
325
|
+
self._logger.error(f"Rollback failed: {str(rollback_error)}")
|
326
|
+
result.errors.append(f"Rollback failed: {str(rollback_error)}")
|
327
|
+
|
328
|
+
finally:
|
329
|
+
result.completed_at = datetime.now()
|
330
|
+
if result.started_at:
|
331
|
+
result.duration_seconds = (
|
332
|
+
result.completed_at - result.started_at
|
333
|
+
).total_seconds()
|
334
|
+
|
335
|
+
# Store in history
|
336
|
+
self._deployment_history.append(result)
|
337
|
+
|
338
|
+
# Collect metrics
|
339
|
+
result.metrics = self._collect_metrics(context, result)
|
340
|
+
|
341
|
+
return result
|
342
|
+
|
343
|
+
# Abstract methods to be implemented by subclasses
|
344
|
+
|
345
|
+
@abstractmethod
|
346
|
+
def validate(self, context: DeploymentContext) -> List[str]:
|
347
|
+
"""
|
348
|
+
Validate deployment configuration.
|
349
|
+
|
350
|
+
Args:
|
351
|
+
context: Deployment context
|
352
|
+
|
353
|
+
Returns:
|
354
|
+
List of validation errors (empty if valid)
|
355
|
+
"""
|
356
|
+
pass
|
357
|
+
|
358
|
+
@abstractmethod
|
359
|
+
def prepare(self, context: DeploymentContext) -> List[Path]:
|
360
|
+
"""
|
361
|
+
Prepare deployment artifacts.
|
362
|
+
|
363
|
+
Args:
|
364
|
+
context: Deployment context
|
365
|
+
|
366
|
+
Returns:
|
367
|
+
List of prepared artifact paths
|
368
|
+
"""
|
369
|
+
pass
|
370
|
+
|
371
|
+
@abstractmethod
|
372
|
+
def execute(
|
373
|
+
self, context: DeploymentContext, artifacts: List[Path]
|
374
|
+
) -> Dict[str, Any]:
|
375
|
+
"""
|
376
|
+
Execute the deployment.
|
377
|
+
|
378
|
+
Args:
|
379
|
+
context: Deployment context
|
380
|
+
artifacts: Prepared artifacts
|
381
|
+
|
382
|
+
Returns:
|
383
|
+
Deployment information including deployment_id, deployed_path, etc.
|
384
|
+
"""
|
385
|
+
pass
|
386
|
+
|
387
|
+
@abstractmethod
|
388
|
+
def verify(
|
389
|
+
self, context: DeploymentContext, deployment_info: Dict[str, Any]
|
390
|
+
) -> bool:
|
391
|
+
"""
|
392
|
+
Verify deployment success.
|
393
|
+
|
394
|
+
Args:
|
395
|
+
context: Deployment context
|
396
|
+
deployment_info: Information from execute phase
|
397
|
+
|
398
|
+
Returns:
|
399
|
+
True if deployment verified successfully
|
400
|
+
"""
|
401
|
+
pass
|
402
|
+
|
403
|
+
@abstractmethod
|
404
|
+
def rollback(
|
405
|
+
self, context: DeploymentContext, result: DeploymentResult
|
406
|
+
) -> bool:
|
407
|
+
"""
|
408
|
+
Rollback failed deployment.
|
409
|
+
|
410
|
+
Args:
|
411
|
+
context: Deployment context
|
412
|
+
result: Current deployment result
|
413
|
+
|
414
|
+
Returns:
|
415
|
+
True if rollback successful
|
416
|
+
"""
|
417
|
+
pass
|
418
|
+
|
419
|
+
@abstractmethod
|
420
|
+
def get_health_status(
|
421
|
+
self, deployment_info: Dict[str, Any]
|
422
|
+
) -> Dict[str, Any]:
|
423
|
+
"""
|
424
|
+
Get health status of deployment.
|
425
|
+
|
426
|
+
Args:
|
427
|
+
deployment_info: Deployment information
|
428
|
+
|
429
|
+
Returns:
|
430
|
+
Health status information
|
431
|
+
"""
|
432
|
+
pass
|
433
|
+
|
434
|
+
# Helper methods
|
435
|
+
|
436
|
+
def _detect_deployment_type(
|
437
|
+
self, source: Union[str, Path], config: Optional[Dict[str, Any]]
|
438
|
+
) -> DeploymentType:
|
439
|
+
"""
|
440
|
+
Detect deployment type from source and config.
|
441
|
+
|
442
|
+
Args:
|
443
|
+
source: Deployment source
|
444
|
+
config: Deployment configuration
|
445
|
+
|
446
|
+
Returns:
|
447
|
+
Detected deployment type
|
448
|
+
"""
|
449
|
+
if config and "type" in config:
|
450
|
+
type_str = config["type"].lower()
|
451
|
+
try:
|
452
|
+
return DeploymentType[type_str.upper()]
|
453
|
+
except KeyError:
|
454
|
+
pass
|
455
|
+
|
456
|
+
# Detect from source path patterns
|
457
|
+
source_path = Path(source)
|
458
|
+
|
459
|
+
if "agent" in source_path.name.lower():
|
460
|
+
return DeploymentType.AGENT
|
461
|
+
elif "config" in source_path.name.lower():
|
462
|
+
return DeploymentType.CONFIG
|
463
|
+
elif "template" in source_path.name.lower():
|
464
|
+
return DeploymentType.TEMPLATE
|
465
|
+
elif source_path.suffix in [".yaml", ".yml", ".json"]:
|
466
|
+
return DeploymentType.CONFIG
|
467
|
+
else:
|
468
|
+
return DeploymentType.RESOURCE
|
469
|
+
|
470
|
+
def _collect_metrics(
|
471
|
+
self, context: DeploymentContext, result: DeploymentResult
|
472
|
+
) -> Dict[str, Any]:
|
473
|
+
"""
|
474
|
+
Collect deployment metrics.
|
475
|
+
|
476
|
+
Args:
|
477
|
+
context: Deployment context
|
478
|
+
result: Deployment result
|
479
|
+
|
480
|
+
Returns:
|
481
|
+
Collected metrics
|
482
|
+
"""
|
483
|
+
return {
|
484
|
+
"deployment_type": context.deployment_type.value,
|
485
|
+
"source_size": self._get_size(context.source) if context.source else 0,
|
486
|
+
"artifact_count": len(result.artifacts),
|
487
|
+
"artifact_total_size": sum(
|
488
|
+
self._get_size(a) for a in result.artifacts
|
489
|
+
),
|
490
|
+
"duration_seconds": result.duration_seconds,
|
491
|
+
"error_count": len(result.errors),
|
492
|
+
"warning_count": len(result.warnings),
|
493
|
+
}
|
494
|
+
|
495
|
+
def _get_size(self, path: Union[str, Path]) -> int:
|
496
|
+
"""Get size of file or directory in bytes."""
|
497
|
+
path = Path(path)
|
498
|
+
if path.is_file():
|
499
|
+
return path.stat().st_size
|
500
|
+
elif path.is_dir():
|
501
|
+
return sum(f.stat().st_size for f in path.rglob("*") if f.is_file())
|
502
|
+
return 0
|
503
|
+
|
504
|
+
# Implement base class abstract methods
|
505
|
+
|
506
|
+
def can_handle(self, context: StrategyContext) -> bool:
|
507
|
+
"""Check if strategy can handle the given context."""
|
508
|
+
# Check if operation is supported
|
509
|
+
if context.operation not in self.metadata.supported_operations:
|
510
|
+
return False
|
511
|
+
|
512
|
+
# Check if target type is supported
|
513
|
+
if self.metadata.supported_types != ["*"]:
|
514
|
+
if context.target_type not in self.metadata.supported_types:
|
515
|
+
return False
|
516
|
+
|
517
|
+
return True
|
518
|
+
|
519
|
+
def validate_input(self, input_data: Any) -> List[str]:
|
520
|
+
"""Validate input data for strategy."""
|
521
|
+
errors = []
|
522
|
+
|
523
|
+
if not isinstance(input_data, dict):
|
524
|
+
errors.append("Input must be a dictionary")
|
525
|
+
return errors
|
526
|
+
|
527
|
+
if "source" not in input_data:
|
528
|
+
errors.append("Source is required")
|
529
|
+
|
530
|
+
if "target" not in input_data:
|
531
|
+
errors.append("Target is required")
|
532
|
+
|
533
|
+
return errors
|
534
|
+
|
535
|
+
def prepare_rollback(self, deployment_info: Dict[str, Any]) -> Dict[str, Any]:
|
536
|
+
"""Prepare rollback information."""
|
537
|
+
return {
|
538
|
+
"deployment_id": deployment_info.get("deployment_id"),
|
539
|
+
"deployed_path": str(deployment_info.get("deployed_path")),
|
540
|
+
"timestamp": datetime.now().isoformat(),
|
541
|
+
"artifacts": deployment_info.get("artifacts", []),
|
542
|
+
}
|
543
|
+
|
544
|
+
def cleanup(self, target: Union[str, Path]) -> bool:
|
545
|
+
"""Clean up deployment artifacts."""
|
546
|
+
try:
|
547
|
+
target_path = Path(target)
|
548
|
+
if target_path.exists():
|
549
|
+
if target_path.is_file():
|
550
|
+
target_path.unlink()
|
551
|
+
elif target_path.is_dir():
|
552
|
+
import shutil
|
553
|
+
shutil.rmtree(target_path)
|
554
|
+
return True
|
555
|
+
except Exception as e:
|
556
|
+
self._logger.error(f"Cleanup failed: {str(e)}")
|
557
|
+
return False
|