- 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;
import inc.sdt.blokworks.devicedeployer.presentation.rest.RestTemplateResponseErrorHandler;
import org.springframework.boot.SpringApplication;
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
public class DeviceDeployerApplication {
@ -10,4 +14,11 @@ public class DeviceDeployerApplication {
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
public class BashCommand implements CommandInfo{
@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());
return map;
}

View File

@ -5,5 +5,5 @@ import inc.sdt.blokworks.devicedeployer.domain.OutboundMessage;
import java.util.LinkedHashMap;
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;
import inc.sdt.blokworks.devicedeployer.domain.OutboundMessage;
import inc.sdt.blokworks.devicedeployer.presentation.GiteaApiRequestHandler;
import org.springframework.stereotype.Component;
import java.util.LinkedHashMap;
@ -13,7 +14,11 @@ public class CommandInvoker {
private final JsonCommand jsonCommand;
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.systemdCommand = systemdCommand;
this.dockerCommand = dockerCommand;
@ -21,22 +26,25 @@ public class CommandInvoker {
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()) {
case bash -> {
return bashCommand.put(message, map);
return bashCommand.put(message);
}
case systemd -> {
return systemdCommand.put(message, map);
return systemdCommand.put(message);
}
case docker -> {
return dockerCommand.put(message, map);
return dockerCommand.put(message);
}
case deploy -> {
return deployCommandInvoker.invoke(message, map);
return deployCommandInvoker.invoke(message);
}
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.infrastructure.mqtt.InboundDeployMessagePayload;
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.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
@ -25,36 +25,27 @@ public class DefaultDeployerService implements DeployerService{
private final ObjectMapper objectMapper;
private final DeployerRepositoryDelegate deployerRepositoryDelegate;
private final DeployRequestRepositoryDelegate requestRepositoryDelegate;
private final CommandInvoker commandInvoker;
public DefaultDeployerService(IMqttClient mqttClient,
ObjectMapper objectMapper,
DeployerRepositoryDelegate deployerRepositoryDelegate,
DeployRequestRepositoryDelegate requestRepositoryDelegate,
CommandInvoker commandInvoker) {
DeployRequestRepositoryDelegate requestRepositoryDelegate) {
this.log = LoggerFactory.getLogger(this.getClass());
this.mqttClient = mqttClient;
this.objectMapper = objectMapper;
this.deployerRepositoryDelegate = deployerRepositoryDelegate;
this.requestRepositoryDelegate = requestRepositoryDelegate;
this.commandInvoker = commandInvoker;
}
@Override
public void publish(OutboundMessage outboundMessage, String assetCode) {
log.info("[publish] deployMessage = {}, assetCode = {}", outboundMessage, assetCode);
final DeviceType deviceType = outboundMessage.getDeviceType();
LinkedHashMap<String, Object> commandInfo = new LinkedHashMap<>();
public void publish(OutboundMessage outboundMessage) {
log.info("[publish] outboundMessage = {}", outboundMessage);
try {
commandInfo = commandInvoker.invoke(outboundMessage, commandInfo);
OutboundMessagePayload payload = new OutboundMessagePayload(
commandInfo,
outboundMessage.getCommandInfo(),
outboundMessage.getCommandType(),
outboundMessage.getSubCommandType(),
deviceType,
assetCode,
outboundMessage.getAssetCode(),
outboundMessage.getRequestId()
);
@ -62,8 +53,8 @@ public class DefaultDeployerService implements DeployerService{
MqttMessage message = new MqttMessage();
message.setPayload(bytes);
mqttClient.publish("/devicecontrol/"+deviceType+"/"+assetCode, message);
log.info("[publish] message = {}", message);
mqttClient.publish("/device-control/"+outboundMessage.getAssetCode(), message);
log.info("[publish] payload = {}", payload);
}catch (JsonProcessingException | MqttException e) {
throw new IllegalArgumentException();
}

View File

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

View File

@ -1,27 +1,42 @@
package inc.sdt.blokworks.devicedeployer.application;
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 java.util.LinkedHashMap;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
@Component
public class DeployCommandInvoker {
private final DockerCommand dockerCommand;
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.deployCommand = deployCommand;
this.jsonCommand = jsonCommand;
}
public LinkedHashMap<String, Object> invoke(OutboundMessage message) {
if(message.getSubCommandType() == null) {
throw new IllegalArgumentException();
}
public LinkedHashMap<String, Object> invoke(OutboundMessage message, LinkedHashMap<String, Object> map) {
switch (message.getSubCommandType()) {
case systemd -> {
return deployCommand.put(message, map);
return deployCommand.put(message);
}
case docker -> {
return dockerCommand.put(message);
}
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 reactor.core.publisher.Mono;
import java.util.LinkedHashMap;
import java.util.Optional;
import java.util.function.Function;
public interface DeployerService extends Function<InboundDeployMessagePayload, Mono<Void>> {
void publish(OutboundMessage assetApp, String assetCode);
void publish(OutboundMessage message);
DeployRequest save(DeployRequest deployRequest);
Page<AssetApp> getAll(String assetCode, int page, int size);
}

View File

@ -9,8 +9,10 @@ import java.util.LinkedHashMap;
public class DockerCommand implements CommandInfo {
@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("appName", message.getAppName());
map.put("name", message.getName()); // container 이름
map.put("image", message.getImage());
map.put("options", message.getOptions());

View File

@ -8,7 +8,8 @@ import java.util.LinkedHashMap;
@Component
public class JsonCommand implements CommandInfo {
@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("appName", message.getAppName());
map.put("parameter", message.getParameters());

View File

@ -8,7 +8,8 @@ import java.util.LinkedHashMap;
@Component
public class SystemdCommand implements CommandInfo {
@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("service", message.getAppName());
return map;

View File

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

View File

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

View File

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

View File

@ -1,5 +1,6 @@
package inc.sdt.blokworks.devicedeployer.infrastructure.mqtt;
import inc.sdt.blokworks.devicedeployer.domain.Result;
import inc.sdt.blokworks.devicedeployer.domain.Status;
public record InboundDeployMessagePayload(
@ -9,12 +10,4 @@ public record InboundDeployMessagePayload(
Result result,
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,
CommandType cmdType,
SubCommandType subCmdType,
DeviceType deviceType,
String assetCode,
String requestId
) {

View File

@ -1,6 +1,5 @@
package inc.sdt.blokworks.devicedeployer.infrastructure.relational;
import inc.sdt.blokworks.devicedeployer.domain.Status;
import jakarta.persistence.*;
@Entity(name = "asset_app")
@ -21,20 +20,25 @@ class AssetAppEntity {
private Long updatedAt;
@Column(name = "pid")
private int pid;
@Column(name = "status")
@Enumerated(EnumType.STRING)
private Status status;
@Column(name = "succeed")
private int succeed;
@Column(name = "statusCode")
private int statusCode;
@Column(name = "error_message")
private String errorMessage;
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.name = name;
this.size = size;
this.releasedAt = releasedAt;
this.updatedAt = updatedAt;
this.pid = pid;
this.status = status;
this.succeed = succeed;
this.statusCode = statusCode;
this.errorMessage = errorMessage;
}
public String getId() {
@ -65,7 +69,15 @@ class AssetAppEntity {
return pid;
}
public Status getStatus() {
return status;
public int getSucceed() {
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.domain.AssetApp;
import inc.sdt.blokworks.devicedeployer.domain.Status;
import inc.sdt.blokworks.devicedeployer.infrastructure.mqtt.InboundDeployMessagePayload;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -52,7 +53,9 @@ public class AssetAppRelationalRepository implements DeployerRepositoryDelegate
assetApp.getReleaseAt(),
assetApp.getUpdatedAt(),
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())
.releasedAt(entity.getReleasedAt())
.updatedAt(entity.getUpdatedAt())
.status(entity.getStatus())
.status(new Status(entity.getSucceed(), entity.getStatusCode(), entity.getErrorMessage()))
.build();
}

View File

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

View File

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

View File

@ -1,8 +1,12 @@
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.domain.*;
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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -11,8 +15,11 @@ import org.springframework.data.domain.Page;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.Optional;
import java.util.UUID;
import java.util.function.Consumer;
@RestController
public class DeployerController {
@ -20,14 +27,20 @@ public class DeployerController {
private final DeployerService deployerService;
private final OutboundMessageResourceConverter outboundMessageResourceConverter;
private final AssetAppResourceConverter assetAppResourceConverter;
private final CommandInvoker commandInvoker;
private final GiteaApiRequestHandler giteaApiRequestHandler;
public DeployerController(DeployerService deployerService,
OutboundMessageResourceConverter outboundMessageResourceConverter,
AssetAppResourceConverter assetAppResourceConverter) {
AssetAppResourceConverter assetAppResourceConverter,
CommandInvoker commandInvoker,
GiteaApiRequestHandler giteaApiRequestHandler) {
this.log = LoggerFactory.getLogger(this.getClass());
this.deployerService = deployerService;
this.outboundMessageResourceConverter = outboundMessageResourceConverter;
this.assetAppResourceConverter = assetAppResourceConverter;
this.commandInvoker = commandInvoker;
this.giteaApiRequestHandler = giteaApiRequestHandler;
}
/**
@ -39,24 +52,36 @@ public class DeployerController {
@ResponseStatus(HttpStatus.CREATED)
@PostMapping("/assets/{assetCode}/apps")
public void deploy(@PathVariable String assetCode,
@RequestBody OutboundMessageResource resource) {
@RequestBody OutboundMessageResource resource,
HttpServletRequest httpServletRequest) {
log.info("[deploy] assetCode = {}, resource = {}", assetCode, resource);
String authorization = httpServletRequest.getHeader("Authorization");
String requestId = UUID.randomUUID().toString();
OutboundMessage outboundMessage = outboundMessageResourceConverter.fromResource(resource);
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()
.requestId(requestId)
.assetCode(assetCode)
.appName(outboundMessage.getName())
.operationType(OperationType.DEPLOY)
.deviceType(resource.deviceType())
.commandType(resource.commandType())
.subCommandType(resource.subCommandType())
.build();
DeployRequest request = deployerService.save(deployRequest);
deployerService.publish(outboundMessage, assetCode);
deployerService.save(deployRequest);
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.SubCommandType;
import org.wildfly.common.annotation.NotNull;
import org.wildfly.common.annotation.Nullable;
import java.util.LinkedHashMap;
record OutboundMessageResource(
String fileId,
String url,
String fileType,
String appName, // 사용자가 정한 파일 이름
String name, // stackbase 에 저장된 파일 이름
String image,
String command,
DeviceType deviceType,
CommandType commandType,
SubCommandType subCommandType,
LinkedHashMap<String, Object> options,

View File

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

View File

@ -32,4 +32,16 @@ class ControllerAdvice {
public ErrorResponse handleConflictException(Exception exception) {
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}
password: ${INBOUND_MQTT_CREDENTIALS_PASSWORD}
topics:
- /devicecontrol/result/+/+/+
- /device-control/+/result
iam:
enabled: ${IAM_REGISTER_ENABLED}

View File

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