-
Notifications
You must be signed in to change notification settings - Fork 4
chore(ci): on demand review app #736
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
f76a1fc
25bddbb
c3c4ff0
36908b3
00efb27
16abaf3
b7df66a
a29dd0d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would be cool if new commits triggered redeploy for sites that were previously |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,147 @@ | ||||||
name: Trigger Review App Deployment from Comments | ||||||
|
||||||
on: | ||||||
issue_comment: | ||||||
types: [created] | ||||||
|
||||||
jobs: | ||||||
process_comment: | ||||||
runs-on: ubuntu-latest | ||||||
if: ${{ github.event.issue.pull_request && contains(github.event.comment.body, '/deploy') }} | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
? |
||||||
|
||||||
steps: | ||||||
- name: Checkout code to access workflow file | ||||||
uses: actions/checkout@v4 | ||||||
|
||||||
- name: Extract available sites | ||||||
id: available_sites | ||||||
uses: mikefarah/yq@de2f77b49cbd40fd67031ee602245d0acc4ac482 | ||||||
with: | ||||||
cmd: yq '.on.workflow_dispatch.inputs.site.options[]' '.github/workflows/review-app.yml' | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could go all yq and merge Extract+Format steps:
Suggested change
|
||||||
|
||||||
- name: Format available sites | ||||||
id: format_sites | ||||||
run: | | ||||||
# Format the sites as a comma-separated list, excluding ecospheres | ||||||
AVAILABLE_SITES=$(echo "${{ steps.available_sites.outputs.result }}" | grep -v "ecospheres" | tr '\n' ',' | sed 's/,$//') | ||||||
echo "AVAILABLE_SITES=$AVAILABLE_SITES" >> $GITHUB_ENV | ||||||
echo "available_sites=$AVAILABLE_SITES" >> $GITHUB_OUTPUT | ||||||
echo "Available sites: $AVAILABLE_SITES" | ||||||
|
||||||
- name: Get PR details | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you just compare https://github.com/orgs/community/discussions/26829#discussioncomment-3253575 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wouldn't it be simpler to fail the workflow when |
||||||
id: pr_details | ||||||
uses: actions/github-script@v6 | ||||||
with: | ||||||
script: | | ||||||
const prNumber = context.issue.number; | ||||||
core.setOutput('pr_number', prNumber); | ||||||
|
||||||
// Get PR information to verify it's from the same repository | ||||||
const { data: pullRequest } = await github.rest.pulls.get({ | ||||||
owner: context.repo.owner, | ||||||
repo: context.repo.repo, | ||||||
pull_number: prNumber | ||||||
}); | ||||||
|
||||||
const isSameRepo = pullRequest.head.repo.full_name === context.repo.full_name; | ||||||
core.setOutput('is_same_repo', isSameRepo.toString()); | ||||||
|
||||||
return { prNumber, isSameRepo }; | ||||||
|
||||||
- name: Parse deployment command | ||||||
id: parse_command | ||||||
if: ${{ steps.pr_details.outputs.is_same_repo == 'true' }} | ||||||
run: | | ||||||
COMMENT="${{ github.event.comment.body }}" | ||||||
AVAILABLE_SITES="${{ env.AVAILABLE_SITES }}" | ||||||
|
||||||
# Extract sites from command | ||||||
if [[ $COMMENT =~ /deploy\ +([a-zA-Z0-9,-]+) ]]; then | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That comma got me wondering for a while. I'd move it up front and possibly add a comment? Or go with space-delimited site names? |
||||||
SITES="${BASH_REMATCH[1]}" | ||||||
|
||||||
# Remove spaces if any | ||||||
SITES=$(echo $SITES | tr -d '[:space:]') | ||||||
|
||||||
# If "all" is specified, use all available sites (excluding ecospheres) | ||||||
if [[ "$SITES" == "all" ]]; then | ||||||
SITES="$AVAILABLE_SITES" | ||||||
fi | ||||||
|
||||||
echo "SITES=$SITES" >> $GITHUB_ENV | ||||||
echo "sites=$SITES" >> $GITHUB_OUTPUT | ||||||
echo "has_valid_sites=true" >> $GITHUB_OUTPUT | ||||||
else | ||||||
# No site specified, fail | ||||||
echo "No site specified in the deployment command" >> $GITHUB_STEP_SUMMARY | ||||||
echo "has_valid_sites=false" >> $GITHUB_OUTPUT | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fail directly? |
||||||
fi | ||||||
|
||||||
- name: Add reaction to comment | ||||||
if: ${{ steps.pr_details.outputs.is_same_repo == 'true' && steps.parse_command.outputs.has_valid_sites == 'true' }} | ||||||
uses: actions/github-script@v6 | ||||||
with: | ||||||
script: | | ||||||
await github.rest.reactions.createForIssueComment({ | ||||||
owner: context.repo.owner, | ||||||
repo: context.repo.repo, | ||||||
comment_id: context.payload.comment.id, | ||||||
content: 'rocket' | ||||||
}); | ||||||
|
||||||
- name: Trigger deployments | ||||||
if: ${{ steps.pr_details.outputs.is_same_repo == 'true' && steps.parse_command.outputs.has_valid_sites == 'true' }} | ||||||
uses: actions/github-script@v6 | ||||||
with: | ||||||
github-token: ${{ secrets.GITHUB_TOKEN }} | ||||||
script: | | ||||||
const sites = '${{ steps.parse_command.outputs.sites }}'.split(','); | ||||||
const prNumber = '${{ steps.pr_details.outputs.pr_number }}'; | ||||||
const availableSites = '${{ steps.format_sites.outputs.available_sites }}'.split(',').filter(Boolean); | ||||||
|
||||||
// Create a comment to inform about the deployments | ||||||
let deploymentMessage = `📦 Triggering deployment for site(s): **${sites.join(', ')}**\n\n`; | ||||||
let validSitesFound = false; | ||||||
|
||||||
for (const site of sites) { | ||||||
// Skip ecospheres as it's deployed directly by PR events | ||||||
if (site === 'ecospheres') { | ||||||
deploymentMessage += `ℹ️ Note: Site 'ecospheres' is deployed automatically by PR events and will be skipped.\n`; | ||||||
continue; | ||||||
} | ||||||
|
||||||
// Validate against available sites | ||||||
if (!availableSites.includes(site)) { | ||||||
deploymentMessage += `⚠️ Warning: Site '${site}' is not recognized and will be skipped.\n`; | ||||||
continue; | ||||||
} | ||||||
|
||||||
validSitesFound = true; | ||||||
deploymentMessage += `🚀 Starting deployment for **${site}**...\n`; | ||||||
|
||||||
try { | ||||||
await github.rest.actions.createWorkflowDispatch({ | ||||||
owner: context.repo.owner, | ||||||
repo: context.repo.repo, | ||||||
workflow_id: 'review-app.yml', | ||||||
ref: 'main', | ||||||
inputs: { | ||||||
site: site, | ||||||
pr_number: prNumber | ||||||
} | ||||||
}); | ||||||
} catch (error) { | ||||||
deploymentMessage += `❌ Failed to trigger deployment for ${site}: ${error.message}\n`; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
continue; | ||||||
} | ||||||
} | ||||||
|
||||||
if (!validSitesFound) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure that makes sense. If I |
||||||
deploymentMessage += `\n❌ No valid sites were found to deploy. Please check your command and try again.`; | ||||||
} | ||||||
|
||||||
await github.rest.issues.createComment({ | ||||||
owner: context.repo.owner, | ||||||
repo: context.repo.repo, | ||||||
issue_number: prNumber, | ||||||
body: deploymentMessage | ||||||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,56 +5,80 @@ name: Deploy review app | |
on: | ||
pull_request: | ||
types: [opened, synchronize, reopened, closed] | ||
workflow_dispatch: | ||
inputs: | ||
site: | ||
description: 'Site to deploy (ecospheres, meteo-france, or logistique)' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Description doesn't match options. I'd remove them to avoid syncing issues. |
||
required: true | ||
default: 'ecospheres' | ||
type: choice | ||
options: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sort for maintainability |
||
- ecospheres | ||
- meteo-france | ||
- logistique | ||
- hackathon | ||
- defis | ||
pr_number: | ||
description: 'PR number (required for manual deployments)' | ||
required: true | ||
type: string | ||
|
||
jobs: | ||
handle_review_app: | ||
runs-on: ubuntu-latest | ||
concurrency: | ||
group: ${{ github.workflow }}-${{ github.ref }}-${{ matrix.site }} | ||
group: ${{ github.workflow }}-${{ github.ref }}-${{ inputs.site || 'ecospheres' }} | ||
cancel-in-progress: false | ||
strategy: | ||
matrix: | ||
site: | ||
- ecospheres | ||
- meteo-france | ||
- logistique | ||
permissions: | ||
deployments: write | ||
# only run if the PR is from the same repo | ||
if: github.event.pull_request.head.repo.full_name == github.repository && github.event_name == 'pull_request' | ||
if: | | ||
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) || | ||
github.event_name == 'workflow_dispatch' | ||
steps: | ||
- name: Set environment variables | ||
run: | | ||
if [ "${{ github.event_name }}" = "pull_request" ]; then | ||
echo "SITE_ID=${{ github.event.pull_request.head.ref == 'main' && 'ecospheres' || 'ecospheres' }}" >> $GITHUB_ENV | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doesn't that always result in |
||
echo "PR_NUMBER=${{ github.event.pull_request.number }}" >> $GITHUB_ENV | ||
else | ||
echo "SITE_ID=${{ inputs.site }}" >> $GITHUB_ENV | ||
echo "PR_NUMBER=${{ inputs.pr_number }}" >> $GITHUB_ENV | ||
fi | ||
|
||
- name: Debug event | ||
run: | | ||
echo "Event name: ${{ github.event_name }}" | ||
echo "Event action: ${{ github.event.action }}" | ||
echo "Pull request state: ${{ github.event.pull_request.state }}" | ||
echo "Pull request state: ${{ github.event.pull_request.state || 'N/A' }}" | ||
echo "Site ID: ${{ env.SITE_ID }}" | ||
echo "PR number: ${{ env.PR_NUMBER }}" | ||
|
||
- name: Cloning repo | ||
if: github.event.action != 'closed' | ||
if: github.event_name == 'workflow_dispatch' || github.event.action != 'closed' | ||
uses: actions/checkout@v4 | ||
with: | ||
fetch-depth: 0 | ||
|
||
- name: Start deployment | ||
if: github.event.action != 'closed' | ||
if: github.event_name == 'workflow_dispatch' || github.event.action != 'closed' | ||
uses: chrnorm/deployment-action@v2 | ||
id: deployment | ||
with: | ||
token: ${{ github.token }} | ||
environment: ${{ matrix.site }}-preview | ||
environment: ${{ env.SITE_ID }}-preview | ||
initial-status: in_progress | ||
transient-environment: true | ||
|
||
- name: Create the review app | ||
if: github.event.action == 'opened' || github.event.action == 'reopened' | ||
if: github.event_name == 'workflow_dispatch' || github.event.action == 'opened' || github.event.action == 'reopened' | ||
uses: dokku/github-action@master | ||
# ignore errors as the app might already exist on workflow_dispatch | ||
continue-on-error: true | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wouldn't it be safer to split the two cases (workflow dispatch vs pr deploy) then? |
||
with: | ||
command: review-apps:create | ||
git_remote_url: ${{ secrets.REVIEW_APP_SSH_URL }} | ||
review_app_name: deploy-preview-${{ github.event.pull_request.number }}--${{ matrix.site }} | ||
review_app_name: deploy-preview-${{ env.PR_NUMBER }}--${{ env.SITE_ID }} | ||
ssh_private_key: ${{ secrets.REVIEW_APP_SSH_PRIVATE_KEY }} | ||
# omitting this will prevent the app from being built (which is what we want) | ||
# branch: 'main' | ||
|
||
- name: Set site id as build arg | ||
if: always() | ||
|
@@ -65,43 +89,51 @@ jobs: | |
key: ${{ secrets.REVIEW_APP_SSH_PRIVATE_KEY }} | ||
port: 22 | ||
script: | | ||
docker-options:add deploy-preview-${{ github.event.pull_request.number }}--${{ matrix.site }} build "--build-arg VITE_SITE_ID=${{ matrix.site }}" | ||
docker-options:add deploy-preview-${{ env.PR_NUMBER }}--${{ env.SITE_ID }} build "--build-arg VITE_SITE_ID=${{ env.SITE_ID }}" | ||
|
||
- name: Push to dokku | ||
if: github.event.action != 'closed' | ||
if: github.event_name == 'workflow_dispatch' || github.event.action != 'closed' | ||
uses: dokku/github-action@master | ||
with: | ||
git_remote_url: ${{ secrets.REVIEW_APP_SSH_URL }} | ||
review_app_name: deploy-preview-${{ github.event.pull_request.number }}--${{ matrix.site }} | ||
review_app_name: deploy-preview-${{ env.PR_NUMBER }}--${{ env.SITE_ID }} | ||
ssh_private_key: ${{ secrets.REVIEW_APP_SSH_PRIVATE_KEY }} | ||
git_push_flags: '--force' | ||
branch: 'main' | ||
|
||
- name: Enable SSL with Let's Encrypt | ||
if: github.event.action == 'opened' || github.event.action == 'reopened' | ||
if: github.event_name == 'workflow_dispatch' || github.event.action == 'opened' || github.event.action == 'reopened' | ||
uses: appleboy/[email protected] | ||
with: | ||
host: ${{ secrets.REVIEW_APP_SSH_HOST }} | ||
username: dokku | ||
key: ${{ secrets.REVIEW_APP_SSH_PRIVATE_KEY }} | ||
port: 22 | ||
script: | | ||
letsencrypt:enable deploy-preview-${{ github.event.pull_request.number }}--${{ matrix.site }} | ||
letsencrypt:enable deploy-preview-${{ env.PR_NUMBER }}--${{ env.SITE_ID }} | ||
|
||
- name: Destroy the review app | ||
if: github.event.action == 'closed' | ||
uses: dokku/github-action@master | ||
- name: Extract available sites | ||
uses: mikefarah/yq@de2f77b49cbd40fd67031ee602245d0acc4ac482 | ||
id: get-sites | ||
with: | ||
command: review-apps:destroy | ||
git_remote_url: ${{ secrets.REVIEW_APP_SSH_URL }} | ||
review_app_name: deploy-preview-${{ github.event.pull_request.number }}--${{ matrix.site }} | ||
ssh_private_key: ${{ secrets.REVIEW_APP_SSH_PRIVATE_KEY }} | ||
cmd: yq '.on.workflow_dispatch.inputs.site.options[]' '.github/workflows/review-app.yml' | ||
|
||
- name: Destroy all review apps | ||
if: github.event.action == 'closed' | ||
run: | | ||
echo "${{ steps.get-sites.outputs.result }}" | while read site; do | ||
echo "Destroying review app for site: $site" | ||
ssh -i <(echo "${{ secrets.REVIEW_APP_SSH_PRIVATE_KEY }}") \ | ||
-o StrictHostKeyChecking=no \ | ||
dokku@${{ secrets.REVIEW_APP_SSH_HOST }} \ | ||
apps:destroy deploy-preview-${{ env.PR_NUMBER }}--$site || true | ||
done | ||
|
||
- name: Update deployment status | ||
if: github.event.action != 'closed' | ||
if: github.event_name == 'workflow_dispatch' || github.event.action != 'closed' | ||
uses: chrnorm/deployment-status@v2 | ||
with: | ||
token: ${{ github.token }} | ||
environment-url: https://deploy-preview-${{ github.event.pull_request.number }}--${{ matrix.site }}.sandbox.data.developpement-durable.gouv.fr | ||
environment-url: https://deploy-preview-${{ env.PR_NUMBER }}--${{ env.SITE_ID }}.sandbox.data.developpement-durable.gouv.fr | ||
state: ${{ job.status == 'success' && 'success' || 'failure' }} | ||
deployment-id: ${{ steps.deployment.outputs.deployment_id }} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIUC, we'd now only deploy "ecospheres" by default, and all other sites would require
/deploy
. I'm tempted to have a list of defaults sites to deploy instead of just "ecospheres", but maybe not now...