Переглянути джерело

更新 DeployerController 接口和相关实现

reghao 3 місяців тому
батько
коміт
1fc7d93329

+ 33 - 8
web/src/main/java/cn/reghao/bnt/web/devops/deployer/controller/DeployerController.java

@@ -1,8 +1,12 @@
 package cn.reghao.bnt.web.devops.deployer.controller;
 
+import cn.reghao.bnt.web.devops.deployer.model.dto.EntityIdDto;
+import cn.reghao.bnt.web.devops.deployer.model.dto.RemoteMachine;
 import cn.reghao.bnt.web.devops.deployer.model.dto.UpdateApp;
+import cn.reghao.bnt.web.devops.deployer.model.po.RemoteAgentConfig;
 import cn.reghao.bnt.web.devops.deployer.model.po.RemoteHost;
 import cn.reghao.bnt.web.devops.deployer.service.DeployApp;
+import cn.reghao.jutil.jdk.web.result.Result;
 import cn.reghao.jutil.web.WebResult;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -18,7 +22,7 @@ import java.util.List;
  * @date 2025-12-21 21:13:26
  */
 @Slf4j
-@Tag(name = "Nginx 接口")
+@Tag(name = "deployer 接口")
 @RestController
 @RequestMapping("/api/devops/deployer")
 public class DeployerController {
@@ -28,17 +32,38 @@ public class DeployerController {
         this.deployApp = deployApp;
     }
 
+    @Operation(summary = "添加节点配置", description = "N")
+    @PostMapping(value = "/agent_config/add", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String addAgentConfig(@RequestBody @Validated RemoteAgentConfig remoteAgentConfig) {
+        deployApp.addAgentConfig(remoteAgentConfig);
+        return WebResult.success();
+    }
+
+    @Operation(summary = "删除节点配置", description = "N")
+    @PostMapping(value = "/agent_config/delete", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String deleteAgentConfig(@RequestBody @Validated EntityIdDto entityIdDto) {
+        deployApp.deleteAgentConfig(entityIdDto.getId());
+        return WebResult.success();
+    }
+
+    @Operation(summary = "获取节点配置", description = "N")
+    @GetMapping(value = "/agent_config/list", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String getAgentConfigs() {
+        List<RemoteAgentConfig> list = deployApp.getAgentConfigs();
+        return WebResult.success(list);
+    }
+
     @Operation(summary = "添加节点", description = "N")
     @PostMapping(value = "/remote_host/add", produces = MediaType.APPLICATION_JSON_VALUE)
-    public String addRemoteHost(@RequestBody @Validated RemoteHost remoteHost) {
-        deployApp.addRemoteHost(remoteHost);
-        return WebResult.success();
+    public String addRemoteHost(@RequestBody @Validated RemoteMachine remoteMachine) {
+        Result result = deployApp.addRemoteHost(remoteMachine);
+        return WebResult.result(result);
     }
 
     @Operation(summary = "删除节点", description = "N")
     @PostMapping(value = "/remote_host/delete", produces = MediaType.APPLICATION_JSON_VALUE)
-    public String deleteRemoteHost(@RequestBody String host) {
-        deployApp.deleteRemoteHost(host);
+    public String deleteRemoteHost(@RequestBody @Validated EntityIdDto entityIdDto) {
+        deployApp.deleteRemoteHost(entityIdDto.getId());
         return WebResult.success();
     }
 
@@ -52,7 +77,7 @@ public class DeployerController {
     @Operation(summary = "部署 devops-mgr", description = "N")
     @PostMapping(value = "/update", produces = MediaType.APPLICATION_JSON_VALUE)
     public String deployMgr(@RequestBody UpdateApp updateApp) {
-        //deployApp.deployApp(host);
-        return WebResult.success();
+        Result result = deployApp.deployApp(updateApp);
+        return WebResult.result(result);
     }
 }

+ 12 - 0
web/src/main/java/cn/reghao/bnt/web/devops/deployer/db/RemoteAgentConfigRepository.java

@@ -0,0 +1,12 @@
+package cn.reghao.bnt.web.devops.deployer.db;
+
+import cn.reghao.bnt.web.devops.deployer.model.po.RemoteAgentConfig;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+/**
+ * @author reghao
+ * @date 2025-12-22 16:16:24
+ */
+public interface RemoteAgentConfigRepository extends JpaRepository<RemoteAgentConfig, Integer> {
+    RemoteAgentConfig findByMgrHost(String mgrHost);
+}

+ 9 - 0
web/src/main/java/cn/reghao/bnt/web/devops/deployer/model/constant/NodeType.java

@@ -0,0 +1,9 @@
+package cn.reghao.bnt.web.devops.deployer.model.constant;
+
+/**
+ * @author reghao
+ * @date 2025-12-22 09:49:17
+ */
+public enum NodeType {
+    mgr, agent
+}

+ 9 - 0
web/src/main/java/cn/reghao/bnt/web/devops/deployer/model/constant/SshAuthType.java

@@ -0,0 +1,9 @@
+package cn.reghao.bnt.web.devops.deployer.model.constant;
+
+/**
+ * @author reghao
+ * @date 2025-12-22 09:49:05
+ */
+public enum SshAuthType {
+    password, privateKey
+}

+ 16 - 0
web/src/main/java/cn/reghao/bnt/web/devops/deployer/model/dto/EntityIdDto.java

@@ -0,0 +1,16 @@
+package cn.reghao.bnt.web.devops.deployer.model.dto;
+
+import jakarta.validation.constraints.NotNull;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * @author reghao
+ * @date 2025-12-22 16:50:51
+ */
+@Setter
+@Getter
+public class EntityIdDto {
+    @NotNull
+    private Integer id;
+}

+ 43 - 0
web/src/main/java/cn/reghao/bnt/web/devops/deployer/model/dto/RemoteMachine.java

@@ -0,0 +1,43 @@
+package cn.reghao.bnt.web.devops.deployer.model.dto;
+
+import jakarta.persistence.Column;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * @author reghao
+ * @date 2024-02-20 09:12:10
+ */
+@Getter
+@Setter
+public class RemoteMachine {
+    @NotBlank
+    @Size(max = 255, message = "最大长度不能超过 255 个字符")
+    private String nodeType;
+    @NotBlank
+    @Size(max = 255, message = "最大长度不能超过 255 个字符")
+    @Column(unique = true, nullable = false)
+    private String host;
+    @NotNull
+    private Integer port;
+    @NotBlank
+    @Size(max = 255, message = "最大长度不能超过 255 个字符")
+    private String authType;
+    @NotBlank
+    @Size(max = 255, message = "最大长度不能超过 255 个字符")
+    private String username;
+//    @NotBlank
+//    @Size(max = 255, message = "最大长度不能超过 255 个字符")
+    private String password;
+//    @NotBlank
+//    @Size(max = 10240, message = "最大长度不能超过 10240 个字符")
+    @Column(columnDefinition="text")
+    private String privateKey;
+    @NotBlank
+    @Size(max = 255, message = "最大长度不能超过 255 个字符")
+    private String appDir;
+    private Integer remoteAgentConfig;
+}

+ 31 - 0
web/src/main/java/cn/reghao/bnt/web/devops/deployer/model/po/RemoteAgentConfig.java

@@ -0,0 +1,31 @@
+package cn.reghao.bnt.web.devops.deployer.model.po;
+
+import cn.reghao.bnt.web.util.BaseEntity;
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.Table;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * @author reghao
+ * @date 2025-12-22 15:55:09
+ */
+@Getter
+@Setter
+@Entity
+@Table(name = "devops_remote_agent_config")
+public class RemoteAgentConfig extends BaseEntity {
+    @NotBlank
+    @Size(max = 4, message = "mgrProtocol 最大长度不能超过 4 个字符")
+    private String mgrProtocol;
+    @NotBlank
+    @Size(max = 255, message = "mgrHost 最大长度不能超过 255 个字符")
+    @Column(unique = true, nullable = false)
+    private String mgrHost;
+    @NotNull(message = "mgrPort 不能为空")
+    private Integer mgrPort;
+}

+ 32 - 5
web/src/main/java/cn/reghao/bnt/web/devops/deployer/model/po/RemoteHost.java

@@ -1,15 +1,18 @@
 package cn.reghao.bnt.web.devops.deployer.model.po;
 
+import cn.reghao.bnt.web.devops.deployer.model.constant.NodeType;
+import cn.reghao.bnt.web.devops.deployer.model.constant.SshAuthType;
+import cn.reghao.bnt.web.devops.deployer.model.dto.RemoteMachine;
 import cn.reghao.bnt.web.util.BaseEntity;
 import cn.reghao.jutil.jdk.web.result.NotAvailable;
 import jakarta.persistence.Column;
 import jakarta.persistence.Entity;
+import jakarta.persistence.OneToOne;
 import jakarta.persistence.Table;
 import jakarta.validation.constraints.NotBlank;
 import jakarta.validation.constraints.NotNull;
 import jakarta.validation.constraints.Size;
 import lombok.Getter;
-import lombok.NoArgsConstructor;
 import lombok.Setter;
 
 /**
@@ -33,8 +36,8 @@ public class RemoteHost extends BaseEntity {
     @NotBlank
     @Size(max = 255, message = "最大长度不能超过 255 个字符")
     private String authType;
-//    @NotBlank
-//    @Size(max = 255, message = "最大长度不能超过 255 个字符")
+    @NotBlank
+    @Size(max = 255, message = "最大长度不能超过 255 个字符")
     private String username;
 //    @NotBlank
 //    @Size(max = 255, message = "最大长度不能超过 255 个字符")
@@ -49,15 +52,39 @@ public class RemoteHost extends BaseEntity {
     @NotBlank
     @Size(max = 255, message = "最大长度不能超过 255 个字符")
     private String appVersion;
+    @OneToOne
+    private RemoteAgentConfig remoteAgentConfig;
 
     public RemoteHost() {
         this.appVersion = NotAvailable.na.getDesc();
     }
 
-    public RemoteHost(String host, int port, String username, String password) {
+    public RemoteHost(RemoteMachine remoteMachine, RemoteAgentConfig remoteAgentConfig) {
+        this.nodeType = remoteMachine.getNodeType();
+        this.host = remoteMachine.getHost();
+        this.port = remoteMachine.getPort();
+        this.authType = remoteMachine.getAuthType();
+        this.username = remoteMachine.getUsername();
+        this.password = remoteMachine.getPassword();
+        this.privateKey = remoteMachine.getPrivateKey();
+        this.appDir = remoteMachine.getAppDir();
+        this.appVersion = NotAvailable.na.getDesc();
+        this.remoteAgentConfig = remoteAgentConfig;
+    }
+
+    public RemoteHost(String host, String appDir, String appVersion) {
+        this.nodeType = NodeType.mgr.name();
+        this.host = host;
+        this.port = 22;
+        this.appDir = appDir;
+        this.appVersion = appVersion;
+        this.authType = SshAuthType.password.name();
+    }
+
+    public RemoteHost(String host, int port, String authType, String username, String password) {
         this.host = host;
         this.port = port;
-        this.authType = "password";
+        this.authType = authType;
         this.username = username;
         this.password = password;
     }

+ 170 - 20
web/src/main/java/cn/reghao/bnt/web/devops/deployer/service/DeployApp.java

@@ -1,13 +1,33 @@
 package cn.reghao.bnt.web.devops.deployer.service;
 
+import cn.reghao.bnt.web.devops.app.db.query.AppBuildQuery;
+import cn.reghao.bnt.web.devops.app.model.po.AppBuilding;
+import cn.reghao.bnt.web.devops.app.model.po.config.AppConfig;
+import cn.reghao.bnt.web.devops.builder.model.LocalBuildDir;
+import cn.reghao.bnt.web.devops.deployer.db.RemoteAgentConfigRepository;
 import cn.reghao.bnt.web.devops.deployer.db.RemoteHostRepository;
+import cn.reghao.bnt.web.devops.deployer.model.constant.NodeType;
+import cn.reghao.bnt.web.devops.deployer.model.dto.RemoteMachine;
+import cn.reghao.bnt.web.devops.deployer.model.dto.UpdateApp;
+import cn.reghao.bnt.web.devops.deployer.model.po.RemoteAgentConfig;
 import cn.reghao.bnt.web.devops.deployer.model.po.RemoteHost;
 import cn.reghao.bnt.web.devops.deployer.util.Sftp;
+import cn.reghao.jutil.jdk.io.TextFile;
+import cn.reghao.jutil.jdk.serializer.JsonConverter;
+import cn.reghao.jutil.jdk.thread.ThreadPoolWrapper;
+import cn.reghao.jutil.jdk.web.result.Result;
+import cn.reghao.jutil.jdk.web.result.ResultStatus;
+import com.jcraft.jsch.Session;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.data.domain.PageRequest;
 import org.springframework.stereotype.Service;
 
+import java.io.File;
+import java.time.LocalDateTime;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
 
 /**
  * @author reghao
@@ -16,26 +36,81 @@ import java.util.List;
 @Slf4j
 @Service
 public class DeployApp {
+    private final ExecutorService threadPool = ThreadPoolWrapper.threadPool("update-pool", 5);
     private final RemoteHostRepository remoteHostRepository;
+    private final RemoteAgentConfigRepository remoteAgentConfigRepository;
+    private final AppBuildQuery appBuildQuery;
     private final Sftp sftp = new Sftp();
+    private final TextFile textFile = new TextFile();
+    private final String appId = "bnt";
 
-    public DeployApp(RemoteHostRepository remoteHostRepository) {
+    public DeployApp(RemoteHostRepository remoteHostRepository, RemoteAgentConfigRepository remoteAgentConfigRepository,
+                     AppBuildQuery appBuildQuery) {
         this.remoteHostRepository = remoteHostRepository;
+        this.remoteAgentConfigRepository = remoteAgentConfigRepository;
+        this.appBuildQuery = appBuildQuery;
     }
 
-    public void addRemoteHost(RemoteHost remoteHost) {
-        String host = remoteHost.getHost();
-        RemoteHost remoteHostEntity = remoteHostRepository.findByHost(host);
-        if (remoteHostEntity == null) {
-            remoteHostRepository.save(remoteHost);
+    public void addAgentConfig(RemoteAgentConfig remoteAgentConfig) {
+        RemoteAgentConfig entity = remoteAgentConfigRepository.findByMgrHost(remoteAgentConfig.getMgrHost());
+        if (entity == null) {
+            remoteAgentConfigRepository.save(remoteAgentConfig);
         }
     }
 
-    public void deleteRemoteHost(String host) {
-        RemoteHost remoteHostEntity = remoteHostRepository.findByHost(host);
-        if (remoteHostEntity != null) {
-            //remoteHostRepository.delete(remoteHostEntity);
+    public void deleteAgentConfig(int id) {
+        remoteAgentConfigRepository.deleteById(id);
+    }
+
+    public List<RemoteAgentConfig> getAgentConfigs() {
+        PageRequest pageRequest = PageRequest.of(0, 100);
+        return remoteAgentConfigRepository.findAll(pageRequest).getContent();
+    }
+
+    public Result addRemoteHost(RemoteMachine remoteMachine) {
+        Integer id = remoteMachine.getRemoteAgentConfig();
+        String nodeType = remoteMachine.getNodeType();
+        RemoteHost remoteHost;
+        if (NodeType.mgr.name().equals(nodeType)) {
+            remoteHost = new RemoteHost(remoteMachine, null);
+        } else if (id == null) {
+            return Result.fail("Remote agent config is required for agent node");
+        } else {
+            RemoteAgentConfig remoteAgentConfig = remoteAgentConfigRepository.findById(id).orElse(null);
+            if (remoteAgentConfig == null) {
+                return Result.fail("Remote agent config not exists");
+            }
+            remoteHost = new RemoteHost(remoteMachine, remoteAgentConfig);
+        }
+
+        Result result = Result.success();
+        try {
+            Session session = sftp.getSession(remoteHost);
+            if (session.isConnected()) {
+                boolean exist = sftp.mkdir(session, remoteHost.getAppDir());
+                if (exist) {
+                    log.error(remoteHost.getAppDir() + " exists");
+                    //result = Result.fail(remoteHost.getAppDir() + " exists");
+                }
+                session.disconnect();
+
+                String host = remoteHost.getHost();
+                RemoteHost remoteHostEntity = remoteHostRepository.findByHost(host);
+                if (remoteHostEntity == null) {
+                    remoteHostRepository.save(remoteHost);
+                    return result;
+                }
+            } else {
+                result = Result.fail("Connect to " + remoteHost.getHost() + " failed");
+            }
+        } catch (Exception e) {
+            result = Result.error(e.getMessage());
         }
+        return result;
+    }
+
+    public void deleteRemoteHost(int id) {
+        remoteHostRepository.deleteById(id);
     }
 
     public List<RemoteHost> getRemoteHosts(String nodeType) {
@@ -43,18 +118,93 @@ public class DeployApp {
         return remoteHostRepository.findByNodeType(nodeType, pageRequest).getContent();
     }
 
-    public void deployApp(String host) {
-        RemoteHost remoteHost = remoteHostRepository.findByHost(host);
-        if (remoteHost == null) {
-            return;
+    public Result deployApp(UpdateApp updateApp) {
+        AppConfig appConfig = appBuildQuery.getAppConfig(appId);
+        if (appConfig == null) {
+            return Result.fail(appId + " not exists");
         }
 
-        String localDir = "";
-        String remoteDir = remoteHost.getAppDir();
-        try {
-            sftp.deploy(localDir, remoteDir, remoteHost);
-        } catch (Exception e) {
-            e.printStackTrace();
+        AppBuilding appBuilding = appBuildQuery.getAppBuilding(appId);
+        if (appBuilding == null) {
+            return Result.fail(appId + " not built");
+        }
+
+        updateApp.getHostList().forEach(host -> {
+            /*UpdateTask updateTask = new UpdateTask(host, appBuilding);
+            threadPool.submit(updateTask);*/
+            RemoteHost remoteHost = remoteHostRepository.findByHost(host);
+            if (remoteHost == null) {
+                log.error("host {} not exists", host);
+                return;
+            }
+
+            String commitId = appBuilding.getCommitId();
+            String nodeType = remoteHost.getNodeType();
+            String localDir = String.format("%s/%s/%s_%s/%s", LocalBuildDir.packDir, appId, appId, commitId, nodeType);
+            String remoteDir = remoteHost.getAppDir();
+            try {
+                String agentConfigPath = String.format("%s/%s", localDir, "devopsagent.json");
+                RemoteAgentConfig remoteAgentConfig = remoteHost.getRemoteAgentConfig();
+                Map<String, String> map = new HashMap<>();
+                map.put("protocol", remoteAgentConfig.getMgrProtocol());
+                map.put("host", remoteAgentConfig.getMgrHost());
+                map.put("port", remoteAgentConfig.getMgrPort() + "");
+                textFile.write(new File(agentConfigPath), JsonConverter.objectToJson(map));
+
+                Result result = sftp.deploy(localDir, remoteDir, remoteHost);
+                if (result.getCode() == ResultStatus.SUCCESS.getCode()) {
+                    remoteHost.setAppVersion(commitId);
+                    remoteHost.setUpdateTime(LocalDateTime.now());
+                    remoteHostRepository.save(remoteHost);
+                }
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        });
+
+        return Result.success();
+    }
+
+    class UpdateTask implements Runnable {
+        private final String host;
+        private final AppBuilding appBuilding;
+
+        public UpdateTask(String host, AppBuilding appBuilding) {
+            this.host = host;
+            this.appBuilding = appBuilding;
+        }
+
+        @Override
+        public void run() {
+            try {
+                RemoteHost remoteHost = remoteHostRepository.findByHost(host);
+                if (remoteHost == null) {
+                    log.error("host {} not exists", host);
+                    return;
+                }
+
+                String commitId = appBuilding.getCommitId();
+                String nodeType = remoteHost.getNodeType();
+                String localDir = String.format("%s/%s/%s_%s/%s", LocalBuildDir.packDir, appId, appId, commitId, nodeType);
+                String remoteDir = remoteHost.getAppDir();
+
+                String agentConfigPath = String.format("%s/%s", localDir, "devopsagent.json");
+                RemoteAgentConfig remoteAgentConfig = remoteHost.getRemoteAgentConfig();
+                Map<String, String> map = new HashMap<>();
+                map.put("protocol", remoteAgentConfig.getMgrProtocol());
+                map.put("host", remoteAgentConfig.getMgrHost());
+                map.put("port", remoteAgentConfig.getMgrPort() + "");
+                textFile.write(new File(agentConfigPath), JsonConverter.objectToJson(map));
+
+                Result result = sftp.deploy(localDir, remoteDir, remoteHost);
+                if (result.getCode() == ResultStatus.SUCCESS.getCode()) {
+                    remoteHost.setAppVersion(commitId);
+                    remoteHost.setUpdateTime(LocalDateTime.now());
+                    remoteHostRepository.save(remoteHost);
+                }
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
         }
     }
 }

+ 62 - 30
web/src/main/java/cn/reghao/bnt/web/devops/deployer/util/Sftp.java

@@ -1,9 +1,11 @@
 package cn.reghao.bnt.web.devops.deployer.util;
 
+import cn.reghao.bnt.web.devops.deployer.model.constant.SshAuthType;
 import cn.reghao.bnt.web.devops.deployer.model.po.RemoteHost;
 import cn.reghao.bnt.web.devops.deployer.model.UserInfoImpl;
 import cn.reghao.jutil.jdk.io.TextFile;
 import cn.reghao.jutil.jdk.shell.ShellResult;
+import cn.reghao.jutil.jdk.web.result.Result;
 import com.jcraft.jsch.*;
 import lombok.extern.slf4j.Slf4j;
 
@@ -11,6 +13,7 @@ import java.io.*;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
+import java.util.UUID;
 
 /**
  * @author reghao
@@ -48,7 +51,9 @@ public class Sftp {
             session.setConfig("StrictHostKeyChecking", "no");
         } else if (remoteHost.getAuthType().equals("privateKey")) {
             String privateKey = remoteHost.getPrivateKey();
-            String prikeyPath = "";
+            String tmpDir = System.getProperty("java.io.tmpdir");
+            String prikeyPath = String.format("%s/devops_%s", tmpDir, UUID.randomUUID());
+            textFile.write(new File(prikeyPath), privateKey);
 
             session.setConfig("PreferredAuthentications", "publickey");
             session.setConfig("userauth.gssapi-with-mic", "no");
@@ -114,7 +119,7 @@ public class Sftp {
      * @return
      * @date 2024-02-20 09:20:11
      */
-    public boolean mkdir(Session session, String remotePath) throws SftpException, JSchException {
+    public boolean mkdir1(Session session, String remotePath) throws SftpException, JSchException {
         boolean exist = true;
         // 创建 sftp 通信通道
         Channel channel = session.openChannel("sftp");
@@ -144,6 +149,23 @@ public class Sftp {
         return exist;
     }
 
+    public boolean mkdir(Session session, String remotePath) throws Exception {
+        boolean exist = true;
+        // 创建 sftp 通信通道
+        Channel channel = session.openChannel("sftp");
+        channel.connect(5_000);
+        ChannelSftp sftp = (ChannelSftp) channel;
+
+        try {
+            sftp.stat(remotePath);
+        } catch (SftpException e) {
+            exist = false;
+            sftp.mkdir(remotePath);
+        }
+
+        return exist;
+    }
+
     public ShellResult exec(Session session, String command) throws JSchException, IOException {
         StringBuilder sb = new StringBuilder();
         // 创建 exec 通信通道
@@ -213,58 +235,68 @@ public class Sftp {
         return shellResult;
     }
 
-    public void deploy(String localDir, String remoteDir, RemoteHost remoteHost) throws Exception {
-        Session session = getSession(remoteHost);
-        boolean exist = mkdir(session, remoteDir);
-        if (exist) {
-            String command = String.format("cd %s && %s shutdown.sh", remoteDir, bash);
+    public Result deploy(String localDir, String remoteDir, RemoteHost remoteHost) {
+        Result result = Result.success();
+        try {
+            Session session = getSession(remoteHost);
+            boolean exist = mkdir(session, remoteDir);
+            if (exist) {
+                String command = String.format("cd %s && %s shutdown.sh", remoteDir, bash);
+                ShellResult shellResult = exec(session, command);
+                if (!shellResult.isSuccess()) {
+                    log.info("shutdown application failed\nexitCode: {}\nresult:\n{}", shellResult.getExitCode(), shellResult.getResult());
+                    result = Result.fail("");
+                } else {
+                    log.info("shutdown application successfully");
+                }
+            } else {
+                log.info("remote dir {} created", remoteDir);
+            }
+
+            upload(session, localDir, remoteDir);
+            log.info("files uploaded");
+
+            String command = String.format("cd %s && %s start.sh", remoteDir, bash);
             ShellResult shellResult = exec(session, command);
             if (!shellResult.isSuccess()) {
-                log.info("shutdown application failed\nexitCode: {}\nresult:\n{}", shellResult.getExitCode(), shellResult.getResult());
+                log.info("start application failed\nexitCode: {}\nresult:\n{}", shellResult.getExitCode(), shellResult.getResult());
                 System.exit(-1);
             } else {
-                log.info("shutdown application successfully");
+                log.info("start application successfully");
             }
-        } else {
-            log.info("remote dir {} created", remoteDir);
-        }
-
-        upload(session, localDir, remoteDir);
-        log.info("files uploaded");
 
-        String command = String.format("cd %s && %s start.sh", remoteDir, bash);
-        ShellResult shellResult = exec(session, command);
-        if (!shellResult.isSuccess()) {
-            log.info("start application failed\nexitCode: {}\nresult:\n{}", shellResult.getExitCode(), shellResult.getResult());
-            System.exit(-1);
-        } else {
-            log.info("start application successfully");
+            if (session.isConnected()) {
+                session.disconnect();
+            }
+        } catch (Exception e) {
+            result = Result.fail(e.getMessage());
         }
 
-        if (session.isConnected()) {
-            session.disconnect();
-        }
+        return result;
     }
 
-    public void exec(RemoteHost remoteHost, String command) throws Exception {
+    public ShellResult exec(RemoteHost remoteHost, String command) throws Exception {
         Session session = getSession(remoteHost);
         ShellResult shellResult = exec(session, command);
         log.info("\nexitCode: {}\nresult:\n{}", shellResult.getExitCode(), shellResult.getResult());
-
         if (session.isConnected()) {
             session.disconnect();
         }
+
+        return shellResult;
     }
 
     public static void main(String[] args) throws Exception {
-        String host = "192.168.0.180";
+        String host = "192.168.0.108";
+        host = "127.0.0.1";
         int port = 22;
+        String authType = SshAuthType.password.name();
         String username = "root";
         String password = "gsh";
-        RemoteHost remoteHost = new RemoteHost(host, port,  username, password);
+        RemoteHost remoteHost = new RemoteHost(host, port, authType, username, password);
+        Sftp sftp = new Sftp();
 
         String command = "docker ps -a";
-        Sftp sftp = new Sftp();
         sftp.exec(remoteHost, command);
     }
 }