feat(ci): Add cross-repository dev deployment workflow (#10059)

### Description 📝

This PR introduces a GitHub Actions workflow that enables
cross-repository event dispatching for development environment
deployments. The workflow listens for specific PR events and dispatches
corresponding deployment/undeployment actions to our cloud
infrastructure repository.

**How it works:**
- The workflow triggers on PR events (opened, synchronized, closed) and
PR target events (labeled, unlabeled)
- When a PR comment containing `!deploy` is detected from authorized
users (PR author, repo owners, members, or collaborators), it dispatches
a deployment event
- When a PR with existing deployments is closed, it automatically
dispatches an undeployment event to clean up resources

**Interaction with target repository:**
The workflow dispatches events to
`Significant-Gravitas/AutoGPT_cloud_infrastructure` with a payload
containing:
- `action`: Either "deploy" or "undeploy"
- `pr_number`: The PR number for tracking
- `pr_title`: Human-readable identifier
- `pr_state`: Current PR state
- `repo`: Source repository name

This enables the infrastructure repository to spin up isolated
development environments for each PR on demand.

### Changes 🏗️
- Added `.github/workflows/dev-deploy-pr-dispatcher.yml` workflow file
- Implements secure cross-repository communication using repository
dispatch events
- Includes authorization checks to ensure only authorized users can
trigger deployments

### Checklist 📋

#### For code changes:
- [x] No code changes - this is a workflow addition only

#### For configuration changes:
- [x] New workflow file added that requires testing
- [x] Requires `DISPATCH_TOKEN` secret to be configured with appropriate
permissions for cross-repository dispatch
- [x] No environment variable changes needed
- [ ] Workflow will be tested after initial merge to verify proper event
dispatching
This commit is contained in:
Swifty
2025-05-31 10:39:57 +02:00
committed by GitHub
parent 16d6f5377c
commit 56ddffeaa0
2 changed files with 312 additions and 43 deletions

View File

@@ -1,51 +1,263 @@
name: AutoGPT Platform - Deploy Dev Environment
name: Dev Deploy PR Event Dispatcher
on:
push:
branches: [ dev ]
paths:
- 'autogpt_platform/**'
permissions:
contents: 'read'
id-token: 'write'
pull_request:
types: [closed]
issue_comment:
types: [created]
jobs:
migrate:
environment: develop
name: Run migrations for AutoGPT Platform
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install Python dependencies
run: |
python -m pip install --upgrade pip
pip install prisma
- name: Run Backend Migrations
working-directory: ./autogpt_platform/backend
run: |
python -m prisma migrate deploy
env:
DATABASE_URL: ${{ secrets.BACKEND_DATABASE_URL }}
DIRECT_URL: ${{ secrets.BACKEND_DATABASE_URL }}
trigger:
needs: migrate
dispatch:
runs-on: ubuntu-latest
steps:
- name: Trigger deploy workflow
uses: peter-evans/repository-dispatch@v3
- name: Check comment permissions and deployment status
id: check_status
if: github.event_name == 'issue_comment' && github.event.issue.pull_request
uses: actions/github-script@v7
with:
token: ${{ secrets.DEPLOY_TOKEN }}
script: |
const commentBody = context.payload.comment.body.trim();
const commentUser = context.payload.comment.user.login;
const prAuthor = context.payload.issue.user.login;
const authorAssociation = context.payload.comment.author_association;
// Check permissions
const hasPermission = (
commentUser === prAuthor ||
authorAssociation === 'OWNER' ||
authorAssociation === 'MEMBER' ||
authorAssociation === 'COLLABORATOR'
);
core.setOutput('comment_body', commentBody);
core.setOutput('has_permission', hasPermission);
`
if (!hasPermission && (commentBody === '!deploy' || commentBody === '!undeploy')) {
core.setOutput('permission_denied', 'true');
return;
}
if (commentBody !== '!deploy' && commentBody !== '!undeploy') {
return;
}
// Get all comments to check deployment status
const comments = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number
});
let lastDeployIndex = -1;
let lastUndeployIndex = -1;
comments.data.forEach((comment, index) => {
if (comment.body.trim() === '!deploy') {
lastDeployIndex = index;
} else if (comment.body.trim() === '!undeploy') {
lastUndeployIndex = index;
}
});
// Determine current deployment state
const hasActiveDeployment = lastDeployIndex !== -1 && lastDeployIndex > lastUndeployIndex;
core.setOutput('has_active_deployment', hasActiveDeployment);
core.setOutput('last_deploy_index', lastDeployIndex);
core.setOutput('last_undeploy_index', lastUndeployIndex);
// Determine actions
if (commentBody === '!deploy') {
if (hasActiveDeployment) {
core.setOutput('deploy_blocked', 'already_deployed');
} else {
core.setOutput('should_deploy', 'true');
}
} else if (commentBody === '!undeploy') {
if (!hasActiveDeployment) {
if (lastDeployIndex === -1) {
core.setOutput('undeploy_blocked', 'never_deployed');
} else {
core.setOutput('undeploy_blocked', 'already_undeployed');
}
} else {
core.setOutput('should_undeploy', 'true');
}
}
- name: Post permission denied comment
if: steps.check_status.outputs.permission_denied == 'true'
uses: actions/github-script@v7
with:
script: |
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: `❌ **Permission denied**: Only the PR author, repository owners, members, or collaborators can use deployment commands.`
});
- name: Post deploy blocked comment
if: steps.check_status.outputs.deploy_blocked == 'already_deployed'
uses: actions/github-script@v7
with:
script: |
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: `⚠️ **Deploy skipped**: This PR already has an active deployment. Use \`!undeploy\` first if you want to redeploy.`
});
- name: Post undeploy blocked comment
if: steps.check_status.outputs.undeploy_blocked != ''
uses: actions/github-script@v7
with:
script: |
const reason = '${{ steps.check_status.outputs.undeploy_blocked }}';
let message;
if (reason === 'never_deployed') {
message = `⚠️ **Undeploy skipped**: This PR has never been deployed. Use \`!deploy\` first.`;
} else if (reason === 'already_undeployed') {
message = `⚠️ **Undeploy skipped**: This PR is already undeployed.`;
}
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: message
});
- name: Get PR details for deployment
id: pr_details
if: steps.check_status.outputs.should_deploy == 'true' || steps.check_status.outputs.should_undeploy == 'true'
uses: actions/github-script@v7
with:
script: |
const pr = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.issue.number
});
core.setOutput('pr_number', pr.data.number);
core.setOutput('pr_title', pr.data.title);
core.setOutput('pr_state', pr.data.state);
- name: Dispatch Deploy Event
if: steps.check_status.outputs.should_deploy == 'true'
uses: peter-evans/repository-dispatch@v2
with:
token: ${{ secrets.DISPATCH_TOKEN }}
repository: Significant-Gravitas/AutoGPT_cloud_infrastructure
event-type: build_deploy_dev
client-payload: '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}", "repository": "${{ github.repository }}"}'
event-type: pr-event
client-payload: |
{
"action": "deploy",
"pr_number": "${{ steps.pr_details.outputs.pr_number }}",
"pr_title": "${{ steps.pr_details.outputs.pr_title }}",
"pr_state": "${{ steps.pr_details.outputs.pr_state }}",
"repo": "${{ github.repository }}"
}
- name: Post deploy success comment
if: steps.check_status.outputs.should_deploy == 'true'
uses: actions/github-script@v7
with:
script: |
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: `🚀 **Deploying PR #${{ steps.pr_details.outputs.pr_number }}** to development environment...`
});
- name: Dispatch Undeploy Event (from comment)
if: steps.check_status.outputs.should_undeploy == 'true'
uses: peter-evans/repository-dispatch@v2
with:
token: ${{ secrets.DISPATCH_TOKEN }}
repository: Significant-Gravitas/AutoGPT_cloud_infrastructure
event-type: pr-event
client-payload: |
{
"action": "undeploy",
"pr_number": "${{ steps.pr_details.outputs.pr_number }}",
"pr_title": "${{ steps.pr_details.outputs.pr_title }}",
"pr_state": "${{ steps.pr_details.outputs.pr_state }}",
"repo": "${{ github.repository }}"
}
- name: Post undeploy success comment
if: steps.check_status.outputs.should_undeploy == 'true'
uses: actions/github-script@v7
with:
script: |
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: `🗑️ **Undeploying PR #${{ steps.pr_details.outputs.pr_number }}** from development environment...`
});
- name: Check deployment status on PR close
id: check_pr_close
if: github.event_name == 'pull_request' && github.event.action == 'closed'
uses: actions/github-script@v7
with:
script: |
const comments = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number
});
let lastDeployIndex = -1;
let lastUndeployIndex = -1;
comments.data.forEach((comment, index) => {
if (comment.body.trim() === '!deploy') {
lastDeployIndex = index;
} else if (comment.body.trim() === '!undeploy') {
lastUndeployIndex = index;
}
});
// Should undeploy if there's a !deploy without a subsequent !undeploy
const shouldUndeploy = lastDeployIndex !== -1 && lastDeployIndex > lastUndeployIndex;
core.setOutput('should_undeploy', shouldUndeploy);
- name: Dispatch Undeploy Event (PR closed with active deployment)
if: >-
github.event_name == 'pull_request' &&
github.event.action == 'closed' &&
steps.check_pr_close.outputs.should_undeploy == 'true'
uses: peter-evans/repository-dispatch@v2
with:
token: ${{ secrets.DISPATCH_TOKEN }}
repository: Significant-Gravitas/AutoGPT_cloud_infrastructure
event-type: pr-event
client-payload: |
{
"action": "undeploy",
"pr_number": "${{ github.event.pull_request.number }}",
"pr_title": "${{ github.event.pull_request.title }}",
"pr_state": "${{ github.event.pull_request.state }}",
"repo": "${{ github.repository }}"
}
- name: Post PR close undeploy comment
if: >-
github.event_name == 'pull_request' &&
github.event.action == 'closed' &&
steps.check_pr_close.outputs.should_undeploy == 'true'
uses: actions/github-script@v7
with:
script: |
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: `🧹 **Auto-undeploying**: PR closed with active deployment. Cleaning up development environment for PR #${{ github.event.pull_request.number }}.`
});

