提交容器 ,镜像,的操作代码,现在只有启动没有写单元测试了,其他的都完成了

This commit is contained in:
mianbin 2025-05-27 21:14:30 +08:00
parent 1d64da41bb
commit 1daa1f12df
35 changed files with 1685 additions and 8 deletions

View File

@ -63,6 +63,9 @@
<inspection_tool class="AlibabaUnsupportedExceptionWithModifyAsList" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AlibabaUseQuietReferenceNotation" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AlibabaUseRightCaseForDateFormat" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AutoCloseableResource" enabled="true" level="WARNING" enabled_by_default="true">
<option name="METHOD_MATCHER_CONFIG" value="java.util.Formatter,format,java.io.Writer,append,com.google.common.base.Preconditions,checkNotNull,org.hibernate.Session,close,java.io.PrintWriter,printf,java.io.PrintStream,printf,java.util.Optional,orElseThrow" />
</inspection_tool>
<inspection_tool class="JavadocDeclaration" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ADDITIONAL_TAGS" value="Created,Date" />
</inspection_tool>

View File

@ -13,6 +13,7 @@
<podam.version>8.0.0.RELEASE</podam.version>
<flatten-maven-plugin.version>1.6.0</flatten-maven-plugin.version>
<opengauss.jdbc.version>5.1.0</opengauss.jdbc.version>
<okhttp.version>4.12.0</okhttp.version>
<mapstruct.version>1.6.2</mapstruct.version>
<fastjson.version>1.2.83</fastjson.version>
<sqlite.version>3.47.1.0</sqlite.version>
@ -52,7 +53,7 @@
<pf4j.version>3.12.1</pf4j.version>
<logback.version>1.2.13</logback.version>
<mybatis-plus-join.version>1.4.13</mybatis-plus-join.version>
<docker-java.version>3.5.0</docker-java.version>
<docker-java.version>3.2.13</docker-java.version>
<hutool-5.version>5.8.32</hutool-5.version>
<revision>2.0.0-jdk17</revision>
<jsch.version>0.1.55</jsch.version>
@ -573,11 +574,32 @@
<groupId>com.github.docker-java</groupId>
<artifactId>docker-java</artifactId>
<version>${docker-java.version}</version>
<exclusions>
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</exclusion>
<exclusion>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.github.docker-java</groupId>
<artifactId>docker-java-transport-httpclient5</artifactId>
<version>${docker-java.version}</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>${okhttp.version}</version>
</dependency>
<dependency>
<groupId>org.jvnet.winp</groupId>

24
dependencies/pom.xml vendored
View File

@ -68,6 +68,7 @@
<bizlog-sdk.version>3.0.6</bizlog-sdk.version>
<netty.version>4.1.113.Final</netty.version>
<oshi-version>6.6.5</oshi-version>
<okhttp.version>4.12.0</okhttp.version>
<!-- 三方云服务相关 -->
<commons-io.version>2.17.0</commons-io.version>
<commons-compress.version>1.27.1</commons-compress.version>
@ -656,14 +657,33 @@
<groupId>com.github.docker-java</groupId>
<artifactId>docker-java</artifactId>
<version>${docker-java.version}</version>
<exclusions>
<exclusion>
<artifactId>guava</artifactId>
<groupId>com.google.guava</groupId>
</exclusion>
<exclusion>
<artifactId>commons-io</artifactId>
<groupId>commons-io</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.github.docker-java</groupId>
<artifactId>docker-java-transport-httpclient5</artifactId>
<version>${docker-java.version}</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>${okhttp.version}</version>
</dependency>
<!-- devops ci- worker start-->
<dependency>
<groupId>org.jvnet.winp</groupId>

View File

@ -37,7 +37,6 @@ public class BaseDbUnitTest {
OpsMybatisAutoConfiguration.class, // 自己的 MyBatis 配置类
MybatisPlusAutoConfiguration.class, // MyBatis 的自动配置类
MybatisPlusJoinAutoConfiguration.class, // MyBatis 的Join配置类
// 其它配置类
SpringUtil.class
})

View File

@ -0,0 +1,7 @@
这个目前制作了docker 的服务,
问题一目的是之前使用ssh进行cmd命令但是对话框中要使用docker exec/attch等进入容器执行对象的命令这种方式不太理想
问题二如afl等之类的场景你只有等容器启动了后才能够知道你的容器idname虽然可以进行提前设置但是有冲突的风险name冲突可以使用
Ops+随机数+镜像名称解决关闭时候需要输入命令docker rm -f xxx进行关闭或者脚本目前考虑使用docker 的api进行操作
问题三:容器的启动,停止等操作,缺乏统一的操作,同时缺少容器的监控方面内容
问题四容器的日志方面同问题一一样需要获取到日志依赖ssh存在问题
问题五最好其实和k8s一样使用go这种云原生的方式但是这种方式增加了运维的难度暂时都放到一起为了方便部署

View File

