1
0
mirror of synced 2025-12-19 18:10:59 -05:00

Auto codespace reviews (#54675)

This commit is contained in:
Kevin Heis
2025-03-05 10:14:17 -08:00
committed by GitHub
parent 70f09d91d0
commit 2b462c8894
5 changed files with 481 additions and 23 deletions

View File

@@ -0,0 +1,126 @@
name: Codespace review - Check
# **What it does**: Check on a regular basis for if a codespace is about to shut down, and comment on the pull request.
# **Why we have it**: We want to notify contributors when their codespace is about to shut down.
# **Who does it impact**: Contributors who open a pull request.
on:
schedule:
- cron: '20,35,50,5 * * * *' # Check every 15 minutes, without hitting the top of the hour
pull_request:
paths:
- '.github/workflows/codespace-review-check.yml'
workflow_dispatch:
permissions:
contents: read
pull-requests: write
jobs:
codespace-review-check-find:
runs-on: ubuntu-latest
if: ${{ github.repository == 'github/docs-internal' }}
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Check codespaces
id: set-matrix
env:
GH_TOKEN: ${{ secrets.DOCS_BOT_PAT_CODESPACE }}
LOGIN: docs-bot
REPO: github/docs-internal
run: |
ago=$(date -d '225 minutes ago' -Iseconds)
echo "- Ago: $ago"
# on mac: date -v-225M -Iseconds
# -v-225M means 225 minutes ago, 4 * 60 - 15 = 225
# -Iseconds means ISO 8601 format, to seconds
branches=$(
gh codespace list \
--repo "$REPO" \
--limit 1000 \
--json name,owner,lastUsedAt,gitStatus \
--jq ".[] | select(.owner == \"$LOGIN\" and .lastUsedAt < \"$ago\") | .gitStatus.ref" \
)
echo "- Branches:"
echo "$(echo "$branches" | sed 's/^/ /')"
count=$(echo "$branches" | sed '/^\s*$/d' | wc -l)
echo "- Count: $count"
if [[ $count -gt 0 ]]
then
echo "Codespaces found that are idle or soon to idle"
else
echo "Codespaces not found, exiting..."
exit 0
fi
# https://stackoverflow.com/a/70716837
# This might not need `| sed 's/"/\\"/g'`
matrix=$(echo "$branches" | jq -scR 'split("\n") | map(select(. != ""))' | sed 's/"/\\"/g')
echo "- Matrix: $matrix"
echo "matrix=$matrix" >> $GITHUB_OUTPUT
- uses: ./.github/actions/slack-alert
if: ${{ failure() && github.event_name != 'workflow_dispatch' }}
with:
slack_channel_id: ${{ secrets.DOCS_ALERTS_SLACK_CHANNEL_ID }}
slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }}
codespace-review-check-comment:
needs:
- codespace-review-check-find
strategy:
matrix:
value: ${{ fromJSON(needs.codespace-review-check-find.outputs.matrix) }}
runs-on: ubuntu-latest
if: ${{ github.repository == 'github/docs-internal' }}
env:
repo: github/docs-internal
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Find the pull request
id: findPr
run: |
echo "Looking up pull request"
echo "- Branch: ${{ matrix.value }}"
number=$(gh pr view "${{ matrix.value }}" --json number --jq '.number')
echo "- Number: $number"
echo "pr-number=$number" >> $GITHUB_OUTPUT
- name: Find code changes comment
uses: peter-evans/find-comment@3eae4d37986fb5a8592848f6a574fdf654e61f9e
id: findComment
with:
issue-number: ${{ steps.findPr.outputs.pr-number }}
comment-author: 'github-actions[bot]'
body-includes: '<!-- AUTO_CODESPACE -->'
- name: Update comment
uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043
with:
comment-id: ${{ steps.findComment.outputs.comment-id }}
issue-number: ${{ steps.findPr.outputs.pr-number }}
edit-mode: replace
body: |
<!-- AUTO_CODESPACE -->
### Review this PR in a codespace 📦
Your codespace is no longer active.
Youve reached the 4 hour limit.
In order to reactivate your codespace, please update your pull request by adding the https://github.com/${{ env.REPO }}/labels/extend-codespace label.
If the label is already applied, you can remove and reapply the label to reactivate your codespace.
🤖 This comment is [automatically generated][workflow].
[workflow]: ${{ github.server_url }}/${{ github.repository }}/blob/${{ github.sha }}/.github/workflows/codespace-review-check.yml
- uses: ./.github/actions/slack-alert
if: ${{ failure() && github.event_name != 'workflow_dispatch' }}
with:
slack_channel_id: ${{ secrets.DOCS_ALERTS_SLACK_CHANNEL_ID }}
slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }}

