🔨 批量执行.

This commit is contained in:
lijiahang
2024-03-11 18:46:23 +08:00
parent f885ae7599
commit 5349237fb3
16 changed files with 404 additions and 24 deletions

View File

@@ -87,4 +87,6 @@ public interface ErrorMessage {
String ILLEGAL_STATUS = "当前状态不支持此操作"; String ILLEGAL_STATUS = "当前状态不支持此操作";
String CHECK_AUTHORIZED_HOST = "请选择已授权的主机";
} }

View File

@@ -185,7 +185,7 @@ orion:
local: local:
primary: true primary: true
enabled: true enabled: true
timestamp-prefix: true timestamp-prefix: false
storage-path: ${user.home} storage-path: ${user.home}
base-path: /orion/storage/orion-ops-pro base-path: /orion/storage/orion-ops-pro
security: security:

View File

@@ -4,6 +4,7 @@ import com.orion.ops.framework.biz.operator.log.core.annotation.OperatorLog;
import com.orion.ops.framework.web.core.annotation.RestWrapper; import com.orion.ops.framework.web.core.annotation.RestWrapper;
import com.orion.ops.module.asset.define.operator.ExecOperatorType; import com.orion.ops.module.asset.define.operator.ExecOperatorType;
import com.orion.ops.module.asset.entity.request.exec.ExecRequest; import com.orion.ops.module.asset.entity.request.exec.ExecRequest;
import com.orion.ops.module.asset.entity.vo.ExecVO;
import com.orion.ops.module.asset.service.ExecService; import com.orion.ops.module.asset.service.ExecService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -40,8 +41,8 @@ public class ExecController {
@PostMapping("/start") @PostMapping("/start")
@Operation(summary = "批量执行") @Operation(summary = "批量执行")
@PreAuthorize("@ss.hasPermission('asset:exec:start')") @PreAuthorize("@ss.hasPermission('asset:exec:start')")
public void startExecCommand(@RequestBody ExecRequest request) { public ExecVO startExecCommand(@RequestBody ExecRequest request) {
execService.startExecCommand(request); return execService.startExecCommand(request);
} }
} }

View File

@@ -0,0 +1,31 @@
package com.orion.ops.module.asset.entity.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* 批量执行启动对象
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/3/11 15:46
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "ExecStartDTO", description = "批量执行启动对象")
public class ExecStartDTO {
@Schema(description = "hostId")
private Long logId;
@Schema(description = "主机")
private List<ExecStartHostDTO> hosts;
}

View File

@@ -0,0 +1,35 @@
package com.orion.ops.module.asset.entity.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 批量执行启动主机对象
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/3/11 15:46
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "ExecStartHostDTO", description = "批量执行启动主机对象")
public class ExecStartHostDTO {
@Schema(description = "hostLogId")
private Long hostLogId;
@Schema(description = "hostId")
private Long hostId;
@Schema(description = "日志文件路径")
private String logPath;
@Schema(description = "执行命令")
private String command;
}

View File

@@ -8,7 +8,6 @@ import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import java.io.Serializable; import java.io.Serializable;
import java.util.Date;
/** /**
* 执行模板 缓存对象 * 执行模板 缓存对象
@@ -41,16 +40,4 @@ public class ExecTemplateCacheDTO implements LongCacheIdModel, Serializable {
@Schema(description = "参数") @Schema(description = "参数")
private String parameter; private String parameter;
@Schema(description = "创建时间")
private Date createTime;
@Schema(description = "修改时间")
private Date updateTime;
@Schema(description = "创建人")
private String creator;
@Schema(description = "修改人")
private String updater;
} }

View File

@@ -4,6 +4,7 @@ import com.orion.ops.framework.common.entity.PageRequest;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*; import lombok.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Size; import javax.validation.constraints.Size;
import java.util.List; import java.util.List;
@@ -23,16 +24,19 @@ import java.util.List;
@Schema(name = "ExecRequest", description = "批量执行 请求对象") @Schema(name = "ExecRequest", description = "批量执行 请求对象")
public class ExecRequest extends PageRequest { public class ExecRequest extends PageRequest {
@Schema(description = "执行模板id")
private Long templateId;
@NotBlank
@Size(max = 128) @Size(max = 128)
@Schema(description = "执行描述") @Schema(description = "执行描述")
private String desc; private String desc;
@Schema(description = "执行模板id") @NotBlank
private Long templateId;
@Schema(description = "执行命令") @Schema(description = "执行命令")
private String command; private String command;
@NotBlank
@Schema(description = "执行参数") @Schema(description = "执行参数")
private String parameter; private String parameter;

View File

@@ -0,0 +1,32 @@
package com.orion.ops.module.asset.entity.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Map;
/**
* 批量执行 视图响应对象
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/3/11 14:57
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "ExecVO", description = "批量执行 视图响应对象")
public class ExecVO implements Serializable {
@Schema(description = "id")
private Long id;
@Schema(description = "主机 id 映射 host:id")
private Map<String, Long> hostIdRel;
}

View File

@@ -0,0 +1,51 @@
package com.orion.ops.module.asset.enums;
/**
* 批量执行主机状态
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/3/11 17:08
*/
public enum ExecHostStatusEnum {
/**
* 等待中
*/
WAITING,
/**
* 执行中
*/
RUNNING,
/**
* 执行完成
*/
COMPLETED,
/**
* 执行失败
*/
FAILED,
/**
* 中断执行
*/
INTERRUPTED,
;
public static ExecHostStatusEnum of(String status) {
if (status == null) {
return null;
}
for (ExecHostStatusEnum value : values()) {
if (value.name().equals(status)) {
return value;
}
}
return null;
}
}

