it0/packages/services/ops-service/src/application/use-cases/execute-runbook.use-case.ts

102 lines
3.5 KiB
TypeScript

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<string>(
'AGENT_SERVICE_URL',
'http://localhost:3001',
);
}
async execute(
runbookId: string,
targetServerIds: string[],
triggerContext?: Record<string, unknown>,
): Promise<OpsTask> {
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'] : [],
};
}
}