Files
blackroad-operating-system/sop/templates/github-actions/deploy.yml
Claude 7cde897040 Add complete automation SOP system for BlackRoad ERP
This implements the "Automate The Company" initiative with comprehensive
Standard Operating Procedures for GitHub + Salesforce + Asana integration.

New directory: sop/
├── workflows/ - End-to-end process documentation
│   ├── new-client-kickoff.md - Flagship workflow from deal → repos → Asana
│   └── release-pipeline.md - Deploy → update Salesforce + Asana
├── playbooks/ - Human-friendly checklists
│   └── brenda-new-client-checklist.md - Non-technical operator guide
├── salesforce/ - Salesforce automation specifications
│   ├── flows/opp-automation-onstagechange.md - Trigger on Closed Won
│   └── orchestrations/new-client-kickoff-orchestration.md - Multi-stage process
├── integrations/ - API integration specifications
│   ├── salesforce-to-github.md - Create repos from Salesforce
│   ├── github-to-salesforce.md - Update Salesforce after deploy
│   └── salesforce-to-asana.md - Create Asana projects from Salesforce
└── templates/ - Reusable templates
    ├── github-actions/ - CI/CD workflows (ci.yml, deploy.yml, safety.yml)
    └── repo-template/ - Standard repo config (PR template, labels, branch protection)

Key Features:
- Event-driven automation (Closed Won → repos + Asana creation)
- GitHub Actions templates for CI/CD baseline
- Salesforce Flow & Orchestration specs
- Complete API integration documentation
- Operator-friendly playbooks
- Two-view approach (operator + engineer)
- No manual status syncing across systems

This provides the complete backbone for next-gen ERP automation.
2025-11-17 08:17:51 +00:00

