- command type 이 deploy 타입일 경우 파일 유효성 검사 로직 추가

This commit is contained in:
hyunjujeong 2023-09-06 14:49:10 +09:00
parent c625560cc6
commit 15be9161f3
31 changed files with 327 additions and 127 deletions

View File

@ -1,7 +1,11 @@
package inc.sdt.blokworks.devicedeployer; package inc.sdt.blokworks.devicedeployer;
import inc.sdt.blokworks.devicedeployer.presentation.rest.RestTemplateResponseErrorHandler;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication @SpringBootApplication
public class DeviceDeployerApplication { public class DeviceDeployerApplication {
@ -10,4 +14,11 @@ public class DeviceDeployerApplication {
SpringApplication.run(DeviceDeployerApplication.class, args); SpringApplication.run(DeviceDeployerApplication.class, args);
} }
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder
.errorHandler(new RestTemplateResponseErrorHandler())
.build();
}
} }

View File

@ -8,7 +8,8 @@ import java.util.LinkedHashMap;
@Component @Component
public class BashCommand implements CommandInfo{ public class BashCommand implements CommandInfo{
@Override @Override
public LinkedHashMap<String, Object> put(OutboundMessage message, LinkedHashMap<String, Object> map) { public LinkedHashMap<String, Object> put(OutboundMessage message) {
LinkedHashMap<String, Object> map = new LinkedHashMap<>();
map.put("cmd", message.getCommand()); map.put("cmd", message.getCommand());
return map; return map;
} }

View File

@ -5,5 +5,5 @@ import inc.sdt.blokworks.devicedeployer.domain.OutboundMessage;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
public interface CommandInfo { public interface CommandInfo {
LinkedHashMap<String, Object> put(OutboundMessage message, LinkedHashMap<String, Object> map); LinkedHashMap<String, Object> put(OutboundMessage message);
} }

View File

@ -1,6 +1,7 @@
package inc.sdt.blokworks.devicedeployer.application; package inc.sdt.blokworks.devicedeployer.application;
import inc.sdt.blokworks.devicedeployer.domain.OutboundMessage; import inc.sdt.blokworks.devicedeployer.domain.OutboundMessage;
import inc.sdt.blokworks.devicedeployer.presentation.GiteaApiRequestHandler;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
@ -13,7 +14,11 @@ public class CommandInvoker {
private final JsonCommand jsonCommand; private final JsonCommand jsonCommand;
private final DeployCommandInvoker deployCommandInvoker; private final DeployCommandInvoker deployCommandInvoker;
public CommandInvoker(BashCommand bashCommand, SystemdCommand systemdCommand, DockerCommand dockerCommand, JsonCommand jsonCommand, DeployCommandInvoker deployCommandInvoker) { public CommandInvoker(BashCommand bashCommand,
SystemdCommand systemdCommand,
DockerCommand dockerCommand,
JsonCommand jsonCommand,
DeployCommandInvoker deployCommandInvoker) {
this.bashCommand = bashCommand; this.bashCommand = bashCommand;
this.systemdCommand = systemdCommand; this.systemdCommand = systemdCommand;
this.dockerCommand = dockerCommand; this.dockerCommand = dockerCommand;
@ -21,22 +26,25 @@ public class CommandInvoker {
this.deployCommandInvoker = deployCommandInvoker; this.deployCommandInvoker = deployCommandInvoker;
} }
public LinkedHashMap<String, Object> invoke(OutboundMessage message, LinkedHashMap<String, Object> map) { public LinkedHashMap<String, Object> invoke(OutboundMessage message) {
if(message.getCommandType() == null) {
throw new IllegalArgumentException();
}
switch (message.getCommandType()) { switch (message.getCommandType()) {
case bash -> { case bash -> {
return bashCommand.put(message, map); return bashCommand.put(message);
} }
case systemd -> { case systemd -> {
return systemdCommand.put(message, map); return systemdCommand.put(message);
} }
case docker -> { case docker -> {
return dockerCommand.put(message, map); return dockerCommand.put(message);
} }
case deploy -> { case deploy -> {
return deployCommandInvoker.invoke(message, map); return deployCommandInvoker.invoke(message);
} }
default -> { default -> {
return jsonCommand.put(message, map); return jsonCommand.put(message);
} }
} }
} }

View File

@ -5,12 +5,12 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import inc.sdt.blokworks.devicedeployer.domain.*; import inc.sdt.blokworks.devicedeployer.domain.*;
import inc.sdt.blokworks.devicedeployer.infrastructure.mqtt.InboundDeployMessagePayload; import inc.sdt.blokworks.devicedeployer.infrastructure.mqtt.InboundDeployMessagePayload;
import inc.sdt.blokworks.devicedeployer.infrastructure.mqtt.OutboundMessagePayload; import inc.sdt.blokworks.devicedeployer.infrastructure.mqtt.OutboundMessagePayload;
import inc.sdt.blokworks.devicedeployer.presentation.GiteaApiRequestHandler;
import org.eclipse.paho.client.mqttv3.IMqttClient; import org.eclipse.paho.client.mqttv3.IMqttClient;
import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage; import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
@ -25,36 +25,27 @@ public class DefaultDeployerService implements DeployerService{
private final ObjectMapper objectMapper; private final ObjectMapper objectMapper;
private final DeployerRepositoryDelegate deployerRepositoryDelegate; private final DeployerRepositoryDelegate deployerRepositoryDelegate;
private final DeployRequestRepositoryDelegate requestRepositoryDelegate; private final DeployRequestRepositoryDelegate requestRepositoryDelegate;
private final CommandInvoker commandInvoker;
public DefaultDeployerService(IMqttClient mqttClient, public DefaultDeployerService(IMqttClient mqttClient,
ObjectMapper objectMapper, ObjectMapper objectMapper,
DeployerRepositoryDelegate deployerRepositoryDelegate, DeployerRepositoryDelegate deployerRepositoryDelegate,
DeployRequestRepositoryDelegate requestRepositoryDelegate, DeployRequestRepositoryDelegate requestRepositoryDelegate) {
CommandInvoker commandInvoker) {
this.log = LoggerFactory.getLogger(this.getClass()); this.log = LoggerFactory.getLogger(this.getClass());
this.mqttClient = mqttClient; this.mqttClient = mqttClient;
this.objectMapper = objectMapper; this.objectMapper = objectMapper;
this.deployerRepositoryDelegate = deployerRepositoryDelegate; this.deployerRepositoryDelegate = deployerRepositoryDelegate;
this.requestRepositoryDelegate = requestRepositoryDelegate; this.requestRepositoryDelegate = requestRepositoryDelegate;
this.commandInvoker = commandInvoker;
} }
@Override @Override
public void publish(OutboundMessage outboundMessage, String assetCode) { public void publish(OutboundMessage outboundMessage) {
log.info("[publish] deployMessage = {}, assetCode = {}", outboundMessage, assetCode); log.info("[publish] outboundMessage = {}", outboundMessage);
final DeviceType deviceType = outboundMessage.getDeviceType();
LinkedHashMap<String, Object> commandInfo = new LinkedHashMap<>();
try { try {
commandInfo = commandInvoker.invoke(outboundMessage, commandInfo);
OutboundMessagePayload payload = new OutboundMessagePayload( OutboundMessagePayload payload = new OutboundMessagePayload(
commandInfo, outboundMessage.getCommandInfo(),
outboundMessage.getCommandType(), outboundMessage.getCommandType(),
outboundMessage.getSubCommandType(), outboundMessage.getSubCommandType(),
deviceType, outboundMessage.getAssetCode(),
assetCode,
outboundMessage.getRequestId() outboundMessage.getRequestId()
); );
@ -62,8 +53,8 @@ public class DefaultDeployerService implements DeployerService{
MqttMessage message = new MqttMessage(); MqttMessage message = new MqttMessage();
message.setPayload(bytes); message.setPayload(bytes);
mqttClient.publish("/devicecontrol/"+deviceType+"/"+assetCode, message); mqttClient.publish("/device-control/"+outboundMessage.getAssetCode(), message);
log.info("[publish] message = {}", message); log.info("[publish] payload = {}", payload);
}catch (JsonProcessingException | MqttException e) { }catch (JsonProcessingException | MqttException e) {
throw new IllegalArgumentException(); throw new IllegalArgumentException();
} }

View File

@ -8,19 +8,14 @@ import java.util.LinkedHashMap;
@Component @Component
public class DeployCommand implements CommandInfo { public class DeployCommand implements CommandInfo {
private final String filePath;
public DeployCommand(@Value("${stackbase.api.host}") String filePath) {
this.filePath = filePath;
}
@Override @Override
public LinkedHashMap<String, Object> put(OutboundMessage message, LinkedHashMap<String, Object> map) { public LinkedHashMap<String, Object> put(OutboundMessage message) {
final String url = filePath + message.getFileId(); LinkedHashMap<String, Object> map = new LinkedHashMap<>();
map.put("cmd", message.getCommand()); map.put("cmd", message.getCommand());
map.put("appName", message.getAppName()); map.put("appName", message.getAppName());
map.put("name", message.getName()); map.put("name", message.getName());
map.put("fileUrl", url); map.put("fileUrl", message.getUrl());
map.put("fileType", message.getFileType()); map.put("fileType", message.getFileType());
return map; return map;
} }

View File

@ -1,27 +1,42 @@
package inc.sdt.blokworks.devicedeployer.application; package inc.sdt.blokworks.devicedeployer.application;
import inc.sdt.blokworks.devicedeployer.domain.OutboundMessage; import inc.sdt.blokworks.devicedeployer.domain.OutboundMessage;
import inc.sdt.blokworks.devicedeployer.domain.SubCommandType;
import inc.sdt.blokworks.devicedeployer.presentation.GiteaApiRequestHandler;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
@Component @Component
public class DeployCommandInvoker { public class DeployCommandInvoker {
private final DockerCommand dockerCommand; private final DockerCommand dockerCommand;
private final DeployCommand deployCommand; private final DeployCommand deployCommand;
private final JsonCommand jsonCommand;
public DeployCommandInvoker(DockerCommand dockerCommand, DeployCommand deployCommand) { public DeployCommandInvoker(DockerCommand dockerCommand, DeployCommand deployCommand, JsonCommand jsonCommand) {
this.dockerCommand = dockerCommand; this.dockerCommand = dockerCommand;
this.deployCommand = deployCommand; this.deployCommand = deployCommand;
this.jsonCommand = jsonCommand;
} }
public LinkedHashMap<String, Object> invoke(OutboundMessage message, LinkedHashMap<String, Object> map) { public LinkedHashMap<String, Object> invoke(OutboundMessage message) {
if(message.getSubCommandType() == null) {
throw new IllegalArgumentException();
}
switch (message.getSubCommandType()) { switch (message.getSubCommandType()) {
case systemd -> { case systemd -> {
return deployCommand.put(message, map); return deployCommand.put(message);
}
case docker -> {
return dockerCommand.put(message);
} }
default -> { default -> {
return dockerCommand.put(message, map); return jsonCommand.put(message);
} }
} }
} }

View File

@ -7,10 +7,12 @@ import inc.sdt.blokworks.devicedeployer.infrastructure.mqtt.InboundDeployMessage
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import java.util.LinkedHashMap;
import java.util.Optional;
import java.util.function.Function; import java.util.function.Function;
public interface DeployerService extends Function<InboundDeployMessagePayload, Mono<Void>> { public interface DeployerService extends Function<InboundDeployMessagePayload, Mono<Void>> {
void publish(OutboundMessage assetApp, String assetCode); void publish(OutboundMessage message);
DeployRequest save(DeployRequest deployRequest); DeployRequest save(DeployRequest deployRequest);
Page<AssetApp> getAll(String assetCode, int page, int size); Page<AssetApp> getAll(String assetCode, int page, int size);
} }

View File

@ -9,8 +9,10 @@ import java.util.LinkedHashMap;
public class DockerCommand implements CommandInfo { public class DockerCommand implements CommandInfo {
@Override @Override
public LinkedHashMap<String, Object> put(OutboundMessage message, LinkedHashMap<String, Object> map) { public LinkedHashMap<String, Object> put(OutboundMessage message) {
LinkedHashMap<String, Object> map = new LinkedHashMap<>();
map.put("cmd", message.getCommand()); map.put("cmd", message.getCommand());
map.put("appName", message.getAppName());
map.put("name", message.getName()); // container 이름 map.put("name", message.getName()); // container 이름
map.put("image", message.getImage()); map.put("image", message.getImage());
map.put("options", message.getOptions()); map.put("options", message.getOptions());

View File

@ -8,7 +8,8 @@ import java.util.LinkedHashMap;
@Component @Component
public class JsonCommand implements CommandInfo { public class JsonCommand implements CommandInfo {
@Override @Override
public LinkedHashMap<String, Object> put(OutboundMessage message, LinkedHashMap<String, Object> map) { public LinkedHashMap<String, Object> put(OutboundMessage message) {
LinkedHashMap<String, Object> map = new LinkedHashMap<>();
map.put("cmd", String.valueOf(message.getCommandType())); map.put("cmd", String.valueOf(message.getCommandType()));
map.put("appName", message.getAppName()); map.put("appName", message.getAppName());
map.put("parameter", message.getParameters()); map.put("parameter", message.getParameters());

View File

@ -8,7 +8,8 @@ import java.util.LinkedHashMap;
@Component @Component
public class SystemdCommand implements CommandInfo { public class SystemdCommand implements CommandInfo {
@Override @Override
public LinkedHashMap<String, Object> put(OutboundMessage message, LinkedHashMap<String, Object> map) { public LinkedHashMap<String, Object> put(OutboundMessage message) {
LinkedHashMap<String, Object> map = new LinkedHashMap<>();
map.put("cmd", message.getCommand()); map.put("cmd", message.getCommand());
map.put("service", message.getAppName()); map.put("service", message.getAppName());
return map; return map;

View File

@ -5,18 +5,16 @@ public class DeployRequest {
private String assetCode; private String assetCode;
private String appName; private String appName;
private OperationType operationType; private OperationType operationType;
private DeviceType deviceType;
private CommandType commandType; private CommandType commandType;
private SubCommandType subCommandType; private SubCommandType subCommandType;
protected DeployRequest() {} protected DeployRequest() {}
public DeployRequest(String requestId, String assetCode, String appName, OperationType operationType, DeviceType deviceType, CommandType commandType, SubCommandType subCommandType) { public DeployRequest(String requestId, String assetCode, String appName, OperationType operationType, CommandType commandType, SubCommandType subCommandType) {
this.requestId = requestId; this.requestId = requestId;
this.assetCode = assetCode; this.assetCode = assetCode;
this.appName = appName; this.appName = appName;
this.operationType = operationType; this.operationType = operationType;
this.deviceType = deviceType;
this.commandType = commandType; this.commandType = commandType;
this.subCommandType = subCommandType; this.subCommandType = subCommandType;
} }
@ -37,10 +35,6 @@ public class DeployRequest {
return operationType; return operationType;
} }
public DeviceType getDeviceType() {
return deviceType;
}
public CommandType getCommandType() { public CommandType getCommandType() {
return commandType; return commandType;
} }
@ -56,7 +50,6 @@ public class DeployRequest {
", assetCode='" + assetCode + '\'' + ", assetCode='" + assetCode + '\'' +
", appName='" + appName + '\'' + ", appName='" + appName + '\'' +
", operationType=" + operationType + ", operationType=" + operationType +
", deviceType=" + deviceType +
", commandType=" + commandType + ", commandType=" + commandType +
", subCommandType=" + subCommandType + ", subCommandType=" + subCommandType +
'}'; '}';
@ -71,7 +64,6 @@ public class DeployRequest {
private String assetCode; private String assetCode;
private String appName; private String appName;
private OperationType operationType; private OperationType operationType;
private DeviceType deviceType;
private CommandType commandType; private CommandType commandType;
private SubCommandType subCommandType; private SubCommandType subCommandType;
@ -95,11 +87,6 @@ public class DeployRequest {
return this; return this;
} }
public Builder deviceType(DeviceType deviceType) {
this.deviceType = deviceType;
return this;
}
public Builder commandType(CommandType commandType) { public Builder commandType(CommandType commandType) {
this.commandType = commandType; this.commandType = commandType;
return this; return this;
@ -116,7 +103,6 @@ public class DeployRequest {
deployRequest.assetCode = this.assetCode; deployRequest.assetCode = this.assetCode;
deployRequest.appName = this.appName; deployRequest.appName = this.appName;
deployRequest.operationType = this.operationType; deployRequest.operationType = this.operationType;
deployRequest.deviceType = this.deviceType;
deployRequest.commandType = this.commandType; deployRequest.commandType = this.commandType;
deployRequest.subCommandType = this.subCommandType; deployRequest.subCommandType = this.subCommandType;
return deployRequest; return deployRequest;

View File

@ -3,29 +3,38 @@ package inc.sdt.blokworks.devicedeployer.domain;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
public class OutboundMessage { public class OutboundMessage {
private String fileId; private String url;
private String fileType; private String fileType;
private String assetCode;
private String appName; // 사용자가 정한 파일 이름 private String appName; // 사용자가 정한 파일 이름
private String name; // stackbase 에 저장된 파일 이름 private String name; // stackbase 에 저장된 파일 이름
private String image; private String image;
private LinkedHashMap<String, Object> options; private LinkedHashMap<String, Object> options;
private String command; private String command;
private String requestId; private String requestId;
private DeviceType deviceType;
private CommandType commandType; private CommandType commandType;
private SubCommandType subCommandType; private SubCommandType subCommandType;
private LinkedHashMap<String, String> parameters; private LinkedHashMap<String, String> parameters;
private LinkedHashMap<String, Object> commandInfo;
protected OutboundMessage() {} protected OutboundMessage() {}
public String getFileId() { public String getUrl() {
return fileId; return url;
} }
public String getFileType() { public String getFileType() {
return fileType; return fileType;
} }
public String getAssetCode() {
return assetCode;
}
public void setAssetCode(String assetCode) {
this.assetCode = assetCode;
}
public String getAppName() { public String getAppName() {
return appName; return appName;
} }
@ -54,10 +63,6 @@ public class OutboundMessage {
return requestId; return requestId;
} }
public DeviceType getDeviceType() {
return deviceType;
}
public CommandType getCommandType() { public CommandType getCommandType() {
return commandType; return commandType;
} }
@ -70,21 +75,30 @@ public class OutboundMessage {
return parameters; return parameters;
} }
public LinkedHashMap<String, Object> getCommandInfo() {
return commandInfo;
}
public void setCommandInfo(LinkedHashMap<String, Object> commandInfo) {
this.commandInfo = commandInfo;
}
@Override @Override
public String toString() { public String toString() {
return "OutboundMessage{" + return "OutboundMessage{" +
"fileId='" + fileId + '\'' + "url='" + url + '\'' +
", fileType='" + fileType + '\'' + ", fileType='" + fileType + '\'' +
", assetCode='" + assetCode + '\'' +
", appName='" + appName + '\'' + ", appName='" + appName + '\'' +
", name='" + name + '\'' + ", name='" + name + '\'' +
", image='" + image + '\'' + ", image='" + image + '\'' +
", options=" + options + ", options=" + options +
", command='" + command + '\'' + ", command='" + command + '\'' +
", requestId='" + requestId + '\'' + ", requestId='" + requestId + '\'' +
", deviceType=" + deviceType +
", commandType=" + commandType + ", commandType=" + commandType +
", subCommandType=" + subCommandType + ", subCommandType=" + subCommandType +
", parameters=" + parameters + ", parameters=" + parameters +
", commandInfo=" + commandInfo +
'}'; '}';
} }
@ -93,21 +107,22 @@ public class OutboundMessage {
} }
public static final class Builder { public static final class Builder {
private String fileId; private String url;
private String fileType; private String fileType;
private String assetCode;
private String appName; private String appName;
private String name; private String name;
private String image; private String image;
private LinkedHashMap<String, Object> options; private LinkedHashMap<String, Object> options;
private String command; private String command;
private String requestId; private String requestId;
private DeviceType deviceType;
private CommandType commandType; private CommandType commandType;
private SubCommandType subCommandType; private SubCommandType subCommandType;
private LinkedHashMap<String, String> parameters; private LinkedHashMap<String, String> parameters;
private LinkedHashMap<String, Object> commandInfo;
public Builder fileId(String fileId) { public Builder url(String url) {
this.fileId = fileId; this.url = url;
return this; return this;
} }
@ -116,6 +131,11 @@ public class OutboundMessage {
return this; return this;
} }
public Builder assetCode(String assetCode) {
this.assetCode = assetCode;
return this;
}
public Builder appName(String appName) { public Builder appName(String appName) {
this.appName = appName; this.appName = appName;
return this; return this;
@ -146,11 +166,6 @@ public class OutboundMessage {
return this; return this;
} }
public Builder deviceType(DeviceType deviceType) {
this.deviceType = deviceType;
return this;
}
public Builder commandType(CommandType commandType) { public Builder commandType(CommandType commandType) {
this.commandType = commandType; this.commandType = commandType;
return this; return this;
@ -166,20 +181,26 @@ public class OutboundMessage {
return this; return this;
} }
public Builder commandInfo(LinkedHashMap<String, Object> commandInfo) {
this.commandInfo = commandInfo;
return this;
}
public OutboundMessage build() { public OutboundMessage build() {
OutboundMessage deployMessage = new OutboundMessage(); OutboundMessage deployMessage = new OutboundMessage();
deployMessage.fileId = this.fileId; deployMessage.url = this.url;
deployMessage.fileType = this.fileType; deployMessage.fileType = this.fileType;
deployMessage.assetCode = this.assetCode;
deployMessage.appName = this.appName; deployMessage.appName = this.appName;
deployMessage.name = this.name; deployMessage.name = this.name;
deployMessage.image = this.image; deployMessage.image = this.image;
deployMessage.options = this.options; deployMessage.options = this.options;
deployMessage.command = this.command; deployMessage.command = this.command;
deployMessage.requestId = this.requestId; deployMessage.requestId = this.requestId;
deployMessage.deviceType = this.deviceType;
deployMessage.commandType = this.commandType; deployMessage.commandType = this.commandType;
deployMessage.subCommandType = this.subCommandType; deployMessage.subCommandType = this.subCommandType;
deployMessage.parameters = this.parameters; deployMessage.parameters = this.parameters;
deployMessage.commandInfo = this.commandInfo;
return deployMessage; return deployMessage;
} }
} }

View File

@ -0,0 +1,10 @@
package inc.sdt.blokworks.devicedeployer.domain;
public record Result(
String name,
int pid,
int size,
String message,
long releasedAt,
long updatedAt
){}

View File

@ -1,6 +1,7 @@
package inc.sdt.blokworks.devicedeployer.domain; package inc.sdt.blokworks.devicedeployer.domain;
public enum Status { public record Status(
success, int succeed,
fail int statusCode,
} String errMsg
){}

View File

@ -2,5 +2,6 @@ package inc.sdt.blokworks.devicedeployer.domain;
public enum SubCommandType { public enum SubCommandType {
systemd, systemd,
docker docker,
single
} }

View File

@ -1,5 +1,6 @@
package inc.sdt.blokworks.devicedeployer.infrastructure.mqtt; package inc.sdt.blokworks.devicedeployer.infrastructure.mqtt;
import inc.sdt.blokworks.devicedeployer.domain.Result;
import inc.sdt.blokworks.devicedeployer.domain.Status; import inc.sdt.blokworks.devicedeployer.domain.Status;
public record InboundDeployMessagePayload( public record InboundDeployMessagePayload(
@ -9,12 +10,4 @@ public record InboundDeployMessagePayload(
Result result, Result result,
String requestId String requestId
) { ) {
public record Result(
String name,
int pid,
int size,
String message,
long releasedAt,
long updatedAt
){}
} }

View File

@ -10,7 +10,6 @@ public record OutboundMessagePayload(
HashMap<String, Object> cmdInfo, HashMap<String, Object> cmdInfo,
CommandType cmdType, CommandType cmdType,
SubCommandType subCmdType, SubCommandType subCmdType,
DeviceType deviceType,
String assetCode, String assetCode,
String requestId String requestId
) { ) {

View File

@ -1,6 +1,5 @@
package inc.sdt.blokworks.devicedeployer.infrastructure.relational; package inc.sdt.blokworks.devicedeployer.infrastructure.relational;
import inc.sdt.blokworks.devicedeployer.domain.Status;
import jakarta.persistence.*; import jakarta.persistence.*;
@Entity(name = "asset_app") @Entity(name = "asset_app")
@ -21,20 +20,25 @@ class AssetAppEntity {
private Long updatedAt; private Long updatedAt;
@Column(name = "pid") @Column(name = "pid")
private int pid; private int pid;
@Column(name = "status") @Column(name = "succeed")
@Enumerated(EnumType.STRING) private int succeed;
private Status status; @Column(name = "statusCode")
private int statusCode;
@Column(name = "error_message")
private String errorMessage;
protected AssetAppEntity() {} protected AssetAppEntity() {}
public AssetAppEntity(String assetCode, String name, int size, Long releasedAt, Long updatedAt, int pid, Status status) { public AssetAppEntity(String assetCode, String name, int size, Long releasedAt, Long updatedAt, int pid, int succeed, int statusCode, String errorMessage) {
this.assetCode = assetCode; this.assetCode = assetCode;
this.name = name; this.name = name;
this.size = size; this.size = size;
this.releasedAt = releasedAt; this.releasedAt = releasedAt;
this.updatedAt = updatedAt; this.updatedAt = updatedAt;
this.pid = pid; this.pid = pid;
this.status = status; this.succeed = succeed;
this.statusCode = statusCode;
this.errorMessage = errorMessage;
} }
public String getId() { public String getId() {
@ -65,7 +69,15 @@ class AssetAppEntity {
return pid; return pid;
} }
public Status getStatus() { public int getSucceed() {
return status; return succeed;
}
public int getStatusCode() {
return statusCode;
}
public String getErrorMessage() {
return errorMessage;
} }
} }

View File

@ -2,6 +2,7 @@ package inc.sdt.blokworks.devicedeployer.infrastructure.relational;
import inc.sdt.blokworks.devicedeployer.application.DeployerRepositoryDelegate; import inc.sdt.blokworks.devicedeployer.application.DeployerRepositoryDelegate;
import inc.sdt.blokworks.devicedeployer.domain.AssetApp; import inc.sdt.blokworks.devicedeployer.domain.AssetApp;
import inc.sdt.blokworks.devicedeployer.domain.Status;
import inc.sdt.blokworks.devicedeployer.infrastructure.mqtt.InboundDeployMessagePayload; import inc.sdt.blokworks.devicedeployer.infrastructure.mqtt.InboundDeployMessagePayload;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -52,7 +53,9 @@ public class AssetAppRelationalRepository implements DeployerRepositoryDelegate
assetApp.getReleaseAt(), assetApp.getReleaseAt(),
assetApp.getUpdatedAt(), assetApp.getUpdatedAt(),
assetApp.getPid(), assetApp.getPid(),
assetApp.getStatus() assetApp.getStatus().succeed(),
assetApp.getStatus().statusCode(),
assetApp.getStatus().errMsg()
); );
} }
@ -64,7 +67,7 @@ public class AssetAppRelationalRepository implements DeployerRepositoryDelegate
.pid(entity.getPid()) .pid(entity.getPid())
.releasedAt(entity.getReleasedAt()) .releasedAt(entity.getReleasedAt())
.updatedAt(entity.getUpdatedAt()) .updatedAt(entity.getUpdatedAt())
.status(entity.getStatus()) .status(new Status(entity.getSucceed(), entity.getStatusCode(), entity.getErrorMessage()))
.build(); .build();
} }

View File

@ -22,9 +22,6 @@ public class DeployRequestEntity {
@Column(name = "operation_type", length = 255) @Column(name = "operation_type", length = 255)
private OperationType operationType; private OperationType operationType;
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Column(name = "device_type", length = 255)
private DeviceType deviceType;
@Enumerated(EnumType.STRING)
@Column(name = "command_type", length = 255) @Column(name = "command_type", length = 255)
private CommandType commandType; private CommandType commandType;
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@ -33,12 +30,11 @@ public class DeployRequestEntity {
protected DeployRequestEntity() {} protected DeployRequestEntity() {}
public DeployRequestEntity(String requestId, String assetCode, String appName, OperationType operationType, DeviceType deviceType, CommandType commandType, SubCommandType subCommandType) { public DeployRequestEntity(String requestId, String assetCode, String appName, OperationType operationType, CommandType commandType, SubCommandType subCommandType) {
this.requestId = requestId; this.requestId = requestId;
this.assetCode = assetCode; this.assetCode = assetCode;
this.appName = appName; this.appName = appName;
this.operationType = operationType; this.operationType = operationType;
this.deviceType = deviceType;
this.commandType = commandType; this.commandType = commandType;
this.subCommandType = subCommandType; this.subCommandType = subCommandType;
} }
@ -63,10 +59,6 @@ public class DeployRequestEntity {
return operationType; return operationType;
} }
public DeviceType getDeviceType() {
return deviceType;
}
public CommandType getCommandType() { public CommandType getCommandType() {
return commandType; return commandType;
} }

View File

@ -40,7 +40,6 @@ public class DeployRequestRelationalRepository implements DeployRequestRepositor
deployRequest.getAssetCode(), deployRequest.getAssetCode(),
deployRequest.getAppName(), deployRequest.getAppName(),
deployRequest.getOperationType(), deployRequest.getOperationType(),
deployRequest.getDeviceType(),
deployRequest.getCommandType(), deployRequest.getCommandType(),
deployRequest.getSubCommandType() deployRequest.getSubCommandType()
); );
@ -52,7 +51,6 @@ public class DeployRequestRelationalRepository implements DeployRequestRepositor
.assetCode(entity.getAssetCode()) .assetCode(entity.getAssetCode())
.appName(entity.getAppName()) .appName(entity.getAppName())
.operationType(entity.getOperationType()) .operationType(entity.getOperationType())
.deviceType(entity.getDeviceType())
.commandType(entity.getCommandType()) .commandType(entity.getCommandType())
.subCommandType(entity.getSubCommandType()) .subCommandType(entity.getSubCommandType())
.build(); .build();

View File

@ -1,8 +1,12 @@
package inc.sdt.blokworks.devicedeployer.presentation; package inc.sdt.blokworks.devicedeployer.presentation;
import inc.sdt.blokworks.devicedeployer.application.CommandInvoker;
import inc.sdt.blokworks.devicedeployer.application.DeployCommandInvoker;
import inc.sdt.blokworks.devicedeployer.application.DeployerService; import inc.sdt.blokworks.devicedeployer.application.DeployerService;
import inc.sdt.blokworks.devicedeployer.domain.*; import inc.sdt.blokworks.devicedeployer.domain.*;
import inc.sdt.blokworks.devicedeployer.infrastructure.amqp.ResourceMapping; import inc.sdt.blokworks.devicedeployer.infrastructure.amqp.ResourceMapping;
import inc.sdt.blokworks.devicedeployer.presentation.exception.NotFoundException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -11,8 +15,11 @@ import org.springframework.data.domain.Page;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import java.util.function.Consumer;
@RestController @RestController
public class DeployerController { public class DeployerController {
@ -20,14 +27,20 @@ public class DeployerController {
private final DeployerService deployerService; private final DeployerService deployerService;
private final OutboundMessageResourceConverter outboundMessageResourceConverter; private final OutboundMessageResourceConverter outboundMessageResourceConverter;
private final AssetAppResourceConverter assetAppResourceConverter; private final AssetAppResourceConverter assetAppResourceConverter;
private final CommandInvoker commandInvoker;
private final GiteaApiRequestHandler giteaApiRequestHandler;
public DeployerController(DeployerService deployerService, public DeployerController(DeployerService deployerService,
OutboundMessageResourceConverter outboundMessageResourceConverter, OutboundMessageResourceConverter outboundMessageResourceConverter,
AssetAppResourceConverter assetAppResourceConverter) { AssetAppResourceConverter assetAppResourceConverter,
CommandInvoker commandInvoker,
GiteaApiRequestHandler giteaApiRequestHandler) {
this.log = LoggerFactory.getLogger(this.getClass()); this.log = LoggerFactory.getLogger(this.getClass());
this.deployerService = deployerService; this.deployerService = deployerService;
this.outboundMessageResourceConverter = outboundMessageResourceConverter; this.outboundMessageResourceConverter = outboundMessageResourceConverter;
this.assetAppResourceConverter = assetAppResourceConverter; this.assetAppResourceConverter = assetAppResourceConverter;
this.commandInvoker = commandInvoker;
this.giteaApiRequestHandler = giteaApiRequestHandler;
} }
/** /**
@ -39,24 +52,36 @@ public class DeployerController {
@ResponseStatus(HttpStatus.CREATED) @ResponseStatus(HttpStatus.CREATED)
@PostMapping("/assets/{assetCode}/apps") @PostMapping("/assets/{assetCode}/apps")
public void deploy(@PathVariable String assetCode, public void deploy(@PathVariable String assetCode,
@RequestBody OutboundMessageResource resource) { @RequestBody OutboundMessageResource resource,
HttpServletRequest httpServletRequest) {
log.info("[deploy] assetCode = {}, resource = {}", assetCode, resource); log.info("[deploy] assetCode = {}, resource = {}", assetCode, resource);
String authorization = httpServletRequest.getHeader("Authorization");
String requestId = UUID.randomUUID().toString(); String requestId = UUID.randomUUID().toString();
OutboundMessage outboundMessage = outboundMessageResourceConverter.fromResource(resource); OutboundMessage outboundMessage = outboundMessageResourceConverter.fromResource(resource);
outboundMessage.setRequestId(requestId); outboundMessage.setRequestId(requestId);
outboundMessage.setAssetCode(assetCode);
outboundMessage.setCommandInfo(commandInvoker.invoke(outboundMessage));
if(resource.commandType() == null) {
throw new IllegalArgumentException();
}
if(resource.commandType() == CommandType.deploy) {
giteaApiRequestHandler.get(authorization, outboundMessage);
}
DeployRequest deployRequest = DeployRequest.builder() DeployRequest deployRequest = DeployRequest.builder()
.requestId(requestId) .requestId(requestId)
.assetCode(assetCode) .assetCode(assetCode)
.appName(outboundMessage.getName()) .appName(outboundMessage.getName())
.operationType(OperationType.DEPLOY) .operationType(OperationType.DEPLOY)
.deviceType(resource.deviceType())
.commandType(resource.commandType()) .commandType(resource.commandType())
.subCommandType(resource.subCommandType()) .subCommandType(resource.subCommandType())
.build(); .build();
DeployRequest request = deployerService.save(deployRequest); deployerService.save(deployRequest);
deployerService.publish(outboundMessage, assetCode); deployerService.publish(outboundMessage);
} }
/** /**

View File

@ -0,0 +1,80 @@
package inc.sdt.blokworks.devicedeployer.presentation;
import inc.sdt.blokworks.devicedeployer.domain.OutboundMessage;
import inc.sdt.blokworks.devicedeployer.domain.SubCommandType;
import inc.sdt.blokworks.devicedeployer.presentation.exception.NotFoundException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
@Component
public class GiteaApiRequestHandler {
private final Logger log;
private final RestTemplate restTemplate;
private final String url;
private final List<String> extensions;
public GiteaApiRequestHandler(RestTemplate restTemplate,
@Value("${stackbase.api.host}") String url) {
this.log = LoggerFactory.getLogger(this.getClass());
this.restTemplate = restTemplate;
this.url = url;
this.extensions = Arrays.asList(".py", ".jar", ".sh", ".service", ".json", ".yaml");
}
public void get(String authorization, OutboundMessage message) {
log.info("[get] message = {}", message);
if(message.getSubCommandType() == null) {
throw new IllegalArgumentException();
}
if(message.getSubCommandType() == SubCommandType.systemd) {
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", authorization);
try {
download(message.getAssetCode(), message.getUrl());
}catch (IOException e) {
throw new RuntimeException();
}
}
}
private void download(String assetCode, String url) throws IOException {
log.info("[download] assetCode = {}, url = {}", assetCode, url);
byte[] bytes = restTemplate.getForObject(url, byte[].class);
File tempFile = File.createTempFile(assetCode+"_"+LocalDateTime.now(), ".zip");
try(FileOutputStream fos = new FileOutputStream(tempFile)) {
assert bytes != null;
fos.write(bytes);
}
try(ZipInputStream zis = new ZipInputStream(Files.newInputStream(Path.of(tempFile.getAbsolutePath())))) {
ZipEntry zipEntry;
while((zipEntry = zis.getNextEntry()) != null) {
if(!zipEntry.isDirectory()) {
String extension = zipEntry.getName().split("\\.")[1];
if(!extensions.toString().contains(extension)) {
throw new NotFoundException("Executable file not found.");
}
}
}
}
}
}

View File

@ -4,17 +4,17 @@ import inc.sdt.blokworks.devicedeployer.domain.CommandType;
import inc.sdt.blokworks.devicedeployer.domain.DeviceType; import inc.sdt.blokworks.devicedeployer.domain.DeviceType;
import inc.sdt.blokworks.devicedeployer.domain.SubCommandType; import inc.sdt.blokworks.devicedeployer.domain.SubCommandType;
import org.wildfly.common.annotation.NotNull; import org.wildfly.common.annotation.NotNull;
import org.wildfly.common.annotation.Nullable;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
record OutboundMessageResource( record OutboundMessageResource(
String fileId, String url,
String fileType, String fileType,
String appName, // 사용자가 정한 파일 이름 String appName, // 사용자가 정한 파일 이름
String name, // stackbase 에 저장된 파일 이름 String name, // stackbase 에 저장된 파일 이름
String image, String image,
String command, String command,
DeviceType deviceType,
CommandType commandType, CommandType commandType,
SubCommandType subCommandType, SubCommandType subCommandType,
LinkedHashMap<String, Object> options, LinkedHashMap<String, Object> options,

View File

@ -1,8 +1,8 @@
package inc.sdt.blokworks.devicedeployer.presentation; package inc.sdt.blokworks.devicedeployer.presentation;
import inc.sdt.blokworks.devicedeployer.domain.CommandType; import inc.sdt.blokworks.devicedeployer.domain.CommandType;
import inc.sdt.blokworks.devicedeployer.domain.DeviceType;
import inc.sdt.blokworks.devicedeployer.domain.OutboundMessage; import inc.sdt.blokworks.devicedeployer.domain.OutboundMessage;
import inc.sdt.blokworks.devicedeployer.domain.SubCommandType;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.*; import java.util.*;
@ -11,15 +11,15 @@ import java.util.*;
public class OutboundMessageResourceConverter { public class OutboundMessageResourceConverter {
public OutboundMessage fromResource(OutboundMessageResource resource) { public OutboundMessage fromResource(OutboundMessageResource resource) {
return OutboundMessage.builder() return OutboundMessage.builder()
.fileId(resource.fileId()) .url(resource.url())
.fileType(resource.fileType()) .fileType(resource.fileType())
.appName(resource.appName() == null ? "" : resource.appName()) .appName(resource.appName())
.name(resource.name()) .name(resource.name())
.image(resource.image() == null ? "" : resource.image()) .image(resource.image())
.command(resource.command()) .command(resource.command())
.options(resource.options() == null ? new LinkedHashMap<>() : resource.options()) .options(resource.options() == null ? new LinkedHashMap<>() : resource.options())
.deviceType(resource.deviceType() == null ? DeviceType.ecn : resource.deviceType())
.commandType(resource.commandType() == null ? CommandType.deploy : resource.commandType()) .commandType(resource.commandType() == null ? CommandType.deploy : resource.commandType())
.subCommandType(resource.subCommandType() == null ? SubCommandType.single : resource.subCommandType())
.parameters(resource.parameters() == null ? new LinkedHashMap<>() : resource.parameters()) .parameters(resource.parameters() == null ? new LinkedHashMap<>() : resource.parameters())
.build(); .build();

View File

@ -32,4 +32,16 @@ class ControllerAdvice {
public ErrorResponse handleConflictException(Exception exception) { public ErrorResponse handleConflictException(Exception exception) {
return new ErrorResponse(HttpStatus.CONFLICT, exception.getMessage()); return new ErrorResponse(HttpStatus.CONFLICT, exception.getMessage());
} }
@ExceptionHandler(NotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ErrorResponse handleNotFoundException(Exception exception) {
return new ErrorResponse(HttpStatus.NOT_FOUND, exception.getMessage());
}
@ExceptionHandler(UnauthorizedException.class)
@ResponseStatus(HttpStatus.UNAUTHORIZED)
public ErrorResponse handleUnauthorizedException(Exception exception) {
return new ErrorResponse(HttpStatus.UNAUTHORIZED, exception.getMessage());
}
} }

View File

@ -0,0 +1,12 @@
package inc.sdt.blokworks.devicedeployer.presentation.exception;
public class NotFoundException extends RuntimeException{
private static final String DEFAULT_MESSAGE = "File not found";
public NotFoundException() {
super(DEFAULT_MESSAGE);
}
public NotFoundException(String message) {
super(message);
}
}

View File

@ -0,0 +1,28 @@
package inc.sdt.blokworks.devicedeployer.presentation.rest;
import inc.sdt.blokworks.devicedeployer.presentation.exception.UnauthorizedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.web.client.DefaultResponseErrorHandler;
import java.io.IOException;
public class RestTemplateResponseErrorHandler extends DefaultResponseErrorHandler {
private final Logger log;
public RestTemplateResponseErrorHandler() {
this.log = LoggerFactory.getLogger(this.getClass());
}
@Override
protected void handleError(ClientHttpResponse response, HttpStatusCode statusCode) throws IOException {
log.error("[handleError] statusCode = {}, response = {}", statusCode, response);
if(response.getStatusCode() == HttpStatus.UNAUTHORIZED) {
throw new UnauthorizedException();
}
}
}

View File

@ -16,7 +16,7 @@ inbound:
username: ${INBOUND_MQTT_CREDENTIALS_USERNAME} username: ${INBOUND_MQTT_CREDENTIALS_USERNAME}
password: ${INBOUND_MQTT_CREDENTIALS_PASSWORD} password: ${INBOUND_MQTT_CREDENTIALS_PASSWORD}
topics: topics:
- /devicecontrol/result/+/+/+ - /device-control/+/result
iam: iam:
enabled: ${IAM_REGISTER_ENABLED} enabled: ${IAM_REGISTER_ENABLED}

View File

@ -17,7 +17,7 @@ inbound:
username: sdt username: sdt
password: 251327 password: 251327
topics: topics:
- /devicecontrol/result/+/+/+ - /device-control/+/result
stackbase: stackbase:
api: api: