|
|
@@ -1,33 +1,33 @@
|
|
|
package cn.seecoder.web.service.impl.pipeline;
|
|
|
|
|
|
+import cn.seecoder.api.ApplicationProperties;
|
|
|
import cn.seecoder.api.k8s.K8sConstants;
|
|
|
+import cn.seecoder.common.exceptions.AccessDeniedException;
|
|
|
+import cn.seecoder.common.exceptions.ServiceException;
|
|
|
import cn.seecoder.common.util.LogTrackingUtil;
|
|
|
import cn.seecoder.common.util.OpType;
|
|
|
+import cn.seecoder.web.core.pipeline.Pipeline;
|
|
|
+import cn.seecoder.web.core.pipeline.PipelineException;
|
|
|
+import cn.seecoder.web.core.pipeline.PipelineFactory;
|
|
|
+import cn.seecoder.web.core.pipeline.inspector.PipelineInspector;
|
|
|
+import cn.seecoder.web.core.pipeline.inspector.PipelineInspectorFactory;
|
|
|
import cn.seecoder.web.dao.pipeline.DeploymentMapper;
|
|
|
-import cn.seecoder.web.dao.pipeline.PipelineRecordMapper;
|
|
|
import cn.seecoder.web.dao.pipeline.PipelineMapper;
|
|
|
+import cn.seecoder.web.dao.pipeline.PipelineRecordMapper;
|
|
|
import cn.seecoder.web.dao.project.ProjectMapper;
|
|
|
import cn.seecoder.web.model.po.pipeline.DeploymentPO;
|
|
|
-import cn.seecoder.web.model.po.pipeline.PipelineRecordPO;
|
|
|
import cn.seecoder.web.model.po.pipeline.PipelinePO;
|
|
|
+import cn.seecoder.web.model.po.pipeline.PipelineRecordPO;
|
|
|
import cn.seecoder.web.model.po.project.ProjectPO;
|
|
|
+import cn.seecoder.web.model.vo.pipeline.DeploymentInfoVO;
|
|
|
+import cn.seecoder.web.service.pipeline.DeploymentService;
|
|
|
import com.alibaba.fastjson.JSONObject;
|
|
|
import io.kubernetes.client.openapi.models.V1DeploymentStatus;
|
|
|
+import lombok.RequiredArgsConstructor;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
import org.apache.http.HttpStatus;
|
|
|
-import org.springframework.beans.factory.annotation.Autowired;
|
|
|
import org.springframework.scheduling.annotation.Async;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
-import cn.seecoder.api.ApplicationProperties;
|
|
|
-import cn.seecoder.common.exceptions.AccessDeniedException;
|
|
|
-import cn.seecoder.common.exceptions.ServiceException;
|
|
|
-import cn.seecoder.web.core.pipeline.Pipeline;
|
|
|
-import cn.seecoder.web.core.pipeline.PipelineException;
|
|
|
-import cn.seecoder.web.core.pipeline.PipelineFactory;
|
|
|
-import cn.seecoder.web.core.pipeline.inspector.PipelineInspector;
|
|
|
-import cn.seecoder.web.core.pipeline.inspector.PipelineInspectorFactory;
|
|
|
-import cn.seecoder.web.model.vo.pipeline.DeploymentInfoVO;
|
|
|
-import cn.seecoder.web.service.pipeline.DeploymentService;
|
|
|
|
|
|
import java.sql.Timestamp;
|
|
|
import java.util.ArrayList;
|
|
|
@@ -38,74 +38,77 @@ import static cn.seecoder.web.core.pipeline.template.PipelineTemplateTable.MYSQL
|
|
|
import static cn.seecoder.web.core.pipeline.template.PipelineTemplateTable.SONAR_JAVA8;
|
|
|
|
|
|
/**
|
|
|
- * @author PuHong Weng
|
|
|
- * @date 2021/3/12
|
|
|
- * @description:
|
|
|
+ * DeploymentServiceImpl
|
|
|
+ * 流水线部署服务实现类
|
|
|
*/
|
|
|
@Service
|
|
|
@Slf4j
|
|
|
+@RequiredArgsConstructor
|
|
|
public class DeploymentServiceImpl implements DeploymentService {
|
|
|
|
|
|
private final PipelineMapper pipelineMapper;
|
|
|
-
|
|
|
private final PipelineRecordMapper pipelineRecordMapper;
|
|
|
-
|
|
|
private final ProjectMapper projectMapper;
|
|
|
-
|
|
|
private final ApplicationProperties properties;
|
|
|
-
|
|
|
private final DeploymentMapper deploymentMapper;
|
|
|
|
|
|
-
|
|
|
- @Autowired
|
|
|
- public DeploymentServiceImpl(PipelineMapper pipelineMapper, PipelineRecordMapper pipelineRecordMapper, ProjectMapper projectMapper, ApplicationProperties properties, DeploymentMapper deploymentMapper) {
|
|
|
- this.pipelineMapper = pipelineMapper;
|
|
|
- this.pipelineRecordMapper = pipelineRecordMapper;
|
|
|
- this.projectMapper = projectMapper;
|
|
|
- this.properties = properties;
|
|
|
- this.deploymentMapper = deploymentMapper;
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
+ /**
|
|
|
+ * 获取指定项目的部署信息列表
|
|
|
+ */
|
|
|
@Override
|
|
|
public List<DeploymentInfoVO> retrieveDeploymentInfos(Integer projectId) throws AccessDeniedException {
|
|
|
- //projectAuthentication(projectId);
|
|
|
+ // 获取项目对应的流水线列表
|
|
|
List<PipelinePO> pipelines = pipelineMapper.selectByProjectId(projectId);
|
|
|
- if (pipelines.size() == 0) {
|
|
|
+ if (pipelines.isEmpty()) {
|
|
|
return new ArrayList<>();
|
|
|
}
|
|
|
- List<Integer> pipelineIds = pipelines.stream().map(PipelinePO::getId).collect(Collectors.toList());
|
|
|
+
|
|
|
+ List<Integer> pipelineIds = pipelines.stream()
|
|
|
+ .map(PipelinePO::getId)
|
|
|
+ .collect(Collectors.toList());
|
|
|
+
|
|
|
List<DeploymentPO> deployments = deploymentMapper.selectByPipelineIds(pipelineIds);
|
|
|
- return deployments.stream().map(x -> {
|
|
|
- PipelineInspector inspector = PipelineInspectorFactory.init(x.getNamespace(), x.getDeployName());
|
|
|
- //获取状态
|
|
|
- String status = null;
|
|
|
+
|
|
|
+ // 构建部署信息列表
|
|
|
+ return deployments.stream().map(deployment -> {
|
|
|
+ PipelineInspector inspector = PipelineInspectorFactory.init(
|
|
|
+ deployment.getNamespace(),
|
|
|
+ deployment.getDeployName()
|
|
|
+ );
|
|
|
+
|
|
|
+ // 获取部署状态
|
|
|
+ String status;
|
|
|
V1DeploymentStatus deploymentStatus = inspector.inspectStatus();
|
|
|
if (deploymentStatus == null) {
|
|
|
status = "部署不存在,请重新部署";
|
|
|
- } else if (deploymentStatus.getAvailableReplicas() != null && deploymentStatus.getAvailableReplicas() >= 1) {
|
|
|
+ } else if (deploymentStatus.getAvailableReplicas() != null
|
|
|
+ && deploymentStatus.getAvailableReplicas() >= 1) {
|
|
|
status = "正常运行";
|
|
|
} else {
|
|
|
status = "不可用";
|
|
|
}
|
|
|
+
|
|
|
return DeploymentInfoVO.builder()
|
|
|
- .id(x.getId())
|
|
|
- .pipelineId(x.getPipelineId())
|
|
|
- .name(x.getDeployName() + "." + x.getNamespace())
|
|
|
- .deployedTime(x.getDeployedTime())
|
|
|
- .accessUrl(x.getAccessUrl())
|
|
|
+ .id(deployment.getId())
|
|
|
+ .pipelineId(deployment.getPipelineId())
|
|
|
+ .name(deployment.getDeployName() + "." + deployment.getNamespace())
|
|
|
+ .deployedTime(deployment.getDeployedTime())
|
|
|
+ .accessUrl(deployment.getAccessUrl())
|
|
|
.status(status)
|
|
|
.build();
|
|
|
}).collect(Collectors.toList());
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 部署指定项目流水线
|
|
|
+ */
|
|
|
@Override
|
|
|
@Async
|
|
|
public void deploy(Integer projectId, Integer pipelineId, Integer userId) throws ServiceException {
|
|
|
- //projectAuthentication(projectId);
|
|
|
PipelinePO pipelinePO = pipelineMapper.selectById(pipelineId);
|
|
|
ProjectPO projectPO = projectMapper.getProjectById(projectId);
|
|
|
- //历史记录
|
|
|
+
|
|
|
+ // 创建部署历史记录
|
|
|
PipelineRecordPO record = PipelineRecordPO.builder()
|
|
|
.result(PipelineRecordPO.DEPLOYING)
|
|
|
.pipelineId(pipelinePO.getId())
|
|
|
@@ -114,32 +117,28 @@ public class DeploymentServiceImpl implements DeploymentService {
|
|
|
.startTime(new Timestamp(System.currentTimeMillis()))
|
|
|
.build();
|
|
|
pipelineRecordMapper.insert(record);
|
|
|
- log.info("流水线触发部署,对应id为:{}",record.getPipelineId());
|
|
|
-
|
|
|
- // 有一些流水线是不产生部署产物的
|
|
|
- boolean hasDeployment = true;
|
|
|
- switch (pipelinePO.getTemplateName()) {
|
|
|
- case SONAR_JAVA8:
|
|
|
- hasDeployment = false;
|
|
|
- default:
|
|
|
- }
|
|
|
+ log.info("流水线触发部署,对应 Pipeline ID:{}", record.getPipelineId());
|
|
|
+
|
|
|
+ // 判断流水线是否产生部署产物
|
|
|
+ boolean hasDeployment = !SONAR_JAVA8.equals(pipelinePO.getTemplateName());
|
|
|
|
|
|
- // 部署产物记录
|
|
|
+ // 处理部署产物记录
|
|
|
if (hasDeployment) {
|
|
|
DeploymentPO deployment = deploymentMapper.selectByPipelineId(pipelineId);
|
|
|
if (deployment == null) {
|
|
|
- //第一次部署
|
|
|
+ // 第一次部署,生成访问 URL
|
|
|
String accessUrl;
|
|
|
- //根据模板类型设置起的访问路径
|
|
|
- switch (pipelinePO.getTemplateName()) {
|
|
|
- case MYSQL:
|
|
|
- //集群无法暴露tcp端口,只提供了集群内部访问的url
|
|
|
- accessUrl = pipelinePO.getName() + K8sConstants.SERVICE_SUFFIX + "." + projectPO.getUniqueK8sNamespace() + ".svc.cluster.local";
|
|
|
- break;
|
|
|
- default:
|
|
|
- accessUrl = pipelinePO.getName() + "-" + projectPO.getUniqueK8sNamespace() + properties.getK8s().getIngressHostSuffix();
|
|
|
- break;
|
|
|
+ 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())
|
|
|
@@ -148,66 +147,101 @@ public class DeploymentServiceImpl implements DeploymentService {
|
|
|
.deployedTime(record.getStartTime())
|
|
|
.build());
|
|
|
} else {
|
|
|
+ // 更新已有部署记录的时间
|
|
|
deployment.setDeployedTime(record.getStartTime());
|
|
|
deploymentMapper.update(deployment);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ // 执行流水线
|
|
|
Pipeline pipeline = null;
|
|
|
try {
|
|
|
- pipeline = PipelineFactory.init(pipelinePO.getId(), pipelinePO.getConfigJson(), projectPO.getUniqueK8sNamespace(), pipelinePO.getName());
|
|
|
+ pipeline = PipelineFactory.init(
|
|
|
+ pipelinePO.getId(),
|
|
|
+ pipelinePO.getConfigJson(),
|
|
|
+ projectPO.getUniqueK8sNamespace(),
|
|
|
+ pipelinePO.getName()
|
|
|
+ );
|
|
|
+
|
|
|
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);
|
|
|
record.setDetails(pipeline.getContext().getResult());
|
|
|
- log.error("流水线配置转换发生错误");
|
|
|
+ log.error("流水线配置转换发生错误", e);
|
|
|
throw new ServiceException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "应用部署失败", e);
|
|
|
+
|
|
|
} catch (RuntimeException e) {
|
|
|
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);
|
|
|
}
|
|
|
- //ANA 日志需要打出部署流水线的信息
|
|
|
- List<Integer> pipelineIds = new ArrayList<>();
|
|
|
- pipelineIds.add(pipelineId);
|
|
|
- PipelineRecordPO pipelineRecordPO = pipelineRecordMapper.selectPipelinesLatestRecord(pipelineIds);
|
|
|
+
|
|
|
+ // ANA 日志记录部署信息
|
|
|
try {
|
|
|
- JSONObject object = new JSONObject();
|
|
|
- object.put("user_id", userId);
|
|
|
- object.put("project_id", projectId);
|
|
|
- object.put("record_id", pipelineRecordPO.getId());
|
|
|
- object.put("pipeline_id", pipelineId);
|
|
|
- object.put("type", record.getResult());
|
|
|
- object.put("result", record.getResult());
|
|
|
- object.put("success", record.getResult().equals(PipelineRecordPO.DEPLOY_SUCCESS));
|
|
|
- //success?
|
|
|
- String data = JSONObject.toJSONString(object);
|
|
|
- LogTrackingUtil.log(data, OpType.DEPLOY_PIPELINE);
|
|
|
- } catch (Exception e) {
|
|
|
+ List<Integer> pipelineIds = new ArrayList<>();
|
|
|
+ pipelineIds.add(pipelineId);
|
|
|
+ PipelineRecordPO latestRecord = pipelineRecordMapper.selectPipelinesLatestRecord(pipelineIds);
|
|
|
+
|
|
|
+ JSONObject logObject = new JSONObject();
|
|
|
+ logObject.put("user_id", userId);
|
|
|
+ logObject.put("project_id", projectId);
|
|
|
+ logObject.put("record_id", latestRecord.getId());
|
|
|
+ logObject.put("pipeline_id", pipelineId);
|
|
|
+ logObject.put("type", record.getResult());
|
|
|
+ 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 日志异常不影响部署
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 删除指定项目流水线部署
|
|
|
+ */
|
|
|
@Override
|
|
|
@Async
|
|
|
public void delete(Integer projectId, Integer pipelineId) throws AccessDeniedException {
|
|
|
- //projectAuthentication(projectId);
|
|
|
DeploymentPO deployment = deploymentMapper.selectByPipelineId(pipelineId);
|
|
|
+
|
|
|
+ if (deployment == null) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
deploymentMapper.delete(pipelineId);
|
|
|
- PipelineInspector inspector = PipelineInspectorFactory.init(deployment.getNamespace(), deployment.getDeployName());
|
|
|
+ PipelineInspector inspector = PipelineInspectorFactory.init(
|
|
|
+ deployment.getNamespace(),
|
|
|
+ deployment.getDeployName()
|
|
|
+ );
|
|
|
inspector.delete();
|
|
|
-
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 获取指定流水线部署日志
|
|
|
+ */
|
|
|
@Override
|
|
|
public String log(Integer projectId, Integer pipelineId) {
|
|
|
DeploymentPO deployment = deploymentMapper.selectByPipelineId(pipelineId);
|
|
|
- PipelineInspector inspector = PipelineInspectorFactory.init(deployment.getNamespace(), deployment.getDeployName());
|
|
|
+
|
|
|
+ if (deployment == null) {
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+
|
|
|
+ PipelineInspector inspector = PipelineInspectorFactory.init(
|
|
|
+ deployment.getNamespace(),
|
|
|
+ deployment.getDeployName()
|
|
|
+ );
|
|
|
return inspector.log();
|
|
|
}
|
|
|
}
|