View File

@@ -0,0 +1,84 @@
name: Codespace review - Down
# **What it does**: When closing or merging a pull request, if there are any associated codespaces, to shut them down.
# **Why we have it**: To conserve resources.
# **Who does it impact**: Contributors who open a pull request.
on:
pull_request:
types:
- closed
workflow_dispatch:
permissions:
contents: read
pull-requests: write
jobs:
codespace-review-down:
runs-on: ubuntu-latest
if: ${{ github.repository == 'github/docs-internal' }}
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Delete codespace
env:
GH_TOKEN: ${{ secrets.DOCS_BOT_PAT_CODESPACE }}
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
LOGIN: docs-bot
REPO: github/docs-internal
run: |
echo "Checking if there's codespaces for this PR..."
names=$( \
gh codespace list \
--repo "$REPO" \
--limit 1000 \
--json "name,gitStatus,owner" \
--jq ".[] | select(.owner == \"$LOGIN\" and .gitStatus.ref == \"$BRANCH_NAME\") | .name" \
)
echo "- Names:"
echo "$(echo "$names" | sed 's/^/ /')"
count=$(echo "$names" | sed '/^\s*$/d' | wc -l)
echo "- Count: $count"
if [[ $count -gt 0 ]]
then
echo "Codespaces found for this PR"
else
echo "Codespaces not found, exiting..."
exit 0
fi
echo "Shutting down the codespaces..."
echo "$names" | while read -r name
do
echo "Deleting $name..."
gh codespace delete --codespace "$name"
echo "Deleted $name"
done
echo "Shut down the codespaces"
- name: Find code changes comment
uses: peter-evans/find-comment@3eae4d37986fb5a8592848f6a574fdf654e61f9e
id: findComment
with:
issue-number: ${{ github.event.pull_request.number }}
comment-author: 'github-actions[bot]'
body-includes: '<!-- AUTO_CODESPACE -->'
- name: Update comment
uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043
if: ${{ steps.findComment.outputs.comment-id }} # only update if it exists
with:
comment-id: ${{ steps.findComment.outputs.comment-id }}
issue-number: ${{ github.event.pull_request.number }}
edit-mode: replace
body: |
<!-- AUTO_CODESPACE -->
### Review this PR in a codespace 📦
Your pull request is now merged or closed, so I've removed all automatically created codespaces.
🤖 This comment is [automatically generated][workflow].
[workflow]: ${{ github.server_url }}/${{ github.repository }}/blob/${{ github.sha }}/.github/workflows/codespace-review-down.yml

View File