@ -19,22 +19,37 @@
<groupId>cd.casic.boot</groupId>
<artifactId>commons</artifactId>
</dependency>
<dependency>
<groupId>cd.casic.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>cd.casic.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>com.github.docker-java</groupId>
<artifactId>docker-java</artifactId>
</dependency>
<dependency>
<groupId>com.github.docker-java</groupId>
<artifactId>docker-java-transport-httpclient5</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.4</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents.core5</groupId>
<artifactId>httpcore5</artifactId>
<version>5.3.4</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.17.0</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,76 @@
package cd.casic.module.execute.docker;
import cd.casic.module.execute.docker.dao.DockerEndpointDao;
import cd.casic.module.execute.docker.dataobject.convert.DockerEndpointConvert;
import cd.casic.module.execute.docker.dataobject.model.DockerEndpoint;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.core.DockerClientBuilder;
import com.github.dockerjava.httpclient5.ApacheDockerHttpClient;
import jakarta.annotation.Resource;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import static java.lang.String.format;
/**
* @description: docker客户端工厂
* @author: mianbin
* @date: 2025/5/26 10:39
* @version: 1.0
*/
@Slf4j
@Component
public class DockerClientFactory implements CommandLineRunner {
@Resource
private DockerEndpointDao dockerEndpointDao;
@Getter
private final Map<String, DockerClient> clientGroup = new ConcurrentHashMap<>();
@Override
public void run(String... args) throws Exception {
/*如果在ops里面之前添加过数据库之前有那么启动就加载如果是手动添加的后面要注意控制*/
dockerEndpointDao.selectList().stream()
.map(DockerEndpointConvert.INSTANCE::convert)
.forEach(endpoint -> {
ApacheDockerHttpClient httpClient = createHttpClient(endpoint);
if (httpClient != null) {
DockerClient client = DockerClientBuilder.getInstance().withDockerHttpClient(httpClient).build();
clientGroup.put(endpoint.getId(), client);
}
});
}
private ApacheDockerHttpClient createHttpClient(DockerEndpoint endpoint) {
try {
URI dockerHost;
if (endpoint.getType() == DockerEndpoint.DockerEndpointTypeEnum.LOCAL) {
// 使用本地挂载
dockerHost = new URI("unix:///var/run/docker.sock");
} else if (endpoint.getType() == DockerEndpoint.DockerEndpointTypeEnum.REMOTE) {
// 远程挂载
dockerHost = new URI(format("tcp://%s:%s", endpoint.getHost(), endpoint.getPort()));
} else {
log.error("Unsupported Docker endpoint type: {}", endpoint.getType());
return null;
}
return new ApacheDockerHttpClient.Builder()
.dockerHost(dockerHost)
.build();
} catch (URISyntaxException e) {
log.error("Failed to create URI for Docker endpoint {}: {}", endpoint.getId(), e.getMessage(), e);
return null;
}
}
public DockerClient getdockerClient(String id) {
return clientGroup.get(id);
}
}

View File

@ -0,0 +1,49 @@
package cd.casic.module.execute.docker.api;
import cd.casic.framework.commons.pojo.CommonResult;
import cd.casic.module.execute.docker.DockerClientFactory;
import cd.casic.module.execute.docker.dao.DockerEndpointDao;
import cd.casic.module.execute.docker.dataobject.convert.DockerEndpointConvert;
import cd.casic.module.execute.docker.dataobject.model.DockerEndpoint;
import com.github.dockerjava.api.DockerClient;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* @description: 容器的管理功能
* @author: mianbin
* @date: 2025/5/26 11:14
* @version: 1.0
*/
@Tag(name = "docker管理 - 获取docker列表")
@RestController
@RequestMapping("/api/dockerClient")
@RequiredArgsConstructor
public class DockerClientController {
private final DockerEndpointDao dockerEndpointDao;
private final DockerClientFactory dockerClientFactory;
@GetMapping
public CommonResult<List<DockerEndpoint>> list() {
List<DockerEndpoint> collect = dockerEndpointDao
.selectList()
.stream()
.map(DockerEndpointConvert.INSTANCE::convert)
.toList();
return CommonResult.success(collect);
}
@GetMapping
public CommonResult<Boolean> testDockerClient(String dockerClientId) {
DockerClient dockerClient = dockerClientFactory.getdockerClient(dockerClientId);
dockerClient.pingCmd().exec();
return CommonResult.success(Boolean.TRUE);
}
}

View File

@ -0,0 +1,24 @@
package cd.casic.module.execute.docker.api;
import cd.casic.module.execute.docker.DockerClientFactory;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @description: TODO
* @author: mianbin
* @date: 2025/5/26 11:13
* @version: 1.0
*/
@Tag(name = "docker管理 - 容器管理")
@RestController
@RequestMapping("/api/container")
@RequiredArgsConstructor
public class DockerContainerController {
private final DockerClientFactory dockerClientFactory;
}

View File

@ -0,0 +1,20 @@
package cd.casic.module.execute.docker.api;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @description: TODO
* @author: mianbin
* @date: 2025/5/26 10:25
* @version: 1.0
*/
@Tag(name = "docker管理 - 镜像管理")
@RequiredArgsConstructor
@RestController
@RequestMapping("/api/images")
public class DockerImageController {
}

View File

@ -0,0 +1,10 @@
package cd.casic.module.execute.docker.api;
/**
* @description: TODO
* @author: mianbin
* @date: 2025/5/26 11:14
* @version: 1.0
*/
public class DockerMonitorController {
}

View File

@ -0,0 +1,57 @@
package cd.casic.module.execute.docker.callback;
import com.github.dockerjava.api.async.ResultCallback;
import com.github.dockerjava.api.model.Frame;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* @description: 之前版本用的ExecStartResultCallback实现现在废弃了使用ResultCallback.Adapter<Frame>未测试
* @author: mianbin
* @date: 2025/5/26 18:09
* @version: 1.0
*/
@Slf4j
@RequiredArgsConstructor
public class LoggingCallback extends ResultCallback.Adapter<Frame> {
private final String containerId;
private final String execId;
private StringBuffer buffer = new StringBuffer(1024);
@Override
public void onNext(Frame frame) {
String streamType = frame.getStreamType().name();
String message = new String(frame.getPayload(), StandardCharsets.UTF_8);
if (streamType.equals("STDOUT")) {
log.info("[容器: {}, ExecID: {}] 标准输出: {}", containerId, execId, message.trim());
} else if (streamType.equals("STDERR")) {
log.error("[容器: {}, ExecID: {}] 错误输出: {}", containerId, execId, message.trim());
}
buffer.append(message);
super.onNext(frame);
}
@Override
public void onError(Throwable throwable) {
log.error("[容器: {}, ExecID: {}] 执行命令时出错: {}", containerId, execId, throwable.getMessage());
super.onError(throwable);
}
@Override
public void onComplete() {
log.info("[容器: {}, ExecID: {}] 命令执行完毕", containerId, execId);
super.onComplete();
}
@Override
public void close() throws IOException {
log.debug("[容器: {}, ExecID: {}] 回调已关闭", containerId, execId);
super.close();
}
}

View File

@ -0,0 +1,15 @@
package cd.casic.module.execute.docker.dao;
import cd.casic.framework.mybatis.core.mapper.BaseMapperX;
import cd.casic.module.execute.docker.dataobject.dto.DockerEndpointDo;
import org.apache.ibatis.annotations.Mapper;
/**
* @description: TODO
* @author: mianbin
* @date: 2025/5/26 10:33
* @version: 1.0
*/
@Mapper
public interface DockerEndpointDao extends BaseMapperX<DockerEndpointDo> {
}