View File

@@ -0,0 +1,19 @@
package com.orion.ops.module.asset.enums;
/**
* 批量执行来源
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/3/11 16:00
*/
public enum ExecSourceEnum {
/**
* 批量执行
*/
BATCH,
;
}

View File

@@ -0,0 +1,46 @@
package com.orion.ops.module.asset.enums;
/**
* 批量执行状态
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/3/11 17:08
*/
public enum ExecStatusEnum {
/**
* 等待中
*/
WAITING,
/**
* 执行中
*/
RUNNING,
/**
* 执行完成
*/
COMPLETED,
/**
* 执行失败
*/
FAILED,
;
public static ExecStatusEnum of(String status) {
if (status == null) {
return null;
}
for (ExecStatusEnum value : values()) {
if (value.name().equals(status)) {
return value;
}
}
return null;
}
}

View File

@@ -4,6 +4,7 @@ import com.orion.ops.module.asset.entity.request.asset.AssetAuthorizedDataQueryR
import com.orion.ops.module.asset.entity.vo.AuthorizedHostWrapperVO; import com.orion.ops.module.asset.entity.vo.AuthorizedHostWrapperVO;
import com.orion.ops.module.asset.entity.vo.HostIdentityVO; import com.orion.ops.module.asset.entity.vo.HostIdentityVO;
import com.orion.ops.module.asset.entity.vo.HostKeyVO; import com.orion.ops.module.asset.entity.vo.HostKeyVO;
import com.orion.ops.module.asset.enums.HostConfigTypeEnum;
import com.orion.ops.module.infra.enums.DataPermissionTypeEnum; import com.orion.ops.module.infra.enums.DataPermissionTypeEnum;
import java.util.List; import java.util.List;
@@ -27,7 +28,16 @@ public interface AssetAuthorizedDataService {
List<Long> getAuthorizedDataRelId(DataPermissionTypeEnum type, AssetAuthorizedDataQueryRequest request); List<Long> getAuthorizedDataRelId(DataPermissionTypeEnum type, AssetAuthorizedDataQueryRequest request);
/** /**
* 查询用户已授权的主机主机 * 查询用户已授权的主机
*
* @param userId userId
* @param type type
* @return hostId
*/
List<Long> getUserAuthorizedHostId(Long userId, HostConfigTypeEnum type);
/**
* 查询用户已授权的主机
* *
* @param userId userId * @param userId userId
* @param type type * @param type type

View File

@@ -1,6 +1,7 @@
package com.orion.ops.module.asset.service; package com.orion.ops.module.asset.service;
import com.orion.ops.module.asset.entity.request.exec.ExecRequest; import com.orion.ops.module.asset.entity.request.exec.ExecRequest;
import com.orion.ops.module.asset.entity.vo.ExecVO;
/** /**
* 批量执行服务 * 批量执行服务
@@ -15,7 +16,8 @@ public interface ExecService {
* 批量执行 * 批量执行
* *
* @param request request * @param request request
* @return result
*/ */
void startExecCommand(ExecRequest request); ExecVO startExecCommand(ExecRequest request);
} }

View File

