Skip to content

Instantly share code, notes, and snippets.

@LarryBarker
Created May 21, 2024 05:19
Show Gist options
  • Save LarryBarker/4f1c1fce5e29735aa34049c06aafc457 to your computer and use it in GitHub Desktop.
Save LarryBarker/4f1c1fce5e29735aa34049c06aafc457 to your computer and use it in GitHub Desktop.
Automatically deploy PRs to preview environments with the help of Laravel Forge
name: Preview Environment
on:
pull_request:
types: [opened]
env:
FORGE_API_TOKEN: ${{ secrets.FORGE_API_TOKEN }}
FORGE_SERVER_ID: ${{ secrets.FORGE_SERVER_ID }}
jobs:
provision_site:
runs-on: ubuntu-latest
if: github.event.action == 'opened'
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Define Variables
run: |
echo "PR_BRANCH=${{ github.head_ref }}" >> $GITHUB_ENV
echo "PR_NUMBER=${{ github.event.number }}" >> $GITHUB_ENV
echo "SITE_URL=https://forge.laravel.com/api/v1/servers/$FORGE_SERVER_ID/sites" >> $GITHUB_ENV
echo "DATABASE_URL=https://forge.laravel.com/api/v1/servers/$FORGE_SERVER_ID/databases" >> $GITHUB_ENV
- name: Create Site
id: create_site
run: |
SITE_CREATION_RESPONSE=$(curl -s -X POST "https://forge.laravel.com/api/v1/servers/$FORGE_SERVER_ID/sites" \
-H "Authorization: Bearer $FORGE_API_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d "{\"domain\": \"$PR_NUMBER.dev.example.com\", \"project_type\": \"php\", \"directory\": \"/public\", \"database\": \"$PR_NUMBER\"}")
echo "Site Creation Response: $SITE_CREATION_RESPONSE"
SITE_ID=$(echo "$SITE_CREATION_RESPONSE" | jq -r '.site.id')
echo "SITE_ID=$SITE_ID" >> $GITHUB_ENV
echo "::set-output name=SITE_ID::$SITE_ID"
- name: Check Site Status
run: |
SITE_STATUS="installing"
while [ "$SITE_STATUS" != "installed" ]; do
echo "Checking site status..."
SITE_STATUS_RESPONSE=$(curl -s -X GET "https://forge.laravel.com/api/v1/servers/$FORGE_SERVER_ID/sites/$SITE_ID" \
-H "Authorization: Bearer $FORGE_API_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json")
SITE_STATUS=$(echo "$SITE_STATUS_RESPONSE" | jq -r '.site.status')
if [ "$SITE_STATUS" != "installed" ]; then
echo "Site status: $SITE_STATUS. Waiting for installation to complete..."
sleep 30
fi
done
echo "Site setup is complete."
- name: Install PR Branch to New Site
run: |
INSTALL_RESPONSE=$(curl -s -X POST $SITE_URL/$SITE_ID/git -H "Authorization: Bearer $FORGE_API_TOKEN" -H "Accept: application/json" -H "Content-Type: application/json" -d "{\"provider\":\"custom\",\"repository\":\"git@github.com:your-repo/your-app\",\"branch\":\"$PR_BRANCH\",\"composer\":true}")
if echo $INSTALL_RESPONSE | grep -q "error"; then
echo "Failed to install PR branch to new site"
exit 1
fi
- name: Check Git Repository Status
run: |
API_RESPONSE=$(curl -s -X GET "https://forge.laravel.com/api/v1/servers/$FORGE_SERVER_ID/sites/$SITE_ID" \
-H "Authorization: Bearer $FORGE_API_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json")
echo "API Response: $API_RESPONSE"
if [[ -z "$API_RESPONSE" ]] || ! (echo "$API_RESPONSE" | jq . > /dev/null 2>&1); then
echo "Error: Invalid or empty response from Forge API"
exit 1
fi
SITE_STATUS=$(echo "$API_RESPONSE" | jq -r '.site.repository_status')
while [ "$SITE_STATUS" != "installed" ]; do
echo "Waiting for site to be ready. Current status: $SITE_STATUS"
sleep 15
API_RESPONSE=$(curl -s -X GET "https://forge.laravel.com/api/v1/servers/$FORGE_SERVER_ID/sites/$SITE_ID" \
-H "Authorization: Bearer $FORGE_API_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json")
SITE_STATUS=$(echo "$API_RESPONSE" | jq -r '.site.repository_status')
done
echo "Git install is ready."
- name: Update Deployment Script
run: |
DEPLOY_SCRIPT_UPDATE_RESPONSE=$(curl -s -X PUT "https://forge.laravel.com/api/v1/servers/$FORGE_SERVER_ID/sites/$SITE_ID/deployment/script" \
-H "Authorization: Bearer $FORGE_API_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d "{
\"content\": \"cd /home/forge/$PR_NUMBER.dev.example.com\\ngit pull origin \$FORGE_SITE_BRANCH\\n\\ncomposer install --no-interaction --prefer-dist --optimize-autoloader\\n\\n( flock -w 10 9 || exit 1\\n echo 'Restarting FPM...'; sudo -S service \$FORGE_PHP_FPM reload ) 9>/tmp/fpmlock\\n\\nif [ -f artisan ]; then\\n php artisan key:generate\\n php artisan migrate:fresh --seed --force\\nfi\\n\\nphp artisan queue:restart\\nphp artisan storage:link\\nnpm ci && npm run build\"
}")
echo "Deploy Script Update Response: $DEPLOY_SCRIPT_UPDATE_RESPONSE"
- name: Set Environment Variables
run: |
# Concatenate all secrets into one command string
COMMAND_TO_RUN="echo -e '\n# Appended by GitHub Actions\n"
SECRETS=(
"APP_ENV=dev"
"APP_DEBUG=true"
"DB_DATABASE=$PR_NUMBER"
)
for secret in "${SECRETS[@]}"; do
COMMAND_TO_RUN+="${secret}\\n"
done
COMMAND_TO_RUN+="' >> .env"
# Send the command to the server
SET_SECRET_RESPONSE=$(curl -s -X POST "https://forge.laravel.com/api/v1/servers/$FORGE_SERVER_ID/sites/$SITE_ID/commands" \
-H "Authorization: Bearer $FORGE_API_TOKEN" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d "{\"command\":\"$COMMAND_TO_RUN\"}")
# Extract the command ID
COMMAND_ID=$(echo "$SET_SECRET_RESPONSE" | jq -r '.command.id')
if [[ "$COMMAND_ID" == "null" || -z "$COMMAND_ID" ]]; then
echo "Failed to extract command ID."
exit 1
fi
# Wait for the command to finish
COMMAND_STATUS="running"
while [[ "$COMMAND_STATUS" != "finished" ]]; do
COMMAND_STATUS_RESPONSE=$(curl -s -X GET "https://forge.laravel.com/api/v1/servers/$FORGE_SERVER_ID/sites/$SITE_ID/commands/$COMMAND_ID" \
-H "Authorization: Bearer $FORGE_API_TOKEN" \
-H "Accept: application/json")
COMMAND_STATUS=$(echo "$COMMAND_STATUS_RESPONSE" | jq -r '.command.status')
if [[ "$COMMAND_STATUS" != "finished" ]]; then
echo "Waiting for command ID $COMMAND_ID to finish..."
sleep 10
fi
done
echo "Command completed successfully."
- name: Trigger Deployment
run: |
DEPLOY_TRIGGER_RESPONSE=$(curl -s -X POST "$SITE_URL/$SITE_ID/deployment/deploy" -H "Authorization: Bearer $FORGE_API_TOKEN" -H "Accept: application/json" -H "Content-Type: application/json")
DEPLOYMENT_STATUS="queued"
while [ "$DEPLOYMENT_STATUS" != "null" ]; do
echo "Checking deployment status..."
DEPLOY_STATUS_RESPONSE=$(curl -s -X GET "$SITE_URL/$SITE_ID" -H "Authorization: Bearer $FORGE_API_TOKEN" -H "Accept: application/json")
DEPLOYMENT_STATUS=$(echo "$DEPLOY_STATUS_RESPONSE" | jq -r '.site.deployment_status')
if [ "$DEPLOYMENT_STATUS" != "null" ]; then
if [ "$DEPLOYMENT_STATUS" == "error" ]; then
echo "Deployment failed with status: $DEPLOYMENT_STATUS"
exit 1
fi
echo "Deployment status: $DEPLOYMENT_STATUS. Waiting for deployment to complete..."
sleep 30
fi
done
- name: Create Queue Worker
run: |
QUEUE_WORKER_RESPONSE=$(curl -s -X POST "https://forge.laravel.com/api/v1/servers/$FORGE_SERVER_ID/sites/$SITE_ID/workers" \
-H "Authorization: Bearer $FORGE_API_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"connection": "database",
"timeout": 0,
"sleep": 0,
"tries": null,
"processes": 1,
"stopwaitsecs": 600,
"daemon": true,
"force": false,
"php_version": "php"
}')
QUEUE_WORKER_ID=$(echo "$QUEUE_WORKER_RESPONSE" | jq -r '.worker.id')
if [[ "$QUEUE_WORKER_ID" == "null" || -z "$QUEUE_WORKER_ID" ]]; then
echo "Failed to create queue worker."
exit 1
fi
# Wait for the queue worker to be installed
QUEUE_WORKER_STATUS="installing"
while [[ "$QUEUE_WORKER_STATUS" != "installed" ]]; do
QUEUE_WORKER_STATUS_RESPONSE=$(curl -s -X GET "https://forge.laravel.com/api/v1/servers/$FORGE_SERVER_ID/sites/$SITE_ID/workers/$QUEUE_WORKER_ID" \
-H "Authorization: Bearer $FORGE_API_TOKEN" \
-H "Accept: application/json")
QUEUE_WORKER_STATUS=$(echo "$QUEUE_WORKER_STATUS_RESPONSE" | jq -r '.worker.status')
if [[ "$QUEUE_WORKER_STATUS" != "installed" ]]; then
echo "Waiting for queue worker ID $QUEUE_WORKER_ID to be installed..."
sleep 10
fi
done
echo "Queue worker created successfully."
- name: Provision Let's Encrypt SSL Certificate
id: ssl_provision
run: |
SSL_RESPONSE=$(curl -s -X POST "https://forge.laravel.com/api/v1/servers/$FORGE_SERVER_ID/sites/$SITE_ID/certificates/letsencrypt" \
-H "Authorization: Bearer $FORGE_API_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d "{\"domains\": [\"$PR_NUMBER.dev.example.com\"]}")
echo "$SSL_RESPONSE"
CERT_ID=$(echo "$SSL_RESPONSE" | jq -r '.certificate.id')
echo "CERT_ID=$CERT_ID" >> $GITHUB_ENV
echo "::set-output name=CERT_ID::$CERT_ID"
- name: Check Certificate Installation Status
if: steps.ssl_provision.outputs.CERT_ID != null
run: |
CERT_INSTALL_STATUS="installing"
while [ "$CERT_INSTALL_STATUS" != "installed" ]; do
echo "Checking certificate installation status..."
CERT_STATUS_RESPONSE=$(curl -s -X GET "https://forge.laravel.com/api/v1/servers/$FORGE_SERVER_ID/sites/$SITE_ID/certificates/$CERT_ID" \
-H "Authorization: Bearer $FORGE_API_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json")
CERT_INSTALL_STATUS=$(echo "$CERT_STATUS_RESPONSE" | jq -r '.certificate.status')
if [ "$CERT_INSTALL_STATUS" != "installed" ]; then
echo "Certificate status: $CERT_INSTALL_STATUS. Waiting for installation to complete..."
sleep 30
fi
done
- name: Activate SSL Certificate
if: steps.ssl_provision.outputs.CERT_ID != null
run: |
ACTIVATE_RESPONSE=$(curl -s -X POST "https://forge.laravel.com/api/v1/servers/$FORGE_SERVER_ID/sites/$SITE_ID/certificates/$CERT_ID/activate" \
-H "Authorization: Bearer $FORGE_API_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json")
echo "Activate SSL Response: $ACTIVATE_RESPONSE"
echo "Certificate installation complete."
- name: Comment on PR
uses: actions/github-script@v5
with:
github-token: ${{secrets.GITHUB_TOKEN}}
script: |
const prNumber = context.payload.pull_request.number;
const repository = context.repo;
github.rest.issues.createComment({
owner: repository.owner,
repo: repository.repo,
issue_number: prNumber,
body: 'Your new environment is ready! :tada: Visit it at: https://' + prNumber + '.dev.example.com'
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment