367 lines
14 KiB
Python
367 lines
14 KiB
Python
"""
|
|
Automation Commands Service for ChatOps Integration
|
|
Handles execution of automation commands via chat interface
|
|
"""
|
|
from django.utils import timezone
|
|
from django.contrib.auth import get_user_model
|
|
from typing import Dict, Any, Optional, List
|
|
|
|
from ..models import ChatCommand, WarRoomMessage
|
|
from automation_orchestration.models import Runbook, RunbookExecution, AutoRemediation, AutoRemediationExecution
|
|
from incident_intelligence.models import Incident
|
|
|
|
User = get_user_model()
|
|
|
|
|
|
class AutomationCommandService:
|
|
"""Service for handling automation commands in chat"""
|
|
|
|
@staticmethod
|
|
def execute_runbook_command(chat_command: ChatCommand, runbook_name: str, user: User) -> Dict[str, Any]:
|
|
"""Execute a runbook via chat command"""
|
|
try:
|
|
incident = chat_command.message.war_room.incident
|
|
|
|
# Find the runbook
|
|
runbook = Runbook.objects.filter(
|
|
name__icontains=runbook_name,
|
|
is_active=True
|
|
).first()
|
|
|
|
if not runbook:
|
|
return {
|
|
'error': f'Runbook "{runbook_name}" not found or inactive',
|
|
'suggestions': AutomationCommandService._get_runbook_suggestions(runbook_name)
|
|
}
|
|
|
|
# Check if runbook applies to this incident
|
|
if not AutomationCommandService._runbook_applies_to_incident(runbook, incident):
|
|
return {
|
|
'error': f'Runbook "{runbook.name}" does not apply to this incident type',
|
|
'incident_category': incident.category,
|
|
'incident_severity': incident.severity
|
|
}
|
|
|
|
# Execute the runbook
|
|
execution = RunbookExecution.objects.create(
|
|
runbook=runbook,
|
|
incident=incident,
|
|
triggered_by=user,
|
|
trigger_type='CHAT_COMMAND',
|
|
trigger_data={
|
|
'chat_command_id': str(chat_command.id),
|
|
'command_text': chat_command.command_text
|
|
}
|
|
)
|
|
|
|
# Update chat command with execution reference
|
|
chat_command.automation_execution = execution
|
|
chat_command.save()
|
|
|
|
# Create status message in chat
|
|
AutomationCommandService._create_execution_status_message(
|
|
chat_command.message.war_room,
|
|
f"🚀 **Runbook Execution Started**\n\n"
|
|
f"**Runbook:** {runbook.name}\n"
|
|
f"**Execution ID:** {execution.id}\n"
|
|
f"**Triggered by:** {user.username}\n"
|
|
f"**Status:** {execution.status}\n\n"
|
|
f"Monitor progress in the automation dashboard."
|
|
)
|
|
|
|
return {
|
|
'success': True,
|
|
'execution_id': str(execution.id),
|
|
'runbook_name': runbook.name,
|
|
'status': execution.status,
|
|
'message': f'Runbook "{runbook.name}" execution started successfully'
|
|
}
|
|
|
|
except Exception as e:
|
|
return {
|
|
'error': f'Failed to execute runbook: {str(e)}'
|
|
}
|
|
|
|
@staticmethod
|
|
def execute_auto_remediation_command(chat_command: ChatCommand, remediation_name: str, user: User) -> Dict[str, Any]:
|
|
"""Execute auto-remediation via chat command"""
|
|
try:
|
|
incident = chat_command.message.war_room.incident
|
|
|
|
# Find the auto-remediation
|
|
remediation = AutoRemediation.objects.filter(
|
|
name__icontains=remediation_name,
|
|
is_active=True
|
|
).first()
|
|
|
|
if not remediation:
|
|
return {
|
|
'error': f'Auto-remediation "{remediation_name}" not found or inactive',
|
|
'suggestions': AutomationCommandService._get_remediation_suggestions(remediation_name)
|
|
}
|
|
|
|
# Check if remediation applies to this incident
|
|
if not AutomationCommandService._remediation_applies_to_incident(remediation, incident):
|
|
return {
|
|
'error': f'Auto-remediation "{remediation.name}" does not apply to this incident type',
|
|
'incident_category': incident.category,
|
|
'incident_severity': incident.severity
|
|
}
|
|
|
|
# Execute the auto-remediation
|
|
execution = AutoRemediationExecution.objects.create(
|
|
auto_remediation=remediation,
|
|
incident=incident,
|
|
triggered_by=user,
|
|
trigger_type='CHAT_COMMAND',
|
|
trigger_data={
|
|
'chat_command_id': str(chat_command.id),
|
|
'command_text': chat_command.command_text
|
|
}
|
|
)
|
|
|
|
# Create status message in chat
|
|
AutomationCommandService._create_execution_status_message(
|
|
chat_command.message.war_room,
|
|
f"🔧 **Auto-Remediation Started**\n\n"
|
|
f"**Remediation:** {remediation.name}\n"
|
|
f"**Execution ID:** {execution.id}\n"
|
|
f"**Triggered by:** {user.username}\n"
|
|
f"**Status:** {execution.status}\n\n"
|
|
f"Monitor progress in the automation dashboard."
|
|
)
|
|
|
|
return {
|
|
'success': True,
|
|
'execution_id': str(execution.id),
|
|
'remediation_name': remediation.name,
|
|
'status': execution.status,
|
|
'message': f'Auto-remediation "{remediation.name}" execution started successfully'
|
|
}
|
|
|
|
except Exception as e:
|
|
return {
|
|
'error': f'Failed to execute auto-remediation: {str(e)}'
|
|
}
|
|
|
|
@staticmethod
|
|
def get_incident_status(chat_command: ChatCommand) -> Dict[str, Any]:
|
|
"""Get comprehensive incident status"""
|
|
try:
|
|
incident = chat_command.message.war_room.incident
|
|
|
|
# Get SLA status
|
|
from .sla_notifications import SLANotificationService
|
|
sla_status = SLANotificationService.get_sla_status_for_incident(incident)
|
|
|
|
# Get recent runbook executions
|
|
recent_executions = RunbookExecution.objects.filter(
|
|
incident=incident
|
|
).order_by('-created_at')[:5]
|
|
|
|
executions_data = []
|
|
for execution in recent_executions:
|
|
executions_data.append({
|
|
'id': str(execution.id),
|
|
'runbook_name': execution.runbook.name,
|
|
'status': execution.status,
|
|
'started_at': execution.started_at.isoformat() if execution.started_at else None,
|
|
'completed_at': execution.completed_at.isoformat() if execution.completed_at else None
|
|
})
|
|
|
|
return {
|
|
'incident_id': str(incident.id),
|
|
'title': incident.title,
|
|
'status': incident.status,
|
|
'severity': incident.severity,
|
|
'priority': incident.priority,
|
|
'category': incident.category,
|
|
'assigned_to': incident.assigned_to.username if incident.assigned_to else None,
|
|
'reporter': incident.reporter.username if incident.reporter else None,
|
|
'created_at': incident.created_at.isoformat(),
|
|
'updated_at': incident.updated_at.isoformat(),
|
|
'resolution_time': str(incident.resolution_time) if incident.resolution_time else None,
|
|
'sla_status': sla_status,
|
|
'recent_executions': executions_data,
|
|
'automation_enabled': incident.automation_enabled,
|
|
'runbook_suggested': incident.runbook_suggested,
|
|
'auto_remediation_attempted': incident.auto_remediation_attempted
|
|
}
|
|
|
|
except Exception as e:
|
|
return {
|
|
'error': f'Failed to get incident status: {str(e)}'
|
|
}
|
|
|
|
@staticmethod
|
|
def list_available_runbooks(incident: Incident) -> List[Dict[str, Any]]:
|
|
"""List runbooks available for the incident"""
|
|
try:
|
|
runbooks = Runbook.objects.filter(is_active=True)
|
|
available_runbooks = []
|
|
|
|
for runbook in runbooks:
|
|
if AutomationCommandService._runbook_applies_to_incident(runbook, incident):
|
|
available_runbooks.append({
|
|
'id': str(runbook.id),
|
|
'name': runbook.name,
|
|
'description': runbook.description,
|
|
'category': runbook.category,
|
|
'severity_levels': runbook.severity_levels,
|
|
'estimated_duration': runbook.estimated_duration
|
|
})
|
|
|
|
return available_runbooks
|
|
|
|
except Exception as e:
|
|
return []
|
|
|
|
@staticmethod
|
|
def list_available_remediations(incident: Incident) -> List[Dict[str, Any]]:
|
|
"""List auto-remediations available for the incident"""
|
|
try:
|
|
remediations = AutoRemediation.objects.filter(is_active=True)
|
|
available_remediations = []
|
|
|
|
for remediation in remediations:
|
|
if AutomationCommandService._remediation_applies_to_incident(remediation, incident):
|
|
available_remediations.append({
|
|
'id': str(remediation.id),
|
|
'name': remediation.name,
|
|
'description': remediation.description,
|
|
'category': remediation.category,
|
|
'severity_levels': remediation.severity_levels,
|
|
'estimated_duration': remediation.estimated_duration
|
|
})
|
|
|
|
return available_remediations
|
|
|
|
except Exception as e:
|
|
return []
|
|
|
|
@staticmethod
|
|
def _runbook_applies_to_incident(runbook: Runbook, incident: Incident) -> bool:
|
|
"""Check if runbook applies to the incident"""
|
|
# Check categories
|
|
if runbook.categories and incident.category not in runbook.categories:
|
|
return False
|
|
|
|
# Check severity levels
|
|
if runbook.severity_levels and incident.severity not in runbook.severity_levels:
|
|
return False
|
|
|
|
# Check if runbook is active
|
|
if not runbook.is_active:
|
|
return False
|
|
|
|
return True
|
|
|
|
@staticmethod
|
|
def _remediation_applies_to_incident(remediation: AutoRemediation, incident: Incident) -> bool:
|
|
"""Check if auto-remediation applies to the incident"""
|
|
# Check categories
|
|
if remediation.categories and incident.category not in remediation.categories:
|
|
return False
|
|
|
|
# Check severity levels
|
|
if remediation.severity_levels and incident.severity not in remediation.severity_levels:
|
|
return False
|
|
|
|
# Check if remediation is active
|
|
if not remediation.is_active:
|
|
return False
|
|
|
|
return True
|
|
|
|
@staticmethod
|
|
def _get_runbook_suggestions(partial_name: str) -> List[str]:
|
|
"""Get runbook name suggestions based on partial input"""
|
|
try:
|
|
runbooks = Runbook.objects.filter(
|
|
name__icontains=partial_name,
|
|
is_active=True
|
|
).values_list('name', flat=True)[:5]
|
|
|
|
return list(runbooks)
|
|
except:
|
|
return []
|
|
|
|
@staticmethod
|
|
def _get_remediation_suggestions(partial_name: str) -> List[str]:
|
|
"""Get auto-remediation name suggestions based on partial input"""
|
|
try:
|
|
remediations = AutoRemediation.objects.filter(
|
|
name__icontains=partial_name,
|
|
is_active=True
|
|
).values_list('name', flat=True)[:5]
|
|
|
|
return list(remediations)
|
|
except:
|
|
return []
|
|
|
|
@staticmethod
|
|
def _create_execution_status_message(war_room: 'WarRoom', content: str):
|
|
"""Create a status message in the war room"""
|
|
try:
|
|
WarRoomMessage.objects.create(
|
|
war_room=war_room,
|
|
content=content,
|
|
message_type='SYSTEM',
|
|
sender=None,
|
|
sender_name='Automation System',
|
|
external_data={
|
|
'message_type': 'automation_status'
|
|
}
|
|
)
|
|
except Exception as e:
|
|
print(f"Error creating status message: {e}")
|
|
|
|
@staticmethod
|
|
def update_execution_status(execution_id: str, status: str, result: Dict[str, Any] = None):
|
|
"""Update execution status and notify chat room"""
|
|
try:
|
|
# Find the chat command that triggered this execution
|
|
chat_command = ChatCommand.objects.filter(
|
|
automation_execution_id=execution_id
|
|
).first()
|
|
|
|
if not chat_command:
|
|
return False
|
|
|
|
# Update chat command status
|
|
chat_command.execution_status = status
|
|
if result:
|
|
chat_command.execution_result = result
|
|
chat_command.save()
|
|
|
|
# Create status update message
|
|
status_emoji = {
|
|
'SUCCESS': '✅',
|
|
'FAILED': '❌',
|
|
'RUNNING': '🔄',
|
|
'CANCELLED': '⏹️'
|
|
}.get(status, '📊')
|
|
|
|
message_content = (
|
|
f"{status_emoji} **Execution Status Update**\n\n"
|
|
f"**Status:** {status}\n"
|
|
f"**Execution ID:** {execution_id}\n"
|
|
)
|
|
|
|
if result:
|
|
if 'error' in result:
|
|
message_content += f"**Error:** {result['error']}\n"
|
|
if 'output' in result:
|
|
message_content += f"**Output:** {result['output'][:200]}...\n"
|
|
|
|
AutomationCommandService._create_execution_status_message(
|
|
chat_command.message.war_room,
|
|
message_content
|
|
)
|
|
|
|
return True
|
|
|
|
except Exception as e:
|
|
print(f"Error updating execution status: {e}")
|
|
return False
|