216 lines
7.8 KiB
Java
216 lines
7.8 KiB
Java
package com.cfdeployer.service;
|
|
|
|
import com.cfdeployer.model.CfDeployRequest;
|
|
import com.cfdeployer.model.CfDeployResponse;
|
|
import lombok.RequiredArgsConstructor;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import org.apache.commons.io.FileUtils;
|
|
import org.springframework.beans.factory.annotation.Value;
|
|
import org.springframework.stereotype.Service;
|
|
import org.springframework.web.multipart.MultipartFile;
|
|
|
|
import java.io.BufferedReader;
|
|
import java.io.File;
|
|
import java.io.IOException;
|
|
import java.io.InputStreamReader;
|
|
import java.nio.file.Files;
|
|
import java.nio.file.Path;
|
|
import java.nio.file.StandardCopyOption;
|
|
import java.nio.file.attribute.PosixFilePermission;
|
|
import java.util.ArrayList;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Set;
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
@Slf4j
|
|
@Service
|
|
@RequiredArgsConstructor
|
|
public class CfCliService {
|
|
|
|
@Value("${cf.cli.timeout:600}")
|
|
private long timeout;
|
|
|
|
@Value("${cf.cli.path:}")
|
|
private String cfCliPath;
|
|
|
|
public CfDeployResponse deployApplication(CfDeployRequest request, MultipartFile jarFile, MultipartFile manifest) {
|
|
Path tempDir = null;
|
|
try {
|
|
tempDir = Files.createTempDirectory("cf-deploy-");
|
|
log.info("Created temporary directory: {}", tempDir);
|
|
|
|
Path jarPath = tempDir.resolve(jarFile.getOriginalFilename());
|
|
Path manifestPath = tempDir.resolve("manifest.yml");
|
|
|
|
Files.copy(jarFile.getInputStream(), jarPath, StandardCopyOption.REPLACE_EXISTING);
|
|
Files.copy(manifest.getInputStream(), manifestPath, StandardCopyOption.REPLACE_EXISTING);
|
|
|
|
log.info("Copied JAR and manifest files to temporary directory");
|
|
|
|
StringBuilder output = new StringBuilder();
|
|
|
|
login(request, output);
|
|
pushApplication(request, tempDir, output);
|
|
logout(output);
|
|
|
|
log.info("Deployment completed successfully for app: {}", request.getAppName());
|
|
return CfDeployResponse.success(output.toString());
|
|
|
|
} catch (Exception e) {
|
|
log.error("Deployment failed for app: {}", request.getAppName(), e);
|
|
return CfDeployResponse.failure(e.getMessage(), e.toString());
|
|
} finally {
|
|
if (tempDir != null) {
|
|
cleanupTempDirectory(tempDir);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void login(CfDeployRequest request, StringBuilder output) throws Exception {
|
|
log.info("Logging into Cloud Foundry at: {}", request.getApiEndpoint());
|
|
|
|
List<String> command = new ArrayList<>();
|
|
command.add(getCfCliExecutable());
|
|
command.add("login");
|
|
command.add("-a");
|
|
command.add(request.getApiEndpoint());
|
|
command.add("-u");
|
|
command.add(request.getUsername());
|
|
command.add("-p");
|
|
command.add(request.getPassword());
|
|
command.add("-o");
|
|
command.add(request.getOrganization());
|
|
command.add("-s");
|
|
command.add(request.getSpace());
|
|
|
|
if (Boolean.TRUE.equals(request.getSkipSslValidation())) {
|
|
command.add("--skip-ssl-validation");
|
|
}
|
|
|
|
executeCommand(command, output, true);
|
|
log.info("Successfully logged into Cloud Foundry");
|
|
}
|
|
|
|
private void pushApplication(CfDeployRequest request, Path workingDir, StringBuilder output) throws Exception {
|
|
log.info("Pushing application: {}", request.getAppName());
|
|
|
|
List<String> command = new ArrayList<>();
|
|
command.add(getCfCliExecutable());
|
|
command.add("push");
|
|
command.add(request.getAppName());
|
|
command.add("-f");
|
|
command.add("manifest.yml");
|
|
|
|
executeCommand(command, output, false, workingDir.toFile());
|
|
log.info("Successfully pushed application: {}", request.getAppName());
|
|
}
|
|
|
|
private void logout(StringBuilder output) throws Exception {
|
|
log.info("Logging out from Cloud Foundry");
|
|
|
|
List<String> command = new ArrayList<>();
|
|
command.add(getCfCliExecutable());
|
|
command.add("logout");
|
|
|
|
executeCommand(command, output, false);
|
|
log.info("Successfully logged out from Cloud Foundry");
|
|
}
|
|
|
|
private void executeCommand(List<String> command, StringBuilder output, boolean maskPassword) throws Exception {
|
|
executeCommand(command, output, maskPassword, null);
|
|
}
|
|
|
|
private void executeCommand(List<String> command, StringBuilder output, boolean maskPassword, File workingDir) throws Exception {
|
|
ProcessBuilder processBuilder = new ProcessBuilder(command);
|
|
if (workingDir != null) {
|
|
processBuilder.directory(workingDir);
|
|
}
|
|
processBuilder.redirectErrorStream(true);
|
|
|
|
if (maskPassword) {
|
|
List<String> maskedCommand = new ArrayList<>(command);
|
|
for (int i = 0; i < maskedCommand.size(); i++) {
|
|
if ("-p".equals(maskedCommand.get(i)) && i + 1 < maskedCommand.size()) {
|
|
maskedCommand.set(i + 1, "********");
|
|
}
|
|
}
|
|
log.debug("Executing command: {}", String.join(" ", maskedCommand));
|
|
} else {
|
|
log.debug("Executing command: {}", String.join(" ", command));
|
|
}
|
|
|
|
Process process = processBuilder.start();
|
|
|
|
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
|
|
String line;
|
|
while ((line = reader.readLine()) != null) {
|
|
output.append(line).append("\n");
|
|
log.debug("CF CLI output: {}", line);
|
|
}
|
|
}
|
|
|
|
boolean finished = process.waitFor(timeout, TimeUnit.SECONDS);
|
|
if (!finished) {
|
|
process.destroyForcibly();
|
|
throw new RuntimeException("Command execution timed out after " + timeout + " seconds");
|
|
}
|
|
|
|
int exitCode = process.exitValue();
|
|
if (exitCode != 0) {
|
|
throw new RuntimeException("Command failed with exit code: " + exitCode);
|
|
}
|
|
}
|
|
|
|
private String getCfCliExecutable() throws IOException {
|
|
if (cfCliPath != null && !cfCliPath.isEmpty()) {
|
|
return cfCliPath;
|
|
}
|
|
|
|
String os = getOperatingSystem();
|
|
String executable = os.equals("windows") ? "cf.exe" : "cf";
|
|
|
|
String resourcePath = String.format("/cf-cli/%s/%s", os, executable);
|
|
File tempFile = File.createTempFile("cf-cli-", os.equals("windows") ? ".exe" : "");
|
|
tempFile.deleteOnExit();
|
|
|
|
try (var inputStream = getClass().getResourceAsStream(resourcePath)) {
|
|
if (inputStream == null) {
|
|
throw new IOException("CF CLI binary not found for OS: " + os);
|
|
}
|
|
Files.copy(inputStream, tempFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
|
}
|
|
|
|
if (!os.equals("windows")) {
|
|
Set<PosixFilePermission> perms = new HashSet<>();
|
|
perms.add(PosixFilePermission.OWNER_READ);
|
|
perms.add(PosixFilePermission.OWNER_WRITE);
|
|
perms.add(PosixFilePermission.OWNER_EXECUTE);
|
|
Files.setPosixFilePermissions(tempFile.toPath(), perms);
|
|
}
|
|
|
|
log.debug("Using CF CLI executable: {}", tempFile.getAbsolutePath());
|
|
return tempFile.getAbsolutePath();
|
|
}
|
|
|
|
private String getOperatingSystem() {
|
|
String osName = System.getProperty("os.name").toLowerCase();
|
|
if (osName.contains("win")) {
|
|
return "windows";
|
|
} else if (osName.contains("mac")) {
|
|
return "macos";
|
|
} else {
|
|
return "linux";
|
|
}
|
|
}
|
|
|
|
private void cleanupTempDirectory(Path tempDir) {
|
|
try {
|
|
FileUtils.deleteDirectory(tempDir.toFile());
|
|
log.debug("Cleaned up temporary directory: {}", tempDir);
|
|
} catch (IOException e) {
|
|
log.warn("Failed to clean up temporary directory: {}", tempDir, e);
|
|
}
|
|
}
|
|
}
|