|
|
@@ -0,0 +1,157 @@
|
|
|
+package cn.seecoder.web.core.pipeline.handler;
|
|
|
+
|
|
|
+import cn.seecoder.api.docker.DockerApi;
|
|
|
+import cn.seecoder.api.git.GitApi;
|
|
|
+import cn.seecoder.common.util.SpringUtil;
|
|
|
+import cn.seecoder.web.core.pipeline.Context;
|
|
|
+import cn.seecoder.web.core.pipeline.PipelineException;
|
|
|
+import cn.seecoder.web.core.pipeline.template.PipelineTemplateTable;
|
|
|
+import cn.seecoder.web.model.vo.project.ProjectVO;
|
|
|
+import cn.seecoder.web.service.project.BranchService;
|
|
|
+import cn.seecoder.web.service.project.ProjectService;
|
|
|
+import com.nju.edu.gitlab.vo.BranchVO;
|
|
|
+import com.spotify.docker.client.ProgressHandler;
|
|
|
+import com.spotify.docker.client.exceptions.DockerException;
|
|
|
+import com.spotify.docker.client.messages.ProgressMessage;
|
|
|
+import lombok.Data;
|
|
|
+import lombok.EqualsAndHashCode;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.apache.commons.lang.StringUtils;
|
|
|
+import org.apache.http.HttpStatus;
|
|
|
+import org.eclipse.jgit.api.Git;
|
|
|
+import org.springframework.web.util.UriComponentsBuilder;
|
|
|
+
|
|
|
+import java.io.IOException;
|
|
|
+import java.net.MalformedURLException;
|
|
|
+import java.net.URL;
|
|
|
+import java.nio.file.Files;
|
|
|
+import java.nio.file.Path;
|
|
|
+import java.nio.file.Paths;
|
|
|
+import java.nio.file.StandardOpenOption;
|
|
|
+import java.util.Date;
|
|
|
+import java.util.List;
|
|
|
+
|
|
|
+@Slf4j
|
|
|
+@Data
|
|
|
+@EqualsAndHashCode(callSuper = false)
|
|
|
+public class SonarJavaImageBuildHandler extends AbstractHandler{
|
|
|
+
|
|
|
+ private final GitApi gitApi;
|
|
|
+ private final DockerApi dockerApi;
|
|
|
+ private final ProjectService projectService;
|
|
|
+ private final BranchService branchService;
|
|
|
+
|
|
|
+ // properties from pipeline config json
|
|
|
+// private String repoUrl;
|
|
|
+ private String branchName;
|
|
|
+ private int projectId;
|
|
|
+// private String commitHash;
|
|
|
+
|
|
|
+ public SonarJavaImageBuildHandler() {
|
|
|
+ this.gitApi = SpringUtil.getBean(GitApi.class);
|
|
|
+ this.dockerApi = SpringUtil.getBean(DockerApi.class);
|
|
|
+ this.projectService = SpringUtil.getBean(ProjectService.class);
|
|
|
+ this.branchService = SpringUtil.getBean(BranchService.class);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void process(Context context) throws PipelineException {
|
|
|
+ // 0. prepare
|
|
|
+ String templateName = PipelineTemplateTable.SONAR_JAVA8;
|
|
|
+ String imageName = context.getNamespace() + '-' + context.getDeployName() + "-sonar";
|
|
|
+ String imageTag = String.valueOf(new Date().getTime());
|
|
|
+ ProjectVO project = projectService.getProjectById(projectId);
|
|
|
+ if (project == null) {
|
|
|
+ throw new PipelineException(HttpStatus.SC_SERVICE_UNAVAILABLE, PipelineException.PROJECT_NOT_FOUND_ERROR);
|
|
|
+ }
|
|
|
+ String repoUrl = project.getGitRemoteUrl();
|
|
|
+ // convert url for local test
|
|
|
+ UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(repoUrl);
|
|
|
+ repoUrl = builder.scheme("https").host("seecoder-gitlab-gitlab.seec.seecoder.cn").path(".git/").toUriString();
|
|
|
+ BranchVO branch = branchService.getBranchById(projectId, branchName);
|
|
|
+ if (branch == null) {
|
|
|
+ throw new PipelineException(HttpStatus.SC_SERVICE_UNAVAILABLE, PipelineException.BRANCH_NOT_FOUND_ERROR);
|
|
|
+ }
|
|
|
+ String commitHash = branch.getCommit().getId();
|
|
|
+
|
|
|
+ // 1. 有git从git拿到地址仓库
|
|
|
+ Path directory;
|
|
|
+ try {
|
|
|
+ //注:temp文件/目录系统会定时删,实际文件名喂 prefix + 它生成的suffix随机数,所以不会命名冲突
|
|
|
+ directory = Files.createTempDirectory("seecoder-devcloud-");
|
|
|
+ Git git = gitApi.clone(repoUrl, directory.toString());
|
|
|
+ git.checkout().setCreateBranch(false).setStartPoint("origin/" + branchName).setName("origin/" + branchName).call();
|
|
|
+ } catch (Exception e) {
|
|
|
+ context.appendErrorResult(PipelineException.GIT_CLONE_ERROR, e);
|
|
|
+ throw new PipelineException(HttpStatus.SC_SERVICE_UNAVAILABLE, PipelineException.GIT_CLONE_ERROR, e);
|
|
|
+ }
|
|
|
+
|
|
|
+ //2. 帮助学生创建dockerfile和maven的setting.xml文件, 读取学生项目architect名字
|
|
|
+ Path dockerfile = Paths.get(directory.toString(), "Dockerfile");
|
|
|
+ Path mavenSetting = Paths.get(directory.toString(), "settings.xml");
|
|
|
+ try {
|
|
|
+ Files.deleteIfExists(dockerfile);
|
|
|
+ Files.deleteIfExists(mavenSetting);
|
|
|
+ PipelineTemplateTable.copyTemplateDockerfile(templateName, dockerfile);
|
|
|
+ PipelineTemplateTable.copyTemplateMavenSetting(mavenSetting);
|
|
|
+ } catch (IOException e) {
|
|
|
+ context.appendErrorResult(PipelineException.DOCKERFILE_CREATE_ERROR, e);
|
|
|
+ throw new PipelineException(HttpStatus.SC_SERVICE_UNAVAILABLE, PipelineException.DOCKERFILE_CREATE_ERROR, e);
|
|
|
+ }
|
|
|
+
|
|
|
+ //3. 修改dockerfile内部需要替换的内容
|
|
|
+ List<String> dockerfileLines = null;
|
|
|
+ try {
|
|
|
+ dockerfileLines = Files.readAllLines(dockerfile);
|
|
|
+ StringBuilder sb = new StringBuilder();
|
|
|
+ for (String line : dockerfileLines) {
|
|
|
+ line = line
|
|
|
+ .replace("%commitHash%", commitHash)
|
|
|
+ .replace("%projectId%", Integer.toString(projectId))
|
|
|
+ .replace("%imageName%",imageName)
|
|
|
+ .replace("%imageTag%", imageTag);
|
|
|
+
|
|
|
+ sb.append(line).append("\n");
|
|
|
+ }
|
|
|
+ Files.write(dockerfile, sb.toString().getBytes(), StandardOpenOption.WRITE);
|
|
|
+ } catch (IOException e) {
|
|
|
+ throw new PipelineException(HttpStatus.SC_SERVICE_UNAVAILABLE, PipelineException.DOCKERFILE_MODIFY_ERROR, e);
|
|
|
+ }
|
|
|
+
|
|
|
+ //4. 镜像构建
|
|
|
+ StringBuilder sb = new StringBuilder();
|
|
|
+ final int DEFAULT_BUFFER_LENGTH = 200;
|
|
|
+ final long MAX_BUFFER_SIZE = 1 << 18;
|
|
|
+ try {
|
|
|
+ dockerApi.buildAndPush(directory.toString(), imageName, imageTag, new ProgressHandler() {
|
|
|
+ @Override
|
|
|
+ public void progress(ProgressMessage message) throws DockerException {
|
|
|
+ String value = message.stream();
|
|
|
+ if (value == null) {
|
|
|
+ value = "";
|
|
|
+ }
|
|
|
+ if (!StringUtils.isEmpty(message.error())) {
|
|
|
+ value += "\n" + message.error();
|
|
|
+ }
|
|
|
+ sb.append(value);
|
|
|
+ if (sb.length() > DEFAULT_BUFFER_LENGTH) {
|
|
|
+ context.appendResult(sb.toString());
|
|
|
+ if (context.getResult().length() > MAX_BUFFER_SIZE) {
|
|
|
+ context.setResult(context.getResult().substring((int) (context.getResult().length() - MAX_BUFFER_SIZE)));
|
|
|
+ }
|
|
|
+ sb.setLength(0);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("镜像构建失败,项目目录为:{}", directory.toString());
|
|
|
+ context.appendErrorResult(PipelineException.IMAGE_BUILD_ERROR, e);
|
|
|
+ throw new PipelineException(HttpStatus.SC_SERVICE_UNAVAILABLE, PipelineException.IMAGE_BUILD_ERROR, e);
|
|
|
+ }
|
|
|
+
|
|
|
+ //5. 将镜像名字加入context
|
|
|
+ context.getConfigs().put("imageName", imageName);
|
|
|
+ context.getConfigs().put("imageTag", imageTag);
|
|
|
+ context.appendSuccessResult("镜像构建成功,已推送进仓库");
|
|
|
+ }
|
|
|
+}
|