View File

@ -0,0 +1,15 @@
package cd.casic.module.execute.docker.dao;
import cd.casic.framework.mybatis.core.mapper.BaseMapperX;
import cd.casic.module.execute.docker.dataobject.dto.OperateRecordDo;
import org.apache.ibatis.annotations.Mapper;
/**
* @description: TODO
* @author: mianbin
* @date: 2025/5/26 16:58
* @version: 1.0
*/
@Mapper
public interface OperateRecordDao extends BaseMapperX<OperateRecordDo> {
}

View File

@ -0,0 +1,28 @@
package cd.casic.module.execute.docker.dataobject.convert;
import cd.casic.module.execute.docker.dao.DockerEndpointDao;
import cd.casic.module.execute.docker.dataobject.dto.DockerEndpointDo;
import cd.casic.module.execute.docker.dataobject.model.DockerEndpoint;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.List;
/**
* @description: TODO
* @author: mianbin
* @date: 2025/5/26 10:36
* @version: 1.0
*/
@Mapper
public interface DockerEndpointConvert {
DockerEndpointConvert INSTANCE = Mappers.getMapper(DockerEndpointConvert.class);
DockerEndpointDo convert(DockerEndpointDao endpointDao);
DockerEndpoint convert(DockerEndpointDo endpointDo);
List<DockerEndpoint> convertList(List<DockerEndpointDo> endpointDos);
List<DockerEndpointDo> convertList02(List<DockerEndpoint> endpointDos);
}

View File

@ -0,0 +1,15 @@
package cd.casic.module.execute.docker.dataobject.convert;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
/**
* @description: TODO
* @author: mianbin
* @date: 2025/5/26 16:57
* @version: 1.0
*/
@Mapper
public interface OperateRecordConvert {
OperateRecordConvert INSTANCE = Mappers.getMapper(OperateRecordConvert.class);
}

View File

@ -0,0 +1,40 @@
package cd.casic.module.execute.docker.dataobject.dto;
import cd.casic.framework.mybatis.core.dataobject.BaseDO;
import cd.casic.module.execute.docker.dataobject.model.DockerEndpoint;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
/**
* @description: docker 的端点配置
* @author: mianbin
* @date: 2025/5/26 10:28
* @version: 1.0
*/
@TableName("pipeline_docker_endpoint")
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
public class DockerEndpointDo extends BaseDO {
@TableId
private String id;
private String name;
private DockerEndpoint.DockerEndpointTypeEnum type;
private DockerEndpoint.DockerEndpointStateEnum state;
private String host;
private Integer port;
private LocalDateTime latestTestTime;
}

View File

@ -0,0 +1,36 @@
package cd.casic.module.execute.docker.dataobject.dto;
import cd.casic.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import static cd.casic.module.execute.docker.dataobject.model.OperateRecord.OperatorResource;
/**
* @description: TODO
* @author: mianbin
* @date: 2025/5/26 16:48
* @version: 1.0
*/
@TableName("pipeline_docker_record")
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
public class OperateRecordDo extends BaseDO {
@TableId(type = IdType.AUTO)
private Long id;
private Long clientId;
private Long userId;
private String name;
private OperatorResource resource;
private String content;
}

View File

@ -0,0 +1,59 @@
package cd.casic.module.execute.docker.dataobject.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import java.util.Optional;
/**
* @description: docker 的断电配置类
* @author: mianbin
* @date: 2025/5/26 10:11
* @version: 1.0
*/
@Data
public class DockerEndpoint {
private String id;
private String name;
private DockerEndpointTypeEnum type;
private DockerEndpointStateEnum state = DockerEndpointStateEnum.NORMAL;
private String host;
private Integer port;
private String latestTestTime;
private String createdAt;
private String updatedAt;
@Getter
@AllArgsConstructor
public enum DockerEndpointTypeEnum {
LOCAL("本地"),
REMOTE("远程");
private final String desc;
}
@Getter
@AllArgsConstructor
public enum DockerEndpointStateEnum {
NORMAL("正常"),
NOT_CONNECT("无法连接"),
AUTH_FAIL("授权失败");
private final String desc;
}
public String getTypeName() {
return Optional.ofNullable(this.type).map(DockerEndpointTypeEnum::getDesc).orElse("未知");
}
public String getStateName() {
return Optional.ofNullable(this.state).map(DockerEndpointStateEnum::getDesc).orElse("未知");
}
}

View File

@ -0,0 +1,37 @@
package cd.casic.module.execute.docker.dataobject.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.Getter;
/**
* @description: TODO
* @author: mianbin
* @date: 2025/5/26 16:45
* @version: 1.0
*/
@Data
@Builder
public class OperateRecord {
private String clientId;
private Long userId;
private String name;
private OperatorResource resource;
private Object content;
@Getter
@AllArgsConstructor
public enum OperatorResource {
IMAGE_v1("操作镜像"),
CONTAINER_v1("操作容器"),
VOLUME_v1("操作储存卷"),
NETWORK_v1("操作网络");
private final String desc;
}
}

View File

@ -0,0 +1,62 @@
package cd.casic.module.execute.docker.dataobject.model;
import cn.hutool.core.collection.CollUtil;
import lombok.Data;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @description: 新运行的请求类
* @author: mianbin
* @date: 2025/5/26 16:30
* @version: 1.0
*/
@Data
public class RunNewContainer {
/**
* 使用的镜像ID
*/
private String imageId;
/**
* 容器名称
*/
private String containerName;
/**
* 环境变量
*/
private Map<String, String> envGroup;
/**
* 端口绑定
*/
private Map<Long, Long> portBound;
/**
* DNS
*/
private String dns;
/**
* 容器别名
*/
private String alias;
/**
* 主机名
*/
private String hostname;
public List<String> findEnvList() {
// envGroup 为空返回空列表否则将 envGroup 转换为包含键值对字符串的列表
return CollUtil.isEmpty(this.envGroup) ? Collections.emptyList() :
this.envGroup.entrySet().stream()
.map(entry -> String.format("%s=%s", entry.getKey(), entry.getValue()))
.collect(Collectors.toList());
}
}

View File

