feat: 开放 Jenkins 构建日志接口
This commit is contained in:
@@ -68,3 +68,14 @@ export type DeployRunJenkinsSyncSummary = {
|
||||
syncedRuns: DeployRunSummary[];
|
||||
failedRuns: DeployRunJenkinsSyncError[];
|
||||
};
|
||||
|
||||
export type DeployRunJenkinsLog = {
|
||||
runId: string;
|
||||
jobPath: string;
|
||||
buildNumber: number;
|
||||
start: number;
|
||||
nextStart: number;
|
||||
hasMore: boolean;
|
||||
text: string;
|
||||
fetchedAt: string;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
import { Body, Controller, Get, Param, Post, UseGuards } from '@nestjs/common';
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Get,
|
||||
Param,
|
||||
Post,
|
||||
Query,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import {
|
||||
ApiBody,
|
||||
ApiCreatedResponse,
|
||||
@@ -18,6 +26,7 @@ import { CurrentUser } from '../auth/current-user.decorator';
|
||||
import { PasswordChangeGuard } from '../auth/password-change.guard';
|
||||
import { SuperAdminGuard } from '../auth/super-admin.guard';
|
||||
import {
|
||||
DeployRunJenkinsLog,
|
||||
DeployRunJenkinsSyncSummary,
|
||||
DeployRunSummary,
|
||||
} from './deploy-run.types';
|
||||
@@ -46,6 +55,16 @@ export class DeployRunsController {
|
||||
return this.deployRunsService.getRun(id, user);
|
||||
}
|
||||
|
||||
@Get(':id/jenkins-log')
|
||||
@ApiOkResponse({ description: '读取发布单对应 Jenkins build 日志。' })
|
||||
async getJenkinsLog(
|
||||
@Param('id') id: string,
|
||||
@Query('start') start: string | undefined,
|
||||
@CurrentUser() user: AuthenticatedUser,
|
||||
): Promise<DeployRunJenkinsLog> {
|
||||
return this.deployRunsService.getJenkinsLog(id, start, user);
|
||||
}
|
||||
|
||||
@Post()
|
||||
@ApiBody({ type: CreateDeployRunDto })
|
||||
@ApiCreatedResponse({ description: '创建发布单并触发构建。' })
|
||||
|
||||
@@ -19,6 +19,7 @@ import { DeployExecutionService } from './deploy-execution.service';
|
||||
import { DeployRunRepository } from './deploy-run.repository';
|
||||
import {
|
||||
CreateDeployRunInput,
|
||||
DeployRunJenkinsLog,
|
||||
DeployRunJenkinsSyncSummary,
|
||||
DeployRunSummary,
|
||||
} from './deploy-run.types';
|
||||
@@ -237,6 +238,63 @@ export class DeployRunsService {
|
||||
return this.deployExecutionService.syncJenkinsRun(run, project);
|
||||
}
|
||||
|
||||
async getJenkinsLog(
|
||||
id: string,
|
||||
start: string | undefined,
|
||||
user?: AuthenticatedUser,
|
||||
): Promise<DeployRunJenkinsLog> {
|
||||
const run = await this.getRun(id, user);
|
||||
|
||||
if (run.jenkinsBuildNumber === undefined || run.jenkinsBuildNumber === null) {
|
||||
throw new AppError(
|
||||
'VALIDATION_FAILED',
|
||||
`发布记录 ${id} 尚未关联 Jenkins build,无法读取日志`,
|
||||
400,
|
||||
{
|
||||
id,
|
||||
jenkinsQueueId: run.jenkinsQueueId,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
const project = await this.projectsService.getProject(run.projectKey);
|
||||
const environment = this.findEnvironment(project, run);
|
||||
const normalizedStart = this.normalizeLogStart(start);
|
||||
const log = await this.jenkinsClient.getProgressiveText(
|
||||
environment.jenkinsJobPath,
|
||||
run.jenkinsBuildNumber,
|
||||
normalizedStart,
|
||||
);
|
||||
const text = redactSensitive({ text: log.text }).text;
|
||||
|
||||
await this.auditService.record({
|
||||
action: 'JENKINS_LOG_VIEWED',
|
||||
resourceType: 'deploy_run',
|
||||
resourceId: run.id,
|
||||
actorName: user?.account ?? run.operator,
|
||||
after: {
|
||||
projectKey: run.projectKey,
|
||||
environment: run.environment,
|
||||
jenkinsBuildNumber: run.jenkinsBuildNumber,
|
||||
start: normalizedStart,
|
||||
nextStart: log.nextStart,
|
||||
hasMore: log.hasMore,
|
||||
textLength: text.length,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
runId: run.id,
|
||||
jobPath: environment.jenkinsJobPath,
|
||||
buildNumber: run.jenkinsBuildNumber,
|
||||
start: normalizedStart,
|
||||
nextStart: log.nextStart,
|
||||
hasMore: log.hasMore,
|
||||
text,
|
||||
fetchedAt: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
async syncJenkinsRuns(
|
||||
user?: AuthenticatedUser,
|
||||
): Promise<DeployRunJenkinsSyncSummary> {
|
||||
@@ -311,6 +369,27 @@ export class DeployRunsService {
|
||||
);
|
||||
}
|
||||
|
||||
private normalizeLogStart(start: string | undefined): number {
|
||||
if (start === undefined || start === '') {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const value = Number(start);
|
||||
|
||||
if (!Number.isSafeInteger(value) || value < 0) {
|
||||
throw new AppError(
|
||||
'VALIDATION_FAILED',
|
||||
'Jenkins 日志 start 必须是非负整数',
|
||||
400,
|
||||
{
|
||||
start,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private async cancelJenkinsExecution(
|
||||
run: DeployRunSummary,
|
||||
project: ProjectSummary,
|
||||
|
||||
Reference in New Issue
Block a user