diff --git a/modules/module-ci-execute/pom.xml b/modules/module-ci-execute/pom.xml
index 135f79de..3db1d4ef 100644
--- a/modules/module-ci-execute/pom.xml
+++ b/modules/module-ci-execute/pom.xml
@@ -52,6 +52,16 @@
commons-io
2.17.0
+
+ cd.casic.boot
+ module-ci-commons
+
+
+ jakarta.ws.rs
+ jakarta.ws.rs-api
+ 3.1.0
+
+
\ No newline at end of file
diff --git a/modules/module-ci-execute/src/main/java/cd/casic/module/execute/docker/api/DockerClientController.java b/modules/module-ci-execute/src/main/java/cd/casic/module/execute/docker/api/DockerClientController.java
index 10fa696a..9d6bd6ac 100644
--- a/modules/module-ci-execute/src/main/java/cd/casic/module/execute/docker/api/DockerClientController.java
+++ b/modules/module-ci-execute/src/main/java/cd/casic/module/execute/docker/api/DockerClientController.java
@@ -29,7 +29,7 @@ public class DockerClientController {
private final DockerEndpointDao dockerEndpointDao;
private final DockerClientFactory dockerClientFactory;
- @GetMapping
+// @GetMapping
public CommonResult> list() {
List collect = dockerEndpointDao
.selectList()
@@ -39,7 +39,7 @@ public class DockerClientController {
return CommonResult.success(collect);
}
- @GetMapping
+// @GetMapping
public CommonResult testDockerClient(String dockerClientId) {
DockerClient dockerClient = dockerClientFactory.getdockerClient(dockerClientId);
dockerClient.pingCmd().exec();
diff --git a/modules/module-ci-process-biz/pom.xml b/modules/module-ci-process-biz/pom.xml
index eece7187..343f38a9 100644
--- a/modules/module-ci-process-biz/pom.xml
+++ b/modules/module-ci-process-biz/pom.xml
@@ -90,6 +90,10 @@
httpclient5
5.2.1
+
+ cd.casic.boot
+ module-ci-execute
+
diff --git a/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/engine/worker/AFLWorker.java b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/engine/worker/AFLWorker.java
index 86eecacb..13928827 100644
--- a/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/engine/worker/AFLWorker.java
+++ b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/engine/worker/AFLWorker.java
@@ -4,10 +4,12 @@ import cd.casic.ci.process.common.WorkAtom;
import cd.casic.ci.process.engine.constant.AFLConstant;
import cd.casic.ci.process.engine.constant.DIYImageExecuteCommandConstant;
import cd.casic.ci.process.engine.runContext.TaskRunContext;
+import cd.casic.ci.process.engine.worker.base.DockerWorker;
import cd.casic.ci.process.engine.worker.base.SshWorker;
import cd.casic.ci.process.process.dataObject.machine.MachineInfo;
import cd.casic.ci.process.process.dataObject.pipeline.PipPipeline;
import cd.casic.ci.process.process.dataObject.task.PipTask;
+import cd.casic.module.execute.docker.dataobject.model.DockerEndpoint;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
@@ -15,7 +17,7 @@ import java.util.Map;
@WorkAtom(taskType = "AFL")
@Slf4j
-public class AFLWorker extends SshWorker {
+public class AFLWorker extends DockerWorker {
@Override
public void execute(TaskRunContext context) {
@@ -42,11 +44,13 @@ public class AFLWorker extends SshWorker {
//如果machineId为0,则说明该节点没有配置机器,则使用开始节点的机器
//获取机器
- MachineInfo machineInfoDO = this.getMachineInfoService().getById(machineId);
- statusCode = shell(machineInfoDO, null, context,
- "echo \"自定义镜像执行命令\"",
- commandScript
- );
+// MachineInfo machineInfoDO = this.getMachineInfoService().getById(machineId);
+ // 获取docker 暂时先写固定值
+ DockerEndpoint dockerEndpoint = new DockerEndpoint();
+ dockerEndpoint.setHost("175.6.27.228");
+ dockerEndpoint.setPort(22375);
+ dockerEndpoint.setType(DockerEndpoint.DockerEndpointTypeEnum.REMOTE);
+ dockerRun(commandScript,dockerEndpoint,context);
} catch (Exception e) {
String errorMessage = "该节点配置信息为空,请先配置该节点信息" + "\r\n";
log.error("执行ssh失败:", e);
diff --git a/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/engine/worker/base/DockerWorker.java b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/engine/worker/base/DockerWorker.java
new file mode 100644
index 00000000..a8cee029
--- /dev/null
+++ b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/engine/worker/base/DockerWorker.java
@@ -0,0 +1,238 @@
+package cd.casic.ci.process.engine.worker.base;
+
+
+import cd.casic.ci.process.engine.runContext.BaseRunContext;
+import cd.casic.module.execute.docker.dataobject.model.DockerEndpoint;
+import com.github.dockerjava.api.DockerClient;
+import com.github.dockerjava.api.async.ResultCallbackTemplate;
+import com.github.dockerjava.api.command.CreateContainerResponse;
+import com.github.dockerjava.api.command.ExecCreateCmdResponse;
+import com.github.dockerjava.api.model.Bind;
+import com.github.dockerjava.api.model.Frame;
+import com.github.dockerjava.api.model.HostConfig;
+import com.github.dockerjava.core.DockerClientBuilder;
+import com.github.dockerjava.httpclient5.ApacheDockerHttpClient;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.assertj.core.util.Lists;
+import org.springframework.util.CollectionUtils;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.TimeUnit;
+
+import static java.lang.String.format;
+@Slf4j
+public abstract class DockerWorker extends BaseWorker{
+ public void dockerRun(String command, DockerEndpoint dockerEndpoint, BaseRunContext context){
+ // 第一行必须是docker run 命令 option 支持 -it -v
+ String[] split = command.split("\n");
+ List commandLine = new ArrayList<>(Arrays.stream(split).filter(StringUtils::isNotBlank).toList());
+ Optional first = commandLine.stream().filter(StringUtils::isNotBlank).findFirst();
+ String dockerRunCommand="";
+ // 找到第一个不为空的判断是否为docker run
+ if (first.isPresent()) {
+ dockerRunCommand = first.get();
+ }
+ if (StringUtils.isEmpty(dockerRunCommand)) {
+ log.error("未找到有效指令");
+ append(context,"未找到有效指令");
+ return;
+ }
+ DockerExecHandler handler = null;
+ try {
+ handler = loadRunCommand(dockerRunCommand, dockerEndpoint);
+ // 删除第一个 dockerRun 其他都要在容器内部执行
+ commandLine.remove(0);
+ if (handler == null) {
+ log.error("容器创建失败");
+ append(context,"容器创建失败");
+ }
+ if (!CollectionUtils.isEmpty(commandLine)) {
+ StringBuffer stringBuffer = new StringBuffer();
+ // docker连接
+ DockerClient client = handler.getClient();
+ for (String cmd : commandLine) {
+ stringBuffer.append(cmd);
+ stringBuffer.append("&&");
+ }
+ stringBuffer.setLength(stringBuffer.length()-2);
+ // 启动的容器id
+ String containerId = handler.getContainerId();
+ String allCommand = stringBuffer.toString();
+ String[] commandList = new String[3];
+ commandList[0]="sh";
+ commandList[1]="-c";
+ commandList[2]=allCommand;
+ log.info("容器内执行命令:{}",commandList);
+ append(context,"容器创建失败");
+ // 创建命令
+ ExecCreateCmdResponse exec = client.execCreateCmd(containerId)
+ .withAttachStdin(true)
+ .withAttachStdout(true)
+ .withAttachStderr(true)
+ .withCmd(commandList).exec();
+
+ try {
+ client.execStartCmd(exec.getId()).exec(new ResultCallbackTemplate<>() {
+ @Override
+ public void onStart(Closeable stream) {
+ super.onStart(stream);
+ log.info("命令执行开始,容器id:{},执行id:{}",containerId,exec.getId());
+ append(context,"命令执行开始,容器id:"+containerId);
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ super.onError(throwable);
+ log.error("命令执行出现错误,容器id:{},执行id:{},错误原因",containerId,exec.getId(),throwable);
+ append(context,"命令执行出现错误,容器id::"+containerId);
+ }
+
+ @Override
+ public void onComplete() {
+ super.onComplete();
+ log.info("命令执行完毕,容器id:{},执行id:{}",containerId,exec.getId());
+ append(context,"命令执行完毕,容器id::"+containerId);
+ }
+
+ @Override
+ public void onNext(Frame frame) {
+ String output = new String(frame.getPayload(), StandardCharsets.UTF_8);
+ switch (frame.getStreamType()) {
+ case STDOUT:
+ System.out.print(output);
+ log.info("标准输出: {}", output.trim());
+ append(context,"标准输出: ,容器id:"+containerId+" content"+output);
+ break;
+ case STDERR:
+ System.err.print(output);
+ log.error("错误输出: {}", output.trim());
+ append(context,"错误输出: ,容器id:"+containerId+" content"+output);
+ break;
+ default:
+ log.warn("未知流类型: {}", frame.getStreamType());
+ }
+ }
+ }).awaitCompletion(60, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ log.error("执行异常",e);
+ append(context,"执行异常,容器id:"+containerId);
+ } finally {
+ log.info("停止容器{}",containerId);
+ append(context,"停止容器: ,容器id:"+containerId);
+ handler.getClient().stopContainerCmd(handler.containerId).exec();
+ log.info("删除容器{}",containerId);
+ append(context,"删除容器: ,容器id:"+containerId);
+ handler.getClient().removeContainerCmd(handler.containerId).exec();
+ }
+ }
+ } finally {
+ if (handler!=null) {
+ try {
+ append(context,"断开链接: ,容器id:"+handler.getContainerId());
+ handler.getClient().close();
+ } catch (IOException e) {
+ log.error("关闭容器失败",e);
+ }
+ }
+ }
+
+ }
+ public DockerExecHandler loadRunCommand(String runCommand, DockerEndpoint dockerEndpoint){
+ String[] split = runCommand.split("\\s+");
+ List keywords = new ArrayList<>(split.length);
+ keywords.addAll(Lists.list(split));
+ keywords.removeIf(next -> next.equals("docker") || next.equals("run"));
+ // 容器路径映射
+ String volume ="";
+ // 镜像名
+ String image = "";
+ // 容器命令 通常为bash或者 /bin/bash
+ String initCommand = "";
+ for (int i = 0; i < keywords.size(); i++) {
+ String keyword = keywords.get(i);
+ switch (keyword){
+ case "-v":{
+ volume = keywords.get(++i);
+ break;
+ }
+ case "-p":{
+ log.warn("忽略暂时不支持的-p {}",keywords.get(++i));
+ break;
+ }
+ case "--name":{
+ log.warn("忽略暂时不支持的--name {}",keywords.get(++i));
+ break;
+ }
+ default:{
+ if(!keyword.startsWith("-")){
+ if (StringUtils.isEmpty(image)){
+ image = keyword;
+ log.info("识别到镜像 :{}",image);
+ } else if (StringUtils.isEmpty(initCommand)) {
+ initCommand = keyword;
+ log.info("识别到容器运行命令 :{}",initCommand);
+ }
+ }
+ else {
+ log.warn("未识别部分:{}",keyword);
+ }
+ }
+ }
+ }
+ URI uri = null;
+ try {
+ if (dockerEndpoint.getType() == DockerEndpoint.DockerEndpointTypeEnum.LOCAL) {
+ // 使用本地挂载
+ uri = new URI("unix:///var/run/docker.sock");
+ } else if (dockerEndpoint.getType() == DockerEndpoint.DockerEndpointTypeEnum.REMOTE) {
+ // 远程挂载
+ uri = new URI(format("tcp://%s:%s", dockerEndpoint.getHost(), dockerEndpoint.getPort()));
+ } else {
+ log.error("Unsupported Docker endpoint type: {}", dockerEndpoint.getType());
+ return null;
+ }
+ } catch (URISyntaxException e) {
+ log.error("docker运行时创建uri失败",e);
+ }
+ if (uri == null) {
+ return null;
+ }
+ ApacheDockerHttpClient httpClient = new ApacheDockerHttpClient.Builder().dockerHost(uri).build();
+ DockerClient build = DockerClientBuilder.getInstance().withDockerHttpClient(httpClient).build();
+ HostConfig hostConfig = HostConfig
+ .newHostConfig();
+ if (StringUtils.isNotEmpty(volume)) {
+ hostConfig.withBinds(Bind.parse(volume));
+ }
+// .withPortBindings(PortBinding.parse()) // 暂时先不支持端口绑定
+ if (StringUtils.isEmpty(image)||StringUtils.isEmpty(initCommand)) {
+ log.error("缺少参数image或者initCommand");
+ return null;
+ }
+ // 创建容器
+ CreateContainerResponse exec = build.createContainerCmd(image).withHostConfig(hostConfig)
+ .withTty(true).withCmd(initCommand).exec();
+ String containerId = exec.getId();
+ // 启动容器
+ build.startContainerCmd(containerId).exec();
+ log.info("容器创建并启动完成");
+ return new DockerExecHandler(build, containerId);
+ }
+ @Data
+ @AllArgsConstructor
+ private static class DockerExecHandler{
+ private DockerClient client;
+ private String containerId;
+ }
+}
diff --git a/ops-server/src/test/java/cd/casic/server/DockerRunTest.java b/ops-server/src/test/java/cd/casic/server/DockerRunTest.java
new file mode 100644
index 00000000..b8cd089e
--- /dev/null
+++ b/ops-server/src/test/java/cd/casic/server/DockerRunTest.java
@@ -0,0 +1,240 @@
+package cd.casic.server;
+
+import cd.casic.module.execute.docker.callback.CommandExecCallback;
+import cd.casic.module.execute.docker.dataobject.model.DockerEndpoint;
+import com.github.dockerjava.api.DockerClient;
+import com.github.dockerjava.api.async.ResultCallback;
+import com.github.dockerjava.api.async.ResultCallbackTemplate;
+import com.github.dockerjava.api.command.CreateContainerResponse;
+import com.github.dockerjava.api.command.ExecCreateCmdResponse;
+import com.github.dockerjava.api.model.*;
+import com.github.dockerjava.core.DockerClientBuilder;
+import com.github.dockerjava.httpclient5.ApacheDockerHttpClient;
+import jodd.util.StringUtil;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.assertj.core.util.Lists;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.util.CollectionUtils;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+
+import static java.lang.String.format;
+
+@SpringBootTest(classes = {OpsServerApplication.class})
+@ActiveProfiles("local")
+@Slf4j
+public class DockerRunTest {
+ @Test
+ public void test01(){
+ DockerEndpoint dockerEndpoint = new DockerEndpoint();
+ dockerEndpoint.setHost("175.6.27.228");
+ dockerEndpoint.setPort(22375);
+ dockerEndpoint.setType(DockerEndpoint.DockerEndpointTypeEnum.REMOTE);
+ String command = "docker run -v /home/casic/706/yunqi/:/test -it aflplusplus/aflplusplus bash\n" +
+ "cd /test\n" +
+ "afl-fuzz -i case -o ai_afl -t 3000 -Q ./CaseGenerator/testdata/libpng/libpng/pngfix @@";
+ dockerRun(command,dockerEndpoint);
+ }
+ public void dockerRun(String command, DockerEndpoint dockerEndpoint){
+ // 第一行必须是docker run 命令 option 支持 -it -v
+ String[] split = command.split("\n");
+ List commandLine = new ArrayList<>(Arrays.stream(split).filter(StringUtils::isNotBlank).toList());
+ Optional first = commandLine.stream().filter(StringUtils::isNotBlank).findFirst();
+ String dockerRunCommand="";
+ // 找到第一个不为空的判断是否为docker run
+ if (first.isPresent()) {
+ dockerRunCommand = first.get();
+ }
+ if (StringUtils.isEmpty(dockerRunCommand)) {
+ log.error("未找到有效指令");
+ return;
+ }
+ DockerExecHandler handler = null;
+ try {
+ handler = loadRunCommand(dockerRunCommand, dockerEndpoint);
+ // 删除第一个 dockerRun 其他都要在容器内部执行
+ commandLine.remove(0);
+ if (handler == null) {
+ log.error("容器创建失败");
+ }
+ if (!CollectionUtils.isEmpty(commandLine)) {
+ StringBuffer stringBuffer = new StringBuffer();
+ // docker连接
+ DockerClient client = handler.getClient();
+ for (String cmd : commandLine) {
+ stringBuffer.append(cmd);
+ stringBuffer.append("&&");
+ }
+ stringBuffer.setLength(stringBuffer.length()-2);
+ // 启动的容器id
+ String containerId = handler.getContainerId();
+ String allCommand = stringBuffer.toString();
+ String[] commandList = new String[3];
+ commandList[0]="sh";
+ commandList[1]="-c";
+ commandList[2]=allCommand;
+ log.info("容器内执行命令:{}",commandList);
+ // 创建命令
+ ExecCreateCmdResponse exec = client.execCreateCmd(containerId)
+ .withAttachStdin(true)
+ .withAttachStdout(true)
+ .withAttachStderr(true)
+ .withCmd(commandList).exec();
+
+ try {
+ client.execStartCmd(exec.getId()).exec(new ResultCallbackTemplate<>() {
+ @Override
+ public void onStart(Closeable stream) {
+ super.onStart(stream);
+ log.info("命令执行开始,容器id:{},执行id:{}",containerId,exec.getId());
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ super.onError(throwable);
+ log.error("命令执行出现错误,容器id:{},执行id:{},错误原因",containerId,exec.getId(),throwable);
+ }
+
+ @Override
+ public void onComplete() {
+ super.onComplete();
+ log.info("命令执行完毕,容器id:{},执行id:{}",containerId,exec.getId());
+ }
+
+ @Override
+ public void onNext(Frame frame) {
+ String output = new String(frame.getPayload(), StandardCharsets.UTF_8);
+ switch (frame.getStreamType()) {
+ case STDOUT:
+ System.out.print(output);
+ log.info("标准输出: {}", output.trim());
+ break;
+ case STDERR:
+ System.err.print(output);
+ log.error("错误输出: {}", output.trim());
+ break;
+ default:
+ log.warn("未知流类型: {}", frame.getStreamType());
+ }
+ }
+ }).awaitCompletion(60,TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ log.error("执行异常",e);
+ } finally {
+ log.info("停止容器{}",containerId);
+ handler.getClient().stopContainerCmd(handler.containerId).exec();
+ log.info("删除容器{}",containerId);
+ handler.getClient().removeContainerCmd(handler.containerId).exec();
+ }
+ }
+ } finally {
+ if (handler!=null) {
+ try {
+ handler.getClient().close();
+ } catch (IOException e) {
+ log.error("关闭容器失败",e);
+ }
+ }
+ }
+
+ }
+ public DockerExecHandler loadRunCommand(String runCommand, DockerEndpoint dockerEndpoint){
+ String[] split = runCommand.split("\\s+");
+ List keywords = new ArrayList<>(split.length);
+ keywords.addAll(Lists.list(split));
+ keywords.removeIf(next -> next.equals("docker") || next.equals("run"));
+ // 容器路径映射
+ String volume ="";
+ // 镜像名
+ String image = "";
+ // 容器命令 通常为bash或者 /bin/bash
+ String initCommand = "";
+ for (int i = 0; i < keywords.size(); i++) {
+ String keyword = keywords.get(i);
+ switch (keyword){
+ case "-v":{
+ volume = keywords.get(++i);
+ break;
+ }
+ case "-p":{
+ log.warn("忽略暂时不支持的-p {}",keywords.get(++i));
+ break;
+ }
+ case "--name":{
+ log.warn("忽略暂时不支持的--name {}",keywords.get(++i));
+ break;
+ }
+ default:{
+ if(!keyword.startsWith("-")){
+ if (StringUtils.isEmpty(image)){
+ image = keyword;
+ log.info("识别到镜像 :{}",image);
+ } else if (StringUtils.isEmpty(initCommand)) {
+ initCommand = keyword;
+ log.info("识别到容器运行命令 :{}",initCommand);
+ }
+ }
+ else {
+ log.warn("未识别部分:{}",keyword);
+ }
+ }
+ }
+ }
+ URI uri = null;
+ try {
+ if (dockerEndpoint.getType() == DockerEndpoint.DockerEndpointTypeEnum.LOCAL) {
+ // 使用本地挂载
+ uri = new URI("unix:///var/run/docker.sock");
+ } else if (dockerEndpoint.getType() == DockerEndpoint.DockerEndpointTypeEnum.REMOTE) {
+ // 远程挂载
+ uri = new URI(format("tcp://%s:%s", dockerEndpoint.getHost(), dockerEndpoint.getPort()));
+ } else {
+ log.error("Unsupported Docker endpoint type: {}", dockerEndpoint.getType());
+ return null;
+ }
+ } catch (URISyntaxException e) {
+ log.error("docker运行时创建uri失败",e);
+ }
+ if (uri == null) {
+ return null;
+ }
+ ApacheDockerHttpClient httpClient = new ApacheDockerHttpClient.Builder().dockerHost(uri).build();
+ DockerClient build = DockerClientBuilder.getInstance().withDockerHttpClient(httpClient).build();
+ HostConfig hostConfig = HostConfig
+ .newHostConfig();
+ if (StringUtils.isNotEmpty(volume)) {
+ hostConfig.withBinds(Bind.parse(volume));
+ }
+// .withPortBindings(PortBinding.parse()) // 暂时先不支持端口绑定
+ if (StringUtils.isEmpty(image)||StringUtils.isEmpty(initCommand)) {
+ log.error("缺少参数image或者initCommand");
+ return null;
+ }
+ // 创建容器
+ CreateContainerResponse exec = build.createContainerCmd(image).withHostConfig(hostConfig)
+ .withTty(true).withCmd(initCommand).exec();
+ String containerId = exec.getId();
+ // 启动容器
+ build.startContainerCmd(containerId).exec();
+ log.info("容器创建并启动完成");
+ return new DockerExecHandler(build, containerId);
+ }
+ @Data
+ @AllArgsConstructor
+ private static class DockerExecHandler{
+ private DockerClient client;
+ private String containerId;
+ }
+}
diff --git a/ops-server/src/test/java/cd/casic/server/DockerTest.java b/ops-server/src/test/java/cd/casic/server/DockerTest.java
new file mode 100644
index 00000000..097d1753
--- /dev/null
+++ b/ops-server/src/test/java/cd/casic/server/DockerTest.java
@@ -0,0 +1,203 @@
+package cd.casic.server;
+import com.github.dockerjava.api.DockerClient;
+import com.github.dockerjava.api.command.CreateContainerResponse;
+import com.github.dockerjava.api.command.ExecCreateCmdResponse;
+import com.github.dockerjava.api.exception.NotFoundException;
+import com.github.dockerjava.api.model.Bind;
+import com.github.dockerjava.api.model.Frame;
+import com.github.dockerjava.api.model.HostConfig;
+import com.github.dockerjava.api.model.StreamType;
+import com.github.dockerjava.api.model.Volume;
+import com.github.dockerjava.core.DefaultDockerClientConfig;
+import com.github.dockerjava.core.DockerClientBuilder;
+import com.github.dockerjava.core.DockerClientConfig;
+import com.github.dockerjava.core.command.ExecStartResultCallback;
+import com.github.dockerjava.httpclient5.ApacheDockerHttpClient;
+import org.apache.commons.lang3.StringUtils;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.ActiveProfiles;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.concurrent.TimeUnit;
+@SpringBootTest(classes = {OpsServerApplication.class})
+@ActiveProfiles("local")
+public class DockerTest {
+ // TODO 测试通过,明天继续整通用的
+ public static String executeParsedDockerCommand(
+ String dockerHostUri,
+ String dockerRunCommand,
+ String[] commandInContainer,
+ int execTimeoutMinutes) {
+
+ // 1. 解析docker run命令
+ String[] parts = dockerRunCommand.split("\\s+");
+ if (parts.length < 6 || !"docker".equals(parts[0]) || !"run".equals(parts[1])) {
+ throw new RuntimeException("Invalid docker run command format");
+ }
+
+ // 2. 提取参数
+ String volumeArg = null;
+ String imageName = null;
+ String initialCommand = null;
+
+ for (int i = 2; i < parts.length; i++) {
+ if ("-v".equals(parts[i]) && i + 1 < parts.length) {
+ volumeArg = parts[++i];
+ } else if ("-it".equals(parts[i])) {
+ // 跳过交互式参数
+ } else if (imageName == null) {
+ imageName = parts[i];
+ } else if (initialCommand == null) {
+ initialCommand = parts[i];
+ }
+ }
+
+ if (volumeArg == null || imageName == null) {
+ throw new RuntimeException("Missing required parameters in docker run command");
+ }
+
+ // 3. 解析卷挂载参数
+ String[] volumeParts = volumeArg.split(":");
+ if (volumeParts.length != 2) {
+ throw new RuntimeException("Invalid volume format, expected host_path:container_path");
+ }
+ String hostPath = volumeParts[0];
+ String containerPath = volumeParts[1];
+
+ ApacheDockerHttpClient httpClient;
+ try {
+ httpClient = new ApacheDockerHttpClient.Builder().dockerHost(new URI(dockerHostUri)).build();
+ } catch (URISyntaxException e) {
+ throw new RuntimeException(e);
+ }
+ DockerClient dockerClient = DockerClientBuilder.getInstance().withDockerHttpClient(httpClient).build();
+ try {
+ // 5. 创建容器
+ HostConfig hostConfig = HostConfig.newHostConfig()
+ .withBinds(Bind.parse(volumeArg));
+
+ CreateContainerResponse container = dockerClient.createContainerCmd(imageName)
+ .withHostConfig(hostConfig)
+ .withCmd(initialCommand)
+ .withTty(true)
+ .exec();
+
+ String containerId = container.getId();
+
+ // 6. 启动容器
+ dockerClient.startContainerCmd(containerId).exec();
+
+ try {
+ // 7. 构建要在容器内执行的完整命令
+ String[] fullCommand = new String[commandInContainer.length + 2];
+ fullCommand[0] = "sh";
+ fullCommand[1] = "-c";
+ fullCommand[2] = "cd " + containerPath + " && " + StringUtils.join(commandInContainer, " ");
+// System.arraycopy(commandInContainer, 0, fullCommand, 3, commandInContainer.length - 1);
+
+ // 8. 创建exec实例
+ ExecCreateCmdResponse execCreateCmdResponse = dockerClient.execCreateCmd(containerId)
+ .withAttachStdout(true)
+ .withAttachStderr(true)
+ .withCmd(fullCommand)
+ .exec();
+
+ // 9. 执行命令并获取输出
+ String output = dockerClient.execStartCmd(execCreateCmdResponse.getId())
+ .exec(new ExecStartResultCallback())
+ .awaitOutput(execTimeoutMinutes, TimeUnit.MINUTES);
+
+ return output;
+ } finally {
+ // 10. 清理容器
+ dockerClient.stopContainerCmd(containerId).exec();
+ dockerClient.removeContainerCmd(containerId).exec();
+ }
+ } finally {
+ try {
+ dockerClient.close();
+ } catch (IOException e) {
+
+ }
+ }
+ }
+
+ private static class ExecStartResultCallback extends com.github.dockerjava.api.async.ResultCallback.Adapter {
+ private final StringBuilder output = new StringBuilder();
+
+ @Override
+ public void onNext(com.github.dockerjava.api.model.Frame frame) {
+ output.append(new String(frame.getPayload()));
+ }
+
+ public String awaitOutput(long timeout, TimeUnit timeUnit) {
+ try {
+ super.awaitCompletion(timeout, timeUnit);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new RuntimeException("Command execution interrupted", e);
+ }
+ return output.toString();
+ }
+ }
+
+ // 示例用法
+ @Test
+ public void test() {
+ // *** 请将此处的远程 Docker TCP 地址替换为您实际的值 ***
+ String remoteDockerHostUri = "tcp://175.6.27.228:22375"; // <-- !!! REPLACE THIS !!!
+
+ // *** 提供您希望解析的 docker run 命令字符串 ***
+ // 注意:此解析器仅支持简化的格式
+ String dockerRunCmdString = "docker run -v /home/yunqi/:/test -it aflplusplus/aflplusplus bash";
+ System.out.println("待解析并执行的 Docker run 命令: " + dockerRunCmdString);
+
+
+ // 要在容器内执行的 afl-fuzz 命令及其参数
+ // 这部分仍然需要单独提供,因为解析器不负责从 docker run 命令中提取后续要 exec 的命令
+ String[] aflFuzzCommand = {
+ "afl-fuzz",
+ "-i", "case",
+ "-o", "ai_afl",
+ "-t", "3000",
+ "-Q",
+ "./CaseGenerator/testdata/libpng/libpng/pngfix",
+ "@@"
+ };
+ System.out.print("将在容器内执行的命令: ");
+ for (String arg : aflFuzzCommand) {
+ System.out.print(arg + " ");
+ }
+ System.out.println();
+
+
+ // 命令执行超时时间(分钟)
+ int commandTimeoutMinutes = 60; // 例如,给 afl-fuzz 60 分钟运行时间
+
+ try {
+ // 调用解析并执行的方法
+ System.out.println("\n开始执行解析后的 Docker 命令序列...");
+ String aflOutput = executeParsedDockerCommand(
+ remoteDockerHostUri,
+ dockerRunCmdString,
+ aflFuzzCommand,
+ commandTimeoutMinutes
+ );
+
+ System.out.println("\n--- AFL-fuzz 命令的标准输出 ---");
+ System.out.println(aflOutput);
+ System.out.println("------------------------------");
+
+ } catch (RuntimeException e) {
+ System.err.println("\n--- 执行过程中发生错误 ---");
+ System.err.println(e.getMessage());
+ e.printStackTrace();
+ System.err.println("---------------------------");
+ }
+ }
+
+}