@ -0,0 +1,7 @@
/**
* @description: TODO
* @author: mianbin
* @date: 2025/5/26 10:10
* @version: 1.0
*/
package cd.casic.module.execute.docker.dataobject;

View File

@ -0,0 +1,95 @@
package cd.casic.module.execute.docker.service;
import cd.casic.module.execute.docker.dataobject.model.RunNewContainer;
import com.github.dockerjava.api.command.CreateContainerResponse;
import com.github.dockerjava.api.command.InspectContainerResponse;
import com.github.dockerjava.api.command.TopContainerResponse;
import com.github.dockerjava.api.model.Container;
import java.util.List;
/**
* @description: 容器服务接口
* @author: mianbin
* @date: 2025/5/26 14:27
* @version: 1.0
*/
public interface IContainerService {
/**
* 查询容器列表这个接口注意一下showall参数 true 代表的是命令docker ps -a , false代表的是docker ps
*
* @param showAll
*/
List<Container> list(String clientId, boolean showAll);
/**
* 运行新的容器
*
* @return
*/
CreateContainerResponse run(String clientId, RunNewContainer runNewContainer);
/**
* 启动一个已经存在的容器
*
* @param containerId 容器ID
*/
void start(String clientId, String containerId);
/**
* 停止容器
*
* @param containerId 停止的容器ID
*/
void stop(String clientId, String containerId);
/**
* 暂停容器
*
* @apiNote 暂停指定容器(异步暂停)
*/
void pause(String clientId, String containerId);
/**
* 取消暂停容器
*
* @param containerId 容器ID
* @apiNote 取消暂停容器
*/
void unpause(String clientId, String containerId);
/**
* 移除指定容器
*
* @param containerId 容器ID
*/
void remove(String clientId, String containerId);
/**
* 获取容器的资源使用情况
*
* @param containerId 容器ID
*/
void rename(String clientId, String containerId, String newName);
/**
* 查询容器的线程信息,默认使用命令aux
*
* @param containerId 容器ID
* @return
*/
TopContainerResponse top(String clientId, String containerId);
/**
* 查询容器详情
*
* @param containerId
* @return
*/
InspectContainerResponse inspect(String clientId, String containerId);
/**
* 容器日志
*/
void logs(String clientId, String containerId, String... cmd);
}

View File

@ -0,0 +1,14 @@
package cd.casic.module.execute.docker.service;
/**
* @description: TODO
* @author: mianbin
* @date: 2025/5/26 14:31
* @version: 1.0
*/
public interface IDockerPingService {
Boolean ping(String clientId);
}

View File

@ -0,0 +1,105 @@
package cd.casic.module.execute.docker.service;
import com.github.dockerjava.api.command.InspectImageResponse;
import com.github.dockerjava.api.model.Image;
import jakarta.annotation.Nonnull;
import java.util.List;
/**
* @description: 镜像相关服务
* @author: mianbin
* @date: 2025/5/26 14:28
* @version: 1.0
*/
public interface IImageService {
/**
* 获取镜像列表
*
* @param clientId 那个客户端
* @return
*/
List<Image> list(String clientId);
/**
* 通过镜像ID获取镜像
*
* @param imageId
* @return
*/
InspectImageResponse inspect(String clientId, String imageId);
/**
* 重新打tag这个打tag注意
* hello-world hallo-newworld 74cc54e27dc4 4 months ago 10.1kB
* hello-world hallo-newworld1 74cc54e27dc4 4 months ago 10.1kB
* hello-world latest 74cc54e27dc4 4 months ago 10.1kB
* 类似与上面这样但是你删除docker rmi 74cc54e27dc4 ,那这三个全部gg
*
* @param imageId 镜像ID
* @param newTag 新的镜像Tag
*/
void tag(@Nonnull String clientId, String imageId, String newTag);
/**
* 移除镜像
*
* @param imageId 镜像ID
* @param force
* @return
*/
void remove(@Nonnull String clientId, String imageId, Boolean force);
/**
* 导出镜像
*
* @param imageId 镜像ID
*/
void export(@Nonnull String clientId, String imageId);
/**
* 通过文件路径导入镜像导入镜像
*
* @param file 文件路径
*/
void importByFile(@Nonnull String clientId, String file);
/**
* 通过Tar文件导入镜像
*
* @param file
*/
void importByTar(@Nonnull String clientId, String file);
/**
* @param imageId
*/
boolean exist(@Nonnull String clientId, String imageId);
/**
* 清理镜像
*/
void pruneImage(@Nonnull String clientId);
/**
* 镜像保存
*
* @return
*/
Boolean save(@Nonnull String clientId, String imageId, String outputPath);
/**
* 推送保存如果你想推送镜像需要在配置文件中修改镜像仓库
* 1 如果你还想修改名字或者鉴权在这里修改
* dockerClient.pushImageCmd(imageId)
* .withTag("new-tag")
* .withname("new-name")
* .withAuthConfig(authConfig).exec();
* 2如果做了以上操作需要新增接口或者自己封装
*
* @return
*/
Boolean pushImage(@Nonnull String clientId, String imageId);
}

View File

@ -0,0 +1,11 @@
package cd.casic.module.execute.docker.service;
/**
* @description: TODO
* @author: mianbin
* @date: 2025/5/26 14:28
* @version: 1.0
*/
public interface IMonitorService {
}

View File

@ -0,0 +1,16 @@
package cd.casic.module.execute.docker.service;
import cd.casic.module.execute.docker.dataobject.model.OperateRecord;
/**
* @description: 操作日志查询
* @author: mianbin
* @date: 2025/5/26 16:46
* @version: 1.0
*/
public interface IOperateRecordService {
/**
* 新增操作日志
*/
void add(OperateRecord record);
}

View File

