Tanzu Changes
This commit is contained in:
142
README.md
142
README.md
@@ -26,9 +26,9 @@ java -jar build/libs/cf-uploader-1.0.0.jar
|
|||||||
|
|
||||||
The application will start on `http://localhost:8080`
|
The application will start on `http://localhost:8080`
|
||||||
|
|
||||||
## API Endpoint
|
## API Endpoints
|
||||||
|
|
||||||
### Deploy Application to Cloud Foundry
|
### 1. Deploy Application to Cloud Foundry
|
||||||
|
|
||||||
**Endpoint:** `POST /api/cf/deploy`
|
**Endpoint:** `POST /api/cf/deploy`
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ The application will start on `http://localhost:8080`
|
|||||||
- `jarFile` (file): The Java application JAR file
|
- `jarFile` (file): The Java application JAR file
|
||||||
- `manifest` (file): Cloud Foundry manifest.yml file
|
- `manifest` (file): Cloud Foundry manifest.yml file
|
||||||
|
|
||||||
### Sample cURL Request
|
#### Sample cURL Request
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -X POST http://localhost:8080/api/cf/deploy \
|
curl -X POST http://localhost:8080/api/cf/deploy \
|
||||||
@@ -56,7 +56,7 @@ curl -X POST http://localhost:8080/api/cf/deploy \
|
|||||||
-F 'manifest=@/path/to/manifest.yml'
|
-F 'manifest=@/path/to/manifest.yml'
|
||||||
```
|
```
|
||||||
|
|
||||||
### Sample Request with Skip SSL Validation
|
#### Sample Request with Skip SSL Validation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -X POST http://localhost:8080/api/cf/deploy \
|
curl -X POST http://localhost:8080/api/cf/deploy \
|
||||||
@@ -73,6 +73,115 @@ curl -X POST http://localhost:8080/api/cf/deploy \
|
|||||||
-F 'manifest=@./manifest.yml'
|
-F 'manifest=@./manifest.yml'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. List Applications
|
||||||
|
|
||||||
|
**Endpoint:** `POST /api/cf/apps`
|
||||||
|
|
||||||
|
**Content-Type:** `application/json`
|
||||||
|
|
||||||
|
Lists all applications in the specified organization and space.
|
||||||
|
|
||||||
|
#### Sample cURL Request
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST http://localhost:8080/api/cf/apps \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"apiEndpoint": "https://api.cf.example.com",
|
||||||
|
"username": "your-username",
|
||||||
|
"password": "your-password",
|
||||||
|
"organization": "your-org",
|
||||||
|
"space": "your-space",
|
||||||
|
"appName": "",
|
||||||
|
"skipSslValidation": false
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. List Routes
|
||||||
|
|
||||||
|
**Endpoint:** `POST /api/cf/routes`
|
||||||
|
|
||||||
|
**Content-Type:** `application/json`
|
||||||
|
|
||||||
|
Lists all routes in the specified organization and space.
|
||||||
|
|
||||||
|
#### Sample cURL Request
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST http://localhost:8080/api/cf/routes \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"apiEndpoint": "https://api.cf.example.com",
|
||||||
|
"username": "your-username",
|
||||||
|
"password": "your-password",
|
||||||
|
"organization": "your-org",
|
||||||
|
"space": "your-space",
|
||||||
|
"appName": "",
|
||||||
|
"skipSslValidation": false
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. Get Application Details
|
||||||
|
|
||||||
|
**Endpoint:** `POST /api/cf/app/{appName}`
|
||||||
|
|
||||||
|
**Content-Type:** `application/json`
|
||||||
|
|
||||||
|
Gets detailed information about a specific application.
|
||||||
|
|
||||||
|
#### Sample cURL Request
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST http://localhost:8080/api/cf/app/my-app \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"apiEndpoint": "https://api.cf.example.com",
|
||||||
|
"username": "your-username",
|
||||||
|
"password": "your-password",
|
||||||
|
"organization": "your-org",
|
||||||
|
"space": "your-space",
|
||||||
|
"appName": "",
|
||||||
|
"skipSslValidation": false
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5. Get Application Logs
|
||||||
|
|
||||||
|
**Endpoint:** `POST /api/cf/logs/{appName}?recent=true`
|
||||||
|
|
||||||
|
**Content-Type:** `application/json`
|
||||||
|
|
||||||
|
**Query Parameters:**
|
||||||
|
- `recent` (optional, default: `true`): If true, gets recent logs; if false, tails logs
|
||||||
|
|
||||||
|
Gets logs for a specific application.
|
||||||
|
|
||||||
|
#### Sample cURL Request (Recent Logs)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST "http://localhost:8080/api/cf/logs/my-app?recent=true" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"apiEndpoint": "https://api.cf.example.com",
|
||||||
|
"username": "your-username",
|
||||||
|
"password": "your-password",
|
||||||
|
"organization": "your-org",
|
||||||
|
"space": "your-space",
|
||||||
|
"appName": "",
|
||||||
|
"skipSslValidation": false
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### Sample Manifest File (manifest.yml)
|
### Sample Manifest File (manifest.yml)
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
@@ -88,7 +197,9 @@ applications:
|
|||||||
SPRING_PROFILES_ACTIVE: production
|
SPRING_PROFILES_ACTIVE: production
|
||||||
```
|
```
|
||||||
|
|
||||||
## Request Parameters
|
## Common Request Parameters
|
||||||
|
|
||||||
|
All endpoints require the following CF credentials and target information:
|
||||||
|
|
||||||
| Field | Type | Required | Description |
|
| Field | Type | Required | Description |
|
||||||
|-------|------|----------|-------------|
|
|-------|------|----------|-------------|
|
||||||
@@ -97,9 +208,11 @@ applications:
|
|||||||
| `password` | String | Yes | CF password |
|
| `password` | String | Yes | CF password |
|
||||||
| `organization` | String | Yes | Target CF organization |
|
| `organization` | String | Yes | Target CF organization |
|
||||||
| `space` | String | Yes | Target CF space |
|
| `space` | String | Yes | Target CF space |
|
||||||
| `appName` | String | Yes | Application name |
|
| `appName` | String | No* | Application name (required only for `/deploy` endpoint) |
|
||||||
| `skipSslValidation` | Boolean | Yes | Skip SSL certificate validation |
|
| `skipSslValidation` | Boolean | Yes | Skip SSL certificate validation |
|
||||||
|
|
||||||
|
**Note:** Even though `appName` is not used by utility endpoints (`/apps`, `/routes`, `/app/{appName}`, `/logs/{appName}`), it must still be included in the JSON request body (can be empty string). The organization and space determine which apps/routes are listed or queried.
|
||||||
|
|
||||||
## Response Format
|
## Response Format
|
||||||
|
|
||||||
### Success Response
|
### Success Response
|
||||||
@@ -147,13 +260,16 @@ cf.cli.path=
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- RESTful API for CF deployments
|
- **Application Deployment**: Deploy JAR files to Cloud Foundry with manifest support
|
||||||
- Automatic CF CLI binary management (Linux, macOS, Windows)
|
- **Application Management**: List apps, view details, and access logs
|
||||||
- Secure password handling (masked in logs)
|
- **Route Management**: List all routes in your CF space
|
||||||
- Configurable timeout for long-running deployments
|
- **Automatic CF CLI Management**: Bundled CF CLI binaries for Linux, macOS, and Windows
|
||||||
- Comprehensive error handling
|
- **Secure Password Handling**: Passwords are masked in all log output
|
||||||
- Multipart file upload support up to 500MB
|
- **Comprehensive Logging**: Detailed DEBUG-level logging for troubleshooting deployments
|
||||||
- Automatic cleanup of temporary files
|
- **Configurable Timeouts**: Adjustable timeout for long-running deployments (default: 600s)
|
||||||
|
- **Large File Support**: Multipart file upload support up to 500MB
|
||||||
|
- **Automatic Cleanup**: Temporary files are automatically cleaned up after operations
|
||||||
|
- **Error Handling**: Comprehensive exception handling with detailed error messages
|
||||||
|
|
||||||
## Error Handling
|
## Error Handling
|
||||||
|
|
||||||
|
|||||||
39
build.gradle
39
build.gradle
@@ -42,6 +42,8 @@ task downloadCfCli {
|
|||||||
def resourcesDir = file("$projectDir/src/main/resources/cf-cli")
|
def resourcesDir = file("$projectDir/src/main/resources/cf-cli")
|
||||||
resourcesDir.mkdirs()
|
resourcesDir.mkdirs()
|
||||||
|
|
||||||
|
// Download for all platforms (useful for local development on different OSes)
|
||||||
|
// For production Tanzu deployment, only Linux binary will be used
|
||||||
def downloads = [
|
def downloads = [
|
||||||
[os: 'linux', url: "https://packages.cloudfoundry.org/stable?release=linux64-binary&version=${cfCliVersion}&source=github-rel", ext: 'tgz', executable: 'cf'],
|
[os: 'linux', url: "https://packages.cloudfoundry.org/stable?release=linux64-binary&version=${cfCliVersion}&source=github-rel", ext: 'tgz', executable: 'cf'],
|
||||||
[os: 'macos', url: "https://packages.cloudfoundry.org/stable?release=macosx64-binary&version=${cfCliVersion}&source=github-rel", ext: 'tgz', executable: 'cf'],
|
[os: 'macos', url: "https://packages.cloudfoundry.org/stable?release=macosx64-binary&version=${cfCliVersion}&source=github-rel", ext: 'tgz', executable: 'cf'],
|
||||||
@@ -81,5 +83,42 @@ task downloadCfCli {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Task to download only Linux CF CLI (for production Tanzu builds)
|
||||||
|
task downloadCfCliLinuxOnly {
|
||||||
|
group = 'build'
|
||||||
|
description = 'Downloads CF CLI binary for Linux only (production builds)'
|
||||||
|
|
||||||
|
doLast {
|
||||||
|
def cfCliVersion = '8.7.10'
|
||||||
|
def resourcesDir = file("$projectDir/src/main/resources/cf-cli")
|
||||||
|
resourcesDir.mkdirs()
|
||||||
|
|
||||||
|
def download = [os: 'linux', url: "https://packages.cloudfoundry.org/stable?release=linux64-binary&version=${cfCliVersion}&source=github-rel", ext: 'tgz', executable: 'cf']
|
||||||
|
|
||||||
|
def osDir = file("${resourcesDir}/${download.os}")
|
||||||
|
osDir.mkdirs()
|
||||||
|
|
||||||
|
def archiveFile = file("${osDir}/cf-cli.${download.ext}")
|
||||||
|
|
||||||
|
if (!archiveFile.exists()) {
|
||||||
|
println "Downloading CF CLI for Linux..."
|
||||||
|
ant.get(src: download.url, dest: archiveFile, verbose: true)
|
||||||
|
|
||||||
|
println "Extracting CF CLI for Linux..."
|
||||||
|
copy {
|
||||||
|
from tarTree(resources.gzip(archiveFile))
|
||||||
|
into osDir
|
||||||
|
}
|
||||||
|
|
||||||
|
archiveFile.delete()
|
||||||
|
println "CF CLI for Linux downloaded and extracted successfully"
|
||||||
|
} else {
|
||||||
|
println "CF CLI for Linux already exists, skipping download"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure CF CLI is downloaded before building
|
// Ensure CF CLI is downloaded before building
|
||||||
|
// Use downloadCfCli for local development (all platforms)
|
||||||
|
// Use downloadCfCliLinuxOnly for production Tanzu builds (Linux only)
|
||||||
processResources.dependsOn downloadCfCli
|
processResources.dependsOn downloadCfCli
|
||||||
|
|||||||
@@ -11,10 +11,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestPart;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@@ -60,6 +57,67 @@ public class CfDeployController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/apps")
|
||||||
|
public ResponseEntity<String> listApps(@RequestBody String requestJson) {
|
||||||
|
try {
|
||||||
|
log.info("Received request to list apps");
|
||||||
|
CfDeployRequest request = objectMapper.readValue(requestJson, CfDeployRequest.class);
|
||||||
|
String output = cfCliService.listApps(request);
|
||||||
|
return ResponseEntity.ok(output);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Error listing apps", e);
|
||||||
|
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||||
|
.body("Failed to list apps: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/routes")
|
||||||
|
public ResponseEntity<String> listRoutes(@RequestBody String requestJson) {
|
||||||
|
try {
|
||||||
|
log.info("Received request to list routes");
|
||||||
|
CfDeployRequest request = objectMapper.readValue(requestJson, CfDeployRequest.class);
|
||||||
|
String output = cfCliService.listRoutes(request);
|
||||||
|
return ResponseEntity.ok(output);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Error listing routes", e);
|
||||||
|
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||||
|
.body("Failed to list routes: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/app/{appName}")
|
||||||
|
public ResponseEntity<String> getAppDetails(
|
||||||
|
@PathVariable String appName,
|
||||||
|
@RequestBody String requestJson) {
|
||||||
|
try {
|
||||||
|
log.info("Received request to get details for app: {}", appName);
|
||||||
|
CfDeployRequest request = objectMapper.readValue(requestJson, CfDeployRequest.class);
|
||||||
|
String output = cfCliService.getAppDetails(request, appName);
|
||||||
|
return ResponseEntity.ok(output);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Error getting app details for: {}", appName, e);
|
||||||
|
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||||
|
.body("Failed to get app details: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/logs/{appName}")
|
||||||
|
public ResponseEntity<String> getAppLogs(
|
||||||
|
@PathVariable String appName,
|
||||||
|
@RequestParam(defaultValue = "true") boolean recent,
|
||||||
|
@RequestBody String requestJson) {
|
||||||
|
try {
|
||||||
|
log.info("Received request to get {} logs for app: {}", recent ? "recent" : "tail", appName);
|
||||||
|
CfDeployRequest request = objectMapper.readValue(requestJson, CfDeployRequest.class);
|
||||||
|
String output = cfCliService.getAppLogs(request, appName, recent);
|
||||||
|
return ResponseEntity.ok(output);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Error getting logs for: {}", appName, e);
|
||||||
|
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||||
|
.body("Failed to get app logs: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void validateFiles(MultipartFile jarFile, MultipartFile manifest) {
|
private void validateFiles(MultipartFile jarFile, MultipartFile manifest) {
|
||||||
if (jarFile.isEmpty()) {
|
if (jarFile.isEmpty()) {
|
||||||
throw new IllegalArgumentException("JAR file is empty");
|
throw new IllegalArgumentException("JAR file is empty");
|
||||||
|
|||||||
@@ -39,28 +39,41 @@ public class CfCliService {
|
|||||||
public CfDeployResponse deployApplication(CfDeployRequest request, MultipartFile jarFile, MultipartFile manifest) {
|
public CfDeployResponse deployApplication(CfDeployRequest request, MultipartFile jarFile, MultipartFile manifest) {
|
||||||
Path tempDir = null;
|
Path tempDir = null;
|
||||||
try {
|
try {
|
||||||
|
log.info("=== Starting deployment for app: {} ===", request.getAppName());
|
||||||
|
log.info("Target: {}/{}/{}", request.getApiEndpoint(), request.getOrganization(), request.getSpace());
|
||||||
|
|
||||||
tempDir = Files.createTempDirectory("cf-deploy-");
|
tempDir = Files.createTempDirectory("cf-deploy-");
|
||||||
log.info("Created temporary directory: {}", tempDir);
|
log.info("Created temporary directory: {}", tempDir);
|
||||||
|
|
||||||
Path jarPath = tempDir.resolve(jarFile.getOriginalFilename());
|
Path jarPath = tempDir.resolve(jarFile.getOriginalFilename());
|
||||||
Path manifestPath = tempDir.resolve("manifest.yml");
|
Path manifestPath = tempDir.resolve("manifest.yml");
|
||||||
|
|
||||||
|
log.debug("Copying uploaded files - JAR: {}, Manifest: {}", jarFile.getOriginalFilename(), manifest.getOriginalFilename());
|
||||||
|
log.debug("JAR file size: {} bytes", jarFile.getSize());
|
||||||
|
log.debug("Manifest file size: {} bytes", manifest.getSize());
|
||||||
|
|
||||||
Files.copy(jarFile.getInputStream(), jarPath, StandardCopyOption.REPLACE_EXISTING);
|
Files.copy(jarFile.getInputStream(), jarPath, StandardCopyOption.REPLACE_EXISTING);
|
||||||
Files.copy(manifest.getInputStream(), manifestPath, StandardCopyOption.REPLACE_EXISTING);
|
Files.copy(manifest.getInputStream(), manifestPath, StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
|
||||||
log.info("Copied JAR and manifest files to temporary directory");
|
log.info("Successfully copied JAR: {} ({} bytes) to {}", jarFile.getOriginalFilename(), jarFile.getSize(), jarPath);
|
||||||
|
log.info("Successfully copied manifest to {}", manifestPath);
|
||||||
|
log.debug("JAR absolute path: {}", jarPath.toAbsolutePath());
|
||||||
|
log.debug("JAR file exists: {}", Files.exists(jarPath));
|
||||||
|
log.debug("JAR file is readable: {}", Files.isReadable(jarPath));
|
||||||
|
|
||||||
StringBuilder output = new StringBuilder();
|
StringBuilder output = new StringBuilder();
|
||||||
|
|
||||||
login(request, output);
|
login(request, output);
|
||||||
pushApplication(request, tempDir, output);
|
pushApplication(request, tempDir, jarPath, output);
|
||||||
logout(output);
|
logout(output);
|
||||||
|
|
||||||
log.info("Deployment completed successfully for app: {}", request.getAppName());
|
log.info("=== Deployment completed successfully for app: {} ===", request.getAppName());
|
||||||
return CfDeployResponse.success(output.toString());
|
return CfDeployResponse.success(output.toString());
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Deployment failed for app: {}", request.getAppName(), e);
|
log.error("=== Deployment failed for app: {} ===", request.getAppName());
|
||||||
|
log.error("Error type: {}", e.getClass().getName());
|
||||||
|
log.error("Error message: {}", e.getMessage(), e);
|
||||||
return CfDeployResponse.failure(e.getMessage(), e.toString());
|
return CfDeployResponse.failure(e.getMessage(), e.toString());
|
||||||
} finally {
|
} finally {
|
||||||
if (tempDir != null) {
|
if (tempDir != null) {
|
||||||
@@ -94,7 +107,7 @@ public class CfCliService {
|
|||||||
log.info("Successfully logged into Cloud Foundry");
|
log.info("Successfully logged into Cloud Foundry");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void pushApplication(CfDeployRequest request, Path workingDir, StringBuilder output) throws Exception {
|
private void pushApplication(CfDeployRequest request, Path workingDir, Path jarPath, StringBuilder output) throws Exception {
|
||||||
log.info("Pushing application: {}", request.getAppName());
|
log.info("Pushing application: {}", request.getAppName());
|
||||||
|
|
||||||
List<String> command = new ArrayList<>();
|
List<String> command = new ArrayList<>();
|
||||||
@@ -103,6 +116,8 @@ public class CfCliService {
|
|||||||
command.add(request.getAppName());
|
command.add(request.getAppName());
|
||||||
command.add("-f");
|
command.add("-f");
|
||||||
command.add("manifest.yml");
|
command.add("manifest.yml");
|
||||||
|
command.add("-p");
|
||||||
|
command.add(jarPath.toAbsolutePath().toString());
|
||||||
|
|
||||||
executeCommand(command, output, false, workingDir.toFile());
|
executeCommand(command, output, false, workingDir.toFile());
|
||||||
log.info("Successfully pushed application: {}", request.getAppName());
|
log.info("Successfully pushed application: {}", request.getAppName());
|
||||||
@@ -127,6 +142,7 @@ public class CfCliService {
|
|||||||
ProcessBuilder processBuilder = new ProcessBuilder(command);
|
ProcessBuilder processBuilder = new ProcessBuilder(command);
|
||||||
if (workingDir != null) {
|
if (workingDir != null) {
|
||||||
processBuilder.directory(workingDir);
|
processBuilder.directory(workingDir);
|
||||||
|
log.debug("Working directory: {}", workingDir.getAbsolutePath());
|
||||||
}
|
}
|
||||||
processBuilder.redirectErrorStream(true);
|
processBuilder.redirectErrorStream(true);
|
||||||
|
|
||||||
@@ -137,50 +153,70 @@ public class CfCliService {
|
|||||||
maskedCommand.set(i + 1, "********");
|
maskedCommand.set(i + 1, "********");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.debug("Executing command: {}", String.join(" ", maskedCommand));
|
log.info("Executing CF CLI command: {}", String.join(" ", maskedCommand));
|
||||||
} else {
|
} else {
|
||||||
log.debug("Executing command: {}", String.join(" ", command));
|
log.info("Executing CF CLI command: {}", String.join(" ", command));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
long startTime = System.currentTimeMillis();
|
||||||
Process process = processBuilder.start();
|
Process process = processBuilder.start();
|
||||||
|
log.debug("Process started with PID: {}", process.pid());
|
||||||
|
|
||||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
|
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
|
||||||
String line;
|
String line;
|
||||||
|
int lineCount = 0;
|
||||||
while ((line = reader.readLine()) != null) {
|
while ((line = reader.readLine()) != null) {
|
||||||
|
lineCount++;
|
||||||
output.append(line).append("\n");
|
output.append(line).append("\n");
|
||||||
log.debug("CF CLI output: {}", line);
|
log.info("CF CLI: {}", line);
|
||||||
}
|
}
|
||||||
|
log.debug("Command produced {} lines of output", lineCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean finished = process.waitFor(timeout, TimeUnit.SECONDS);
|
boolean finished = process.waitFor(timeout, TimeUnit.SECONDS);
|
||||||
|
long duration = System.currentTimeMillis() - startTime;
|
||||||
|
|
||||||
if (!finished) {
|
if (!finished) {
|
||||||
|
log.error("Command execution timed out after {} seconds", timeout);
|
||||||
process.destroyForcibly();
|
process.destroyForcibly();
|
||||||
throw new RuntimeException("Command execution timed out after " + timeout + " seconds");
|
throw new RuntimeException("Command execution timed out after " + timeout + " seconds");
|
||||||
}
|
}
|
||||||
|
|
||||||
int exitCode = process.exitValue();
|
int exitCode = process.exitValue();
|
||||||
|
log.info("Command completed in {}ms with exit code: {}", duration, exitCode);
|
||||||
|
|
||||||
if (exitCode != 0) {
|
if (exitCode != 0) {
|
||||||
|
log.error("Command failed with exit code: {}", exitCode);
|
||||||
|
log.error("Command output:\n{}", output.toString());
|
||||||
throw new RuntimeException("Command failed with exit code: " + exitCode);
|
throw new RuntimeException("Command failed with exit code: " + exitCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getCfCliExecutable() throws IOException {
|
private String getCfCliExecutable() throws IOException {
|
||||||
if (cfCliPath != null && !cfCliPath.isEmpty()) {
|
if (cfCliPath != null && !cfCliPath.isEmpty()) {
|
||||||
|
log.info("Using custom CF CLI path: {}", cfCliPath);
|
||||||
return cfCliPath;
|
return cfCliPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
String os = getOperatingSystem();
|
String os = getOperatingSystem();
|
||||||
String executable = os.equals("windows") ? "cf.exe" : "cf";
|
String executable = os.equals("windows") ? "cf.exe" : "cf";
|
||||||
|
|
||||||
|
log.info("Detected operating system: {}", os);
|
||||||
|
log.debug("CF CLI executable name: {}", executable);
|
||||||
|
|
||||||
String resourcePath = String.format("/cf-cli/%s/%s", os, executable);
|
String resourcePath = String.format("/cf-cli/%s/%s", os, executable);
|
||||||
File tempFile = File.createTempFile("cf-cli-", os.equals("windows") ? ".exe" : "");
|
File tempFile = File.createTempFile("cf-cli-", os.equals("windows") ? ".exe" : "");
|
||||||
tempFile.deleteOnExit();
|
tempFile.deleteOnExit();
|
||||||
|
|
||||||
|
log.debug("Extracting CF CLI from resource path: {}", resourcePath);
|
||||||
|
|
||||||
try (var inputStream = getClass().getResourceAsStream(resourcePath)) {
|
try (var inputStream = getClass().getResourceAsStream(resourcePath)) {
|
||||||
if (inputStream == null) {
|
if (inputStream == null) {
|
||||||
throw new IOException("CF CLI binary not found for OS: " + os);
|
log.error("CF CLI binary not found in resources for OS: {}. Expected path: {}", os, resourcePath);
|
||||||
|
throw new IOException("CF CLI binary not found for OS: " + os + " at path: " + resourcePath);
|
||||||
}
|
}
|
||||||
Files.copy(inputStream, tempFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
Files.copy(inputStream, tempFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
log.debug("CF CLI extracted to temporary file: {}", tempFile.getAbsolutePath());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!os.equals("windows")) {
|
if (!os.equals("windows")) {
|
||||||
@@ -189,14 +225,16 @@ public class CfCliService {
|
|||||||
perms.add(PosixFilePermission.OWNER_WRITE);
|
perms.add(PosixFilePermission.OWNER_WRITE);
|
||||||
perms.add(PosixFilePermission.OWNER_EXECUTE);
|
perms.add(PosixFilePermission.OWNER_EXECUTE);
|
||||||
Files.setPosixFilePermissions(tempFile.toPath(), perms);
|
Files.setPosixFilePermissions(tempFile.toPath(), perms);
|
||||||
|
log.debug("Set executable permissions on CF CLI binary");
|
||||||
}
|
}
|
||||||
|
|
||||||
log.debug("Using CF CLI executable: {}", tempFile.getAbsolutePath());
|
log.info("Using CF CLI executable: {}", tempFile.getAbsolutePath());
|
||||||
return tempFile.getAbsolutePath();
|
return tempFile.getAbsolutePath();
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getOperatingSystem() {
|
private String getOperatingSystem() {
|
||||||
String osName = System.getProperty("os.name").toLowerCase();
|
String osName = System.getProperty("os.name").toLowerCase();
|
||||||
|
log.debug("System OS name property: {}", osName);
|
||||||
if (osName.contains("win")) {
|
if (osName.contains("win")) {
|
||||||
return "windows";
|
return "windows";
|
||||||
} else if (osName.contains("mac")) {
|
} else if (osName.contains("mac")) {
|
||||||
@@ -222,4 +260,97 @@ public class CfCliService {
|
|||||||
log.warn("Failed to clean up temporary directory: {}", tempDir, e);
|
log.warn("Failed to clean up temporary directory: {}", tempDir, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String listApps(CfDeployRequest request) {
|
||||||
|
try {
|
||||||
|
log.info("Listing applications for org: {}, space: {}", request.getOrganization(), request.getSpace());
|
||||||
|
StringBuilder output = new StringBuilder();
|
||||||
|
|
||||||
|
login(request, output);
|
||||||
|
|
||||||
|
List<String> command = new ArrayList<>();
|
||||||
|
command.add(getCfCliExecutable());
|
||||||
|
command.add("apps");
|
||||||
|
|
||||||
|
executeCommand(command, output, false);
|
||||||
|
logout(output);
|
||||||
|
|
||||||
|
log.info("Successfully retrieved apps list");
|
||||||
|
return output.toString();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Failed to list apps", e);
|
||||||
|
throw new RuntimeException("Failed to list apps: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String listRoutes(CfDeployRequest request) {
|
||||||
|
try {
|
||||||
|
log.info("Listing routes for org: {}, space: {}", request.getOrganization(), request.getSpace());
|
||||||
|
StringBuilder output = new StringBuilder();
|
||||||
|
|
||||||
|
login(request, output);
|
||||||
|
|
||||||
|
List<String> command = new ArrayList<>();
|
||||||
|
command.add(getCfCliExecutable());
|
||||||
|
command.add("routes");
|
||||||
|
|
||||||
|
executeCommand(command, output, false);
|
||||||
|
logout(output);
|
||||||
|
|
||||||
|
log.info("Successfully retrieved routes list");
|
||||||
|
return output.toString();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Failed to list routes", e);
|
||||||
|
throw new RuntimeException("Failed to list routes: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAppDetails(CfDeployRequest request, String appName) {
|
||||||
|
try {
|
||||||
|
log.info("Getting details for app: {} in org: {}, space: {}", appName, request.getOrganization(), request.getSpace());
|
||||||
|
StringBuilder output = new StringBuilder();
|
||||||
|
|
||||||
|
login(request, output);
|
||||||
|
|
||||||
|
List<String> command = new ArrayList<>();
|
||||||
|
command.add(getCfCliExecutable());
|
||||||
|
command.add("app");
|
||||||
|
command.add(appName);
|
||||||
|
|
||||||
|
executeCommand(command, output, false);
|
||||||
|
logout(output);
|
||||||
|
|
||||||
|
log.info("Successfully retrieved app details for: {}", appName);
|
||||||
|
return output.toString();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Failed to get app details for: {}", appName, e);
|
||||||
|
throw new RuntimeException("Failed to get app details: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAppLogs(CfDeployRequest request, String appName, boolean recent) {
|
||||||
|
try {
|
||||||
|
log.info("Getting {} logs for app: {}", recent ? "recent" : "tail", appName);
|
||||||
|
StringBuilder output = new StringBuilder();
|
||||||
|
|
||||||
|
login(request, output);
|
||||||
|
|
||||||
|
List<String> command = new ArrayList<>();
|
||||||
|
command.add(getCfCliExecutable());
|
||||||
|
command.add("logs");
|
||||||
|
command.add(appName);
|
||||||
|
if (recent) {
|
||||||
|
command.add("--recent");
|
||||||
|
}
|
||||||
|
|
||||||
|
executeCommand(command, output, false);
|
||||||
|
logout(output);
|
||||||
|
|
||||||
|
log.info("Successfully retrieved logs for: {}", appName);
|
||||||
|
return output.toString();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Failed to get logs for: {}", appName, e);
|
||||||
|
throw new RuntimeException("Failed to get app logs: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user