|
|
@@ -31,6 +31,7 @@ import org.springframework.stereotype.Service;
|
|
|
|
|
|
import java.sql.Timestamp;
|
|
|
import java.util.ArrayList;
|
|
|
+import java.util.Collections;
|
|
|
import java.util.List;
|
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
@@ -52,12 +53,8 @@ public class DeploymentServiceImpl implements DeploymentService {
|
|
|
private final ApplicationProperties properties;
|
|
|
private final DeploymentMapper deploymentMapper;
|
|
|
|
|
|
- /**
|
|
|
- * 获取指定项目的部署信息列表
|
|
|
- */
|
|
|
@Override
|
|
|
- public List<DeploymentInfoVO> retrieveDeploymentInfos(Integer projectId) throws AccessDeniedException {
|
|
|
- // 获取项目对应的流水线列表
|
|
|
+ public List<DeploymentInfoVO> retrieveDeploymentInfos(Integer projectId) {
|
|
|
List<PipelinePO> pipelines = pipelineMapper.selectByProjectId(projectId);
|
|
|
if (pipelines.isEmpty()) {
|
|
|
return new ArrayList<>();
|
|
|
@@ -69,39 +66,48 @@ public class DeploymentServiceImpl implements DeploymentService {
|
|
|
|
|
|
List<DeploymentPO> deployments = deploymentMapper.selectByPipelineIds(pipelineIds);
|
|
|
|
|
|
- // 构建部署信息列表
|
|
|
- return deployments.stream().map(deployment -> {
|
|
|
- PipelineInspector inspector = PipelineInspectorFactory.init(
|
|
|
- deployment.getNamespace(),
|
|
|
- deployment.getDeployName()
|
|
|
- );
|
|
|
+ return deployments.stream()
|
|
|
+ .map(this::buildDeploymentInfo)
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ }
|
|
|
|
|
|
- // 获取部署状态
|
|
|
- String status;
|
|
|
- V1DeploymentStatus deploymentStatus = inspector.inspectStatus();
|
|
|
- if (deploymentStatus == null) {
|
|
|
- status = "部署不存在,请重新部署";
|
|
|
- } else if (deploymentStatus.getAvailableReplicas() != null
|
|
|
- && deploymentStatus.getAvailableReplicas() >= 1) {
|
|
|
- status = "正常运行";
|
|
|
- } else {
|
|
|
- status = "不可用";
|
|
|
- }
|
|
|
-
|
|
|
- return DeploymentInfoVO.builder()
|
|
|
- .id(deployment.getId())
|
|
|
- .pipelineId(deployment.getPipelineId())
|
|
|
- .name(deployment.getDeployName() + "." + deployment.getNamespace())
|
|
|
- .deployedTime(deployment.getDeployedTime())
|
|
|
- .accessUrl(deployment.getAccessUrl())
|
|
|
- .status(status)
|
|
|
- .build();
|
|
|
- }).collect(Collectors.toList());
|
|
|
+ /**
|
|
|
+ * 构建部署信息 VO
|
|
|
+ */
|
|
|
+ private DeploymentInfoVO buildDeploymentInfo(DeploymentPO deployment) {
|
|
|
+ PipelineInspector inspector = PipelineInspectorFactory.init(
|
|
|
+ deployment.getNamespace(),
|
|
|
+ deployment.getDeployName()
|
|
|
+ );
|
|
|
+
|
|
|
+ String status = getDeploymentStatus(inspector.inspectStatus());
|
|
|
+
|
|
|
+ return DeploymentInfoVO.builder()
|
|
|
+ .id(deployment.getId())
|
|
|
+ .pipelineId(deployment.getPipelineId())
|
|
|
+ .name(deployment.getDeployName() + "." + deployment.getNamespace())
|
|
|
+ .deployedTime(deployment.getDeployedTime())
|
|
|
+ .accessUrl(deployment.getAccessUrl())
|
|
|
+ .status(status)
|
|
|
+ .build();
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 部署指定项目流水线
|
|
|
+ * 获取部署状态描述
|
|
|
*/
|
|
|
+ private String getDeploymentStatus(V1DeploymentStatus deploymentStatus) {
|
|
|
+ if (deploymentStatus == null) {
|
|
|
+ return "部署不存在,请重新部署";
|
|
|
+ }
|
|
|
+
|
|
|
+ if (deploymentStatus.getAvailableReplicas() != null
|
|
|
+ && deploymentStatus.getAvailableReplicas() >= 1) {
|
|
|
+ return "正常运行";
|
|
|
+ }
|
|
|
+
|
|
|
+ return "不可用";
|
|
|
+ }
|
|
|
+
|
|
|
@Override
|
|
|
@Async("deployExecutor")
|
|
|
public void deploy(Integer projectId, Integer pipelineId, Integer userId) throws ServiceException {
|
|
|
@@ -109,6 +115,23 @@ public class DeploymentServiceImpl implements DeploymentService {
|
|
|
ProjectPO projectPO = projectMapper.getProjectById(projectId);
|
|
|
|
|
|
// 创建部署历史记录
|
|
|
+ PipelineRecordPO record = createDeploymentRecord(pipelinePO, userId);
|
|
|
+ log.info("流水线触发部署,Pipeline ID: {}", record.getPipelineId());
|
|
|
+
|
|
|
+ // 处理部署产物记录
|
|
|
+ handleDeploymentArtifact(pipelinePO, projectPO, pipelineId, record);
|
|
|
+
|
|
|
+ // 执行流水线
|
|
|
+ executePipeline(pipelinePO, projectPO, record);
|
|
|
+
|
|
|
+ // 记录 ANA 日志
|
|
|
+ logDeploymentAnalytics(projectId, pipelineId, userId, record);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 创建部署记录
|
|
|
+ */
|
|
|
+ private PipelineRecordPO createDeploymentRecord(PipelinePO pipelinePO, Integer userId) {
|
|
|
PipelineRecordPO record = PipelineRecordPO.builder()
|
|
|
.result(PipelineRecordPO.DEPLOYING)
|
|
|
.pipelineId(pipelinePO.getId())
|
|
|
@@ -117,43 +140,58 @@ public class DeploymentServiceImpl implements DeploymentService {
|
|
|
.startTime(new Timestamp(System.currentTimeMillis()))
|
|
|
.build();
|
|
|
pipelineRecordMapper.insert(record);
|
|
|
- log.info("流水线触发部署,对应 Pipeline ID:{}", record.getPipelineId());
|
|
|
+ return record;
|
|
|
+ }
|
|
|
|
|
|
+ /**
|
|
|
+ * 处理部署产物记录
|
|
|
+ */
|
|
|
+ private void handleDeploymentArtifact(PipelinePO pipelinePO, ProjectPO projectPO,
|
|
|
+ Integer pipelineId, PipelineRecordPO record) {
|
|
|
// 判断流水线是否产生部署产物
|
|
|
boolean hasDeployment = !SONAR_JAVA8.equals(pipelinePO.getTemplateName());
|
|
|
+ if (!hasDeployment) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
- // 处理部署产物记录
|
|
|
- if (hasDeployment) {
|
|
|
- DeploymentPO deployment = deploymentMapper.selectByPipelineId(pipelineId);
|
|
|
- if (deployment == null) {
|
|
|
- // 第一次部署,生成访问 URL
|
|
|
- String accessUrl;
|
|
|
- if (MYSQL.equals(pipelinePO.getTemplateName())) {
|
|
|
- // MySQL 内部访问 URL
|
|
|
- accessUrl = pipelinePO.getName() + K8sConstants.SERVICE_SUFFIX + "."
|
|
|
- + projectPO.getUniqueK8sNamespace() + ".svc.cluster.local";
|
|
|
- } else {
|
|
|
- // 其他模板通过 Ingress 暴露 URL
|
|
|
- accessUrl = pipelinePO.getName() + "-" + projectPO.getUniqueK8sNamespace()
|
|
|
- + properties.getK8s().getIngressHostSuffix();
|
|
|
- }
|
|
|
-
|
|
|
- // 插入部署记录
|
|
|
- deploymentMapper.insert(DeploymentPO.builder()
|
|
|
- .namespace(projectPO.getUniqueK8sNamespace())
|
|
|
- .deployName(pipelinePO.getName())
|
|
|
- .accessUrl(accessUrl)
|
|
|
- .pipelineId(pipelineId)
|
|
|
- .deployedTime(record.getStartTime())
|
|
|
- .build());
|
|
|
- } else {
|
|
|
- // 更新已有部署记录的时间
|
|
|
- deployment.setDeployedTime(record.getStartTime());
|
|
|
- deploymentMapper.update(deployment);
|
|
|
- }
|
|
|
+ DeploymentPO deployment = deploymentMapper.selectByPipelineId(pipelineId);
|
|
|
+ if (deployment == null) {
|
|
|
+ // 第一次部署,创建部署记录
|
|
|
+ String accessUrl = generateAccessUrl(pipelinePO, projectPO);
|
|
|
+ deploymentMapper.insert(DeploymentPO.builder()
|
|
|
+ .namespace(projectPO.getUniqueK8sNamespace())
|
|
|
+ .deployName(pipelinePO.getName())
|
|
|
+ .accessUrl(accessUrl)
|
|
|
+ .pipelineId(pipelineId)
|
|
|
+ .deployedTime(record.getStartTime())
|
|
|
+ .build());
|
|
|
+ } else {
|
|
|
+ // 更新已有部署记录的时间
|
|
|
+ deployment.setDeployedTime(record.getStartTime());
|
|
|
+ deploymentMapper.update(deployment);
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- // 执行流水线
|
|
|
+ /**
|
|
|
+ * 生成访问 URL
|
|
|
+ */
|
|
|
+ private String generateAccessUrl(PipelinePO pipelinePO, ProjectPO projectPO) {
|
|
|
+ if (MYSQL.equals(pipelinePO.getTemplateName())) {
|
|
|
+ // MySQL 内部访问 URL
|
|
|
+ return pipelinePO.getName() + K8sConstants.SERVICE_SUFFIX + "."
|
|
|
+ + projectPO.getUniqueK8sNamespace() + ".svc.cluster.local";
|
|
|
+ } else {
|
|
|
+ // 其他模板通过 Ingress 暴露 URL
|
|
|
+ return pipelinePO.getName() + "-" + projectPO.getUniqueK8sNamespace()
|
|
|
+ + properties.getK8s().getIngressHostSuffix();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 执行流水线
|
|
|
+ */
|
|
|
+ private void executePipeline(PipelinePO pipelinePO, ProjectPO projectPO, PipelineRecordPO record)
|
|
|
+ throws ServiceException {
|
|
|
Pipeline pipeline = null;
|
|
|
try {
|
|
|
pipeline = PipelineFactory.init(
|
|
|
@@ -166,31 +204,54 @@ public class DeploymentServiceImpl implements DeploymentService {
|
|
|
pipeline.addConfig("pipelineRecordId", record.getId().toString());
|
|
|
pipeline.start();
|
|
|
|
|
|
- // 部署成功,更新记录
|
|
|
+ // 部署成功
|
|
|
record.setResult(PipelineRecordPO.DEPLOY_SUCCESS);
|
|
|
record.setDetails(pipeline.getContext().getResult());
|
|
|
|
|
|
} catch (PipelineException e) {
|
|
|
- record.setResult(PipelineRecordPO.DEPLOY_FAIL);
|
|
|
+ handlePipelineException(pipeline, record, e);
|
|
|
+ } catch (RuntimeException e) {
|
|
|
+ handleRuntimeException(pipeline, record, e);
|
|
|
+ } finally {
|
|
|
+ pipelineRecordMapper.update(record);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理流水线异常
|
|
|
+ */
|
|
|
+ private void handlePipelineException(Pipeline pipeline, PipelineRecordPO record,
|
|
|
+ PipelineException e) throws ServiceException {
|
|
|
+ record.setResult(PipelineRecordPO.DEPLOY_FAIL);
|
|
|
+ if (pipeline != null) {
|
|
|
record.setDetails(pipeline.getContext().getResult());
|
|
|
- log.error("流水线配置转换发生错误", e);
|
|
|
- throw new ServiceException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "应用部署失败", e);
|
|
|
+ }
|
|
|
+ log.error("流水线配置转换发生错误", e);
|
|
|
+ throw new ServiceException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "应用部署失败", e);
|
|
|
+ }
|
|
|
|
|
|
- } catch (RuntimeException e) {
|
|
|
+ /**
|
|
|
+ * 处理运行时异常
|
|
|
+ */
|
|
|
+ private void handleRuntimeException(Pipeline pipeline, PipelineRecordPO record,
|
|
|
+ RuntimeException e) throws ServiceException {
|
|
|
+ if (pipeline != null) {
|
|
|
pipeline.getContext().appendErrorResult("运行时错误: ", e);
|
|
|
- record.setResult(PipelineRecordPO.DEPLOY_FAIL);
|
|
|
record.setDetails(pipeline.getContext().getResult());
|
|
|
- throw new ServiceException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "应用部署失败", e);
|
|
|
-
|
|
|
- } finally {
|
|
|
- pipelineRecordMapper.update(record);
|
|
|
}
|
|
|
+ record.setResult(PipelineRecordPO.DEPLOY_FAIL);
|
|
|
+ log.error("流水线运行时错误", e);
|
|
|
+ throw new ServiceException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "应用部署失败", e);
|
|
|
+ }
|
|
|
|
|
|
- // ANA 日志记录部署信息
|
|
|
+ /**
|
|
|
+ * 记录部署分析日志
|
|
|
+ */
|
|
|
+ private void logDeploymentAnalytics(Integer projectId, Integer pipelineId,
|
|
|
+ Integer userId, PipelineRecordPO record) {
|
|
|
try {
|
|
|
- List<Integer> pipelineIds = new ArrayList<>();
|
|
|
- pipelineIds.add(pipelineId);
|
|
|
- PipelineRecordPO latestRecord = pipelineRecordMapper.selectPipelinesLatestRecord(pipelineIds);
|
|
|
+ PipelineRecordPO latestRecord = pipelineRecordMapper.selectPipelinesLatestRecord(
|
|
|
+ Collections.singletonList(pipelineId));
|
|
|
|
|
|
JSONObject logObject = new JSONObject();
|
|
|
logObject.put("user_id", userId);
|
|
|
@@ -201,40 +262,40 @@ public class DeploymentServiceImpl implements DeploymentService {
|
|
|
logObject.put("result", record.getResult());
|
|
|
logObject.put("success", PipelineRecordPO.DEPLOY_SUCCESS.equals(record.getResult()));
|
|
|
|
|
|
- LogTrackingUtil.log(JSONObject.toJSONString(logObject), OpType.DEPLOY_PIPELINE);
|
|
|
- } catch (Exception ignored) {
|
|
|
- // ANA 日志异常不影响部署
|
|
|
+ LogTrackingUtil.log(logObject.toJSONString(), OpType.DEPLOY_PIPELINE);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("记录部署分析日志失败", e);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 删除指定项目流水线部署
|
|
|
- */
|
|
|
@Override
|
|
|
@Async
|
|
|
public void delete(Integer projectId, Integer pipelineId) throws AccessDeniedException {
|
|
|
DeploymentPO deployment = deploymentMapper.selectByPipelineId(pipelineId);
|
|
|
-
|
|
|
if (deployment == null) {
|
|
|
+ log.warn("删除部署失败:Pipeline ID {} 对应的部署不存在", pipelineId);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
+ // 删除数据库记录
|
|
|
deploymentMapper.delete(pipelineId);
|
|
|
+
|
|
|
+ // 删除 K8s 部署资源
|
|
|
PipelineInspector inspector = PipelineInspectorFactory.init(
|
|
|
deployment.getNamespace(),
|
|
|
deployment.getDeployName()
|
|
|
);
|
|
|
inspector.delete();
|
|
|
+
|
|
|
+ log.info("成功删除部署:Pipeline ID {}, Namespace: {}, DeployName: {}",
|
|
|
+ pipelineId, deployment.getNamespace(), deployment.getDeployName());
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 获取指定流水线部署日志
|
|
|
- */
|
|
|
@Override
|
|
|
public String log(Integer projectId, Integer pipelineId) {
|
|
|
DeploymentPO deployment = deploymentMapper.selectByPipelineId(pipelineId);
|
|
|
-
|
|
|
if (deployment == null) {
|
|
|
+ log.warn("获取日志失败:Pipeline ID {} 对应的部署不存在", pipelineId);
|
|
|
return "";
|
|
|
}
|
|
|
|