@ -0,0 +1,165 @@
package cd.casic.module.execute.docker.service.impl;
import cd.casic.framework.commons.util.json.JsonUtils;
import cd.casic.framework.security.core.util.SecurityFrameworkUtils;
import cd.casic.module.execute.docker.DockerClientFactory;
import cd.casic.module.execute.docker.callback.LoggingCallback;
import cd.casic.module.execute.docker.dataobject.model.OperateRecord;
import cd.casic.module.execute.docker.dataobject.model.RunNewContainer;
import cd.casic.module.execute.docker.service.IContainerService;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.async.ResultCallback;
import com.github.dockerjava.api.command.CreateContainerResponse;
import com.github.dockerjava.api.command.InspectContainerResponse;
import com.github.dockerjava.api.command.TopContainerResponse;
import com.github.dockerjava.api.model.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @description: TODO
* @author: mianbin
* @date: 2025/5/26 16:34
* @version: 1.0
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class ContainerService implements IContainerService {
private final DockerClientFactory dockerClientFactory;
private final OperateRecordService operateRecordService;
@Override
public List<Container> list(String clientId, boolean showAll) {
DockerClient dockerClient = dockerClientFactory.getdockerClient(clientId);
return dockerClient.listContainersCmd().withShowAll(showAll).exec();
}
@Override
public CreateContainerResponse run(String clientId, RunNewContainer runNewContainer) {
DockerClient dockerClient = dockerClientFactory.getdockerClient(clientId);
HostConfig hostConfig = HostConfig.newHostConfig();
hostConfig.withBinds(Bind.parse("/host:/container:ro"))
.withPortBindings(PortBinding.parse("12:44")).withLinks(Link.parse(""))
.withDns(runNewContainer.getDns()).withNetworkMode("网络名");
final CreateContainerResponse response = dockerClient
.createContainerCmd(runNewContainer.getImageId())
.withAliases(runNewContainer.getAlias())
.withHostConfig(hostConfig)
.withEnv(runNewContainer.findEnvList())
.withName(runNewContainer.getContainerName())
.withHostName(runNewContainer.getHostname()).exec();
return response;
}
@Override
public void start(String clientId, String containerId) {
DockerClient dockerClient = dockerClientFactory.getdockerClient(clientId);
dockerClient.startContainerCmd(containerId).exec();
log.info("启动容器,containerId={}", containerId);
/*增加日志*/
extracted(containerId, "启动容器");
}
private void extracted(String containerId, String type) {
try {
Map<String, String> param = new HashMap<>();
param.put("containerId", containerId);
param.put("type", type);
OperateRecord operateRecord = OperateRecord.builder()
.userId(SecurityFrameworkUtils.getLoginUserId())
/*这个是没有必要存放的*/
.clientId("")
.content(JsonUtils.toJsonString(param))
.name("未知")
.resource(OperateRecord.OperatorResource.CONTAINER_v1).build();
operateRecordService.add(operateRecord);
} catch (Exception ignore) {
//ignore exception
}
}
@Override
public void stop(String clientId, String containerId) {
DockerClient dockerClient = dockerClientFactory.getdockerClient(clientId);
dockerClient.stopContainerCmd(containerId).exec();
log.info("停止容器,containerId={}", containerId);
/*增加日志*/
extracted(containerId, "停止容器");
}
@Override
public void pause(String clientId, String containerId) {
DockerClient dockerClient = dockerClientFactory.getdockerClient(clientId);
dockerClient.pauseContainerCmd(containerId).exec();
log.info("暂停容器,containerId={}", containerId);
extracted(containerId, "暂停容器");
}
@Override
public void unpause(String clientId, String containerId) {
DockerClient dockerClient = dockerClientFactory.getdockerClient(clientId);
dockerClient.unpauseContainerCmd(containerId).exec();
log.info("继续容器,containerId={}", containerId);
extracted(containerId, "恢复容器");
}
@Override
public void remove(String clientId, String containerId) {
DockerClient dockerClient = dockerClientFactory.getdockerClient(clientId);
dockerClient.removeContainerCmd(containerId)
/*删除默认强制删除,并且挂载内容一并删除*/
.withForce(true)
.withRemoveVolumes(true).exec();
log.info("移除容器,containerId={}", containerId);
extracted(containerId, "移除容器");
}
@Override
public void rename(String clientId, String containerId, String newName) {
DockerClient dockerClient = dockerClientFactory.getdockerClient(clientId);
dockerClient.renameContainerCmd(containerId).withName(newName).exec();
log.info("重命名容器:[{}]为[{}]", containerId, newName);
extracted(containerId, "重命名容器");
}
@Override
public TopContainerResponse top(String clientId, String containerId) {
DockerClient dockerClient = dockerClientFactory.getdockerClient(clientId);
return dockerClient.topContainerCmd(containerId).withPsArgs("aux").exec();
}
@Override
public InspectContainerResponse inspect(String clientId, String containerId) {
DockerClient dockerClient = dockerClientFactory.getdockerClient(clientId);
final InspectContainerResponse response =
dockerClient.inspectContainerCmd(containerId).withSize(Boolean.TRUE).exec();
extracted(containerId, "Inspect容器");
return response;
}
@Override
public void logs(String clientId, String containerId, String... cmd) {
DockerClient dockerClient = dockerClientFactory.getdockerClient(clientId);
/*创建命令*/
String execId = dockerClient.execCreateCmd(containerId)
.withCmd(cmd)
.withAttachStdin(true)
.withAttachStdout(true)
.withAttachStderr(true)
.withTty(true)
.exec().getId();
// 执行命令并记录日志
ResultCallback<Frame> callback = new LoggingCallback(containerId, execId);
/*这个是异步的方法*/
dockerClient.execStartCmd(execId)
.withTty(true)
.exec(callback);
}
}

View File