@@ -0,0 +1,233 @@
name: Codespace review - Up
# **What it does**: On opening or updating a pull request, creates a new codespace to review changes and comments to the user with next steps. Or it will rebuild the codespace if it already exists and is idle.
# **Why we have it**: We want to provide contributors with a way to review their changes in a codespace before merging.
# **Who does it impact**: Contributors who open a pull request.
on:
pull_request:
types:
- assigned
- unassigned
- labeled
- unlabeled
- opened
- edited
# - closed
- reopened
- synchronize
- converted_to_draft
- ready_for_review
# - locked
# - unlocked
# - milestoned
# - demilestoned
- review_requested
- review_request_removed
# - auto_merge_enabled
- auto_merge_disabled
# - enqueued
- dequeued
workflow_dispatch:
permissions:
contents: read
pull-requests: write
jobs:
codespace-review-up:
runs-on: ubuntu-latest
if: >-
${{ github.repository == 'github/docs-internal'
&& contains( github.event.pull_request.labels.*.name, 'auto-codespace') }}
env:
GH_TOKEN: ${{ secrets.DOCS_BOT_PAT_CODESPACE }}
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
LOGIN: docs-bot
REPO: github/docs-internal
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Check for existing codespace
id: check-codespace
run: |
echo "Checking if there's already a codespace for this pull request..."
names=$( \
gh codespace list \
--repo "$REPO" \
--limit 1000 \
--json "name,gitStatus,owner" \
--jq ".[] | select(.owner == \"$LOGIN\" and .gitStatus.ref == \"$BRANCH_NAME\") | .name" \
)
echo "- Names:"
echo "$(echo "$names" | sed 's/^/ /')"
count=$(echo "$names" | sed '/^\s*$/d' | wc -l)
echo "- Count: $count"
if [[ $count -gt 0 ]]
then
echo "Codespace found for this pull request"
echo "has-codespace=yes" >> $GITHUB_OUTPUT
else
echo "Codespace not found for this pull request"
echo "has-codespace=no" >> $GITHUB_OUTPUT
fi
- name: Clean up old codespaces if needed
if: ${{ steps.check-codespace.outputs.has-codespace == 'no' }}
run: |
echo "Checking if there are more than 95 codespaces..."
spaces=$( \
gh codespace list \
--repo "$REPO" \
--limit 1000 \
--json "name,lastUsedAt,gitStatus,owner" \
--jq "sort_by(.lastUsedAt) | reverse | .[] | select(.owner == \"$LOGIN\") | [.name,.gitStatus.ref] | @tsv" \
)
echo "- Spaces:"
echo "$(echo "$spaces" | sed 's/^/ /')"
count=$(echo "$spaces" | sed '/^\s*$/d' | wc -l)
echo "- Count: $count"
if [[ $count -gt 95 ]]
then
tocut=$((count - 95))
echo "$count codespaces found. Deleting the oldest $tocut..."
oldest=$(echo "$spaces" | tail -n $tocut)
echo "- Oldest:"
echo "$(echo "$oldest" | sed 's/^/ /')"
echo "$oldest" | while read -r name branch
do
echo "Deleting $name..."
gh codespace delete --codespace "$name"
echo "Deleted $name"
echo "Commenting on branch $branch"
# We could move this to a matrix and update the AUTO_CODESPACE comment instead
# but that's significantly more code for a scenario I'm not sure will actually happen
gh pr comment \
"$branch" \
--repo "$REPO" \
--body "Thank you for your pull request. I deleted the oldest codespaces to make room for a new one. You can make a new codespace by updating your pull request or closing and reopening your pull request."
echo "Commented on branch $branch"
done
echo "Deleted the oldest $tocut codespaces"
else
echo "$count codespaces found. No deletes needed."
fi
- name: Create a new codespace
if: ${{ steps.check-codespace.outputs.has-codespace == 'no' }}
run: |
echo "Creating a new codespace..."
# Machine types: gh api /repos/github/docs-internal/codespaces/machines
name=$( \
gh codespace create \
--repo "$REPO" \
--branch "$BRANCH_NAME" \
--idle-timeout "4h" \
--retention-period "720h" \
--default-permissions \
--machine "standardLinux32gb" \
)
echo "- Name: $name"
echo "Created a new codespace"
echo "Updating port visibility..."
gh codespace ports visibility \
--codespace "$name" \
4000:public
echo "Updated port visibility"
echo "APP_URL=https://$name-4000.app.github.dev" >> $GITHUB_ENV
- name: Rebuild existing codespace
if: ${{ steps.check-codespace.outputs.has-codespace == 'yes' }}
run: |
echo "Checking if the codespace is in idle mode..."
spaces=$( \
gh codespace list \
--repo "$REPO" \
--limit 1000 \
--json "name,gitStatus,owner,state" \
--jq ".[] | select(.owner == \"$LOGIN\" and .gitStatus.ref == \"$BRANCH_NAME\") | [.name,.state] | @tsv" \
)
echo "- Spaces:"
echo "$(echo "$spaces" | sed 's/^/ /')"
echo "$spaces" | while read -r name state
do
echo "Codespace $name is in state $state"
if [[ $state == "Shutdown" ]]
then
echo "Codespace $name is in idle mode"
echo "Rebuilding the codespace to kick it out of idle mode..."
gh codespace rebuild --codespace "$name"
echo "Rebuilt the codespace to kick it out of idle mode"
echo "Updating port visibility..."
gh codespace ports visibility \
--codespace "$name" \
4000:public
echo "Updated port visibility"
else
echo "Codespace $name is active"
fi
echo "APP_URL=https://$name-4000.app.github.dev" >> $GITHUB_ENV
done
- uses: ./.github/actions/node-npm-setup
- name: Get changes table
id: changes
timeout-minutes: 30
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
APP_URL: ${{ env.APP_URL }}
run: npm run content-changes-table-comment
- name: Find code changes comment
uses: peter-evans/find-comment@3eae4d37986fb5a8592848f6a574fdf654e61f9e
id: findComment
with:
issue-number: ${{ github.event.pull_request.number }}
comment-author: 'github-actions[bot]'
body-includes: '<!-- AUTO_CODESPACE -->'
- name: Update comment
uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043
with:
comment-id: ${{ steps.findComment.outputs.comment-id }}
issue-number: ${{ github.event.pull_request.number }}
edit-mode: replace
body: |
<!-- AUTO_CODESPACE -->
### Review this PR in a codespace 📦
Your codespace will be ready in two to three minutes and you can review changes at:
${{ env.APP_URL }}
Your codespace will be automatically deleted once your pull request is closed or merged.
#### Your codespace will idle after 4 hours of inactivity
After 4 hours, you can reactivate your codespace by applying the https://github.com/${{ env.REPO }}/labels/extend-codespace label to the pull request.
If the label is already applied, you can remove and reapply the label to reactivate your codespace.
#### Maximum of 100 _active_ codespaces per user
Your codespace may be deleted if the limit is reached.
<details><summary>Table of review links</summary>
${{ steps.changes.outputs.changesTable && 'The table shows the files in the `content` directory that were changed in this pull request. This helps you review your changes on the review server. Changes to the `data` directory are not included in this table.' || '' }}
${{ steps.changes.outputs.changesTable || '_This pull request contains code changes, so we will not generate a table of review links._' }}
${{ steps.changes.outputs.changesTable && 'Key: **fpt**: Free, Pro, Team; **ghec**: GitHub Enterprise Cloud; **ghes**: GitHub Enterprise Server' || '' }}
</details>
🤖 This comment is [automatically generated][workflow].
[workflow]: ${{ github.server_url }}/${{ github.repository }}/blob/${{ github.sha }}/.github/workflows/codespace-review-up.yml

