Skip to content

Commit 176aa38

Browse files
authored
Minimize PR overhead for non-code changes (#16279)
* Minimize PR overhead for non-code changes * Replace Tugboat webhook integration with Tugboat API in GHA workflows * Fail workflow step if Tugboat API errors * Remove redundant checkout in workflow * Add a Preview ID refresher workflow to prevent preview IDs from being lost * Increase cache refresh frequency * Add contact details when workflow fails * Use Github Script instead of GH CLI
1 parent b5fa891 commit 176aa38

8 files changed

+309
-45
lines changed

.github/workflows/continuous_integration.yml

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
name: Continuous Integration
2-
on: [pull_request]
2+
on:
3+
pull_request:
4+
paths-ignore:
5+
- '**.md'
36
permissions:
47
pull-requests: write
58
issues: write
+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
name: Set Test Statuses
2+
on:
3+
- pull_request_target
4+
permissions:
5+
pull-requests: write
6+
checks: write
7+
contents: write
8+
statuses: write
9+
jobs:
10+
# Tugboat tests are not automatically set pending, even though they are
11+
# required in branch protection rules (see #10553).
12+
#
13+
# Therefore, a PR can inappropriately appear to be ready to merge if,
14+
# for instance, a composer.lock merge conflict prevents the Tugboat
15+
# preview from successfully building.
16+
#
17+
# Additionally, CI tests are only run for code changes but they are
18+
# required checks, even for documentation only changes. In these cases,
19+
# the tests should be skipped since no functional changes have occured.
20+
#
21+
# To address these two issues, this action sets check statuses directly
22+
# to the appropriate states:
23+
# - For docs only changes, all required checks are set to 'success'
24+
# - For code changes, Tugboat tests are set to 'pending' so that we can
25+
# trust our automated code review processes more.
26+
set-test-statuses:
27+
name: Set Tests Statuses
28+
runs-on: ubuntu-latest
29+
steps:
30+
- name: Check for documentation only changes
31+
id: docs-only
32+
uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6.4.1
33+
with:
34+
script: |
35+
const opts = github.rest.pulls.listFiles.endpoint.merge({
36+
owner: context.repo.owner,
37+
repo: context.repo.repo,
38+
pull_number: context.payload.pull_request.number,
39+
})
40+
const files = await github.paginate(
41+
opts,
42+
(response) => response.data.map(
43+
(file) => file.filename
44+
)
45+
)
46+
47+
for (const file of files) {
48+
console.log(`Checking PR file: ${file}`)
49+
if (!file.endsWith('.md')) {
50+
console.log(`Code change found in: ${file}`)
51+
return "false"
52+
}
53+
}
54+
55+
console.log(`No code change found.`)
56+
return "true"
57+
result-encoding: string
58+
- name: Set status for documentation changes.
59+
if: ${{ steps.docs-only.outputs.result == 'true' }}
60+
run: |
61+
test_names=(
62+
va/tests/cypress
63+
va/tests/phpunit
64+
va/tests/content-build-gql
65+
va/tests/status-error
66+
'Composer Validate'
67+
'Check Fields'
68+
ESLint
69+
Stylelint
70+
PHPStan
71+
PHPUnit
72+
PHP_CodeSniffer
73+
'PHP Lint'
74+
)
75+
for test_name in "${test_names[@]}"; do
76+
gh api \
77+
--method POST \
78+
-H "Accept: application/vnd.github+json" \
79+
"/repos/${GITHUB_REPOSITORY}/statuses/${SHA}" \
80+
-f state='success' \
81+
-f context="${test_name}";
82+
done;
83+
env:
84+
SHA: ${{ github.event.pull_request.head.sha }}
85+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
86+
- name: Set status for code changes.
87+
if: ${{ steps.docs-only.outputs.result == 'false' }}
88+
run: |
89+
test_names=(
90+
va/tests/cypress
91+
va/tests/phpunit
92+
va/tests/content-build-gql
93+
va/tests/status-error
94+
)
95+
for test_name in "${test_names[@]}"; do
96+
gh api \
97+
--method POST \
98+
-H "Accept: application/vnd.github+json" \
99+
"/repos/${GITHUB_REPOSITORY}/statuses/${SHA}" \
100+
-f state='pending' \
101+
-f context="${test_name}";
102+
done;
103+
env:
104+
SHA: ${{ github.event.pull_request.head.sha }}
105+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

.github/workflows/set-tugboat-tests-pending.yml

-43
This file was deleted.
+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
name: Delete Tugboat Preview
2+
on:
3+
pull_request:
4+
types:
5+
- closed
6+
paths-ignore:
7+
- '**.md'
8+
9+
jobs:
10+
tugboat_delete_preview:
11+
runs-on: self-hosted
12+
env:
13+
NODE_EXTRA_CA_CERTS: /etc/ssl/certs/ca-certificates.crt
14+
name: Delete Tugboat Preview
15+
steps:
16+
- name: Restore Preview ID
17+
uses: actions/cache/restore@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
18+
with:
19+
path: .tugboat_preview.txt
20+
key: ${{ runner.os }}-tugboat-preview-id-pr-${{ github.event.pull_request.number }}
21+
- name: Set Preview ID
22+
run: |
23+
if ! [ -f .tugboat_preview.txt ]; then
24+
echo "Preview ID not found, please manually delete Tugboat Preview. Contact platform-cms-qa on Github or CMS QA Engineers in #cms-support on Slack for assistance."
25+
exit 1
26+
fi
27+
PREVIEW_ID=$(cat .tugboat_preview.txt)
28+
echo "Preview ID: ${PREVIEW_ID}"
29+
echo "PREVIEW_ID=$PREVIEW_ID" >> $GITHUB_ENV
30+
- name: Cleanup temporary file
31+
run: rm .tugboat_preview.txt
32+
- name: Delete Tugboat Preview
33+
run: |
34+
curl --fail \
35+
-H "Authorization: Bearer ${{ secrets.TUGBOAT_API_TOKEN }}" \
36+
-H "Content-Type: application/json" \
37+
-X DELETE \
38+
-d '{ "force": "false" }' \
39+
https://api.tugboat.vfs.va.gov/v3/previews/${{ env.PREVIEW_ID }}
+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
name: Create Tugboat Preview
2+
on:
3+
pull_request:
4+
types:
5+
- opened
6+
- reopened
7+
paths-ignore:
8+
- '**.md'
9+
10+
jobs:
11+
tugboat_create_preview:
12+
runs-on: self-hosted
13+
env:
14+
NODE_EXTRA_CA_CERTS: /etc/ssl/certs/ca-certificates.crt
15+
name: Create Tugboat Preview
16+
steps:
17+
- name: Create Tugboat Preview
18+
id: tugboat_pr_preview
19+
run: |
20+
curl --fail \
21+
-H "Authorization: Bearer ${{ secrets.TUGBOAT_API_TOKEN }}" \
22+
-H "Content-Type: application/json" \
23+
-X POST \
24+
-d '{ "repo": "${{ secrets.TUGBOAT_REPOSITORY }}", "ref": "${{ github.event.pull_request.number }}", "name": "${{ github.event.pull_request.title }}", "type": "pullrequest" }' \
25+
-o .tugboat_response.json \
26+
https://api.tugboat.vfs.va.gov/v3/previews
27+
- name: Diagnostics
28+
run: cat .tugboat_response.json
29+
- name: Extract Preview ID
30+
run: jq -r .preview .tugboat_response.json > .tugboat_preview.txt
31+
- name: Delete Previous Preview ID
32+
continue-on-error: true
33+
uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6.4.1
34+
with:
35+
script: |
36+
await github.rest.actions.deleteActionsCacheByKey({
37+
owner: context.repo.owner,
38+
repo: context.repo.repo,
39+
key: `${{ runner.os }}-tugboat-preview-id-pr-${{ github.event.pull_request.number }}`,
40+
});
41+
- name: Save Preview ID
42+
uses: actions/cache/save@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
43+
with:
44+
path: .tugboat_preview.txt
45+
key: ${{ runner.os }}-tugboat-preview-id-pr-${{ github.event.pull_request.number }}
46+
- name: Cleanup temporary file
47+
run: rm .tugboat_preview.txt
+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
name: Rebuild Tugboat Preview
2+
on:
3+
pull_request:
4+
types:
5+
- synchronize
6+
paths-ignore:
7+
- '**.md'
8+
9+
jobs:
10+
tugboat_rebuild_preview:
11+
runs-on: self-hosted
12+
env:
13+
NODE_EXTRA_CA_CERTS: /etc/ssl/certs/ca-certificates.crt
14+
name: Rebuild Tugboat Preview
15+
steps:
16+
- name: Restore Preview ID
17+
uses: actions/cache/restore@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
18+
with:
19+
path: .tugboat_preview.txt
20+
key: ${{ runner.os }}-tugboat-preview-id-pr-${{ github.event.pull_request.number }}
21+
- name: Set Preview ID
22+
run: |
23+
if ! [ -f .tugboat_preview.txt ]; then
24+
echo "Preview ID not found, please manually rebuild Tugboat Preview. Contact platform-cms-qa on Github or CMS QA Engineers in #cms-support on Slack for assistance."
25+
exit 1
26+
fi
27+
PREVIEW_ID=$(cat .tugboat_preview.txt)
28+
echo "Preview ID: ${PREVIEW_ID}"
29+
echo "PREVIEW_ID=$PREVIEW_ID" >> $GITHUB_ENV
30+
- name: Cleanup temporary file
31+
run: rm .tugboat_preview.txt
32+
- name: Rebuild Tugboat Preview
33+
run: |
34+
curl --fail \
35+
-H "Authorization: Bearer ${{ secrets.TUGBOAT_API_TOKEN }}" \
36+
-H "Content-Type: application/json" \
37+
-X POST \
38+
-d '{ "children": "false", "force": "false" }' \
39+
https://api.tugboat.vfs.va.gov/v3/previews/${{ env.PREVIEW_ID }}/rebuild
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
name: Refresh Tugboat Preview ID Cache
2+
on:
3+
# Every 6 hours.
4+
schedule:
5+
- cron: '0 */6 * * *'
6+
jobs:
7+
# Collects the cache keys that need to be refreshed
8+
collect_cache_keys:
9+
name: Collect Tugboat Preview ID cache keys that need to be refreshed
10+
outputs:
11+
matrix: ${{ steps.cache-keys.outputs.result }}
12+
runs-on: ubuntu-latest
13+
steps:
14+
- name: Cross reference open PRs against cache keys in repo
15+
id: cache-keys
16+
uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6.4.1
17+
with:
18+
script: |
19+
const prs = await github.paginate(
20+
github.rest.pulls.list,
21+
{
22+
owner: context.repo.owner,
23+
repo: context.repo.repo,
24+
state: 'open',
25+
},
26+
(response) => response.data.map((pr) => pr.number)
27+
)
28+
29+
for (const pr of prs) {
30+
console.log(`PR: ${pr}`)
31+
}
32+
33+
const cacheKeys = await github.paginate(
34+
github.rest.actions.getActionsCacheList,
35+
{
36+
owner: context.repo.owner,
37+
repo: context.repo.repo,
38+
},
39+
(response) => response.data.map((cache) => cache.key)
40+
)
41+
42+
for (const key of cacheKeys) {
43+
console.log(`Key: ${key}`)
44+
}
45+
46+
const toRefresh = []
47+
for (const pr of prs) {
48+
if (cacheKeys.includes(`${{ runner.os }}-tugboat-preview-id-pr-${pr}`)) {
49+
console.log(`Need to refresh: ${pr}`)
50+
toRefresh.push(pr)
51+
}
52+
}
53+
54+
const result = JSON.stringify(toRefresh)
55+
console.log(`Refresh Keys: ${result}`)
56+
return result
57+
result-encoding: string
58+
59+
# Refresh cache for given keys
60+
refresh_cache:
61+
name: Refresh cache for given keys
62+
needs: [ collect_cache_keys ]
63+
runs-on: ubuntu-latest
64+
strategy:
65+
matrix:
66+
value: ${{fromJSON(needs.collect_cache_keys.outputs.matrix)}}
67+
steps:
68+
- name: Refresh Preview ID
69+
uses: actions/cache/restore@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
70+
with:
71+
path: .tugboat_preview.txt
72+
key: ${{ runner.os }}-tugboat-preview-id-pr-${{ matrix.value }}
73+
- name: Cleanup temporary file
74+
run: rm .tugboat_preview.txt

tests.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ output: 'group'
1010
tasks:
1111

1212
# Any changes to test names or additions or removals must be updated in
13-
# .github/workflows/set-tugboat-tests-pending.yml as well for the
13+
# .github/workflows/set-tests-statuses.yml as well for the
1414
# test to be required effectively.
1515

1616
# The following is necessary to ensure that the tests are set to "pending"

0 commit comments

Comments
 (0)