@ -0,0 +1,36 @@
package cd.casic.module.execute.docker.service.impl;
import cd.casic.module.execute.docker.DockerClientFactory;
import cd.casic.module.execute.docker.service.IDockerPingService;
import com.github.dockerjava.api.DockerClient;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.Locale;
import java.util.Optional;
/**
* @description: TODO
* @author: mianbin
* @date: 2025/5/26 14:32
* @version: 1.0
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class DockerPingService implements IDockerPingService {
private final DockerClientFactory dockerClientFactory;
@Override
public Boolean ping(String clientId) {
DockerClient dockerClient = Optional.of(StringUtils.hasText(clientId) ? clientId : "DEFAULT")
.map(id -> id.toUpperCase(Locale.ROOT))
.map(dockerClientFactory::getdockerClient)
.orElseThrow(() -> new RuntimeException("客户端不存在"));
dockerClient.pingCmd().exec();
return Boolean.TRUE;
}
}

View File

@ -0,0 +1,133 @@
package cd.casic.module.execute.docker.service.impl;
import cd.casic.module.execute.docker.DockerClientFactory;
import cd.casic.module.execute.docker.service.IImageService;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.command.InspectImageResponse;
import com.github.dockerjava.api.command.SaveImageCmd;
import com.github.dockerjava.api.exception.NotFoundException;
import com.github.dockerjava.api.model.Image;
import com.github.dockerjava.api.model.PruneType;
import jakarta.annotation.Nonnull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Service;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
/**
* @description: 镜像的服务类
* @author: mianbin
* @date: 2025/5/26 15:06
* @version: 1.0
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class ImageService implements IImageService {
private final DockerClientFactory dockerClientFactory;
@Override
public List<Image> list(@Nonnull String clientId) {
DockerClient dockerClient = dockerClientFactory.getdockerClient(clientId);
return dockerClient.listImagesCmd().exec();
}
@Override
public InspectImageResponse inspect(@Nonnull String clientId, @Nonnull String imageId) {
DockerClient dockerClient = dockerClientFactory.getdockerClient(clientId);
return dockerClient.inspectImageCmd(imageId).exec();
}
@Override
public void tag(@Nonnull String clientId, String imageId, String newTag) {
InspectImageResponse inspect = inspect(clientId, imageId);
if (inspect == null) {
throw new RuntimeException("操作失败,镜像不存在");
}
DockerClient dockerClient = dockerClientFactory.getdockerClient(clientId);
dockerClient.tagImageCmd(imageId, inspect.getRepoTags().get(0), newTag).exec();
}
@Override
public void remove(@Nonnull String clientId, String imageId, Boolean force) {
DockerClient dockerClient = dockerClientFactory.getdockerClient(clientId);
dockerClient.removeImageCmd(imageId).withForce(force).exec();
}
@Override
public void export(@Nonnull String clientId, String imageId) {
DockerClient dockerClient = dockerClientFactory.getdockerClient(clientId);
/*这个api我要查下不生效也无所感觉场景很少*/
}
/*经过测试只有local 模式下面生效*/
@Override
public void importByFile(@Nonnull String clientId, String file) {
DockerClient dockerClient = dockerClientFactory.getdockerClient(clientId);
/*test 测试下,这个不知道生效不*/
dockerClient.loadImageCmd(FileUtil.getInputStream(file)).exec();
}
/*经过测试只有local 模式下面生效*/
@Override
public void importByTar(@Nonnull String clientId, String file) {
DockerClient dockerClient = dockerClientFactory.getdockerClient(clientId);
/*test 测试下,这个不知道生效不*/
dockerClient.loadImageCmd(FileUtil.getInputStream(file)).exec();
}
@Override
public boolean exist(@Nonnull String clientId, String imageId) {
DockerClient dockerClient = dockerClientFactory.getdockerClient(clientId);
try {
final InspectImageResponse inspectImage = dockerClient.inspectImageCmd(imageId).exec();
return inspectImage != null;
} catch (NotFoundException e) {
return false;
}
}
@Override
public void pruneImage(String clientId) {
DockerClient dockerClient = dockerClientFactory.getdockerClient(clientId);
dockerClient.pruneCmd(PruneType.IMAGES).exec();
}
@Override
public Boolean save(@Nonnull String clientId, String imageId, String outputPath) {
DockerClient dockerClient = dockerClientFactory.getdockerClient(clientId);
/*imageid 如 nginx:latest */
try (OutputStream fos = new FileOutputStream(outputPath);
SaveImageCmd saveImageCmd = dockerClient.saveImageCmd(imageId)) {
// 执行命令并将输出写入文件流
InputStream exec = saveImageCmd.exec();
IoUtil.copy(exec, fos);
log.info("镜像已成功保存到: " + outputPath);
return Boolean.TRUE;
} catch (IOException e) {
log.error("保存镜像时发生 IO 错误: " + e.getMessage());
return Boolean.FALSE;
}
}
@Override
public Boolean pushImage(@NotNull String clientId, String imageId) {
DockerClient dockerClient = dockerClientFactory.getdockerClient(clientId);
try {
dockerClient.pushImageCmd(imageId).exec(null);
} catch (Exception e) {
log.error("推送镜像失败: " + e.getMessage());
return false;
}
return true;
}
}

View File

@ -0,0 +1,31 @@
package cd.casic.module.execute.docker.service.impl;
import cd.casic.module.execute.docker.dao.OperateRecordDao;
import cd.casic.module.execute.docker.dataobject.model.OperateRecord;
import cd.casic.module.execute.docker.service.IOperateRecordService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
* @description: TODO
* @author: mianbin
* @date: 2025/5/26 16:47
* @version: 1.0
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class OperateRecordService implements IOperateRecordService {
private final OperateRecordDao operateRecordDao;
@Override
public void add(OperateRecord record) {
// OperateRecordDo operateRecordDo = OperateRecordConvert.INSTANCE.convert(record);
// int insert = operateRecordDao.insert(operateRecordDo);
// if (insert != 1) {
// log.error("新增操作日志失败");
// }
}
}

View File

