import { Injectable, Logger, NotFoundException } from '@nestjs/common'; import { HttpService } from '@nestjs/axios'; import { ConfigService } from '@nestjs/config'; import { firstValueFrom } from 'rxjs'; import { TaskRepository } from '../../infrastructure/repositories/task.repository'; import { RunbookRepository } from '../../infrastructure/repositories/runbook.repository'; import { OpsTask } from '../../domain/entities/ops-task.entity'; import * as crypto from 'crypto'; @Injectable() export class ExecuteRunbookUseCase { private readonly logger = new Logger(ExecuteRunbookUseCase.name); private readonly agentServiceUrl: string; constructor( private readonly taskRepository: TaskRepository, private readonly runbookRepository: RunbookRepository, private readonly httpService: HttpService, private readonly configService: ConfigService, ) { this.agentServiceUrl = this.configService.get( 'AGENT_SERVICE_URL', 'http://localhost:3001', ); } async execute( runbookId: string, targetServerIds: string[], triggerContext?: Record, ): Promise { const runbook = await this.runbookRepository.findById(runbookId); if (!runbook) { throw new NotFoundException(`Runbook ${runbookId} not found`); } if (!runbook.isActive) { throw new Error(`Runbook ${runbookId} is not active`); } this.logger.log(`Executing runbook "${runbook.name}" on ${targetServerIds.length} servers`); const task = new OpsTask(); task.id = crypto.randomUUID(); task.tenantId = runbook.tenantId; task.type = runbook.triggerType === 'alert' ? 'recovery' : 'inspection'; task.title = `Runbook: ${runbook.name}`; task.description = runbook.description; task.status = 'running'; task.priority = runbook.maxRiskLevel; task.targetServers = targetServerIds; task.runbookId = runbookId; task.createdBy = 'system:runbook'; task.createdAt = new Date(); await this.taskRepository.save(task); try { const response = await firstValueFrom( this.httpService.post(`${this.agentServiceUrl}/api/v1/agent/tasks`, { tenantId: runbook.tenantId, instructions: runbook.promptTemplate, skills: runbook.allowedTools, runbookId, maxRiskLevel: runbook.maxRiskLevel, targets: { serverIds: targetServerIds }, triggerContext, }), ); task.agentSessionId = response.data.sessionId; task.status = response.data.status === 'success' ? 'completed' : 'failed'; task.resultSummary = response.data.summary; task.completedAt = new Date(); } catch (error: any) { task.status = 'failed'; task.resultSummary = `Execution error: ${error.message}`; task.completedAt = new Date(); this.logger.error(`Runbook execution failed: ${error.message}`); } return this.taskRepository.save(task); } async dryRun( runbookId: string, targetServerIds: string[], ): Promise<{ steps: string[]; estimatedDuration: number; riskLevel: number; warnings: string[] }> { const runbook = await this.runbookRepository.findById(runbookId); if (!runbook) { throw new NotFoundException(`Runbook ${runbookId} not found`); } return { steps: [`Execute prompt template on ${targetServerIds.length} servers`], estimatedDuration: targetServerIds.length * 60, riskLevel: runbook.maxRiskLevel, warnings: runbook.autoApprove ? ['Auto-approve is enabled for this runbook'] : [], }; } }