diff --git a/dependencies/pom.xml b/dependencies/pom.xml index e90b5640..728d2d06 100644 --- a/dependencies/pom.xml +++ b/dependencies/pom.xml @@ -89,6 +89,7 @@ 1.5.8 2.9.3 2.3.0 + @@ -650,9 +651,9 @@ - org.pf4j - pf4j-spring - ${pf4j-spring.version} + io.cloudsoft.windows + winrm4j + 0.12.0 @@ -761,6 +762,12 @@ ${resilience4j-circuitbreaker.version} + + + io.cloudsoft.windows + winrm4j + 0.12.3 + diff --git a/modules/module-ci-process-biz/pom.xml b/modules/module-ci-process-biz/pom.xml index bb5f9e2e..12740281 100644 --- a/modules/module-ci-process-biz/pom.xml +++ b/modules/module-ci-process-biz/pom.xml @@ -29,6 +29,36 @@ cd.casic.boot spring-boot-starter-biz-tenant + + + io.cloudsoft.windows + winrm4j + + + com.jcraft + jsch + + + com.antherd + sm-crypto + 0.3.2 + + + com.hierynomus + sshj + 0.32.0 + + + net.sf.expectit + expectit-core + 0.9.0 + + + io.cloudsoft.windows + winrm4j + 0.12.0 + + \ No newline at end of file diff --git a/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/constant/CommandConstant.java b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/constant/CommandConstant.java new file mode 100644 index 00000000..ba447f33 --- /dev/null +++ b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/constant/CommandConstant.java @@ -0,0 +1,13 @@ +package cd.casic.ci.process.constant; + +/** + * 命令常量 + * @author cww + * @date 2022/6/17 + */ +public class CommandConstant { + public static final String ENTER = "\r\n"; + public static final String EXIT = "exit" + "\r\n"; + public static final String SOURCE = "source-"; + public static final String TARGET = "target-"; +} diff --git a/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/constant/PipelineFinalConstant.java b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/constant/PipelineFinalConstant.java deleted file mode 100644 index 75c808b1..00000000 --- a/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/constant/PipelineFinalConstant.java +++ /dev/null @@ -1,220 +0,0 @@ -package cd.casic.ci.process.constant; - -public class PipelineFinalConstant { - - /** - * 项目名称 - */ - public static final String appName = "arbess"; - - /** - * DEFAULT - */ - public static final String DEFAULT = "default"; - - /** - * 流水线文件系统 - */ - public static final String MATFLOW_WORKSPACE = "/source"; - - public static final String MATFLOW_LOGS = "/artifact"; - - - /** - * 流水线运行状态 - */ - //流水线运行状态 - public static final String RUN_SUCCESS = "success"; - - public static final String RUN_ERROR = "error"; - - public static final String RUN_WAIT = "wait"; - - public static final String RUN_HALT = "halt"; - - public static final String RUN_RUN = "run"; - - public static final String RUN_SUSPEND = "suspend"; - - - /** - * 系统编码 - */ - //字节编码 - public static final String UTF_8 = "UTF-8"; - - public static final String GBK = "GBK"; - - - /** - * 消息 - */ - //消息发送类型 - public static final String MES_PIPELINE_RUN = "PIPELINE_RUN"; - - //消息发送方式 - public static final String MES_SEND_SITE = "site"; - public static final String MES_SEND_EMAIL = "email"; - public static final String MES_SEND_DINGDING = "dingding"; - public static final String MES_SEND_WECHAT = "qywechat"; - public static final String MES_SEND_SMS = "sms"; - - //消息通知方案 - public static final String MES_UPDATE = "MF_MES_TYPE_UPDATE"; - public static final String MES_DELETE = "MF_MES_TYPE_DELETE"; - public static final String MES_CREATE = "MF_MES_TYPE_CREATE"; - public static final String MES_RUN = "MF_MES_TYPE_RUN"; - - - // 日志类型 - public static final String LOG_TYPE_CREATE = "MF_LOG_TYPE_CREATE"; - - public static final String LOG_TYPE_DELETE = "MF_LOG_TYPE_DELETE"; - - public static final String LOG_TYPE_UPDATE = "MF_LOG_TYPE_UPDATE"; - - public static final String LOG_TYPE_RUN = "MF_LOG_TYPE_RUN"; - - - public static final String CREATE_LINK = "/pipeline/${pipelineId}/config"; - - public static final String DELETE_LINK = "/pipeline/${pipelineId}/delete"; - - public static final String UPDATE_LINK = "/pipeline/${pipelineId}/set/info"; - - public static final String RUN_LINK = "/pipeline/${pipelineId}/history/${instanceId}"; - - - /** - * 构建产物信息 - */ - // 默认制品地址 - public static final String PROJECT_DEFAULT_ADDRESS = "${PROJECT_DEFAULT_ADDRESS}"; - - public static final String DEFAULT_ARTIFACT_ADDRESS = "DEFAULT_ARTIFACT_ADDRESS"; - - // 默认制品 - public static final String DEFAULT_ARTIFACT_NAME = "DEFAULT_ARTIFACT_NAME"; - - // Docker制品 - public static final String DEFAULT_ARTIFACT_DOCKER = "DEFAULT_ARTIFACT_DOCKER"; - - // Docker名称 - public static final String DEFAULT_ARTIFACT_DOCKER_NAME = "DEFAULT_ARTIFACT_DOCKER_NAME"; - - - // 默认源码位置 - public static final String DEFAULT_CODE_ADDRESS = "${DEFAULT_CODE_ADDRESS}"; - - public static final String DEFAULT_TYPE = "string"; - - /** - * 默认命令 - */ - public static final String TEST_DEFAULT_ORDER = "mvn test"; - public static final String MAVEN_DEFAULT_ORDER = "mvn clean package"; - public static final String NODE_DEFAULT_ORDER = "npm install"; - public static final String DOCKER_DEFAULT_ORDER = "docker image build -t default ."; - - /** - * 文件信息 - */ - public static final String FILE_TEMP_PREFIX = "temp"; - public static final String FILE_TYPE_TXT = ".txt"; - public static final String FILE_TYPE_SH = ".sh"; - public static final String FILE_TYPE_BAT = ".bat"; - - /** - * 系统任务类型 - */ - - // 源码应用类型 - public static final String TASK_TYPE_CODE = "code"; - public static final String TASK_CODE_GIT = "git"; - public static final String TASK_CODE_GITLAB = "gitlab"; - public static final String TASK_CODE_GITHUB = "github"; - public static final String TASK_CODE_GITEE = "gitee"; - public static final String TASK_CODE_SVN = "svn"; - public static final String TASK_CODE_XCODE = "gitpuk"; - public static final String TASK_CODE_DEFAULT_BRANCH = "master"; - - - // 构建应用类型 - public static final String TASK_TYPE_BUILD = "build"; - public static final String TASK_BUILD_MAVEN = "maven"; - public static final String TASK_BUILD_NODEJS = "nodejs"; - - public static final String TASK_BUILD_DOCKER = "build_docker"; - - - // 测试应用类型 - public static final String TASK_TYPE_TEST = "test"; - public static final String TASK_TEST_MAVENTEST = "maventest"; - public static final String TASK_TEST_TESTON = "testhubo"; - - - // 部署应用类型 - public static final String TASK_TYPE_DEPLOY = "deploy"; - public static final String TASK_DEPLOY_LINUX = "liunx"; - public static final String TASK_DEPLOY_DOCKER = "docker"; - public static final String TASK_DEPLOY_K8S = "k8s"; - - - // 推送制品应用类型 - public static final String TASK_TYPE_ARTIFACT = "artifact"; - public static final String TASK_ARTIFACT_MAVEN = "artifact_maven"; - public static final String TASK_ARTIFACT_NODEJS = "artifact_nodejs"; - public static final String TASK_ARTIFACT_DOCKER = "artifact_docker"; - - - // 制品拉取应用类型 - public static final String TASK_TYPE_PULL = "pull"; - public static final String TASK_PULL_MAVEN = "pull_maven"; - public static final String TASK_PULL_NODEJS = "pull_nodejs"; - public static final String TASK_PULL_DOCKER = "pull_docker"; - - // 制品推送应用方式 - public static final String TASK_ARTIFACT_XPACK = "hadess"; - public static final String TASK_ARTIFACT_SSH = "ssh"; - public static final String TASK_ARTIFACT_NEXUS = "nexus"; - - // 代码扫描应用类型 - public static final String TASK_TYPE_CODESCAN = "codescan"; - public static final String TASK_CODESCAN_SONAR = "sonar"; - public static final String TASK_CODESCAN_SPOTBUGS = "spotbugs"; - - // 消息应用类型 - public static final String TASK_TYPE_MESSAGE = "message"; - public static final String TASK_MESSAGE_MSG = "message"; - - // 脚本应用类型 - public static final String TASK_TYPE_SCRIPT = "script"; - public static final String TASK_SCRIPT_SHELL = "shell"; - public static final String TASK_SCRIPT_BAT = "bat"; - - - //触发器 - public static final String TRIGGER_SCHEDULED = "scheduled"; - - - public static final String SIZE_TYPE_MB = "MB"; - - public static final int DEFAULT_SIZE = 2; - - - public static final String SIZE_TYPE_GB = "GB"; - - - public static final Integer DEFAULT_CLEAN_CACHE_DAY = 7; - - - - - - - - - - - -} diff --git a/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/engine/constant/DIYImageExecuteCommandConstant.java b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/engine/constant/DIYImageExecuteCommandConstant.java new file mode 100644 index 00000000..6b2b6257 --- /dev/null +++ b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/engine/constant/DIYImageExecuteCommandConstant.java @@ -0,0 +1,13 @@ +package cd.casic.ci.process.engine.constant; + +public class DIYImageExecuteCommandConstant { + /** + * 机器ID + */ + public static final String MACHINE_ID ="machineId"; + /** + * 人工卡点命令脚本 + */ + public static final String COMMAND_SCRIPT ="commandScript"; + public static final String STATUS_CODE = "statusCode"; +} diff --git a/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/engine/manager/WorkerManager.java b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/engine/manager/WorkerManager.java index 47d976c3..b31d67e9 100644 --- a/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/engine/manager/WorkerManager.java +++ b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/engine/manager/WorkerManager.java @@ -1,6 +1,11 @@ package cd.casic.ci.process.engine.manager; + +import cd.casic.ci.process.engine.message.TaskRunMessage; +import cd.casic.framework.mq.redis.core.stream.AbstractRedisStreamMessageListener; +import org.springframework.context.ApplicationContextAware; + /** * 负责监听队列,找到ContextManager获取runContext,然后实际执行 * */ -public interface WorkerManager { +public abstract class WorkerManager extends AbstractRedisStreamMessageListener implements ApplicationContextAware { } diff --git a/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/engine/manager/impl/DefaultWorkerManager.java b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/engine/manager/impl/DefaultWorkerManager.java index df8163d6..dab5abdc 100644 --- a/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/engine/manager/impl/DefaultWorkerManager.java +++ b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/engine/manager/impl/DefaultWorkerManager.java @@ -1,9 +1,15 @@ package cd.casic.ci.process.engine.manager.impl; import cd.casic.ci.common.pipeline.annotation.Plugin; +import cd.casic.ci.process.engine.enums.ContextStateEnum; +import cd.casic.ci.process.engine.manager.RunContextManager; +import cd.casic.ci.process.engine.manager.WorkerManager; import cd.casic.ci.process.engine.message.TaskRunMessage; +import cd.casic.ci.process.engine.runContext.BaseRunContext; import cd.casic.ci.process.engine.worker.BaseWorker; import cd.casic.ci.process.process.dataObject.task.PipTask; +import cd.casic.framework.commons.exception.ServiceException; +import cd.casic.framework.commons.exception.enums.GlobalErrorCodeConstants; import cd.casic.framework.mq.redis.core.stream.AbstractRedisStreamMessageListener; import jakarta.annotation.PostConstruct; import jakarta.annotation.Resource; @@ -27,13 +33,15 @@ import java.util.Set; @Component @Slf4j -public class DefaultWorkerManager extends AbstractRedisStreamMessageListener implements ApplicationContextAware { +public class DefaultWorkerManager extends WorkerManager { private static final String basePackage = "cd.casic.ci.process.engine.worker"; private Set candidates; private ApplicationContext applicationContext; private Map taskTypeWorkerMap = null; @Resource private ThreadPoolTaskExecutor workerExecutor; + @Resource + private RunContextManager contextManager; private void setTaskTypeWorker(Map taskTypeWorker) { this.taskTypeWorkerMap = taskTypeWorker; @@ -70,13 +78,22 @@ public class DefaultWorkerManager extends AbstractRedisStreamMessageListener taskProperties = taskDef.getTaskProperties(); + Object commandScriptObj = taskProperties.get(DIYImageExecuteCommandConstant.COMMAND_SCRIPT); + Object machineIdObj = taskProperties.get(DIYImageExecuteCommandConstant.MACHINE_ID); + String commandScript = commandScriptObj instanceof String ? ((String) commandScriptObj) : null; + Long machineId = null; + try { + machineId=Long.valueOf(String.valueOf(machineIdObj)); + } catch (NumberFormatException e) { + log.error("缺少参数:{}",DIYImageExecuteCommandConstant.MACHINE_ID); + } + if (StringUtils.isEmpty(commandScript) ||machineIdObj == null) { + throw new ServiceException(GlobalErrorCodeConstants.PIPELINE_ERROR.getCode(),"缺少参数"); + } + + try { + //将节点的配置信息反编译成对象 + log.info("构建脚本" + commandScript); + + //如果machineId为0,则说明该节点没有配置机器,则使用开始节点的机器 + + //获取机器 + MachineInfo machineInfoDO = this.getMachineInfoService().getById(machineId); + statusCode = shell(machineInfoDO, + "echo \"自定义镜像执行命令\"", + commandScript + ); + } catch (Exception e) { + String errorMessage = "该节点配置信息为空,请先配置该节点信息" + "\r\n"; +// errorHandle(e, errorMessage); + } + if (statusCode == 0) { +// log.info("{}节点执行完成", getName()); + } else { +// log.error("{}节点执行失败", getName()); + } + context.getLocalVariables().put(DIYImageExecuteCommandConstant.STATUS_CODE,statusCode); + } + } +} diff --git a/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/engine/worker/SshWorker.java b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/engine/worker/SshWorker.java new file mode 100644 index 00000000..6eb08b9b --- /dev/null +++ b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/engine/worker/SshWorker.java @@ -0,0 +1,81 @@ +package cd.casic.ci.process.engine.worker; + +import cd.casic.ci.process.constant.CommandConstant; +import cd.casic.ci.process.enums.MachineSystemEnum; +import cd.casic.ci.process.process.dataObject.machine.MachineInfo; +import cd.casic.ci.process.ssh.SshClient; +import cd.casic.ci.process.ssh.SshClientFactory; +import cd.casic.ci.process.ssh.WinRMHelper; +import lombok.extern.slf4j.Slf4j; + +import java.util.Arrays; +import java.util.List; +@Slf4j +public abstract class SshWorker extends BaseWorker{ + /** + * 执行shell命令 + * @param machineInfo 机器 + * @param commands 命令 + * @return 0 成功;其他值 失败 + */ + public int shell(MachineInfo machineInfo, String... commands) { + List commandList = Arrays.asList(commands); + if(MachineSystemEnum.WINDOWS.getSystem().equals(machineInfo.getOsSystem())){ + return powerShell(machineInfo, commandList); + } +// NodeLogger nodeLogger = nodeLoggerThreadLocal.get(); + int statusCode = -1; +// String loggerUuid = nodeLogger.getLoggerUuid(); +// String nodeUuid = nodeLogger.getNodeUuid(); + //loggerUuid得转换成String类型,才能生成key,然后才能通过websocket实时推送节点执行日志 + SshClient ssh = null; + try { + ssh = SshClientFactory.createSsh(machineInfo); + //执行命令,并且把命令的执行回传到前端 + statusCode = ssh.execNew(commandList, var -> { + // TODO 记录日志 +// loggerService.sendMessage(key, var); +// nodeLogger.append(var); + }); + log.info("exit-status: " + statusCode); + //主动释放当前socket连接 +// loggerService.close(key); + } catch (Exception e) { + String errorMessage = "与机器建立SSH连接出错" + CommandConstant.ENTER; +// errorHandle(e, errorMessage); + } finally { + if(ssh!=null) { + ssh.disconnect(); + } + } + return statusCode; + } + /** + * 执行shell命令 + * @param machineInfo 机器 + * @param commandList 命令 + * @return 0 成功;其他值 失败 + */ + public int powerShell(MachineInfo machineInfo, List commandList) { + int statusCode = -1; + //loggerUuid得转换成String类型,才能生成key,然后才能通过websocket实时推送节点执行日志 + + try { + WinRMHelper winRmHelper = new WinRMHelper(machineInfo); + statusCode = winRmHelper.executePs(commandList, var ->{ + log.info("================================================================" + var); + // TODO 记录日志 +// loggerService.sendMessage(key, var); +// nodeLogger.append(var); + }); + + log.info("exit-status: " + statusCode); + //主动释放当前socket连接 + + } catch (Exception e) { + String errorMessage = "与Window机器建立winrm连接出错" + CommandConstant.ENTER; +// errorHandle(e, errorMessage); + } + return statusCode; + } +} diff --git a/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/enums/MachineSystemEnum.java b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/enums/MachineSystemEnum.java new file mode 100644 index 00000000..69998036 --- /dev/null +++ b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/enums/MachineSystemEnum.java @@ -0,0 +1,27 @@ +package cd.casic.ci.process.enums; + +/** + * 枚举 + * @author herenbin + * @date 2023/6/25 + */ +public enum MachineSystemEnum { + /** + * Linux + */ + LINUX("Linux"), + /** + * Windows + */ + WINDOWS("Windows"); + + private final String system; + + MachineSystemEnum(String system) { + this.system = system; + } + + public String getSystem() { + return system; + } +} diff --git a/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/dal/machine/MachineInfoDao.java b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/dal/machine/MachineInfoDao.java new file mode 100644 index 00000000..dd39fc8b --- /dev/null +++ b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/dal/machine/MachineInfoDao.java @@ -0,0 +1,15 @@ +package cd.casic.ci.process.process.dal.machine; + + +import cd.casic.ci.process.process.dataObject.machine.MachineInfo; +import cd.casic.framework.mybatis.core.mapper.BaseMapperX; + + +/** + * 机器信息表 + * + * @author herenbin + * @date 2022-09-27 10:10:37 + */ +public interface MachineInfoDao extends BaseMapperX { +} \ No newline at end of file diff --git a/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/dataObject/base/PipBaseElement.java b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/dataObject/base/PipBaseElement.java index fa02ee74..1069a794 100644 --- a/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/dataObject/base/PipBaseElement.java +++ b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/dataObject/base/PipBaseElement.java @@ -4,7 +4,9 @@ import cd.casic.framework.commons.dataobject.BaseDO; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import lombok.Data; +import lombok.EqualsAndHashCode; +@EqualsAndHashCode(callSuper = true) @Data public class PipBaseElement extends BaseDO { /** diff --git a/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/dataObject/machine/MachineInfo.java b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/dataObject/machine/MachineInfo.java new file mode 100644 index 00000000..56f48c08 --- /dev/null +++ b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/dataObject/machine/MachineInfo.java @@ -0,0 +1,84 @@ +package cd.casic.ci.process.process.dataObject.machine; + + +import cd.casic.framework.commons.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 机器信息表 + * + * @author herenbin + * @date 2022-09-27 10:10:37 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@TableName("machine_info") +public class MachineInfo extends BaseDO { + + + /** + * 机器认证方式 1: 账号认证 2: key认证 + */ + private String authType; + + /** + * 机器描述 + */ + private String description; + /** + * id + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * 主机ip + */ + private String machineHost; + + /** + * 机器名称 + */ + private String machineName; + + /** + * 机器状态 1有效 2无效 + */ + private String machineStatus; + + /** + * 机器唯一标识 + */ + private String machineTag; + + /** + * 机器密码 + */ + private String password; + + /** + * 代理id + */ + private Long proxyId; + + /** + * ssh端口 + */ + private Integer sshPort; + + /** + * 机器账号 + */ + private String username; + /** + * 机器账号 + */ + @TableField("os_system") + private String osSystem; + +} diff --git a/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/service/machine/MachineInfoService.java b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/service/machine/MachineInfoService.java new file mode 100644 index 00000000..0d09a0d2 --- /dev/null +++ b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/service/machine/MachineInfoService.java @@ -0,0 +1,17 @@ +package cd.casic.ci.process.process.service.machine; + + +import cd.casic.ci.process.process.dataObject.machine.MachineInfo; +import com.baomidou.mybatisplus.extension.service.IService; + +import java.util.List; + +/** + * 机器信息表service接口 + * + * @author herenbin + * @date 2022-09-27 10:25:29 + */ +public interface MachineInfoService extends IService { + +} diff --git a/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/service/machine/impl/MachineInfoServiceImpl.java b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/service/machine/impl/MachineInfoServiceImpl.java new file mode 100644 index 00000000..16fa7686 --- /dev/null +++ b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/process/service/machine/impl/MachineInfoServiceImpl.java @@ -0,0 +1,25 @@ +package cd.casic.ci.process.process.service.machine.impl; + + +import cd.casic.ci.process.process.dal.machine.MachineInfoDao; +import cd.casic.ci.process.process.dataObject.machine.MachineInfo; +import cd.casic.ci.process.process.service.machine.MachineInfoService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import lombok.extern.slf4j.Slf4j; + +import org.springframework.stereotype.Service; + + + + +/** + * 机器信息表service接口实现类 + * @author herenbin + * @date 2022-09-27 10:25:29 + */ +@Slf4j +@Service +public class MachineInfoServiceImpl extends ServiceImpl implements MachineInfoService { + + +} diff --git a/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/ssh/ExecCallback.java b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/ssh/ExecCallback.java new file mode 100644 index 00000000..d95c7b22 --- /dev/null +++ b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/ssh/ExecCallback.java @@ -0,0 +1,19 @@ +package cd.casic.ci.process.ssh; + +/** + * @ClassName ops + * @Descriptions ssh 命令回调 + * @Author mianbin + * @Date 2022/6/22 15:26 + * @Version 1.0 + **/ +@FunctionalInterface +public interface ExecCallback { + + /** + * 回调执行方法 + * @param out 命令执行的输出 + */ + void callback(String out); + +} diff --git a/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/ssh/SshClient.java b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/ssh/SshClient.java new file mode 100644 index 00000000..49f18fb0 --- /dev/null +++ b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/ssh/SshClient.java @@ -0,0 +1,56 @@ +package cd.casic.ci.process.ssh; + +import java.io.IOException; +import java.util.List; + +/** + * @ClassName ops + * @Description ssh 命令 , 不记录 + * @Author mianbin + * @Date 2022/6/22 15:27 + * @Version 1.0 + **/ +public interface SshClient { + /** + * 执行ssh 命令 + * + * @param command 要执行的命令,多个命令通过";"号隔开 + * @param execCallback 命令执行过程中的回调器 + * @return 执行成功返回0,失败返回1 + * @throws IOException + */ + int exec(String command, ExecCallback execCallback) throws IOException; + /** + * 执行ssh 命令 + * + * @param commands 要执行的命令,多个命令通过";"号隔开 + * @param execCallback 命令执行过程中的回调器 + * @return 执行成功返回0,失败返回1 + * @throws IOException + */ + int execNew(List commands, ExecCallback execCallback) throws IOException; + /** + * 执行ssh 命令 + * + * @param commands 要执行的命令,多个命令通过";"号隔开 + * @param execCallback 命令执行过程中的回调器 + * @return 执行成功返回0,失败返回1 + * @throws IOException + */ + int parallelDebugExec(List commands, ExecCallback execCallback) throws IOException; + + /** + * 执行多行命令 + * + * @param command + * @param execCallback + * @return + * @throws IOException + */ + int exec(List command, ExecCallback execCallback) throws IOException; + + /** + * 断开客户端连接 + */ + void disconnect(); +} diff --git a/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/ssh/SshClientFactory.java b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/ssh/SshClientFactory.java new file mode 100644 index 00000000..dc63dd68 --- /dev/null +++ b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/ssh/SshClientFactory.java @@ -0,0 +1,45 @@ +package cd.casic.ci.process.ssh; + + +import cd.casic.ci.process.process.dataObject.machine.MachineInfo; +import com.jcraft.jsch.JSchException; + +/** + * @ClassName ops + * @Description ssh 工厂 ,这里主要使用的是这个,做个封装 + * @Author mianbin + * @Date 2022/6/22 15:40 + * @Version 1.0 + **/ +public class SshClientFactory { + + /** + * 基于用户名密码认证创建客户端 + * @param username 用户名 + * @param password 密码 + * @param host 主机ip + * @param port ssh端口号 + * @return + */ + public static SshClient createSsh(String username, String password, String host, int port) throws Exception { + try { + return new SshCommand(username, password, host, port); + } catch (JSchException e) { + throw new Exception("创建ssh客户端出错", e); + } + } + /** + * + * 用户名密码或私钥连接客户端 + * @param properties 配置参数 + * 主要包含 用户名 密码 私钥 主机ip 端口 认证类型 + * + * */ + public static SshClient createSsh(MachineInfo properties) throws Exception { + try { + return new SshCommand(properties); + } catch (JSchException e) { + throw new Exception("创建ssh客户端出错", e); + } + } +} diff --git a/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/ssh/SshCommand.java b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/ssh/SshCommand.java new file mode 100644 index 00000000..25cd34c7 --- /dev/null +++ b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/ssh/SshCommand.java @@ -0,0 +1,253 @@ +package cd.casic.ci.process.ssh; + + +import cd.casic.ci.process.constant.CommandConstant; +import cd.casic.ci.process.process.dataObject.machine.MachineInfo; +import cd.casic.ci.process.util.ChannelShellUtil; +import cd.casic.ci.process.util.CryptogramUtil; +import cn.hutool.extra.ssh.JschUtil; +import com.jcraft.jsch.ChannelShell; +import com.jcraft.jsch.JSchException; +import com.jcraft.jsch.Session; + +import java.io.*; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * @ClassName ops + * @Description ssh 的命令操作的集合 ,这里是做个统一,其他pipleline 和web ternminal 的实现方式不同 + * @Author mianbin + * @Date 2022/6/22 15:43 + * @Version 1.0 + **/ +public class SshCommand implements SshClient { + + private static final int BUFF_SIZE = 1024 * 8; // 8KB + + /** + * jsch session + */ + Session session; + + + /** + * 基于用户名密码认证的构造器 + * + * @param username 用户名 + * @param password 密码 + * @param host 主机ip + * @param port ssh端口号 + * @throws JSchException + */ + + public SshCommand(String username, String password, String host, int port) throws JSchException { + this.session = JschUtil.createSession(host, port, username, password); + this.session.setConfig("PreferredAuthentications", "password"); + this.session.setConfig("StrictHostKeyChecking", "no"); + this.session.setPassword(password); + this.session.connect(); + } + + /** + * 基于用户名密码认证的构造器 + * Machine实体类 + * + * @throws JSchException + */ + public SshCommand(MachineInfo machine) throws JSchException { + // 根据用户名,主机ip,端口获取一个Session对象 + String decrypt = CryptogramUtil.doDecrypt(machine.getPassword()); + this.session = JschUtil.createSession(machine.getMachineHost(), machine.getSshPort(), machine.getUsername(), decrypt); + this.session.setConfig("PreferredAuthentications", "password"); + this.session.setConfig("StrictHostKeyChecking", "no"); + // 通过Session建立链接 + this.session.connect(); + } + + @Override + public void disconnect() { + if(session!=null) { + session.disconnect(); + } + } + + @Override + public int exec(String command, ExecCallback execCallback) throws IOException { + + ChannelShell channel = null; + try { + channel = (ChannelShell) session.openChannel("shell"); + channel.connect(); + } catch (JSchException e) { + e.printStackTrace(); + } + + InputStream inputStream = channel.getInputStream(); + OutputStream outputStream = channel.getOutputStream(); + PrintWriter printWriter = new PrintWriter(outputStream); + + printWriter.println(command); + + printWriter.println("exit"); + printWriter.flush(); + BufferedReader in = new BufferedReader(new InputStreamReader(inputStream)); + + String msg = null; + while ((msg = in.readLine()) != null) { + execCallback.callback(msg); + } + int result = channel.getExitStatus(); + + in.close(); + + return result; + } + + + @Override + public int exec(List commands, ExecCallback execCallback) throws IOException { + + ChannelShell channel = null; + try { + channel = (ChannelShell) session.openChannel("shell"); + channel.connect(); + } catch (JSchException e) { + e.printStackTrace(); + } + + assert channel != null; + ChannelShellUtil.setDefault(channel); + InputStream inputStream = channel.getInputStream(); + OutputStream outputStream = channel.getOutputStream(); + InputStreamReader isr = new InputStreamReader(inputStream); + BufferedReader br = new BufferedReader(isr, BUFF_SIZE); + PrintStream commander = new PrintStream(outputStream, true); + + for (String command : commands) { + commander.append(command).append(CommandConstant.ENTER); + } + commander.append("exit" + CommandConstant.ENTER); + + try { + char[] buff = new char[BUFF_SIZE]; + int read; + while ((read = br.read(buff)) != -1) { + String output = String.valueOf(buff, 0, read); + execCallback.callback( output + CommandConstant.ENTER); + TimeUnit.MILLISECONDS.sleep(10L); + // 检测输出,如果满足跳出条件则退出循环 + if (output.contains("# exit")||output.contains("$ exit")) { + commander.append("exit" + CommandConstant.ENTER); + TimeUnit.MILLISECONDS.sleep(1000L); + break; + } + } + } catch (Exception ex) { + ex.printStackTrace(); + } + inputStream.close(); + outputStream.close(); + channel.disconnect(); + int result = channel.getExitStatus(); + commander.close(); + isr.close(); + br.close(); + + return result; + } + + @Override + public int execNew(List commands, ExecCallback execCallback) throws IOException { + + ChannelShell channel = null; + try { + channel = (ChannelShell) session.openChannel("shell"); + channel.connect(); + } catch (JSchException e) { + e.printStackTrace(); + } + + assert channel != null; + ChannelShellUtil.setDefault(channel); + InputStream inputStream = channel.getInputStream(); + OutputStream outputStream = channel.getOutputStream(); + InputStreamReader isr = new InputStreamReader(inputStream); + BufferedReader br = new BufferedReader(isr, BUFF_SIZE); + PrintStream commander = new PrintStream(outputStream, true); + + for (String command : commands) { + commander.append(command).append(CommandConstant.ENTER); + } + commander.append("exit" + CommandConstant.ENTER); + + try { + char[] buff = new char[BUFF_SIZE]; + int read; + while ((read = br.read(buff)) != -1) { + execCallback.callback(String.valueOf(buff, 0, read) + CommandConstant.ENTER); + TimeUnit.MILLISECONDS.sleep(10L); + } + } catch (Exception ex) { + ex.printStackTrace(); + } + + inputStream.close(); + outputStream.close(); + channel.disconnect(); + int result = channel.getExitStatus(); + commander.close(); + isr.close(); + br.close(); + + return result; + } + @Override + public int parallelDebugExec(List commands, ExecCallback execCallback) throws IOException { + + ChannelShell channel = null; + try { + channel = (ChannelShell) session.openChannel("shell"); + channel.connect(); + } catch (JSchException e) { + e.printStackTrace(); + } + + assert channel != null; + ChannelShellUtil.setDefault(channel); + InputStream inputStream = channel.getInputStream(); + OutputStream outputStream = channel.getOutputStream(); + InputStreamReader isr = new InputStreamReader(inputStream); + BufferedReader br = new BufferedReader(isr, BUFF_SIZE); + PrintStream commander = new PrintStream(outputStream, true); + + for (String command : commands) { + commander.append(command).append(CommandConstant.ENTER); + } + + try { + char[] buff = new char[BUFF_SIZE]; + int read; + while ((read = br.read(buff)) != -1) { + String msg = String.valueOf(buff, 0, read); + execCallback.callback(msg + CommandConstant.ENTER); + TimeUnit.MILLISECONDS.sleep(20L); + if(msg.endsWith("$ ") || msg.endsWith("# ")) { + commander.append("exit").append(CommandConstant.ENTER); + } + } + } catch (Exception ex) { + ex.printStackTrace(); + } + int result = channel.getExitStatus(); + + inputStream.close(); + outputStream.close(); + channel.disconnect(); + commander.close(); + isr.close(); + br.close(); + + return result; + } +} diff --git a/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/ssh/WinRMHelper.java b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/ssh/WinRMHelper.java new file mode 100644 index 00000000..14fa8560 --- /dev/null +++ b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/ssh/WinRMHelper.java @@ -0,0 +1,168 @@ +package cd.casic.ci.process.ssh; + +import cd.casic.ci.process.process.dataObject.machine.MachineInfo; +import cd.casic.ci.process.util.CryptogramUtil; +import io.cloudsoft.winrm4j.client.WinRmClientContext; +import io.cloudsoft.winrm4j.winrm.WinRmTool; +import io.cloudsoft.winrm4j.winrm.WinRmToolResponse; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; + +import java.io.*; +import java.util.List; + +/** + * @author herenbin + * @date 2023/6/26 + */ +@Slf4j +public class WinRMHelper { + + private final String ip; + + private final String username; + + private final String password; + + public static final int DEFAULT_PORT = WinRmTool.DEFAULT_WINRM_PORT; + + public WinRMHelper(final String ip, final String username, final String password) { + this.ip = ip; + this.username = username; + this.password = password; + } + + public WinRMHelper(MachineInfo machineInfo) { + this.ip = machineInfo.getMachineHost(); + this.username = machineInfo.getUsername(); + this.password = CryptogramUtil.doDecrypt(machineInfo.getPassword()); + } + + public int execute(final String command, ExecCallback execCallback) { + WinRmClientContext context = WinRmClientContext.newInstance(); + try { + WinRmTool tool = WinRmTool.Builder.builder(ip, username, password) + .port(DEFAULT_PORT).useHttps(false).context(context).build(); + tool.setOperationTimeout(5000L); + WinRmToolResponse resp = tool.executeCommand(command); + int statusCode = resp.getStatusCode(); + execCallback.callback(resp.getStdOut()); + String stdErr = resp.getStdErr(); + if (StringUtils.isNotEmpty(stdErr)) { + execCallback.callback(stdErr); + } + return statusCode; + } finally { + context.shutdown(); + } + } + + public int execute(final List commandList, ExecCallback execCallback) { + WinRmClientContext context = WinRmClientContext.newInstance(); + WinRmTool tool = WinRmTool.Builder.builder(ip, username, password) + .port(DEFAULT_PORT).useHttps(false).context(context).build(); + tool.setOperationTimeout(5000L); + + WinRmToolResponse resp = tool.executeCommand(commandList); + + StringReader reader = new StringReader(resp.getStdOut() + resp.getStdErr()); + try { + char[] buffer = new char[1024]; + int output; + while ((output = reader.read(buffer)) != -1) { + execCallback.callback(new String(buffer, 0, output)); + } + } catch (Exception e) { + e.printStackTrace(); + } + int statusCode = resp.getStatusCode(); + context.shutdown(); + return statusCode; + } + + public int executePs(final List commandList, ExecCallback execCallback) { + WinRmClientContext context = WinRmClientContext.newInstance(); + WinRmTool tool = WinRmTool.Builder.builder(ip, username, password) + .port(DEFAULT_PORT).useHttps(false).context(context).build(); + tool.setOperationTimeout(5000L); + + WinRmToolResponse resp = tool.executePs(commandList); + + StringReader reader = new StringReader(resp.getStdOut() + resp.getStdErr()); + try { + char[] buffer = new char[1024]; + int output; + while ((output = reader.read(buffer)) != -1) { + execCallback.callback(new String(buffer, 0, output)); + } + } catch (Exception e) { + e.printStackTrace(); + } + int statusCode = resp.getStatusCode(); + context.shutdown(); + return statusCode; + } + + public String execute(final List commandList, ExecCallback execCallback, String pass) { + WinRmClientContext context = WinRmClientContext.newInstance(); + WinRmTool tool = WinRmTool.Builder.builder(ip, username, password) + .port(DEFAULT_PORT).useHttps(false).context(context).build(); + tool.setOperationTimeout(5000L); + WinRmToolResponse resp = tool.executeCommand(commandList); + context.shutdown(); + return resp.getStdOut(); + } + + private String join(List commands) { + StringBuilder builder = new StringBuilder(); + boolean first = true; + for (String command : commands) { + if (first) { + first = false; + } else { + builder.append(" & "); + } + builder.append(command); + } + return builder.toString(); + } + + public static void main(String[] args) throws IOException { +// WinRMHelper exec = new WinRMHelper("192.168.0.88", "hrb", "1qaz!QAZ"); +// List commands = new ArrayList<>(); +//// commands.add("powershell"); +// commands.add("dir"); +// commands.add("cd /d D:\\Users\\hrb\\Desktop"); +// commands.add("dir"); +// commands.add("git clone -b master http://192.168.0.12:3000/liuyuchao/testrepo.git"); +//// commands.add("rmdir /s /q 456123"); +// +// int resp = exec.execute(commands, System.out::println); +// System.out.println(resp); + PipedWriter writer = new PipedWriter(); + PipedReader reader = new PipedReader(writer); + + Thread t = new Thread(() -> { + try { + for (int i = 0; i < 10; i++) { + writer.write("Line " + i + "\n"); + writer.flush(); + Thread.sleep(500); + } + writer.close(); + } catch (Exception e) { + e.printStackTrace(); + } + }); + t.start(); + + try (BufferedReader br = new BufferedReader(reader)) { + String line; + while ((line = br.readLine()) != null) { + System.out.println(line); + } + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/util/ChannelShellUtil.java b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/util/ChannelShellUtil.java new file mode 100644 index 00000000..d2c8c50d --- /dev/null +++ b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/util/ChannelShellUtil.java @@ -0,0 +1,14 @@ +package cd.casic.ci.process.util; + +import com.jcraft.jsch.ChannelShell; + +public class ChannelShellUtil { + + public static void setDefault(ChannelShell channel) { + // channel.setEnv("LANG", "zh_CN.UTF-8"); + channel.setEnv("LANG", "en_US.UTF-8"); + // SSH 代理转发 + channel.setAgentForwarding(false); + channel.setPtyType("xterm"); + } +} diff --git a/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/util/CryptogramUtil.java b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/util/CryptogramUtil.java new file mode 100644 index 00000000..20cc788a --- /dev/null +++ b/modules/module-ci-process-biz/src/main/java/cd/casic/ci/process/util/CryptogramUtil.java @@ -0,0 +1,135 @@ +package cd.casic.ci.process.util; + + +import cn.hutool.log.Log; +import com.antherd.smcrypto.sm2.Sm2; +import com.antherd.smcrypto.sm3.Sm3; +import com.antherd.smcrypto.sm4.Sm4; +import com.antherd.smcrypto.sm4.Sm4Options; + +/** + * 加密工具类,本框架目前使用 https://github.com/antherd/sm-crypto 项目中一些加解密方式 + * 使用小伙伴需要过等保密评相关,请在此处更改为自己的加密方法,或加密机,使用加密机同时需要替换公钥,私钥在内部无法导出,提供加密的方法 + * + * @author yubaoshan + */ +public class CryptogramUtil { + + private static final Log log = Log.get(); + + /** + * 加密方法(Sm2 的专门针对前后端分离,非对称秘钥对的方式,暴露出去的公钥,对传输过程中的密码加个密) + * + * @param str 待加密数据 + * @return 加密后的密文 + * @author yubaoshan + */ + public static String doSm2Encrypt(String str) { + return Sm2.doEncrypt(str, Keypair.PUBLIC_KEY); + } + + /** + * 解密方法 + * 如果采用加密机的方法,用try catch 捕捉异常,返回原文值即可 + * + * @param str 密文 + * @return 解密后的明文 + * @author yubaoshan + */ + public static String doSm2Decrypt(String str) { + // 解密 + return Sm2.doDecrypt(str, Keypair.PRIVATE_KEY); + } + + /** + * 加密方法 + * + * @param str 待加密数据 + * @return 加密后的密文 + * @author yubaoshan + */ + public static String doEncrypt(String str) { + // SM4 加密 cbc模式 + Sm4Options sm4Options4 = new Sm4Options(); + sm4Options4.setMode("cbc"); + sm4Options4.setIv("fedcba98765432100123456789abcdef"); + return Sm4.encrypt(str, Keypair.KEY, sm4Options4); + } + + /** + * 解密方法 + * 如果采用加密机的方法,用try catch 捕捉异常,返回原文值即可 + * + * @param str 密文 + * @return 解密后的明文 + * @author yubaoshan + */ + public static String doDecrypt(String str) { + // 解密,cbc 模式,输出 utf8 字符串 + Sm4Options sm4Options8 = new Sm4Options(); + sm4Options8.setMode("cbc"); + sm4Options8.setIv("fedcba98765432100123456789abcdef"); + String docString = Sm4.decrypt(str, Keypair.KEY, sm4Options8); + if (docString.equals("")) { + log.warn(">>> 字段解密失败,返回原文值:{}", str); + return str; + } else { + return docString; + } + } + + /** + * 纯签名 + * + * @param str 待签名数据 + * @return 签名结果 + * @author yubaoshan + */ + public static String doSignature(String str) { + return Sm2.doSignature(str, Keypair.PRIVATE_KEY); + } + + /** + * 验证签名结果 + * + * @param originalStr 签名原文数据 + * @param str 签名结果 + * @return 是否通过 + * @author yubaoshan + */ + public static boolean doVerifySignature(String originalStr, String str) { + return Sm2.doVerifySignature(originalStr, str, Keypair.PUBLIC_KEY); + } + + /** + * 通过杂凑算法取得hash值,用于做数据完整性保护 + * + * @param str 字符串 + * @return hash 值 + * @author yubaoshan + */ + public static String doHashValue(String str) { + return Sm3.sm3(str); + } + + private static class Keypair{ + + /** + * 公钥 + */ + public static String PUBLIC_KEY = "04298364ec840088475eae92a591e01284d1abefcda348b47eb324bb521bb03b0b2a5bc393f6b71dabb8f15c99a0050818b56b23f31743b93df9cf8948f15ddb54"; + + /** + * 私钥 + */ + public static String PRIVATE_KEY = "3037723d47292171677ec8bd7dc9af696c7472bc5f251b2cec07e65fdef22e25"; + + /** + * SM4的对称秘钥(生产环境需要改成自己使用的) + * 16 进制字符串,要求为 128 比特 + */ + public static String KEY = "0123456789abcdeffedcba9876543210"; + + } + +} diff --git a/modules/module-system-biz/src/main/java/cd/casic/module/system/convert/dict/DictDataConvert.java b/modules/module-system-biz/src/main/java/cd/casic/module/system/convert/dict/DictDataConvert.java index 83918554..bf40288a 100644 --- a/modules/module-system-biz/src/main/java/cd/casic/module/system/convert/dict/DictDataConvert.java +++ b/modules/module-system-biz/src/main/java/cd/casic/module/system/convert/dict/DictDataConvert.java @@ -14,7 +14,7 @@ public interface DictDataConvert { DictDataConvert INSTANCE = Mappers.getMapper(DictDataConvert.class); - List convertList(List list); +// List convertList(List list); DictDataRespVO convert(DictDataDO bean); diff --git a/modules/module-system-biz/src/main/java/cd/casic/module/system/convert/dict/DictTypeConvert.java b/modules/module-system-biz/src/main/java/cd/casic/module/system/convert/dict/DictTypeConvert.java index db3eaf71..62964554 100644 --- a/modules/module-system-biz/src/main/java/cd/casic/module/system/convert/dict/DictTypeConvert.java +++ b/modules/module-system-biz/src/main/java/cd/casic/module/system/convert/dict/DictTypeConvert.java @@ -27,5 +27,6 @@ public interface DictTypeConvert { // List convertList02(List list); List convertList03(List list); + DictDataTreeVO converter03(DictTypeDO typeDO); }