@ -0,0 +1,238 @@
package cd.casic.module.execute;
import cd.casic.module.execute.docker.callback.LoggingCallback;
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.command.ExecCreateCmdResponse;
import com.github.dockerjava.api.command.InspectContainerResponse;
import com.github.dockerjava.api.command.TopContainerResponse;
import com.github.dockerjava.api.exception.NotFoundException;
import com.github.dockerjava.api.model.Container;
import com.github.dockerjava.api.model.Frame;
import com.github.dockerjava.core.DockerClientBuilder;
import com.github.dockerjava.httpclient5.ApacheDockerHttpClient;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import static java.lang.String.format;
import static java.lang.Thread.sleep;
/**
* @description: TODO
* @author: mianbin
* @date: 2025/5/26 20:11
* @version: 1.0
*/
@Slf4j
public class ContainerServiceTest {
private final Map<String, DockerClient> clientGroup = new ConcurrentHashMap<>();
private ApacheDockerHttpClient createHttpClient(DockerEndpoint endpoint) {
try {
URI dockerHost;
if (endpoint.getType() == DockerEndpoint.DockerEndpointTypeEnum.LOCAL) {
// 使用本地挂载
dockerHost = new URI("unix:///var/run/docker.sock");
} else if (endpoint.getType() == DockerEndpoint.DockerEndpointTypeEnum.REMOTE) {
// 远程挂载
dockerHost = new URI(format("tcp://%s:%s", endpoint.getHost(), endpoint.getPort()));
} else {
log.error("Unsupported Docker endpoint type: {}", endpoint.getType());
return null;
}
return new ApacheDockerHttpClient.Builder()
.dockerHost(dockerHost)
.build();
} catch (URISyntaxException e) {
log.error("Failed to create URI for Docker endpoint {}: {}", endpoint.getId(), e.getMessage(), e);
return null;
}
}
@BeforeEach
public void setUp() {
DockerEndpoint endpoint = new DockerEndpoint();
endpoint.setId("158");
endpoint.setType(DockerEndpoint.DockerEndpointTypeEnum.REMOTE);
endpoint.setHost("175.6.27.158");
endpoint.setPort(22375);
endpoint.setName("test");
ApacheDockerHttpClient httpClient = createHttpClient(endpoint);
DockerClient dockerClient = DockerClientBuilder.getInstance().withDockerHttpClient(httpClient).build();
clientGroup.put(endpoint.getId(), dockerClient);
}
@Test
public void list() {
DockerClient dockerClient = clientGroup.get("158");
List<Container> exec =
dockerClient.listContainersCmd().withShowAll(true).exec();
System.out.println(exec.size());
}
@Test
public void start() {
String containerId = "d7d747696f43";
DockerClient dockerClient = clientGroup.get("158");
dockerClient.startContainerCmd(containerId).exec();
log.info("启动容器,containerId={}", containerId);
}
@Test
public void stop() {
String containerId = "d7d747696f43";
DockerClient dockerClient = clientGroup.get("158");
dockerClient.stopContainerCmd(containerId).exec();
log.info("停止容器,containerId={}", containerId);
}
/*没有成功,不知道为啥,暂停不研究了*/
@Test
public void pause() {
String containerId = "c1ffcaf2ff8d";
DockerClient dockerClient = clientGroup.get("158");
dockerClient.pauseContainerCmd(containerId).exec();
log.info("停止容器,containerId={}", containerId);
}
@Test
public void unpause() {
String containerId = "c1ffcaf2ff8d";
DockerClient dockerClient = clientGroup.get("158");
try {
dockerClient.unpauseContainerCmd(containerId).exec();
log.info("重启容器,containerId={}", containerId);
} catch (NotFoundException e) {
log.info("重启容器失败,containerId={},异常", containerId, e.getMessage());
}
}
@Test
public void rename() {
String containerId = "d7d747696f43";
String newName = "nginx";
DockerClient dockerClient = clientGroup.get("158");
dockerClient.renameContainerCmd(containerId).withName(newName).exec();
log.info("重命名容器:[{}]为[{}]", containerId, newName);
}
@Test
public void top() {
String containerId = "d7d747696f43";
String newName = "nginx";
DockerClient dockerClient = clientGroup.get("158");
TopContainerResponse aux = dockerClient.topContainerCmd(containerId).withPsArgs("aux").exec();
log.info("", aux.getTitles());
}
@Test
public void inspect() {
String containerId = "d7d747696f43";
DockerClient dockerClient = clientGroup.get("158");
final InspectContainerResponse response =
dockerClient.inspectContainerCmd(containerId).withSize(Boolean.TRUE).exec();
System.out.println(response);
}
@Test
public void remove() {
String containerId = "d7d747696f43";
DockerClient dockerClient = clientGroup.get("158");
dockerClient.removeContainerCmd(containerId)
/*删除默认强制删除,并且挂载内容一并删除*/
.withForce(true)
.withRemoveVolumes(true).exec();
log.info("移除容器,containerId={}", containerId);
}
/*这个测试,用处不大*/
@Test
public void setLog() throws InterruptedException {
String containerId = "d7d747696f43";
DockerClient dockerClient = clientGroup.get("158");
/*创建命令*/
String execId = dockerClient.execCreateCmd(containerId)
.withCmd("ls")
.withAttachStdin(true)
.withAttachStdout(true)
.withAttachStderr(true)
.withTty(true)
.exec().getId();
// 执行命令并记录日志
ResultCallback<Frame> callback = new LoggingCallback(containerId, execId);
/*这个是异步的方法*/
dockerClient.execStartCmd(execId)
.withTty(true)
.exec(callback);
sleep(1000);
}
@Test
public void execCmd() throws InterruptedException {
String containerId = "c1ffcaf2ff8d22ae9f1d9a91d0714d086ec2812809aec1558c9512b04168c125";
DockerClient dockerClient = clientGroup.get("158");
ExecCreateCmdResponse execCreateCmdResponse = dockerClient.execCreateCmd(containerId)
.withAttachStdout(true)
.withAttachStderr(true)
.withCmd("ls", "-l")
.exec();
dockerClient
.execStartCmd(execCreateCmdResponse.getId())
.exec(new CustomExecCallback())
.awaitCompletion();
}
/**
* 自定义执行命令回调类用于处理命令执行过程中的输出和错误信息
*/
private static class CustomExecCallback extends ResultCallback.Adapter<Frame> {
@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());
}
super.onNext(frame);
}
@Override
public void onError(Throwable throwable) {
log.error("执行命令时出错: {}", throwable.getMessage(), throwable);
super.onError(throwable);
}
@Override
public void onComplete() {
log.info("命令执行完毕");
super.onComplete();
}
@Override
public void close() throws IOException {
log.debug("回调已关闭");
super.close();
}
}
}

View File