304 lines
12 KiB
YAML
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
name: Deploy to Production
on:
push:
branches:
- main
release:
types: [published]
workflow_dispatch:
inputs:
environment:
description: 'Environment to deploy to'
required: true
default: 'production'
type: choice
options:
- staging
- production
env:
RAILWAY_SERVICE: backend
ENVIRONMENT: ${{ github.event.inputs.environment || 'production' }}
jobs:
deploy-backend:
name: Deploy Backend to Railway
runs-on: ubuntu-latest
environment: ${{ github.event.inputs.environment || 'production' }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Railway CLI
run: |
curl -fsSL https://railway.app/install.sh | sh
echo "$HOME/.railway/bin" >> $GITHUB_PATH
- name: Deploy to Railway
env:
RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}
run: |
railway up --service ${{ env.RAILWAY_SERVICE }} --environment ${{ env.ENVIRONMENT }}
- name: Wait for deployment to complete
env:
RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}
run: |
echo "Waiting for Railway deployment to stabilize..."
sleep 30
- name: Get deployment URL
id: get-url
env:
RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}
run: |
DEPLOY_URL=$(railway domain --service ${{ env.RAILWAY_SERVICE }} --environment ${{ env.ENVIRONMENT }} 2>/dev/null || echo "")
echo "DEPLOY_URL=$DEPLOY_URL" >> $GITHUB_OUTPUT
echo "Backend deployed to: $DEPLOY_URL"
- name: Health check
if: steps.get-url.outputs.DEPLOY_URL != ''
run: |
echo "Running health check on ${{ steps.get-url.outputs.DEPLOY_URL }}"
# Retry health check up to 10 times
for i in {1..10}; do
if curl -f "${{ steps.get-url.outputs.DEPLOY_URL }}/health" 2>/dev/null; then
echo "✅ Health check passed"
exit 0
fi
echo "Health check attempt $i failed, retrying in 10s..."
sleep 10
done
echo "❌ Health check failed after 10 attempts"
exit 1
- name: Save deployment metadata
id: metadata
run: |
echo "DEPLOY_TIME=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> $GITHUB_OUTPUT
echo "DEPLOY_SHA=${{ github.sha }}" >> $GITHUB_OUTPUT
echo "DEPLOY_ACTOR=${{ github.actor }}" >> $GITHUB_OUTPUT
echo "DEPLOY_BRANCH=${{ github.ref_name }}" >> $GITHUB_OUTPUT
outputs:
deploy_url: ${{ steps.get-url.outputs.DEPLOY_URL }}
deploy_time: ${{ steps.metadata.outputs.DEPLOY_TIME }}
deploy_sha: ${{ steps.metadata.outputs.DEPLOY_SHA }}
deploy-frontend:
name: Deploy Frontend to Cloudflare Pages
runs-on: ubuntu-latest
environment: ${{ github.event.inputs.environment || 'production' }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build frontend
env:
VITE_API_URL: ${{ secrets.VITE_API_URL }}
VITE_ENV: ${{ env.ENVIRONMENT }}
run: npm run build
- name: Deploy to Cloudflare Pages
uses: cloudflare/pages-action@v1
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
projectName: ${{ secrets.CLOUDFLARE_PROJECT_NAME }}
directory: dist
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
branch: ${{ github.ref_name }}
- name: Verify frontend deployment
run: |
FRONTEND_URL="${{ secrets.CLOUDFLARE_PAGES_URL }}"
echo "Verifying frontend at $FRONTEND_URL"
# Wait for Cloudflare propagation
sleep 30
# Check if frontend loads
for i in {1..5}; do
if curl -f "$FRONTEND_URL" 2>/dev/null; then
echo "✅ Frontend is accessible"
exit 0
fi
echo "Attempt $i failed, retrying in 10s..."
sleep 10
done
echo "❌ Frontend verification failed"
exit 1
notify-stakeholders:
name: Notify Stakeholders
needs: [deploy-backend, deploy-frontend]
runs-on: ubuntu-latest
if: success()
steps:
- name: Extract Project Key from repo name
id: project-key
run: |
# Extract from repo name: blackroad-ACME-X7K9-backend → ACME-X7K9
REPO_NAME="${{ github.repository }}"
PROJECT_KEY=$(echo "$REPO_NAME" | sed -n 's/.*blackroad-\([A-Z0-9-]*\)-.*/\1/p')
if [ -z "$PROJECT_KEY" ]; then
echo "Warning: Could not extract project key from repo name"
PROJECT_KEY="UNKNOWN"
fi
echo "PROJECT_KEY=$PROJECT_KEY" >> $GITHUB_OUTPUT
echo "Extracted project key: $PROJECT_KEY"
- name: Update Salesforce Project record
if: steps.project-key.outputs.PROJECT_KEY != 'UNKNOWN'
env:
SF_INSTANCE_URL: ${{ secrets.SALESFORCE_INSTANCE_URL }}
SF_ACCESS_TOKEN: ${{ secrets.SALESFORCE_ACCESS_TOKEN }}
run: |
curl -X PATCH \
"$SF_INSTANCE_URL/services/data/v58.0/sobjects/Project__c/Project_Key__c/${{ steps.project-key.outputs.PROJECT_KEY }}" \
-H "Authorization: Bearer $SF_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"Last_Deploy_At__c": "${{ needs.deploy-backend.outputs.deploy_time }}",
"Last_Deploy_SHA__c": "${{ needs.deploy-backend.outputs.deploy_sha }}",
"Last_Deploy_Branch__c": "${{ github.ref_name }}",
"Last_Deploy_Actor__c": "${{ github.actor }}",
"Deploy_Status__c": "Success",
"Environment__c": "${{ env.ENVIRONMENT }}",
"Backend_URL__c": "${{ needs.deploy-backend.outputs.deploy_url }}",
"Release_Notes_URL__c": "https://github.com/${{ github.repository }}/commit/${{ github.sha }}"
}' || echo "Warning: Failed to update Salesforce"
- name: Create Salesforce Deployment record
if: steps.project-key.outputs.PROJECT_KEY != 'UNKNOWN'
env:
SF_INSTANCE_URL: ${{ secrets.SALESFORCE_INSTANCE_URL }}
SF_ACCESS_TOKEN: ${{ secrets.SALESFORCE_ACCESS_TOKEN }}
run: |
curl -X POST \
"$SF_INSTANCE_URL/services/data/v58.0/sobjects/Deployment__c" \
-H "Authorization: Bearer $SF_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"Name": "${{ steps.project-key.outputs.PROJECT_KEY }} - ${{ github.sha }}",
"Project_Key__c": "${{ steps.project-key.outputs.PROJECT_KEY }}",
"Deployed_At__c": "${{ needs.deploy-backend.outputs.deploy_time }}",
"Git_SHA__c": "${{ github.sha }}",
"Git_Branch__c": "${{ github.ref_name }}",
"Deployed_By__c": "${{ github.actor }}",
"Status__c": "Success",
"Environment__c": "${{ env.ENVIRONMENT }}",
"Repository__c": "${{ github.repository }}",
"Commit_URL__c": "https://github.com/${{ github.repository }}/commit/${{ github.sha }}"
}' || echo "Warning: Failed to create Deployment record"
- name: Update Asana tasks
if: steps.project-key.outputs.PROJECT_KEY != 'UNKNOWN' && env.ENVIRONMENT == 'production'
env:
ASANA_PAT: ${{ secrets.ASANA_PAT }}
ASANA_WORKSPACE_GID: ${{ secrets.ASANA_WORKSPACE_GID }}
run: |
PROJECT_KEY="${{ steps.project-key.outputs.PROJECT_KEY }}"
# Find Asana project by name containing PROJECT_KEY
PROJECT_GID=$(curl -s "https://app.asana.com/api/1.0/projects?workspace=$ASANA_WORKSPACE_GID&opt_fields=name,gid" \
-H "Authorization: Bearer $ASANA_PAT" | \
jq -r ".data[] | select(.name | contains(\"$PROJECT_KEY\")) | .gid" | head -n 1)
if [ -z "$PROJECT_GID" ]; then
echo "Warning: Could not find Asana project for $PROJECT_KEY"
exit 0
fi
echo "Found Asana project: $PROJECT_GID"
# Find "Deploy" task
TASK_GID=$(curl -s "https://app.asana.com/api/1.0/projects/$PROJECT_GID/tasks?opt_fields=name,gid,completed" \
-H "Authorization: Bearer $ASANA_PAT" | \
jq -r '.data[] | select(.name | test("Deploy.*production"; "i")) | select(.completed == false) | .gid' | head -n 1)
if [ -z "$TASK_GID" ]; then
echo "No pending deploy task found in Asana"
exit 0
fi
echo "Found deploy task: $TASK_GID"
# Mark task as complete
curl -X PUT "https://app.asana.com/api/1.0/tasks/$TASK_GID" \
-H "Authorization: Bearer $ASANA_PAT" \
-H "Content-Type: application/json" \
-d '{"data": {"completed": true}}' || echo "Warning: Failed to complete task"
# Add comment with deploy details
curl -X POST "https://app.asana.com/api/1.0/tasks/$TASK_GID/stories" \
-H "Authorization: Bearer $ASANA_PAT" \
-H "Content-Type: application/json" \
-d '{
"data": {
"text": "✅ Deployed to production\n\n**Commit:** ${{ github.sha }}\n**By:** ${{ github.actor }}\n**Time:** ${{ needs.deploy-backend.outputs.deploy_time }}\n**Link:** https://github.com/${{ github.repository }}/commit/${{ github.sha }}"
}
}' || echo "Warning: Failed to add comment"
- name: Notify Slack
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_DEPLOYS }}
run: |
STATUS_EMOJI="✅"
curl -X POST "$SLACK_WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d '{
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "'"$STATUS_EMOJI"' *Deploy to '"${{ env.ENVIRONMENT }}"'*\n\n*Project:* '"${{ steps.project-key.outputs.PROJECT_KEY }}"'\n*Repo:* `${{ github.repository }}`\n*Commit:* <https://github.com/${{ github.repository }}/commit/${{ github.sha }}|'"${GITHUB_SHA:0:7}"'>\n*By:* ${{ github.actor }}\n*Backend:* ${{ needs.deploy-backend.outputs.deploy_url }}"
}
}
]
}' || echo "Warning: Failed to send Slack notification"
rollback-on-failure:
name: Rollback on Failure
needs: [deploy-backend, deploy-frontend]
runs-on: ubuntu-latest
if: failure()
steps:
- name: Notify failure
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_DEPLOYS }}
run: |
curl -X POST "$SLACK_WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d '{
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "❌ *Deploy FAILED*\n\n*Repo:* `${{ github.repository }}`\n*Commit:* <https://github.com/${{ github.repository }}/commit/${{ github.sha }}|'"${GITHUB_SHA:0:7}"'>\n*By:* ${{ github.actor }}\n\n⚠ Manual intervention may be required. Check the <https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}|workflow run> for details."
}
}
]
}' || true