Skip to content

Commit d9d05f1

Browse files
committed
Merge feature/host-deployment-enhancement: host batch import, batch deploy, SSH key auth
2 parents 5dae10c + 78719d0 commit d9d05f1

15 files changed

Lines changed: 1019 additions & 169 deletions

File tree

easyshell-server/src/main/java/com/easyshell/server/controller/HostController.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import com.easyshell.server.repository.AgentTagRepository;
55
import com.easyshell.server.repository.HostCredentialRepository;
66
import org.springframework.transaction.annotation.Transactional;
7+
import com.easyshell.server.service.HostProvisionService;
78

89
import com.easyshell.server.common.result.R;
910
import com.easyshell.server.model.entity.Agent;
@@ -40,6 +41,8 @@ public class HostController {
4041

4142
private final AgentTagRepository agentTagRepository;
4243
private final HostCredentialRepository credentialRepository;
44+
private final HostProvisionService hostProvisionService;
45+
4346

4447
@GetMapping("/list")
4548
public R<List<Agent>> list() {
@@ -65,6 +68,17 @@ public R<Void> deleteHost(@PathVariable String agentId) {
6568
credentialRepository.findByIp(agent.getIp()).ifPresent(credentialRepository::delete);
6669
return R.ok();
6770
}
71+
72+
/**
73+
* Delete a pending/failed host credential (before agent deployment).
74+
* This is different from deleteHost which deletes an agent.
75+
*/
76+
@DeleteMapping("/credential/{id}")
77+
@Transactional
78+
public R<Void> deleteCredential(@PathVariable Long id) {
79+
hostProvisionService.deleteById(id);
80+
return R.ok();
81+
}
6882
@GetMapping("/{agentId}/tags")
6983
public R<List<TagVO>> agentTags(@PathVariable String agentId) {
7084
return R.ok(tagService.getAgentTags(agentId));

easyshell-server/src/main/java/com/easyshell/server/controller/HostProvisionController.java

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@
66
import com.easyshell.server.service.HostProvisionService;
77
import jakarta.validation.Valid;
88
import lombok.RequiredArgsConstructor;
9+
import org.springframework.http.HttpHeaders;
10+
import org.springframework.http.MediaType;
11+
import org.springframework.http.ResponseEntity;
912
import org.springframework.web.bind.annotation.*;
13+
import org.springframework.web.multipart.MultipartFile;
1014

1115
import java.util.List;
1216

@@ -20,7 +24,10 @@ public class HostProvisionController {
2024
@PostMapping
2125
public R<HostCredentialVO> provision(@Valid @RequestBody HostProvisionRequest request) {
2226
HostCredentialVO vo = hostProvisionService.provision(request);
23-
hostProvisionService.startProvisionAsync(vo.getId());
27+
// Only start async deploy if deployNow is true (default)
28+
if (request.getDeployNow() == null || request.getDeployNow()) {
29+
hostProvisionService.startProvisionAsync(vo.getId());
30+
}
2431
return R.ok(vo);
2532
}
2633

@@ -54,6 +61,13 @@ public R<HostCredentialVO> reinstall(@PathVariable String agentId) {
5461
return R.ok(vo);
5562
}
5663

64+
@PostMapping("/reinstall/credential/{credentialId}")
65+
public R<HostCredentialVO> reinstallByCredential(@PathVariable Long credentialId) {
66+
HostCredentialVO vo = hostProvisionService.reinstallByCredentialId(credentialId);
67+
hostProvisionService.startProvisionAsync(vo.getId());
68+
return R.ok(vo);
69+
}
70+
5771
@PostMapping("/reinstall/batch")
5872
public R<List<HostCredentialVO>> batchReinstall(@RequestBody List<String> agentIds) {
5973
return R.ok(hostProvisionService.batchReinstall(agentIds));
@@ -65,4 +79,28 @@ public R<HostCredentialVO> uninstall(@PathVariable String agentId) {
6579
hostProvisionService.startUninstallAsync(vo.getId());
6680
return R.ok(vo);
6781
}
82+
83+
@PostMapping("/deploy/batch")
84+
public R<List<HostCredentialVO>> batchDeploy(@RequestBody List<Long> credentialIds) {
85+
return R.ok(hostProvisionService.batchDeploy(credentialIds));
86+
}
87+
88+
@PostMapping("/import/csv")
89+
public R<List<HostCredentialVO>> importCsv(@RequestParam("file") MultipartFile file) throws Exception {
90+
return R.ok(hostProvisionService.importFromCsv(file.getInputStream()));
91+
}
92+
93+
@GetMapping("/import/template")
94+
public ResponseEntity<byte[]> downloadTemplate() {
95+
byte[] content = hostProvisionService.generateCsvTemplate();
96+
return ResponseEntity.ok()
97+
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=host_import_template.csv")
98+
.contentType(MediaType.APPLICATION_OCTET_STREAM)
99+
.body(content);
100+
}
101+
102+
@GetMapping("/unified-list")
103+
public R<List<HostCredentialVO>> unifiedList() {
104+
return R.ok(hostProvisionService.listUnified());
105+
}
68106
}

easyshell-server/src/main/java/com/easyshell/server/model/dto/HostProvisionRequest.java

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,25 @@ public class HostProvisionRequest {
1818
@NotBlank(message = "SSH username is required")
1919
private String sshUsername;
2020

21-
@NotBlank(message = "SSH password is required")
2221
private String sshPassword;
22+
23+
/**
24+
* Authentication type: password or key (default: password)
25+
*/
26+
private String authType = "password";
27+
28+
/**
29+
* SSH private key content (PEM format), used when authType=key
30+
*/
31+
private String sshPrivateKey;
32+
33+
/**
34+
* Display name / alias for the host
35+
*/
36+
private String hostName;
37+
38+
/**
39+
* Whether to deploy immediately after adding (default: true for backward compatibility)
40+
*/
41+
private Boolean deployNow = true;
2342
}

easyshell-server/src/main/java/com/easyshell/server/model/entity/HostCredential.java

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,27 @@ public class HostCredential extends BaseEntity {
2828
/**
2929
* AES-256-CBC encrypted SSH password (Base64 encoded)
3030
*/
31-
@Column(name = "ssh_password_encrypted", nullable = false, length = 512)
31+
@Column(name = "ssh_password_encrypted", length = 512)
3232
private String sshPasswordEncrypted;
3333

34+
/**
35+
* Authentication type: password or key
36+
*/
37+
@Column(name = "auth_type", nullable = false, length = 20)
38+
private String authType = "password";
39+
40+
/**
41+
* AES-256-CBC encrypted SSH private key (Base64 encoded), used when authType=key
42+
*/
43+
@Column(name = "ssh_private_key_encrypted", columnDefinition = "TEXT")
44+
private String sshPrivateKeyEncrypted;
45+
46+
/**
47+
* Display name / alias for the host
48+
*/
49+
@Column(name = "host_name", length = 128)
50+
private String hostName;
51+
3452
/**
3553
* Associated agent ID after successful provisioning
3654
*/

easyshell-server/src/main/java/com/easyshell/server/model/vo/HostCredentialVO.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,28 @@ public class HostCredentialVO {
1010
private String ip;
1111
private Integer sshPort;
1212
private String sshUsername;
13+
private String authType;
14+
private String hostName;
1315
private String agentId;
1416
private String provisionStatus;
1517
private String provisionLog;
1618
private String errorMessage;
1719
private String createdAt;
1820
private String updatedAt;
21+
22+
// Agent-merged fields (null when no agent registered yet)
23+
private String hostname;
24+
private String os;
25+
private String arch;
26+
private String kernel;
27+
private String cpuModel;
28+
private Integer cpuCores;
29+
private Long memTotal;
30+
private String agentVersion;
31+
private Integer agentStatus;
32+
private String lastHeartbeat;
33+
private Double cpuUsage;
34+
private Double memUsage;
35+
private Double diskUsage;
36+
private String registeredAt;
1937
}

easyshell-server/src/main/java/com/easyshell/server/repository/HostCredentialRepository.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,6 @@
99
public interface HostCredentialRepository extends JpaRepository<HostCredential, Long> {
1010
Optional<HostCredential> findByIp(String ip);
1111
List<HostCredential> findAllByOrderByCreatedAtDesc();
12+
List<HostCredential> findAllByProvisionStatusIn(List<String> statuses);
13+
List<HostCredential> findAllByIdIn(List<Long> ids);
1214
}

easyshell-server/src/main/java/com/easyshell/server/service/HostProvisionService.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import com.easyshell.server.model.vo.HostCredentialVO;
55

66
import java.util.List;
7+
import java.io.InputStream;
78

89
public interface HostProvisionService {
910
HostCredentialVO provision(HostProvisionRequest request);
@@ -15,7 +16,14 @@ public interface HostProvisionService {
1516
void startRetryAsync(Long credentialId);
1617
HostCredentialVO reinstall(String agentId);
1718
void startReinstallAsync(Long credentialId);
19+
HostCredentialVO reinstallByCredentialId(Long credentialId);
1820
List<HostCredentialVO> batchReinstall(List<String> agentIds);
1921
HostCredentialVO uninstall(String agentId);
2022
void startUninstallAsync(Long credentialId);
23+
24+
// New methods for host deployment enhancement
25+
List<HostCredentialVO> listUnified();
26+
List<HostCredentialVO> batchDeploy(List<Long> credentialIds);
27+
List<HostCredentialVO> importFromCsv(InputStream csvInputStream);
28+
byte[] generateCsvTemplate();
2129
}

0 commit comments

Comments
 (0)