@ -0,0 +1,76 @@
package cd.casic.module.execute;
import cd.casic.module.execute.docker.dataobject.model.DockerEndpoint;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.core.DockerClientBuilder;
import com.github.dockerjava.httpclient5.ApacheDockerHttpClient;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.util.StringUtils;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import static java.lang.String.format;
/**
* @description: TODO
* @author: mianbin
* @date: 2025/5/26 20:11
* @version: 1.0
*/
@Slf4j
public class DockerPingService {
private final Map<String, DockerClient> clientGroup = new ConcurrentHashMap<>();
private ApacheDockerHttpClient createHttpClient(DockerEndpoint endpoint) {
try {
URI dockerHost;
if (endpoint.getType() == DockerEndpoint.DockerEndpointTypeEnum.LOCAL) {
// 使用本地挂载
dockerHost = new URI("unix:///var/run/docker.sock");
} else if (endpoint.getType() == DockerEndpoint.DockerEndpointTypeEnum.REMOTE) {
// 远程挂载
dockerHost = new URI(format("tcp://%s:%s", endpoint.getHost(), endpoint.getPort()));
} else {
log.error("Unsupported Docker endpoint type: {}", endpoint.getType());
return null;
}
return new ApacheDockerHttpClient.Builder()
.dockerHost(dockerHost)
.build();
} catch (URISyntaxException e) {
log.error("Failed to create URI for Docker endpoint {}: {}", endpoint.getId(), e.getMessage(), e);
return null;
}
}
@BeforeEach
public void setUp() {
DockerEndpoint endpoint = new DockerEndpoint();
endpoint.setId("158");
endpoint.setType(DockerEndpoint.DockerEndpointTypeEnum.REMOTE);
endpoint.setHost("175.6.27.158");
endpoint.setPort(22375);
endpoint.setName("test");
ApacheDockerHttpClient httpClient = createHttpClient(endpoint);
DockerClient dockerClient = DockerClientBuilder.getInstance().withDockerHttpClient(httpClient).build();
clientGroup.put(endpoint.getId(), dockerClient);
}
@Test
public void ping(){
String clientId = "158";
DockerClient dockerClient = Optional.of(StringUtils.hasText(clientId) ? clientId : "DEFAULT")
.map(id -> id.toUpperCase(Locale.ROOT))
.map(t->clientGroup.get("158"))
.orElseThrow(() -> new RuntimeException("客户端不存在"));
dockerClient.pingCmd().exec();
}
}

View File

@ -0,0 +1,130 @@
package cd.casic.module.execute;
import cd.casic.module.execute.docker.dataobject.model.DockerEndpoint;
import cn.hutool.core.io.FileUtil;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.command.InspectImageResponse;
import com.github.dockerjava.api.model.Image;
import com.github.dockerjava.core.DockerClientBuilder;
import com.github.dockerjava.httpclient5.ApacheDockerHttpClient;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import static java.lang.String.format;
/**
* @description: TODO
* @author: mianbin
* @date: 2025/5/26 20:11
* @version: 1.0
*/
@Slf4j
public class ImageServiceTest {
private final Map<String, DockerClient> clientGroup = new ConcurrentHashMap<>();
private ApacheDockerHttpClient createHttpClient(DockerEndpoint endpoint) {
try {
URI dockerHost;
if (endpoint.getType() == DockerEndpoint.DockerEndpointTypeEnum.LOCAL) {
// 使用本地挂载
dockerHost = new URI("unix:///var/run/docker.sock");
} else if (endpoint.getType() == DockerEndpoint.DockerEndpointTypeEnum.REMOTE) {
// 远程挂载
dockerHost = new URI(format("tcp://%s:%s", endpoint.getHost(), endpoint.getPort()));
} else {
log.error("Unsupported Docker endpoint type: {}", endpoint.getType());
return null;
}
return new ApacheDockerHttpClient.Builder()
.dockerHost(dockerHost)
.build();
} catch (URISyntaxException e) {
log.error("Failed to create URI for Docker endpoint {}: {}", endpoint.getId(), e.getMessage(), e);
return null;
}
}
@BeforeEach
public void setUp() {
DockerEndpoint endpoint = new DockerEndpoint();
endpoint.setId("158");
endpoint.setType(DockerEndpoint.DockerEndpointTypeEnum.REMOTE);
endpoint.setHost("175.6.27.158");
endpoint.setPort(22375);
endpoint.setName("test");
ApacheDockerHttpClient httpClient = createHttpClient(endpoint);
DockerClient dockerClient = DockerClientBuilder.getInstance().withDockerHttpClient(httpClient).build();
clientGroup.put(endpoint.getId(), dockerClient);
}
@Test
public void listTest() {
DockerClient dockerClient = clientGroup.get("158");
List<Image> exec = dockerClient.listImagesCmd().exec();
System.out.println(exec);
}
@Test
public void inspect() {
DockerClient dockerClient = clientGroup.get("158");
InspectImageResponse exec = dockerClient
.inspectImageCmd("5a9e3ee3b8c1be74f5f89b092aea791d3ca058054ec0f3522b79d9985eff3087").exec();
System.out.println(exec);
}
@Test
public void tag() {
/* 测试的hallo-world */
String imagesId = "74cc54e27dc4";
DockerClient dockerClient = clientGroup.get("158");
InspectImageResponse inspect = dockerClient
.inspectImageCmd(imagesId).exec();
if (inspect == null) {
throw new RuntimeException("操作失败,镜像不存在");
}
dockerClient.tagImageCmd(imagesId, inspect.getRepoTags().get(0), "hallo-newworld1").exec();
}
@Test
public void remove() {
String imageId = "be69f2940aaf";
DockerClient dockerClient = clientGroup.get("158");
dockerClient.removeImageCmd(imageId).withForce(true).exec();
}
/*todo local 模式生效*/
@Test
public void importByTar() {
DockerClient dockerClient = clientGroup.get("158");
File file = new File("/home/ubuntu/nginx.tar");
Void exec = dockerClient.loadImageCmd(FileUtil.getInputStream(file)).exec();
}
@Test
public void exist() {
String imageId = "be69f2940aaf";
DockerClient dockerClient = clientGroup.get("158");
System.out.println(dockerClient.inspectImageCmd(imageId).exec() != null);
}
@Test
public void pushImage() {
String imageId = "be69f2940aaf";
DockerClient dockerClient = clientGroup.get("158");
try {
dockerClient.pushImageCmd(imageId).exec(null);
log.info("推送镜像成功: ");
} catch (Exception e) {
log.error("推送镜像失败: " + e.getMessage());
}
}
}

View File

@ -0,0 +1,11 @@
package cd.casic.module.execute;
/**
* @description: TODO
* @author: mianbin
* @date: 2025/5/26 20:11
* @version: 1.0
*/
public class MonitorService {
}