451 lines
12 KiB
Markdown
451 lines
12 KiB
Markdown
# CF Deployer - Cloud Foundry Deployment Service
|
|
|
|
A Spring Boot application that provides a REST API for deploying Java applications to Cloud Foundry/Tanzu environments.
|
|
|
|
## Prerequisites
|
|
|
|
- Java 21
|
|
- Gradle 8.14
|
|
|
|
## Build and Run
|
|
|
|
### Build the application
|
|
```bash
|
|
./gradlew clean build
|
|
```
|
|
|
|
### Run the application
|
|
```bash
|
|
./gradlew bootRun
|
|
```
|
|
|
|
Or run the JAR directly:
|
|
```bash
|
|
java -jar build/libs/cf-uploader-1.0.0.jar
|
|
```
|
|
|
|
The application will start on `http://localhost:8080`
|
|
|
|
## API Endpoints
|
|
|
|
### Deployment Options
|
|
|
|
This service provides two deployment methods:
|
|
|
|
1. **Traditional Upload** (`/api/cf/deploy`) - Single request deployment (may hit nginx size/timeout limits)
|
|
2. **Chunked Upload** (`/api/cf/upload/*`) - Chunked upload with async deployment (recommended for production)
|
|
|
|
---
|
|
|
|
### Option 1: Traditional Deploy (Simple, may timeout with large files)
|
|
|
|
**Endpoint:** `POST /api/cf/deploy`
|
|
|
|
**Content-Type:** `multipart/form-data`
|
|
|
|
**Request Parts:**
|
|
- `request` (JSON string): Deployment configuration
|
|
- `jarFile` (file): The Java application JAR file
|
|
- `manifest` (file): Cloud Foundry manifest.yml file
|
|
|
|
#### Sample cURL Request
|
|
|
|
```bash
|
|
curl -X POST http://localhost:8080/api/cf/deploy \
|
|
-F 'request={
|
|
"apiEndpoint": "https://api.cf.example.com",
|
|
"username": "your-username",
|
|
"password": "your-password",
|
|
"organization": "your-org",
|
|
"space": "your-space",
|
|
"appName": "my-app",
|
|
"skipSslValidation": false
|
|
}' \
|
|
-F 'jarFile=@/path/to/your/application.jar' \
|
|
-F 'manifest=@/path/to/manifest.yml'
|
|
```
|
|
|
|
#### Sample Request with Skip SSL Validation
|
|
|
|
```bash
|
|
curl -X POST http://localhost:8080/api/cf/deploy \
|
|
-F 'request={
|
|
"apiEndpoint": "https://api.sys.tanzu.example.com",
|
|
"username": "admin",
|
|
"password": "admin123",
|
|
"organization": "development",
|
|
"space": "dev",
|
|
"appName": "sample-app",
|
|
"skipSslValidation": true
|
|
}' \
|
|
-F 'jarFile=@./sample-app.jar' \
|
|
-F 'manifest=@./manifest.yml'
|
|
```
|
|
|
|
---
|
|
|
|
### Option 2: Chunked Upload with Async Deployment (Recommended for Production)
|
|
|
|
**Why use chunked upload?**
|
|
- Bypasses nginx `client_max_body_size` restrictions
|
|
- Avoids nginx timeout issues during deployment
|
|
- Better for large JAR files (>10MB)
|
|
- More resilient to network interruptions
|
|
|
|
#### Quick Start: Using the Deployment Script
|
|
|
|
The easiest way to deploy using chunked upload is with the provided bash script:
|
|
|
|
1. **Configure the script:**
|
|
```bash
|
|
# Edit deploy-chunked.sh and set your values:
|
|
API_BASE="https://your-cf-deployer.example.com/api/cf"
|
|
JAR_FILE="./your-app.jar"
|
|
MANIFEST_FILE="./manifest.yml"
|
|
CF_API_ENDPOINT="https://api.cf.example.com"
|
|
CF_USERNAME="your-username"
|
|
CF_PASSWORD="your-password"
|
|
CF_ORGANIZATION="your-org"
|
|
CF_SPACE="your-space"
|
|
CF_APP_NAME="your-app"
|
|
```
|
|
|
|
2. **Make it executable and run:**
|
|
```bash
|
|
chmod +x deploy-chunked.sh
|
|
./deploy-chunked.sh
|
|
```
|
|
|
|
The script handles:
|
|
- File chunking (1MB chunks by default)
|
|
- Progress tracking
|
|
- Async deployment
|
|
- Status polling
|
|
- Error handling
|
|
|
|
#### Manual cURL Commands (For Custom Integration)
|
|
|
|
**Step 1: Initialize Upload Session**
|
|
```bash
|
|
SESSION_ID=$(curl -s -X POST "http://localhost:8080/api/cf/upload/init" \
|
|
-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": "my-app",
|
|
"skipSslValidation": false
|
|
}' | grep -o '"uploadSessionId":"[^"]*' | cut -d'"' -f4)
|
|
|
|
echo "Session ID: $SESSION_ID"
|
|
```
|
|
|
|
**Step 2: Upload JAR File in Chunks**
|
|
```bash
|
|
# Split your JAR into 1MB chunks and upload each
|
|
CHUNK_SIZE=1048576 # 1MB
|
|
FILE="./app.jar"
|
|
TOTAL_CHUNKS=$(( ($(stat -c%s "$FILE") + CHUNK_SIZE - 1) / CHUNK_SIZE ))
|
|
|
|
# Split file and upload each chunk
|
|
split -b $CHUNK_SIZE "$FILE" chunk_
|
|
CHUNK_INDEX=0
|
|
|
|
for chunk_file in chunk_*; do
|
|
curl -X POST "http://localhost:8080/api/cf/upload/chunk" \
|
|
-F "uploadSessionId=$SESSION_ID" \
|
|
-F "fileType=jarFile" \
|
|
-F "chunkIndex=$CHUNK_INDEX" \
|
|
-F "totalChunks=$TOTAL_CHUNKS" \
|
|
-F "fileName=app.jar" \
|
|
-F "chunk=@$chunk_file"
|
|
|
|
CHUNK_INDEX=$((CHUNK_INDEX + 1))
|
|
done
|
|
|
|
rm chunk_* # Cleanup
|
|
```
|
|
|
|
**Step 3: Upload Manifest**
|
|
```bash
|
|
curl -X POST "http://localhost:8080/api/cf/upload/chunk" \
|
|
-F "uploadSessionId=$SESSION_ID" \
|
|
-F "fileType=manifest" \
|
|
-F "chunkIndex=0" \
|
|
-F "totalChunks=1" \
|
|
-F "fileName=manifest.yml" \
|
|
-F "chunk=@./manifest.yml"
|
|
```
|
|
|
|
**Step 4: Start Async Deployment**
|
|
```bash
|
|
curl -X POST "http://localhost:8080/api/cf/upload/finalize?uploadSessionId=$SESSION_ID&async=true"
|
|
|
|
# Response: {"uploadSessionId":"...","status":"IN_PROGRESS","message":"Deployment started...","progress":0}
|
|
```
|
|
|
|
**Step 5: Poll Deployment Status**
|
|
```bash
|
|
# Check deployment status every 5 seconds
|
|
while true; do
|
|
STATUS=$(curl -s "http://localhost:8080/api/cf/deployment/status/$SESSION_ID")
|
|
echo "$STATUS"
|
|
|
|
# Check if completed or failed
|
|
if echo "$STATUS" | grep -q '"status":"COMPLETED"'; then
|
|
echo "Deployment successful!"
|
|
break
|
|
elif echo "$STATUS" | grep -q '"status":"FAILED"'; then
|
|
echo "Deployment failed!"
|
|
break
|
|
fi
|
|
|
|
sleep 5
|
|
done
|
|
```
|
|
|
|
**For more details, see:**
|
|
- `deploy-chunked.sh` - Complete working script
|
|
- `CHUNKED_UPLOAD_GUIDE.md` - Detailed API documentation
|
|
- `TIMEOUT_SOLUTION.md` - Architecture and design details
|
|
- `CHUNK_SIZE_GUIDE.md` - Chunk size recommendations
|
|
- `MEMORY_FIX.md` - JVM memory configuration for Tanzu
|
|
|
|
---
|
|
|
|
### 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)
|
|
|
|
```yaml
|
|
---
|
|
applications:
|
|
- name: sample-app
|
|
memory: 1G
|
|
instances: 2
|
|
path: sample-app.jar
|
|
buildpacks:
|
|
- java_buildpack
|
|
env:
|
|
SPRING_PROFILES_ACTIVE: production
|
|
```
|
|
|
|
## Common Request Parameters
|
|
|
|
All endpoints require the following CF credentials and target information:
|
|
|
|
| Field | Type | Required | Description |
|
|
|-------|------|----------|-------------|
|
|
| `apiEndpoint` | String | Yes | Cloud Foundry API endpoint URL |
|
|
| `username` | String | Yes | CF username |
|
|
| `password` | String | Yes | CF password |
|
|
| `organization` | String | Yes | Target CF organization |
|
|
| `space` | String | Yes | Target CF space |
|
|
| `appName` | String | No* | Application name (required only for `/deploy` endpoint) |
|
|
| `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
|
|
|
|
### Success Response
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"message": "Application deployed successfully",
|
|
"deploymentId": "123e4567-e89b-12d3-a456-426614174000",
|
|
"output": "CF CLI output logs...",
|
|
"error": null
|
|
}
|
|
```
|
|
|
|
### Error Response
|
|
|
|
```json
|
|
{
|
|
"success": false,
|
|
"message": "Application deployment failed",
|
|
"deploymentId": "123e4567-e89b-12d3-a456-426614174000",
|
|
"output": "CF CLI output logs...",
|
|
"error": "Error details..."
|
|
}
|
|
```
|
|
|
|
## Configuration
|
|
|
|
Application settings can be configured in `src/main/resources/application.properties`:
|
|
|
|
```properties
|
|
# Server port
|
|
server.port=8080
|
|
|
|
# Max file size (default: 500MB)
|
|
spring.servlet.multipart.max-file-size=500MB
|
|
spring.servlet.multipart.max-request-size=500MB
|
|
|
|
# CF CLI timeout in seconds (default: 600)
|
|
cf.cli.timeout=600
|
|
|
|
# Optional: Custom CF CLI path (if not using bundled binaries)
|
|
cf.cli.path=
|
|
```
|
|
|
|
## Features
|
|
|
|
- **Application Deployment**: Deploy JAR files to Cloud Foundry with manifest support
|
|
- **Chunked Upload Support**: Upload large files in chunks to bypass nginx size restrictions (NEW)
|
|
- **Async Deployment**: Non-blocking deployment with status polling to avoid timeout issues (NEW)
|
|
- **Application Management**: List apps, view details, and access logs
|
|
- **Route Management**: List all routes in your CF space
|
|
- **Automatic CF CLI Management**: Bundled CF CLI binaries for Linux, macOS, and Windows
|
|
- **Secure Password Handling**: Passwords are masked in all log output
|
|
- **Comprehensive Logging**: Detailed DEBUG-level logging for troubleshooting deployments
|
|
- **Configurable Timeouts**: Adjustable timeout for long-running deployments (default: 600s)
|
|
- **Large File Support**: Chunked uploads support files of any size
|
|
- **Automatic Cleanup**: Temporary files are automatically cleaned up after operations
|
|
- **Error Handling**: Comprehensive exception handling with detailed error messages
|
|
- **Production Ready**: Memory-optimized for Tanzu deployments with low-memory instances
|
|
|
|
## Error Handling
|
|
|
|
The API handles various error scenarios:
|
|
- **400 Bad Request**: Invalid request parameters or file validation errors
|
|
- **413 Payload Too Large**: Files exceed 500MB limit
|
|
- **500 Internal Server Error**: Deployment failures or unexpected errors
|
|
|
|
## Development
|
|
|
|
### Project Structure
|
|
|
|
```
|
|
src/main/java/com/cfdeployer/
|
|
├── controller/ # REST controllers
|
|
├── service/ # Business logic
|
|
├── model/ # DTOs
|
|
└── exception/ # Exception handlers
|
|
|
|
src/main/resources/
|
|
├── application.properties
|
|
└── cf-cli/ # CF CLI binaries (auto-downloaded)
|
|
```
|
|
|
|
### Build with Custom CF CLI Version
|
|
|
|
Edit `build.gradle` to change the CF CLI version:
|
|
|
|
```groovy
|
|
def cfCliVersion = '8.7.10' // Change this version
|
|
```
|
|
|
|
## License
|
|
|
|
This project is licensed under the MIT License.
|