View File

@@ -26,7 +26,7 @@ permissions:
# This allows a subsequently queued workflow run to interrupt previous runs
concurrency:
group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}'
group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }} x ${{ github.event_name }}'
cancel-in-progress: true
jobs:
@@ -60,6 +60,7 @@ jobs:
timeout-minutes: 30
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REVIEW_SERVER: o
REVIEW_SERVER_ACCESS_TOKEN: ${{ secrets.REVIEW_SERVER_ACCESS_TOKEN }}
APP_URL: ${{ env.APP_URL }}
BASE_SHA: ${{ github.event.pull_request.base.sha }}
@@ -71,7 +72,7 @@ jobs:
uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043
with:
comment-id: ${{ steps.findComment.outputs.comment-id }}
issue-number: ${{ github.event.pull_request.number || inputs.PR_NUMBER }}
issue-number: ${{ github.event.pull_request.number }}
edit-mode: replace
body: |
<!-- REVIEW_COMMENT -->
@@ -83,10 +84,7 @@ jobs:
* [Set up a local development environment][local]
${{ github.repository == 'github/docs-internal' && '* Message `#docs-engineering` on Slack for a staging server.' || '' }}
[codespace]: ${{ github.repository == 'github/docs' && 'https://docs.github.com/en/contributing/setting-up-your-environment-to-work-on-github-docs/working-on-github-docs-in-a-codespace' || 'https://github.com/github/docs-team/blob/main/contributing-to-docs/use-a-codespace-to-review.md' }}
[local]: https://docs.github.com/en/contributing/setting-up-your-environment-to-work-on-github-docs/creating-a-local-environment#setting-up-your-local-environment
${{ github.repository == 'github/docs' && 'A Hubber will need to deploy your changes internally to review.' || '' }}
${{ fromJSON('["A Hubber will need to deploy your changes internally to review.",""]')[github.repository == 'github/docs-internal'] }}
<details><summary>Table of review links</summary>
@@ -100,4 +98,8 @@ jobs:
</details>
🤖 This comment is [automatically generated](https://github.com/${{ github.repository }}/blob/${{ github.sha }}/.github/workflows/review-comment.yml).
🤖 This comment is [automatically generated][workflow].
[workflow]: ${{ github.server_url }}/${{ github.repository }}/blob/${{ github.sha }}/.github/workflows/review-comment.yml
[codespace]: ${{ github.repository == 'github/docs-internal' && 'https://github.com/github/docs-team/blob/main/contributing-to-docs/use-a-codespace-to-review.md' || 'https://docs.github.com/en/contributing/setting-up-your-environment-to-work-on-github-docs/working-on-github-docs-in-a-codespace' }}
[local]: https://docs.github.com/en/contributing/setting-up-your-environment-to-work-on-github-docs/creating-a-local-environment#setting-up-your-local-environment

View File

@@ -24,7 +24,15 @@ import { allVersionShortnames } from '@/versions/lib/all-versions.js'
import readFrontmatter from '@/frame/lib/read-frontmatter.js'
import { inLiquid } from './lib/in-liquid'
const { GITHUB_TOKEN, REVIEW_SERVER_ACCESS_TOKEN, APP_URL } = process.env
const {
GITHUB_TOKEN,
REVIEW_SERVER,
REVIEW_SERVER_ACCESS_TOKEN,
APP_URL,
HEAD_BRANCH,
BASE_SHA,
HEAD_SHA,
} = process.env
const context = github.context
// the max size of the comment (in bytes)
@@ -46,8 +54,8 @@ if (import.meta.url.endsWith(process.argv[1])) {
const baseOwner = context.payload.pull_request!.base.repo.owner.login
const baseRepo = context.payload.pull_request!.base.repo.name
const baseSHA = process.env.BASE_SHA || context.payload.pull_request!.base.sha
const headSHA = process.env.HEAD_SHA || context.payload.pull_request!.head.sha
const baseSHA = BASE_SHA || context.payload.pull_request!.base.sha
const headSHA = HEAD_SHA || context.payload.pull_request!.head.sha
const markdownTable = await main(baseOwner, baseRepo, baseSHA, headSHA, {
isFork,
@@ -70,7 +78,6 @@ async function main(
if (!APP_URL) {
throw new Error(`APP_URL environment variable not set`)
}
const headBranch = process.env.HEAD_BRANCH
const RetryingOctokit = Octokit.plugin(retry)
const octokit = new RetryingOctokit({
@@ -78,21 +85,27 @@ async function main(
})
// we'll attach the branch or sha right after this
const searchParams = new URLSearchParams({
'review-server-repository': isFork ? `${headOwner}/${headRepo}` : `${owner}/${repo}`,
})
let queryParams = ''
if (REVIEW_SERVER) {
const searchParams = new URLSearchParams({
'review-server-repository': isFork ? `${headOwner}/${headRepo}` : `${owner}/${repo}`,
})
// this token will be available in the internal repo only, skip it for the open source repo
if (REVIEW_SERVER_ACCESS_TOKEN)
searchParams.append('review-server-access-token', REVIEW_SERVER_ACCESS_TOKEN)
// this token will be available in the internal repo only, skip it for the open source repo
if (REVIEW_SERVER_ACCESS_TOKEN) {
searchParams.append('review-server-access-token', REVIEW_SERVER_ACCESS_TOKEN)
}
// this script compares with SHAs only, so this allows us
// to surface the branch name for the review server bar
headBranch
? searchParams.append('review-server-branch', headBranch)
: searchParams.append('review-server-sha', headSHA)
// this script compares with SHAs only, so this allows us
// to surface the branch name for the review server bar
if (HEAD_BRANCH) {
searchParams.append('review-server-branch', HEAD_BRANCH)
} else if (headSHA) {
searchParams.append('review-server-sha', headSHA)
}
const queryParams = `?${searchParams.toString()}`
queryParams = `?${searchParams.toString()}`
}
// get the list of file changes from the PR
// this works even if the head commit is from a fork