View File

@@ -0,0 +1,57 @@
name: Dev Deploy PR Event Dispatcher
on:
pull_request:
types: [opened, synchronize, closed]
issue_comment:
types: [created]
jobs:
dispatch:
runs-on: ubuntu-latest
steps:
- name: Check if should dispatch
id: check
if: >-
github.event.issue.pull_request &&
github.event.comment.body == '!deploy' &&
(
github.event.comment.user.login == github.event.issue.user.login ||
github.event.comment.author_association == 'OWNER' ||
github.event.comment.author_association == 'MEMBER' ||
github.event.comment.author_association == 'COLLABORATOR'
)
run: |
echo "should_dispatch=true" >> $GITHUB_OUTPUT
- name: Dispatch PR Event
if: steps.check.outputs.should_dispatch == 'true'
uses: peter-evans/repository-dispatch@v2
with:
token: ${{ secrets.DISPATCH_TOKEN }}
repository: Significant-Gravitas/AutoGPT_cloud_infrastructure
event-type: pr-event
client-payload: |
{
"action": "deploy",
"pr_number": "${{ github.event.pull_request.number }}",
"pr_title": "${{ github.event.pull_request.title }}",
"pr_state": "${{ github.event.pull_request.state }}",
"repo": "${{ github.repository }}"
}
- name: Dispatch PR Closure Event
if: github.event.action == 'closed' && contains(github.event.pull_request.comments.*.body, '!deploy')
uses: peter-evans/repository-dispatch@v2
with:
token: ${{ secrets.DISPATCH_TOKEN }}
repository: Significant-Gravitas/AutoGPT_cloud_infrastructure
event-type: pr-event
client-payload: |
{
"action": "undeploy",
"pr_number": "${{ github.event.pull_request.number }}",
"pr_title": "${{ github.event.pull_request.title }}",
"pr_state": "${{ github.event.pull_request.state }}",
"repo": "${{ github.repository }}"
}