@@ -11,6 +11,7 @@ import com.orion.ops.framework.common.utils.Valid;
import com.orion.ops.module.asset.convert.HostGroupConvert; import com.orion.ops.module.asset.convert.HostGroupConvert;
import com.orion.ops.module.asset.entity.request.asset.AssetAuthorizedDataQueryRequest; import com.orion.ops.module.asset.entity.request.asset.AssetAuthorizedDataQueryRequest;
import com.orion.ops.module.asset.entity.vo.*; import com.orion.ops.module.asset.entity.vo.*;
import com.orion.ops.module.asset.enums.HostConfigTypeEnum;
import com.orion.ops.module.asset.enums.HostConnectTypeEnum; import com.orion.ops.module.asset.enums.HostConnectTypeEnum;
import com.orion.ops.module.asset.handler.host.extra.model.HostColorExtraModel; import com.orion.ops.module.asset.handler.host.extra.model.HostColorExtraModel;
import com.orion.ops.module.asset.service.*; import com.orion.ops.module.asset.service.*;
@@ -90,6 +91,20 @@ public class AssetAuthorizedDataServiceImpl implements AssetAuthorizedDataServic
} }
} }
@Override
public List<Long> getUserAuthorizedHostId(Long userId, HostConfigTypeEnum type) {
final boolean allData = systemUserApi.isAdminUser(userId);
if (allData) {
// 管理员查询所有
return this.getEnabledConfigHostId(true, Maps.empty(), type.name());
} else {
// 其他用户 查询授权的数据
Map<Long, Set<Long>> dataGroupRel = dataGroupRelApi.getGroupRelList(DataGroupTypeEnum.HOST);
// 查询配置启用的主机
return this.getEnabledConfigHostId(false, dataGroupRel, type.name());
}
}
@Override @Override
public AuthorizedHostWrapperVO getUserAuthorizedHost(Long userId, String type) { public AuthorizedHostWrapperVO getUserAuthorizedHost(Long userId, String type) {
if (systemUserApi.isAdminUser(userId)) { if (systemUserApi.isAdminUser(userId)) {

View File

@@ -1,12 +1,42 @@
package com.orion.ops.module.asset.service.impl; package com.orion.ops.module.asset.service.impl;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.orion.lang.id.UUIds;
import com.orion.lang.utils.Strings;
import com.orion.lang.utils.collect.Maps;
import com.orion.lang.utils.json.matcher.NoMatchStrategy;
import com.orion.lang.utils.json.matcher.ReplacementFormatter;
import com.orion.lang.utils.json.matcher.ReplacementFormatters;
import com.orion.lang.utils.time.Dates;
import com.orion.ops.framework.common.constant.ErrorMessage;
import com.orion.ops.framework.common.security.LoginUser;
import com.orion.ops.framework.common.utils.Valid;
import com.orion.ops.framework.security.core.utils.SecurityUtils;
import com.orion.ops.module.asset.dao.ExecHostLogDAO;
import com.orion.ops.module.asset.dao.ExecLogDAO; import com.orion.ops.module.asset.dao.ExecLogDAO;
import com.orion.ops.module.asset.dao.HostDAO;
import com.orion.ops.module.asset.entity.domain.ExecHostLogDO;
import com.orion.ops.module.asset.entity.domain.ExecLogDO;
import com.orion.ops.module.asset.entity.domain.HostDO;
import com.orion.ops.module.asset.entity.request.exec.ExecRequest; import com.orion.ops.module.asset.entity.request.exec.ExecRequest;
import com.orion.ops.module.asset.entity.vo.ExecVO;
import com.orion.ops.module.asset.enums.ExecHostStatusEnum;
import com.orion.ops.module.asset.enums.ExecSourceEnum;
import com.orion.ops.module.asset.enums.ExecStatusEnum;
import com.orion.ops.module.asset.enums.HostConfigTypeEnum;
import com.orion.ops.module.asset.service.AssetAuthorizedDataService;
import com.orion.ops.module.asset.service.ExecService; import com.orion.ops.module.asset.service.ExecService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
/** /**
* 批量执行服务实现 * 批量执行服务实现
@@ -19,12 +49,127 @@ import javax.annotation.Resource;
@Service @Service
public class ExecServiceImpl implements ExecService { public class ExecServiceImpl implements ExecService {
private static final ReplacementFormatter FORMATTER = ReplacementFormatters.create("@{{ ", " }}")
.noMatchStrategy(NoMatchStrategy.EMPTY);
@Resource @Resource
private ExecLogDAO execLogDAO; private ExecLogDAO execLogDAO;
@Override @Resource
public void startExecCommand(ExecRequest request) { private ExecHostLogDAO execHostLogDAO;
@Resource
private HostDAO hostDAO;
@Resource
private AssetAuthorizedDataService assetAuthorizedDataService;
@Override
@Transactional(rollbackFor = Exception.class)
public ExecVO startExecCommand(ExecRequest request) {
log.info("ExecService.startExecCommand start params: {}", JSON.toJSONString(request));
LoginUser user = Objects.requireNonNull(SecurityUtils.getLoginUser());
Long userId = user.getId();
String command = request.getCommand();
List<Long> hostIdList = request.getHostIdList();
// 检查主机权限
List<Long> authorizedHostIdList = assetAuthorizedDataService.getUserAuthorizedHostId(userId, HostConfigTypeEnum.SSH);
hostIdList.removeIf(s -> !authorizedHostIdList.contains(s));
Valid.notEmpty(hostIdList, ErrorMessage.CHECK_AUTHORIZED_HOST);
List<HostDO> hosts = hostDAO.selectBatchIds(hostIdList);
// 插入日志
ExecLogDO execLog = ExecLogDO.builder()
.userId(userId)
.source(ExecSourceEnum.BATCH.name())
.desc(request.getDesc())
.command(command)
.status(ExecStatusEnum.COMPLETED.name())
.build();
execLogDAO.insert(execLog);
Long execId = execLog.getId();
// 获取内置参数
Map<String, Object> builtinsParams = getBaseBuiltinsParams(user, execId, request.getParameter());
// 设置主机日志
List<ExecHostLogDO> execHostLogs = hosts.stream()
.map(s -> {
String parameter = JSON.toJSONString(this.getHostParams(builtinsParams, s));
return ExecHostLogDO.builder()
.logId(execId)
.hostId(s.getId())
.hostName(s.getName())
.status(ExecHostStatusEnum.WAITING.name())
.command(FORMATTER.format(command, parameter))
.parameter(parameter)
.logPath(this.buildLogPath(execId, s.getId()))
.build();
}).collect(Collectors.toList());
execHostLogDAO.insertBatch(execHostLogs);
// TODO 开始执行
// 返回
Map<String, Long> hostIdRel = execHostLogs.stream()
.collect(Collectors.toMap(s -> String.valueOf(s.getHostId()), ExecHostLogDO::getId));
return ExecVO.builder()
.id(execId)
.hostIdRel(hostIdRel)
.build();
}
/**
* 构建日志路径
*
* @param logId logId
* @param hostId hostId
* @return logPath
*/
private String buildLogPath(Long logId, Long hostId) {
return "/logs/exec/" + logId + "/" + hostId + ".log";
}
/**
* 获取基础内置参数
*
* @param user user
* @param execId execId
* @return params
*/
private Map<String, Object> getBaseBuiltinsParams(LoginUser user, Long execId, String inputParam) {
String uuid = UUIds.random();
Date date = new Date();
// 输入参数
JSONObject inputParams = JSON.parseObject(inputParam);
// 内置参数
Map<String, Object> params = Maps.newMap(inputParams);
params.put("userId", user.getId());
params.put("username", user.getId());
params.put("execId", execId);
params.put("uuid", uuid);
params.put("uuidShort", uuid.replace("-", Strings.EMPTY));
params.put("timestampMillis", date.getTime());
params.put("timestamp", date.getTime() / Dates.SECOND_STAMP);
params.put("date", Dates.format(date, Dates.YMD));
params.put("datetime", Dates.format(date, Dates.YMD_HMS));
return params;
}
/**
* 获取主机参数
*
* @param baseParams baseParams
* @param host host
* @return params
*/
private Map<String, Object> getHostParams(Map<String, Object> baseParams, HostDO host) {
String uuid = UUIds.random();
Map<String, Object> params = Maps.newMap(baseParams);
params.put("hostId", host.getId());
params.put("hostName", host.getName());
params.put("hostCode", host.getCode());
params.put("hostAddress", host.getAddress());
params.put("hostUuid", uuid);
params.put("hostUuidShort", uuid.replace("-", Strings.EMPTY));
return params;
} }
} }

View File

@@ -17,7 +17,7 @@ public enum DataPermissionTypeEnum {
/** /**
* 主机分组 * 主机分组
*/ */
HOST_GROUP(true, "主机"), HOST_GROUP(true, "主机分组"),
/** /**
* 主机秘钥 * 主机秘钥