Created
May 1, 2025 14:06
-
-
Save joelio/8d6432c405daec5e70c7c5242fba1a6b to your computer and use it in GitHub Desktop.
Java Deployment Test
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
stages: | |
- validate | |
- build | |
- deploy | |
- notify | |
- rollback | |
variables: | |
# Java and application settings | |
JAVA_HOME: "/usr/lib/jvm/java-17-openjdk-amd64" | |
APP_NAME: "java-application" | |
# User settings (can be overridden per environment) | |
APP_USER: "appuser" | |
# Path settings (will be adjusted based on APP_USER) | |
BASE_PATH: "/home/${APP_USER}" | |
DEPLOY_DIR: "${BASE_PATH}/deployments" | |
CURRENT_LINK: "${BASE_PATH}/app/current" | |
BACKUP_DIR: "${BASE_PATH}/backups" | |
# Deployment and retention settings | |
MAX_BACKUPS: 5 | |
HEALTH_CHECK_URL: "http://localhost:8080/healthcheck" | |
HEALTH_CHECK_RETRIES: 10 | |
HEALTH_CHECK_DELAY: 5 | |
# Notification settings | |
NOTIFICATION_EMAIL: "[email protected]" | |
NOTIFICATION_METHOD: "email" # Options: email, slack, teams, etc. | |
# Validate that we're on a protected branch | |
validate_branch: | |
stage: validate | |
script: | |
- | | |
if [[ "$CI_COMMIT_REF_NAME" != "main" && | |
"$CI_COMMIT_REF_NAME" != "master" && | |
"$CI_COMMIT_REF_NAME" != "develop" && | |
"$CI_COMMIT_REF_NAME" != "production" && | |
"$CI_COMMIT_REF_NAME" != "release/"* ]]; then | |
echo "Deployments are only allowed from protected branches (main, master, develop, production, release/*)" | |
exit 1 | |
fi | |
rules: | |
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"' | |
when: never | |
- if: '$CI_COMMIT_BRANCH' | |
# Build the application | |
build: | |
stage: build | |
image: maven:3.8-openjdk-17 | |
script: | |
- mvn clean package -DskipTests | |
artifacts: | |
paths: | |
- target/*.jar | |
expire_in: 1 week | |
rules: | |
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"' | |
when: never | |
- if: '$CI_COMMIT_BRANCH' | |
# Deploy the application | |
deploy: | |
stage: deploy | |
script: | |
- | | |
# Create necessary directories if they don't exist | |
ssh ${APP_USER}@$DEPLOY_HOST "mkdir -p $DEPLOY_DIR $BACKUP_DIR ${BASE_PATH}/app ${BASE_PATH}/tmp" | |
# Backup existing deployment if it exists | |
if ssh ${APP_USER}@$DEPLOY_HOST "test -L $CURRENT_LINK"; then | |
CURRENT_TARGET=$(ssh ${APP_USER}@$DEPLOY_HOST "readlink -f $CURRENT_LINK") | |
BACKUP_NAME="backup-$(date +%Y%m%d%H%M%S)-$CI_JOB_ID" | |
echo "Backing up current deployment to $BACKUP_DIR/$BACKUP_NAME" | |
ssh ${APP_USER}@$DEPLOY_HOST "cp -r $CURRENT_TARGET $BACKUP_DIR/$BACKUP_NAME" | |
fi | |
# Clean up old backups according to retention policy | |
ssh ${APP_USER}@$DEPLOY_HOST "ls -1t $BACKUP_DIR | tail -n +$((MAX_BACKUPS+1)) | xargs -I {} rm -rf $BACKUP_DIR/{}" | |
# Create new deployment directory with build ID | |
NEW_DEPLOY_DIR="$DEPLOY_DIR/${APP_NAME}-${CI_JOB_ID}" | |
ssh ${APP_USER}@$DEPLOY_HOST "mkdir -p $NEW_DEPLOY_DIR" | |
# Atomic upload: First to temp, then move | |
scp target/*.jar ${APP_USER}@$DEPLOY_HOST:${BASE_PATH}/tmp/${APP_NAME}-${CI_JOB_ID}.jar | |
ssh ${APP_USER}@$DEPLOY_HOST "mv ${BASE_PATH}/tmp/${APP_NAME}-${CI_JOB_ID}.jar $NEW_DEPLOY_DIR/" | |
# Create systemd service file for the application | |
cat > ${APP_NAME}.service << EOF | |
[Unit] | |
Description=${APP_NAME} Java Application | |
After=network.target | |
[Service] | |
Type=simple | |
User=${APP_USER} | |
WorkingDirectory=${BASE_PATH}/app | |
ExecStart=/usr/bin/java -jar %h/app/current/${APP_NAME}-${CI_JOB_ID}.jar | |
Environment="BUILD_ID=${CI_JOB_ID}" | |
Environment="ENV=${CI_ENVIRONMENT_NAME}" | |
SuccessExitStatus=143 | |
TimeoutStopSec=10 | |
Restart=on-failure | |
RestartSec=5 | |
[Install] | |
WantedBy=default.target | |
EOF | |
# Upload and install service file | |
ssh ${APP_USER}@$DEPLOY_HOST "mkdir -p ${BASE_PATH}/.config/systemd/user/" | |
scp ${APP_NAME}.service ${APP_USER}@$DEPLOY_HOST:${BASE_PATH}/.config/systemd/user/ | |
# Gracefully stop the current service if running | |
ssh ${APP_USER}@$DEPLOY_HOST "systemctl --user stop ${APP_NAME}.service || echo 'No service running or failed to stop gracefully'" | |
# Update symlink atomically | |
ssh ${APP_USER}@$DEPLOY_HOST "ln -sfn $NEW_DEPLOY_DIR $CURRENT_LINK" | |
# Ensure linger is enabled for user service persistence | |
ssh root@$DEPLOY_HOST "loginctl enable-linger ${APP_USER}" | |
# Start the service and enable it | |
ssh ${APP_USER}@$DEPLOY_HOST "systemctl --user daemon-reload && systemctl --user enable ${APP_NAME}.service && systemctl --user start ${APP_NAME}.service" | |
# Health check loop | |
for i in $(seq 1 $HEALTH_CHECK_RETRIES); do | |
echo "Performing health check attempt $i of $HEALTH_CHECK_RETRIES..." | |
if ssh ${APP_USER}@$DEPLOY_HOST "curl -s -f $HEALTH_CHECK_URL"; then | |
echo "Health check passed!" | |
# Store current deployment ID as the last successful one | |
ssh ${APP_USER}@$DEPLOY_HOST "echo $CI_JOB_ID > ${BASE_PATH}/app/last_successful_deploy" | |
exit 0 | |
fi | |
sleep $HEALTH_CHECK_DELAY | |
done | |
# If we get here, health check failed | |
echo "Health check failed after $HEALTH_CHECK_RETRIES attempts. Initiating rollback..." | |
exit 1 | |
environment: | |
name: production | |
variables: | |
DEPLOY_HOST: "your-deployment-server.example.com" | |
# You can override any of the main variables here for environment-specific settings | |
# APP_USER: "prod-appuser" | |
# Java and other environment settings can be customized per environment | |
# JAVA_OPTS: "-Xmx512m -Dspring.profiles.active=production" | |
rules: | |
- if: '$CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == "master" || $CI_COMMIT_BRANCH == "production"' | |
- if: '$CI_COMMIT_BRANCH == "develop" || $CI_COMMIT_BRANCH =~ /^release\//' | |
when: manual | |
dependencies: | |
- build | |
needs: | |
- build | |
- validate_branch | |
allow_failure: false | |
retry: | |
max: 1 | |
when: script_failure | |
# Manual rollback job that can be triggered if needed | |
rollback_manual: | |
stage: rollback | |
script: | |
- | | |
# Get list of available backups | |
BACKUPS=$(ssh ${APP_USER}@$DEPLOY_HOST "ls -1t $BACKUP_DIR") | |
if [ -z "$BACKUPS" ]; then | |
echo "No backups available for rollback" | |
exit 1 | |
fi | |
LATEST_BACKUP=$(ssh ${APP_USER}@$DEPLOY_HOST "ls -1t $BACKUP_DIR | head -n 1") | |
echo "Rolling back to $LATEST_BACKUP" | |
# Stop current service | |
ssh ${APP_USER}@$DEPLOY_HOST "systemctl --user stop ${APP_NAME}.service" | |
# Update symlink to point to the backup | |
ssh ${APP_USER}@$DEPLOY_HOST "ln -sfn $BACKUP_DIR/$LATEST_BACKUP $CURRENT_LINK" | |
# Start service again | |
ssh ${APP_USER}@$DEPLOY_HOST "systemctl --user start ${APP_NAME}.service" | |
# Health check | |
for i in $(seq 1 $HEALTH_CHECK_RETRIES); do | |
echo "Performing health check attempt $i of $HEALTH_CHECK_RETRIES..." | |
if ssh ${APP_USER}@$DEPLOY_HOST "curl -s -f $HEALTH_CHECK_URL"; then | |
echo "Rollback successful and health check passed!" | |
exit 0 | |
fi | |
sleep $HEALTH_CHECK_DELAY | |
done | |
echo "Health check failed after rollback. Please check the application logs." | |
exit 1 | |
environment: | |
name: production | |
variables: | |
DEPLOY_HOST: "your-deployment-server.example.com" | |
when: manual | |
needs: [] | |
# Auto rollback when deployment fails - triggered by deploy job failure | |
rollback_auto: | |
stage: rollback | |
script: | |
- | | |
echo "Automatic rollback triggered due to deployment failure" | |
# Get last successful deploy ID | |
LAST_DEPLOY_ID=$(ssh ${APP_USER}@$DEPLOY_HOST "cat ${BASE_PATH}/app/last_successful_deploy || echo ''") | |
if [ -z "$LAST_DEPLOY_ID" ]; then | |
# Try to get latest backup if no last successful deploy ID | |
LATEST_BACKUP=$(ssh ${APP_USER}@$DEPLOY_HOST "ls -1t $BACKUP_DIR | head -n 1") | |
if [ -z "$LATEST_BACKUP" ]; then | |
echo "No backups or last successful deploy ID available for rollback" | |
exit 1 | |
fi | |
echo "Rolling back to backup $LATEST_BACKUP" | |
# Stop current service | |
ssh ${APP_USER}@$DEPLOY_HOST "systemctl --user stop ${APP_NAME}.service" | |
# Update symlink to point to the backup | |
ssh ${APP_USER}@$DEPLOY_HOST "ln -sfn $BACKUP_DIR/$LATEST_BACKUP $CURRENT_LINK" | |
else | |
echo "Rolling back to previous successful deployment: $LAST_DEPLOY_ID" | |
PREVIOUS_DEPLOY_DIR="$DEPLOY_DIR/${APP_NAME}-${LAST_DEPLOY_ID}" | |
# Stop current service | |
ssh ${APP_USER}@$DEPLOY_HOST "systemctl --user stop ${APP_NAME}.service" | |
# Update symlink to point to the previous successful deployment | |
ssh ${APP_USER}@$DEPLOY_HOST "ln -sfn $PREVIOUS_DEPLOY_DIR $CURRENT_LINK" | |
fi | |
# Start service again | |
ssh ${APP_USER}@$DEPLOY_HOST "systemctl --user start ${APP_NAME}.service" | |
# Health check | |
for i in $(seq 1 $HEALTH_CHECK_RETRIES); do | |
echo "Performing rollback health check attempt $i of $HEALTH_CHECK_RETRIES..." | |
if ssh ${APP_USER}@$DEPLOY_HOST "curl -s -f $HEALTH_CHECK_URL"; then | |
echo "Rollback successful and health check passed!" | |
exit 0 | |
fi | |
sleep $HEALTH_CHECK_DELAY | |
done | |
echo "Health check failed after rollback. Please check the application logs." | |
exit 1 | |
environment: | |
name: production | |
variables: | |
DEPLOY_HOST: "your-deployment-server.example.com" | |
when: on_failure | |
needs: | |
- deploy | |
# Clean up old deployments according to retention policy | |
cleanup: | |
stage: deploy | |
script: | |
- | | |
# Get all deployments except the current one | |
CURRENT=$(ssh ${APP_USER}@$DEPLOY_HOST "readlink -f $CURRENT_LINK || echo ''") | |
ALL_DEPLOYMENTS=$(ssh ${APP_USER}@$DEPLOY_HOST "find $DEPLOY_DIR -maxdepth 1 -type d -name '${APP_NAME}-*' | sort -r") | |
# Keep track of how many we've kept | |
KEPT=0 | |
# Process each deployment | |
for DEPLOYMENT in $ALL_DEPLOYMENTS; do | |
# Skip if this is the current deployment | |
if [ "$DEPLOYMENT" = "$CURRENT" ]; then | |
continue | |
fi | |
# Keep if we haven't reached our limit | |
if [ $KEPT -lt $MAX_BACKUPS ]; then | |
KEPT=$((KEPT+1)) | |
continue | |
fi | |
# Otherwise, delete it | |
echo "Removing old deployment: $DEPLOYMENT" | |
ssh ${APP_USER}@$DEPLOY_HOST "rm -rf $DEPLOYMENT" | |
done | |
environment: | |
name: production | |
variables: | |
DEPLOY_HOST: "your-deployment-server.example.com" | |
when: on_success | |
needs: | |
- deploy | |
# Notification on success | |
notify_success: | |
stage: notify | |
script: | |
- | | |
if [ "$NOTIFICATION_METHOD" == "email" ]; then | |
echo "Deployment of $APP_NAME version $CI_COMMIT_SHA (Job ID: $CI_JOB_ID) was successful." | | |
mail -s "[SUCCESS] $APP_NAME Deployment to $CI_ENVIRONMENT_NAME" $NOTIFICATION_EMAIL | |
elif [ "$NOTIFICATION_METHOD" == "slack" ]; then | |
# Example for Slack notification - requires webhook URL to be set | |
if [ -n "$SLACK_WEBHOOK_URL" ]; then | |
curl -X POST -H 'Content-type: application/json' --data "{\"text\":\"✅ Deployment of $APP_NAME version $CI_COMMIT_SHA (Job ID: $CI_JOB_ID) to $CI_ENVIRONMENT_NAME was successful.\"}" $SLACK_WEBHOOK_URL | |
else | |
echo "SLACK_WEBHOOK_URL not set. Skipping Slack notification." | |
fi | |
else | |
echo "Notification method $NOTIFICATION_METHOD not configured. Defaulting to console output." | |
echo "✅ Deployment of $APP_NAME version $CI_COMMIT_SHA (Job ID: $CI_JOB_ID) to $CI_ENVIRONMENT_NAME was successful." | |
fi | |
when: on_success | |
needs: | |
- deploy | |
# Notification on failure | |
notify_failure: | |
stage: notify | |
script: | |
- | | |
if [ "$NOTIFICATION_METHOD" == "email" ]; then | |
echo "Deployment of $APP_NAME version $CI_COMMIT_SHA (Job ID: $CI_JOB_ID) to $CI_ENVIRONMENT_NAME failed." | | |
mail -s "[FAILURE] $APP_NAME Deployment" $NOTIFICATION_EMAIL | |
elif [ "$NOTIFICATION_METHOD" == "slack" ]; then | |
# Example for Slack notification - requires webhook URL to be set | |
if [ -n "$SLACK_WEBHOOK_URL" ]; then | |
curl -X POST -H 'Content-type: application/json' --data "{\"text\":\"❌ Deployment of $APP_NAME version $CI_COMMIT_SHA (Job ID: $CI_JOB_ID) to $CI_ENVIRONMENT_NAME failed.\"}" $SLACK_WEBHOOK_URL | |
else | |
echo "SLACK_WEBHOOK_URL not set. Skipping Slack notification." | |
fi | |
else | |
echo "Notification method $NOTIFICATION_METHOD not configured. Defaulting to console output." | |
echo "❌ Deployment of $APP_NAME version $CI_COMMIT_SHA (Job ID: $CI_JOB_ID) to $CI_ENVIRONMENT_NAME failed." | |
fi | |
when: on_failure | |
needs: | |
- deploy |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment