102 lines
3.5 KiB
TypeScript
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'] : [],
|
|
};
|
|
}
|
|
}
|