Files
blackroad-operating-system/sop/integrations/github-to-salesforce.md
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

568 lines
17 KiB
Markdown

# Integration: GitHub → Salesforce
**Purpose:** Enable GitHub Actions to update Salesforce records after deployments
**Direction:** GitHub calls Salesforce REST API
**Authentication:** OAuth 2.0 (Connected App) or Named Credential
**Status:** Active
**Last Updated:** 2025-11-17
---
## Overview
This integration allows GitHub Actions workflows to:
- Update Project records with deployment metadata
- Create Deployment records for audit trail
- Trigger Salesforce flows/automations
- Close tasks or update statuses
**Key Use Case:** Auto-update Salesforce when code is deployed to production
---
## Authentication Setup
### Option A: Salesforce Connected App (Recommended)
**Benefits:**
- OAuth 2.0 standard
- Refresh tokens
- IP restrictions
- Better audit trail
**Setup Steps:**
#### 1. Create Connected App in Salesforce
1. **Setup → App Manager → New Connected App**
2. **Basic Information:**
- Connected App Name: `GitHub Actions Integration`
- API Name: `GitHub_Actions_Integration`
- Contact Email: devops@blackroad.com
3. **API (Enable OAuth Settings):**
- ✅ Enable OAuth Settings
- Callback URL: `https://login.salesforce.com/services/oauth2/callback`
- Selected OAuth Scopes:
- `api` - Perform requests at any time
- `refresh_token, offline_access` - Perform requests at any time
4. **Save** and wait 2-10 minutes for Consumer Key/Secret to be generated
5. **Get Credentials:**
- Consumer Key (Client ID): `3MVG9...`
- Consumer Secret (Client Secret): `ABC123...`
#### 2. Create GitHub Secrets
In each project repository (or organization-level):
1. Go to: Settings → Secrets and variables → Actions
2. Add these secrets:
| Secret Name | Value | Description |
|-------------|-------|-------------|
| `SALESFORCE_INSTANCE_URL` | `https://your-domain.my.salesforce.com` | Salesforce instance URL |
| `SALESFORCE_CLIENT_ID` | `3MVG9...` | Connected App Consumer Key |
| `SALESFORCE_CLIENT_SECRET` | `ABC123...` | Connected App Consumer Secret |
| `SALESFORCE_USERNAME` | `integration-user@blackroad.com` | Service account username |
| `SALESFORCE_PASSWORD` | `password123` | Service account password |
| `SALESFORCE_SECURITY_TOKEN` | `XYZ789...` | Security token for user |
**Security Best Practice:** Use a dedicated integration service account, not a personal user.
#### 3. Get Access Token in GitHub Actions
**Workflow Step:**
```yaml
- name: Get Salesforce Access Token
id: sf-auth
run: |
RESPONSE=$(curl -X POST "https://login.salesforce.com/services/oauth2/token" \
-d "grant_type=password" \
-d "client_id=${{ secrets.SALESFORCE_CLIENT_ID }}" \
-d "client_secret=${{ secrets.SALESFORCE_CLIENT_SECRET }}" \
-d "username=${{ secrets.SALESFORCE_USERNAME }}" \
-d "password=${{ secrets.SALESFORCE_PASSWORD }}${{ secrets.SALESFORCE_SECURITY_TOKEN }}")
ACCESS_TOKEN=$(echo $RESPONSE | jq -r '.access_token')
INSTANCE_URL=$(echo $RESPONSE | jq -r '.instance_url')
echo "ACCESS_TOKEN=$ACCESS_TOKEN" >> $GITHUB_OUTPUT
echo "INSTANCE_URL=$INSTANCE_URL" >> $GITHUB_OUTPUT
```
---
### Option B: Salesforce REST API Endpoint (Webhook Style)
**Setup Steps:**
1. **Create Apex REST Endpoint:**
```apex
@RestResource(urlMapping='/github/webhook')
global class GitHubWebhookHandler {
@HttpPost
global static String handleDeployment() {
RestRequest req = RestContext.request;
String body = req.requestBody.toString();
Map<String, Object> payload = (Map<String, Object>) JSON.deserializeUntyped(body);
String projectKey = (String) payload.get('project_key');
String deployedAt = (String) payload.get('deployed_at');
String gitSHA = (String) payload.get('git_sha');
String deployedBy = (String) payload.get('deployed_by');
// Find Project by external ID
Project__c project = [
SELECT Id, Name
FROM Project__c
WHERE Project_Key__c = :projectKey
LIMIT 1
];
// Update Project
project.Last_Deploy_At__c = Datetime.valueOf(deployedAt);
project.Last_Deploy_SHA__c = gitSHA;
project.Last_Deploy_Actor__c = deployedBy;
project.Deploy_Status__c = 'Success';
update project;
// Create Deployment record
Deployment__c deployment = new Deployment__c(
Project__c = project.Id,
Deployed_At__c = Datetime.valueOf(deployedAt),
Git_SHA__c = gitSHA,
Deployed_By__c = deployedBy,
Status__c = 'Success'
);
insert deployment;
return JSON.serialize(new Map<String, Object>{
'success' => true,
'project_id' => project.Id,
'deployment_id' => deployment.Id
});
}
}
```
2. **Configure Site Guest User Permissions:**
- Grant access to `Project__c` and `Deployment__c` objects
- Or use API key authentication in Apex
3. **Get Endpoint URL:**
- `https://your-domain.my.salesforce.com/services/apexrest/github/webhook`
4. **Add to GitHub Secrets:**
- `SALESFORCE_WEBHOOK_URL`: Full endpoint URL
---
## API Operations
### 1. Update Project Record (Upsert by External ID)
**Endpoint:**
```
PATCH {INSTANCE_URL}/services/data/v58.0/sobjects/Project__c/Project_Key__c/{PROJECT_KEY}
```
**Headers:**
```
Authorization: Bearer {ACCESS_TOKEN}
Content-Type: application/json
```
**Payload:**
```json
{
"Last_Deploy_At__c": "2025-11-17T14:30:00Z",
"Last_Deploy_SHA__c": "a1b2c3d4e5f6",
"Last_Deploy_Branch__c": "main",
"Last_Deploy_Actor__c": "github-user",
"Deploy_Status__c": "Success",
"Environment__c": "Production",
"Backend_URL__c": "https://backend.railway.app",
"Release_Notes_URL__c": "https://github.com/org/repo/commit/a1b2c3d4e5f6"
}
```
**Response (200 OK or 201 Created):**
```json
{
"id": "a0X5e000000XYZ1EAO",
"success": true,
"errors": []
}
```
**GitHub Actions Implementation:**
```yaml
- name: Update Salesforce Project record
env:
SF_INSTANCE_URL: ${{ steps.sf-auth.outputs.INSTANCE_URL }}
SF_ACCESS_TOKEN: ${{ steps.sf-auth.outputs.ACCESS_TOKEN }}
run: |
# Extract PROJECT_KEY from repo name
REPO_NAME="${{ github.repository }}"
PROJECT_KEY=$(echo "$REPO_NAME" | sed -n 's/.*blackroad-\([A-Z0-9-]*\)-.*/\1/p')
curl -X PATCH \
"$SF_INSTANCE_URL/services/data/v58.0/sobjects/Project__c/Project_Key__c/$PROJECT_KEY" \
-H "Authorization: Bearer $SF_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"Last_Deploy_At__c": "'"$(date -u +%Y-%m-%dT%H:%M:%SZ)"'",
"Last_Deploy_SHA__c": "${{ github.sha }}",
"Last_Deploy_Branch__c": "${{ github.ref_name }}",
"Last_Deploy_Actor__c": "${{ github.actor }}",
"Deploy_Status__c": "Success",
"Environment__c": "Production",
"Release_Notes_URL__c": "https://github.com/${{ github.repository }}/commit/${{ github.sha }}"
}'
```
---
### 2. Create Deployment Record
**Endpoint:**
```
POST {INSTANCE_URL}/services/data/v58.0/sobjects/Deployment__c
```
**Headers:**
```
Authorization: Bearer {ACCESS_TOKEN}
Content-Type: application/json
```
**Payload:**
```json
{
"Name": "ACME-1042 - a1b2c3d4",
"Project_Key__c": "ACME-1042",
"Deployed_At__c": "2025-11-17T14:30:00Z",
"Git_SHA__c": "a1b2c3d4e5f6",
"Git_Branch__c": "main",
"Deployed_By__c": "github-user",
"Status__c": "Success",
"Environment__c": "Production",
"Repository__c": "blackboxprogramming/blackroad-ACME-1042-backend",
"Commit_URL__c": "https://github.com/blackboxprogramming/blackroad-ACME-1042-backend/commit/a1b2c3d4e5f6",
"Duration_Seconds__c": 120
}
```
**Response (201 Created):**
```json
{
"id": "a0Y5e000000ABC1EAO",
"success": true,
"errors": []
}
```
**GitHub Actions Implementation:**
```yaml
- name: Create Salesforce Deployment record
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": "'"$PROJECT_KEY"' - '"${GITHUB_SHA:0:8}"'",
"Project_Key__c": "'"$PROJECT_KEY"'",
"Deployed_At__c": "'"$(date -u +%Y-%m-%dT%H:%M:%SZ)"'",
"Git_SHA__c": "${{ github.sha }}",
"Git_Branch__c": "${{ github.ref_name }}",
"Deployed_By__c": "${{ github.actor }}",
"Status__c": "Success",
"Environment__c": "Production",
"Repository__c": "${{ github.repository }}",
"Commit_URL__c": "https://github.com/${{ github.repository }}/commit/${{ github.sha }}"
}'
```
---
### 3. Query Salesforce (Optional)
**Use Case:** Get Project metadata before deploying
**Endpoint:**
```
GET {INSTANCE_URL}/services/data/v58.0/query?q=SELECT+Id,Name,Package_Type__c+FROM+Project__c+WHERE+Project_Key__c='ACME-1042'
```
**Headers:**
```
Authorization: Bearer {ACCESS_TOKEN}
```
**Response:**
```json
{
"totalSize": 1,
"done": true,
"records": [
{
"Id": "a0X5e000000XYZ1EAO",
"Name": "Acme Corp - ACME-1042",
"Package_Type__c": "OS"
}
]
}
```
---
## Required Salesforce Objects
### Deployment Custom Object
**API Name:** `Deployment__c`
**Label:** Deployment
**Record Name:** Deployment Name (Auto Number: `DEP-{0000}`)
#### Fields:
| Field API Name | Type | Length | Description |
|----------------|------|--------|-------------|
| `Project__c` | Lookup(Project__c) | N/A | Related project |
| `Project_Key__c` | Text (External ID) | 20 | For upsert operations |
| `Deployed_At__c` | DateTime | N/A | When deployment occurred |
| `Git_SHA__c` | Text | 40 | Git commit SHA |
| `Git_Branch__c` | Text | 100 | Git branch name |
| `Deployed_By__c` | Text | 100 | GitHub username |
| `Status__c` | Picklist | N/A | Success, Failed, Rollback, In Progress |
| `Environment__c` | Picklist | N/A | Staging, Production |
| `Repository__c` | Text | 255 | Full repo name (org/repo) |
| `Commit_URL__c` | URL | 255 | Link to commit |
| `Duration_Seconds__c` | Number(6,0) | N/A | Deploy duration |
| `Error_Message__c` | Long Text Area | 32768 | If deploy failed |
---
## Complete GitHub Actions Workflow
**File:** `.github/workflows/notify-salesforce.yml`
```yaml
name: Notify Salesforce After Deploy
on:
workflow_run:
workflows: ["Deploy to Production"]
types:
- completed
jobs:
notify-salesforce:
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' }}
steps:
- name: Extract Project Key from repo name
id: project-key
run: |
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"
PROJECT_KEY="UNKNOWN"
fi
echo "PROJECT_KEY=$PROJECT_KEY" >> $GITHUB_OUTPUT
- name: Authenticate with Salesforce
id: sf-auth
env:
SF_CLIENT_ID: ${{ secrets.SALESFORCE_CLIENT_ID }}
SF_CLIENT_SECRET: ${{ secrets.SALESFORCE_CLIENT_SECRET }}
SF_USERNAME: ${{ secrets.SALESFORCE_USERNAME }}
SF_PASSWORD: ${{ secrets.SALESFORCE_PASSWORD }}
SF_SECURITY_TOKEN: ${{ secrets.SALESFORCE_SECURITY_TOKEN }}
run: |
RESPONSE=$(curl -X POST "https://login.salesforce.com/services/oauth2/token" \
-d "grant_type=password" \
-d "client_id=$SF_CLIENT_ID" \
-d "client_secret=$SF_CLIENT_SECRET" \
-d "username=$SF_USERNAME" \
-d "password=$SF_PASSWORD$SF_SECURITY_TOKEN")
ACCESS_TOKEN=$(echo $RESPONSE | jq -r '.access_token')
INSTANCE_URL=$(echo $RESPONSE | jq -r '.instance_url')
echo "ACCESS_TOKEN=$ACCESS_TOKEN" >> $GITHUB_OUTPUT
echo "INSTANCE_URL=$INSTANCE_URL" >> $GITHUB_OUTPUT
- name: Update Salesforce Project record
if: steps.project-key.outputs.PROJECT_KEY != 'UNKNOWN'
env:
SF_INSTANCE_URL: ${{ steps.sf-auth.outputs.INSTANCE_URL }}
SF_ACCESS_TOKEN: ${{ steps.sf-auth.outputs.ACCESS_TOKEN }}
PROJECT_KEY: ${{ steps.project-key.outputs.PROJECT_KEY }}
run: |
curl -X PATCH \
"$SF_INSTANCE_URL/services/data/v58.0/sobjects/Project__c/Project_Key__c/$PROJECT_KEY" \
-H "Authorization: Bearer $SF_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"Last_Deploy_At__c": "'"$(date -u +%Y-%m-%dT%H:%M:%SZ)"'",
"Last_Deploy_SHA__c": "${{ github.sha }}",
"Last_Deploy_Branch__c": "${{ github.ref_name }}",
"Last_Deploy_Actor__c": "${{ github.actor }}",
"Deploy_Status__c": "Success",
"Environment__c": "Production",
"Release_Notes_URL__c": "https://github.com/${{ github.repository }}/commit/${{ github.sha }}"
}' || echo "Warning: Failed to update Project record"
- name: Create Deployment record
if: steps.project-key.outputs.PROJECT_KEY != 'UNKNOWN'
env:
SF_INSTANCE_URL: ${{ steps.sf-auth.outputs.INSTANCE_URL }}
SF_ACCESS_TOKEN: ${{ steps.sf-auth.outputs.ACCESS_TOKEN }}
PROJECT_KEY: ${{ steps.project-key.outputs.PROJECT_KEY }}
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": "'"$PROJECT_KEY"' - '"${GITHUB_SHA:0:8}"'",
"Project_Key__c": "'"$PROJECT_KEY"'",
"Deployed_At__c": "'"$(date -u +%Y-%m-%dT%H:%M:%SZ)"'",
"Git_SHA__c": "${{ github.sha }}",
"Git_Branch__c": "${{ github.ref_name }}",
"Deployed_By__c": "${{ github.actor }}",
"Status__c": "Success",
"Environment__c": "Production",
"Repository__c": "${{ github.repository }}",
"Commit_URL__c": "https://github.com/${{ github.repository }}/commit/${{ github.sha }}"
}' || echo "Warning: Failed to create Deployment record"
```
---
## Error Handling
### Common Errors
| Status Code | Error | Cause | Solution |
|-------------|-------|-------|----------|
| 401 | Unauthorized | Token expired | Re-authenticate |
| 403 | Forbidden | Insufficient permissions | Check user permissions in Salesforce |
| 404 | Not Found | Project_Key__c doesn't exist | Verify project key extraction logic |
| 400 | Bad Request | Invalid field value | Check datetime format (must be ISO 8601) |
### Retry Logic
```yaml
- name: Update Salesforce (with retries)
uses: nick-invision/retry@v2
with:
timeout_minutes: 2
max_attempts: 3
command: |
curl -X PATCH ... (Salesforce API call)
```
---
## Testing
### Manual Test (curl)
```bash
# 1. Get access token
ACCESS_TOKEN=$(curl -X POST "https://login.salesforce.com/services/oauth2/token" \
-d "grant_type=password" \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=YOUR_CLIENT_SECRET" \
-d "username=YOUR_USERNAME" \
-d "password=YOUR_PASSWORD_AND_TOKEN" | jq -r '.access_token')
# 2. Update Project
curl -X PATCH \
"https://your-domain.my.salesforce.com/services/data/v58.0/sobjects/Project__c/Project_Key__c/TEST-1234" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"Last_Deploy_At__c": "2025-11-17T14:30:00Z",
"Deploy_Status__c": "Success"
}'
```
### GitHub Actions Test
1. Push commit to test repo
2. Trigger deploy workflow
3. Verify Salesforce Project updated
4. Check Deployment record created
5. Review GitHub Actions logs for errors
---
## Monitoring
**Track These Metrics:**
| Metric | Target | Alert Threshold |
|--------|--------|-----------------|
| Salesforce API Success Rate | > 98% | < 95% |
| Avg API Response Time | < 1s | > 3s |
| Failed Updates | < 2% | > 5% |
**Salesforce Report:** "Deployments by Status (Last 30 Days)"
---
## Security Best Practices
1. **Use Service Account:**
- Create `integration-user@blackroad.com`
- Assign minimal permissions
- Enable API-only user (no login UI)
2. **Rotate Credentials:**
- Rotate passwords every 90 days
- Use GitHub encrypted secrets
- Never commit credentials to git
3. **IP Restrictions:**
- Whitelist GitHub Actions IP ranges in Salesforce
- See: https://api.github.com/meta
4. **Audit Logging:**
- Enable Salesforce Event Monitoring
- Log all API calls
- Review monthly
---
## Related Documentation
- [Salesforce REST API Docs](https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/)
- [GitHub Actions: Deploy Workflow](../templates/github-actions/deploy.yml)
- [Workflow: Release Pipeline](../workflows/release-pipeline.md)
- [Integration: Salesforce → GitHub](./salesforce-to-github.md)
---
## Changelog
| Date | Version | Change | Author |
|------|---------|--------|--------|
| 2025-11-17 | 1.0 | Initial specification | Cece (Claude) |