org.dromara.jpom.service.node.ssh.SshCommandService Maven / Gradle / Ivy
/*
* Copyright (c) 2019 Of Him Code Technology Studio
* Jpom is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.dromara.jpom.service.node.ssh;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.cron.task.Task;
import cn.hutool.extra.ssh.JschUtil;
import cn.hutool.system.SystemUtil;
import cn.keepbx.jpom.cron.ICron;
import com.jcraft.jsch.Session;
import lombok.extern.slf4j.Slf4j;
import org.dromara.jpom.common.BaseServerController;
import org.dromara.jpom.common.i18n.I18nMessageUtil;
import org.dromara.jpom.common.i18n.I18nThreadUtil;
import org.dromara.jpom.cron.CronUtils;
import org.dromara.jpom.func.assets.model.MachineSshModel;
import org.dromara.jpom.func.assets.server.ScriptLibraryServer;
import org.dromara.jpom.model.EnvironmentMapBuilder;
import org.dromara.jpom.model.data.CommandExecLogModel;
import org.dromara.jpom.model.data.CommandModel;
import org.dromara.jpom.model.data.SshModel;
import org.dromara.jpom.model.user.UserModel;
import org.dromara.jpom.plugins.JschUtils;
import org.dromara.jpom.script.CommandParam;
import org.dromara.jpom.service.ITriggerToken;
import org.dromara.jpom.service.h2db.BaseWorkspaceService;
import org.dromara.jpom.service.system.WorkspaceEnvVarService;
import org.dromara.jpom.util.LogRecorder;
import org.dromara.jpom.util.StrictSyncFinisher;
import org.dromara.jpom.util.StringUtil;
import org.dromara.jpom.util.SyncFinisherUtil;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Map;
/**
* 命令管理
*
* @author : Arno
* @since : 2021/12/6 22:11
*/
@Service
@Slf4j
public class SshCommandService extends BaseWorkspaceService implements ICron, ITriggerToken {
private final SshService sshService;
private final CommandExecLogService commandExecLogService;
private final WorkspaceEnvVarService workspaceEnvVarService;
private final ScriptLibraryServer scriptLibraryServer;
private static final byte[] LINE_BYTES = SystemUtil.getOsInfo().getLineSeparator().getBytes(CharsetUtil.CHARSET_UTF_8);
public SshCommandService(SshService sshService,
CommandExecLogService commandExecLogService,
WorkspaceEnvVarService workspaceEnvVarService,
ScriptLibraryServer scriptLibraryServer) {
this.sshService = sshService;
this.commandExecLogService = commandExecLogService;
this.workspaceEnvVarService = workspaceEnvVarService;
this.scriptLibraryServer = scriptLibraryServer;
}
@Override
public int insert(CommandModel commandModel) {
int count = super.insert(commandModel);
this.checkCron(commandModel);
return count;
}
@Override
public int updateById(CommandModel info, HttpServletRequest request) {
int update = super.updateById(info, request);
if (update > 0) {
this.checkCron(info);
}
return update;
}
@Override
public int delByKey(String keyValue, HttpServletRequest request) {
int delByKey = super.delByKey(keyValue, request);
if (delByKey > 0) {
String taskId = "ssh_command:" + keyValue;
CronUtils.remove(taskId);
}
return delByKey;
}
/**
* 检查定时任务 状态
*
* @param buildInfoModel 构建信息
*/
@Override
public boolean checkCron(CommandModel buildInfoModel) {
String id = buildInfoModel.getId();
String taskId = "ssh_command:" + id;
String autoExecCron = buildInfoModel.getAutoExecCron();
autoExecCron = StringUtil.parseCron(autoExecCron);
if (StrUtil.isEmpty(autoExecCron)) {
CronUtils.remove(taskId);
return false;
}
log.debug("start ssh command cron {} {} {}", id, buildInfoModel.getName(), autoExecCron);
CronUtils.upsert(taskId, autoExecCron, new SshCommandService.CronTask(id));
return true;
}
/**
* 开启定时构建任务
*/
@Override
public List queryStartingList() {
String sql = "select * from " + super.getTableName() + " where autoExecCron is not null and autoExecCron <> ''";
return super.queryList(sql);
}
@Override
public String typeName() {
return getTableName();
}
private class CronTask implements Task {
private final String id;
public CronTask(String id) {
this.id = id;
}
@Override
public void execute() {
try {
BaseServerController.resetInfo(UserModel.EMPTY);
CommandModel commandModel = SshCommandService.this.getByKey(this.id);
SshCommandService.this.executeBatch(commandModel, commandModel.getDefParams(), commandModel.getSshIds(), 1);
} catch (Exception e) {
log.error(I18nMessageUtil.get("i18n.trigger_auto_execute_command_template_exception.4e01"), e);
} finally {
BaseServerController.removeEmpty();
}
}
}
/**
* 批量执行命令
*
* @param id 命令 id
* @param nodes ssh节点
* @param params 参数
* @return 批次ID
*/
public String executeBatch(String id, String params, String nodes) {
CommandModel commandModel = this.getByKey(id);
return this.executeBatch(commandModel, params, nodes, 0);
}
/**
* 批量执行命令
*
* @param commandModel 命令模版
* @param nodes ssh节点
* @param params 参数
* @return 批次ID
*/
public String executeBatch(CommandModel commandModel, String params, String nodes, int triggerExecType) {
return executeBatch(commandModel, params, nodes, triggerExecType, null);
}
/**
* 批量执行命令
*
* @param commandModel 命令模版
* @param nodes ssh节点
* @param params 参数
* @param envMap 环境变量
* @param triggerExecType 触发方式
* @return 批次ID
*/
public String executeBatch(CommandModel commandModel, String params, String nodes, int triggerExecType, Map envMap) {
Assert.notNull(commandModel, I18nMessageUtil.get("i18n.no_corresponding_command.165e"));
List sshIds = StrUtil.split(nodes, StrUtil.COMMA, true, true);
Assert.notEmpty(sshIds, I18nMessageUtil.get("i18n.ssh_node_required.4566"));
String batchId = IdUtil.fastSimpleUUID();
String name = "ssh-command-batch:" + batchId;
StrictSyncFinisher syncFinisher = SyncFinisherUtil.create(name, sshIds.size());
for (String sshId : sshIds) {
this.executeItem(syncFinisher, commandModel, params, sshId, batchId, triggerExecType, envMap);
}
I18nThreadUtil.execute(() -> {
try {
syncFinisher.start();
} catch (Exception e) {
log.error(I18nMessageUtil.get("i18n.ssh_batch_command_execution_exception.029a"), e);
} finally {
SyncFinisherUtil.close(name);
}
});
return batchId;
}
/**
* 准备执行 某一个
*
* @param syncFinisher 线程同步器
* @param commandModel 命令模版
* @param commandParams 参数
* @param sshId ssh id
* @param batchId 批次ID
*/
private void executeItem(StrictSyncFinisher syncFinisher, CommandModel commandModel, String commandParams, String sshId, String batchId, int triggerExecType, Map envMap) {
SshModel sshModel = sshService.getByKey(sshId, false);
CommandExecLogModel commandExecLogModel = new CommandExecLogModel();
commandExecLogModel.setCommandId(commandModel.getId());
commandExecLogModel.setCommandName(commandModel.getName());
commandExecLogModel.setBatchId(batchId);
commandExecLogModel.setSshId(sshId);
commandExecLogModel.setWorkspaceId(commandModel.getWorkspaceId());
commandExecLogModel.setTriggerExecType(triggerExecType);
if (sshModel != null) {
commandExecLogModel.setSshName(sshModel.getName());
} else {
commandExecLogModel.setSshName(I18nMessageUtil.get("i18n.ssh_not_exist.08a2"));
}
commandExecLogModel.setStatus(CommandExecLogModel.Status.ING.getCode());
// 拼接参数
String commandParamsLine = CommandParam.toCommandLine(commandParams);
commandExecLogService.insert(commandExecLogModel);
syncFinisher.addWorker(() -> {
try {
this.execute(commandModel, commandExecLogModel, sshModel, commandParamsLine, envMap);
} catch (Exception e) {
log.error(I18nMessageUtil.get("i18n.command_template_execution_link_exception.51cf"), e);
this.updateStatus(commandExecLogModel.getId(), CommandExecLogModel.Status.SESSION_ERROR);
}
});
}
/**
* 执行命令
*
* @param commandModel 命令模版
* @param commandExecLogModel 执行记录
* @param sshModel ssh
* @param commandParamsLine 参数
*/
private void execute(CommandModel commandModel, CommandExecLogModel commandExecLogModel, SshModel sshModel, String commandParamsLine, Map envMap) {
File file = commandExecLogModel.logFile();
try (LogRecorder logRecorder = LogRecorder.builder().file(file).charset(CharsetUtil.CHARSET_UTF_8).build()) {
if (sshModel == null) {
logRecorder.systemError(I18nMessageUtil.get("i18n.ssh_does_not_exist.88d7"));
this.updateStatus(commandExecLogModel.getId(), CommandExecLogModel.Status.ERROR, -100);
return;
}
EnvironmentMapBuilder environmentMapBuilder = workspaceEnvVarService.getEnv(commandModel.getWorkspaceId());
environmentMapBuilder.put("JPOM_SSH_ID", sshModel.getId());
environmentMapBuilder.put("JPOM_COMMAND_ID", commandModel.getId());
environmentMapBuilder.putStr(envMap);
environmentMapBuilder.eachStr(logRecorder::system);
Map environment = environmentMapBuilder.environment();
String commands = StringUtil.formatStrByMap(commandModel.getCommand(), environment);
// 替换全局脚本
commands = scriptLibraryServer.referenceReplace(commands);
MachineSshModel machineSshModel = sshService.getMachineSshModel(sshModel);
//
Session session = null;
try {
Charset charset = machineSshModel.charset();
int timeout = machineSshModel.timeout();
//
session = sshService.getSessionByModel(machineSshModel);
int exitCode = JschUtils.execCallbackLine(session, charset, timeout, commands, commandParamsLine, logRecorder::info);
logRecorder.system(I18nMessageUtil.get("i18n.exit_code.ea65"), exitCode);
// 更新状态
this.updateStatus(commandExecLogModel.getId(), CommandExecLogModel.Status.DONE, exitCode);
} catch (Exception e) {
log.error(I18nMessageUtil.get("i18n.command_error.d0b4"), e);
// 更新状态
this.updateStatus(commandExecLogModel.getId(), CommandExecLogModel.Status.ERROR);
// 记录错误日志
logRecorder.error(I18nMessageUtil.get("i18n.command_error.d0b4"), e);
} finally {
JschUtil.close(session);
}
}
}
/**
* 修改执行状态
*
* @param id ID
* @param status 状态
*/
private void updateStatus(String id, CommandExecLogModel.Status status) {
this.updateStatus(id, status, null);
}
/**
* 修改执行状态
*
* @param id ID
* @param status 状态
* @param exitCode 退出码
*/
private void updateStatus(String id, CommandExecLogModel.Status status, Integer exitCode) {
CommandExecLogModel commandExecLogModel = new CommandExecLogModel();
commandExecLogModel.setId(id);
commandExecLogModel.setExitCode(exitCode);
commandExecLogModel.setStatus(status.getCode());
commandExecLogService.updateById(commandExecLogModel);
}
/**
* 将ssh 脚本信息同步到其他工作空间
*
* @param ids 多给节点ID
* @param nowWorkspaceId 当前的工作空间ID
* @param workspaceId 同步到哪个工作空间
*/
public void syncToWorkspace(String ids, String nowWorkspaceId, String workspaceId) {
StrUtil.splitTrim(ids, StrUtil.COMMA)
.forEach(id -> {
CommandModel data = super.getByKey(id, false, entity -> entity.set("workspaceId", nowWorkspaceId));
Assert.notNull(data, I18nMessageUtil.get("i18n.no_corresponding_ssh_script_info.1c12"));
//
CommandModel where = new CommandModel();
where.setWorkspaceId(workspaceId);
where.setName(data.getName());
CommandModel exits = super.queryByBean(where);
if (exits == null) {
// 不存在则添加 信息
data.setId(null);
data.setWorkspaceId(workspaceId);
data.setCreateTimeMillis(null);
data.setModifyTimeMillis(null);
data.setSshIds(null);
data.setModifyUser(null);
super.insert(data);
} else {
// 修改信息
CommandModel update = new CommandModel();
update.setId(exits.getId());
update.setCommand(data.getCommand());
update.setDesc(data.getDesc());
update.setDefParams(data.getDefParams());
update.setAutoExecCron(data.getAutoExecCron());
super.updateById(update);
}
});
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy