Skip to content

Commit a1ca0d5

Browse files
authored
Merge pull request #557 from product-os/kyle/dispatch-e2e
Change e2e tests to an optional dispatch job
2 parents 81d1b5b + 73da089 commit a1ca0d5

File tree

7 files changed

+387
-253
lines changed

7 files changed

+387
-253
lines changed

.github/workflows/e2e.yml

Lines changed: 301 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,301 @@
1+
# https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#workflow_dispatch
2+
name: e2e
3+
4+
on:
5+
workflow_dispatch:
6+
inputs:
7+
repositories:
8+
description: Comma-separated repositories to patch
9+
required: false
10+
type: string
11+
default: |
12+
balena-io-modules/open-balena-base,
13+
balena-io/balena-api,
14+
balena-io/docs,
15+
balena-io/environment-staging,
16+
balena-os/balena-engine,
17+
balena-os/fatrw,
18+
product-os/environment-staging
19+
flowzone_ref:
20+
description: Flowzone branch, or tag, or commit SHA
21+
required: false
22+
type: string
23+
default: master
24+
required_checks:
25+
description: Comma-separated list of required successful checks
26+
required: false
27+
type: string
28+
default: |
29+
Flowzone / All tests,
30+
Flowzone / All jobs
31+
dry_run:
32+
description: Patch files but do not push changes
33+
required: false
34+
type: boolean
35+
default: false
36+
auto_close:
37+
description: Close the pull requests at the end of the run
38+
required: false
39+
type: boolean
40+
default: true
41+
token_app_id:
42+
description: GitHub App id to request a temporary token
43+
type: string
44+
required: false
45+
# https://github.com/organizations/product-os/settings/apps/flowzone-app
46+
default: "291899"
47+
token_installation_id:
48+
description: GitHub App installation id to request a temporary token
49+
type: string
50+
required: false
51+
default: ""
52+
53+
# https://docs.github.com/en/actions/using-jobs/using-concurrency
54+
concurrency:
55+
group: ${{ github.workflow }}-${{ inputs.flowzone_ref }}
56+
cancel-in-progress: false
57+
58+
jobs:
59+
process_inputs:
60+
name: Process Inputs
61+
runs-on: ubuntu-latest
62+
timeout-minutes: 20
63+
64+
outputs:
65+
matrix: ${{ steps.matrix.outputs.build }}
66+
67+
env:
68+
# https://github.com/organizations/product-os/settings/installations
69+
# https://github.com/organizations/balena-os/settings/installations
70+
# https://github.com/organizations/balena-io/settings/installations
71+
# https://github.com/organizations/balena-io-modules/settings/installations
72+
KNOWN_INSTALLATION_IDS: >
73+
{
74+
"balena-io-modules": 34046903,
75+
"balena-io": 34046749,
76+
"balena-os": 34046907,
77+
"product-os": 34040165
78+
}
79+
80+
steps:
81+
- name: Log GitHub context
82+
env:
83+
GITHUB_CONTEXT: ${{ toJSON(github) }}
84+
run: echo "${GITHUB_CONTEXT}" || true
85+
86+
- name: Process repositories list
87+
id: repositories_csv
88+
env:
89+
INPUT: ${{ inputs.repositories }}
90+
run: |
91+
while IFS=',' read -r item
92+
do
93+
item="$(echo "${item}" | awk '{$1=$1};NF')"
94+
if [ -n "${item}" ]
95+
then
96+
out="${out},${item}"
97+
fi
98+
done <<< "${INPUT}"
99+
echo "build=${out:1}" >> $GITHUB_OUTPUT
100+
101+
# https://github.com/kanga333/json-array-builder
102+
- name: Build JSON array
103+
id: repositories_json
104+
uses: kanga333/json-array-builder@c7cd9d3a8b17cd368e9c2210bc3c16b0e2714ce5 # v0.2.1
105+
with:
106+
str: ${{ steps.repositories_csv.outputs.build }}
107+
separator: ","
108+
109+
- name: Create JSON matrix
110+
id: matrix
111+
env:
112+
REPOS: ${{ steps.repositories_json.outputs.build }}
113+
INSTALLATION_IDS: ${{ env.KNOWN_INSTALLATION_IDS }}
114+
run: |
115+
echo "build=$(jq -cr --argjson installation_ids "${INSTALLATION_IDS}" '{
116+
include: map({
117+
repository: .,
118+
installation_id: $installation_ids[split("/")[0]] | tostring
119+
})
120+
}' <<< "${REPOS}")" >> $GITHUB_OUTPUT
121+
122+
pull_request:
123+
name: Pull Request
124+
runs-on: ubuntu-22.04
125+
timeout-minutes: 90
126+
needs: process_inputs
127+
128+
strategy:
129+
fail-fast: false
130+
matrix: ${{ fromJSON(needs.process_inputs.outputs.matrix) }}
131+
132+
env:
133+
# https://cli.github.com/manual/gh_help_environment
134+
GH_REPO: ${{ matrix.repository }}
135+
GH_PROMPT_DISABLED: "true"
136+
GH_DEBUG: "true"
137+
GH_PAGER: "cat"
138+
139+
PR_BRANCH: dispatch/flowzone-${{ inputs.flowzone_ref }}
140+
PR_TITLE: Test Flowzone @ ${{ inputs.flowzone_ref }}
141+
PR_BODY: |
142+
Auto-generated by https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
143+
PR_LABELS: flowzone,e2e,do-not-merge,dispatch
144+
145+
steps:
146+
# https://github.com/tibdex/github-app-token
147+
- name: Generate GitHub App token
148+
uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92 # v1.8.0
149+
id: gh_token
150+
with:
151+
app_id: ${{ inputs.token_app_id }}
152+
installation_id: ${{ inputs.token_installation_id || matrix.installation_id }}
153+
private_key: ${{ secrets.GH_APP_PRIVATE_KEY }}
154+
repository: ${{ matrix.repository }}
155+
permissions: >-
156+
{
157+
"actions": "read",
158+
"administration": "write",
159+
"checks": "read",
160+
"contents": "write",
161+
"members": "read",
162+
"metadata": "read",
163+
"pull_requests": "write",
164+
"statuses": "read",
165+
"workflows": "write"
166+
}
167+
168+
# https://cli.github.com/manual/gh_api
169+
- name: Get repository settings
170+
id: repo
171+
env:
172+
GH_TOKEN: ${{ steps.gh_token.outputs.token }}
173+
run: |
174+
echo "default_branch=$(gh api repos/{owner}/{repo} --jq '.default_branch')" >> $GITHUB_OUTPUT
175+
176+
# https://github.com/actions/checkout
177+
- name: Checkout base branch
178+
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3
179+
with:
180+
repository: ${{ matrix.repository }}
181+
token: ${{ steps.gh_token.outputs.token }}
182+
ref: ${{ steps.repo.outputs.default_branch }}
183+
184+
# https://github.com/crazy-max/ghaction-import-gpg
185+
- name: Import GPG key
186+
id: import-gpg
187+
uses: crazy-max/ghaction-import-gpg@111c56156bcc6918c056dbef52164cfa583dc549 # v5.2.0
188+
with:
189+
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
190+
passphrase: ${{ secrets.GPG_PASSPHRASE }}
191+
git_config_global: true
192+
git_user_signingkey: true
193+
git_commit_gpgsign: true
194+
195+
# update flowzone workflow to point to the provided git ref
196+
- name: Update workflow
197+
env:
198+
GIT_AUTHOR_NAME: ${{ steps.import-gpg.outputs.name }}
199+
GIT_AUTHOR_EMAIL: ${{ steps.import-gpg.outputs.email }}
200+
GIT_COMMITTER_NAME: ${{ steps.import-gpg.outputs.name }}
201+
GIT_COMMITTER_EMAIL: ${{ steps.import-gpg.outputs.email }}
202+
WORKFLOW_FILE: .github/workflows/flowzone.yml
203+
run: |
204+
yq '.jobs.flowzone.uses |= sub("(?P<uses>.+)@.+"; "$1@${{ inputs.flowzone_ref }}")' -i "${WORKFLOW_FILE}"
205+
yq '.jobs.flowzone.uses |= . line_comment="${{ github.run_id }}-${{ github.run_attempt }}"' -i "${WORKFLOW_FILE}"
206+
207+
git add "${WORKFLOW_FILE}"
208+
git commit -m "patch: ${PR_TITLE}"
209+
210+
# https://github.com/peter-evans/create-pull-request
211+
- name: Create pull request
212+
uses: peter-evans/create-pull-request@38e0b6e68b4c852a5500a94740f0e535e0d7ba54 # v4.2.4
213+
if: inputs.dry_run != true
214+
id: cpr
215+
with:
216+
token: ${{ steps.gh_token.outputs.token }}
217+
branch: ${{ env.PR_BRANCH }}
218+
title: ${{ env.PR_TITLE }}
219+
body: |
220+
${{ env.PR_BODY }}
221+
labels: |
222+
${{ env.PR_LABELS }}
223+
draft: true
224+
delete-branch: true
225+
226+
- name: Update summary
227+
if: steps.cpr.outputs.pull-request-number != ''
228+
run: |
229+
echo "Pull Request Number: ${{ steps.cpr.outputs.pull-request-number }}" | tee -a $GITHUB_STEP_SUMMARY
230+
echo "Pull Request URL: ${{ steps.cpr.outputs.pull-request-url }}" | tee -a $GITHUB_STEP_SUMMARY
231+
echo "Pull Request Operation: ${{ steps.cpr.outputs.pull-request-operation }}" | tee -a $GITHUB_STEP_SUMMARY
232+
echo "Pull Request Head SHA: ${{ steps.cpr.outputs.pull-request-head-sha }}" | tee -a $GITHUB_STEP_SUMMARY
233+
234+
- name: Wait for required checks
235+
if: steps.cpr.outputs.pull-request-number != ''
236+
env:
237+
GH_TOKEN: ${{ steps.gh_token.outputs.token }}
238+
REQUIRED_CHECKS: ${{ inputs.required_checks }}
239+
run: |
240+
while true
241+
do
242+
sleep $(((RANDOM % 15) + 5))
243+
244+
all_checks="$(gh pr checks ${{ steps.cpr.outputs.pull-request-number }} | cat)"
245+
246+
while IFS='\n' read -r check
247+
do
248+
test -n "${check}" || continue 1
249+
status="$(echo "${check}" | awk -F'\t' '{print $2}')"
250+
251+
case ${status} in
252+
pass|skipping|queued|pending)
253+
continue 1
254+
;;
255+
*)
256+
echo "::error::One or more jobs finished with status ${status}"
257+
echo "${all_checks}" | awk -vSTATUS="${status}" -F'\t' '$2 == STATUS'
258+
exit 1
259+
;;
260+
esac
261+
262+
done <<< "${all_checks}"
263+
264+
while IFS=',' read -r required
265+
do
266+
required="$(echo "${required}" | awk '{$1=$1};NF')"
267+
test -n "${required}" || continue 1
268+
269+
status="$(echo "${all_checks}" | awk -vCHECK="${required}" -F'\t' '$1 == CHECK {print $2}')"
270+
271+
case ${status} in
272+
pass)
273+
continue 1
274+
;;
275+
queued|pending|"")
276+
echo "Waiting for ${required}..."
277+
echo "${all_checks}" | awk -vSTATUS="pending" -F'\t' '$2 == STATUS'
278+
continue 2
279+
;;
280+
*)
281+
echo "::error::A required job finished with status ${status}"
282+
echo "${all_checks}" | awk -vSTATUS="${status}" -F'\t' '$2 == STATUS'
283+
exit 1
284+
;;
285+
esac
286+
done <<< "${REQUIRED_CHECKS}"
287+
288+
break
289+
done
290+
291+
# always close the PR and delete the branch
292+
- name: Close pull request
293+
if: |
294+
always() && inputs.auto_close == true && steps.cpr.outputs.pull-request-number != ''
295+
uses: peter-evans/close-pull@f47e95b46e45ebf8a3a792e3a60e831ec2563f81 # v2.0.1
296+
with:
297+
token: ${{ steps.gh_token.outputs.token }}
298+
repository: ${{ matrix.repository }}
299+
pull-request-number: ${{ steps.cpr.outputs.pull-request-number }}
300+
comment: Auto-closing pull request
301+
delete-branch: true
Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#workflow_dispatch
2-
name: Bulk Patch
2+
name: Patch
33

44
on:
55
workflow_dispatch:
@@ -50,8 +50,7 @@ on:
5050
description: GitHub App installation id to request a temporary token
5151
type: string
5252
required: false
53-
# https://github.com/organizations/product-os/settings/installations
54-
default: "34040165"
53+
default: ""
5554

5655
# https://docs.github.com/en/actions/using-jobs/using-concurrency
5756
concurrency:
@@ -64,13 +63,21 @@ jobs:
6463
runs-on: ubuntu-latest
6564
timeout-minutes: 20
6665

67-
defaults:
68-
run:
69-
working-directory: .
70-
shell: bash --noprofile --norc -eo pipefail -x {0}
71-
7266
outputs:
73-
repositories: ${{ steps.repositories_json.outputs.build }}
67+
matrix: ${{ steps.matrix.outputs.build }}
68+
69+
env:
70+
# https://github.com/organizations/product-os/settings/installations
71+
# https://github.com/organizations/balena-os/settings/installations
72+
# https://github.com/organizations/balena-io/settings/installations
73+
# https://github.com/organizations/balena-io-modules/settings/installations
74+
KNOWN_INSTALLATION_IDS: >
75+
{
76+
"balena-io-modules": 34046903,
77+
"balena-io": 34046749,
78+
"balena-os": 34046907,
79+
"product-os": 34040165
80+
}
7481
7582
steps:
7683
- name: Log GitHub context
@@ -101,6 +108,19 @@ jobs:
101108
str: ${{ steps.repositories_csv.outputs.build }}
102109
separator: ","
103110

111+
- name: Create JSON matrix
112+
id: matrix
113+
env:
114+
REPOS: ${{ steps.repositories_json.outputs.build }}
115+
INSTALLATION_IDS: ${{ env.KNOWN_INSTALLATION_IDS }}
116+
run: |
117+
echo "build=$(jq -cr --argjson installation_ids "${INSTALLATION_IDS}" '{
118+
include: map({
119+
repository: .,
120+
installation_id: $installation_ids[split("/")[0]] | tostring
121+
})
122+
}' <<< "${REPOS}")" >> $GITHUB_OUTPUT
123+
104124
patch_repo:
105125
name: Patch repository
106126
runs-on: ubuntu-latest
@@ -131,7 +151,7 @@ jobs:
131151
id: gh_token
132152
with:
133153
app_id: ${{ inputs.token_app_id }}
134-
installation_id: ${{ inputs.token_installation_id }}
154+
installation_id: ${{ inputs.token_installation_id || matrix.installation_id }}
135155
private_key: ${{ secrets.GH_APP_PRIVATE_KEY }}
136156
repository: ${{ matrix.repository }}
137157
permissions: >-
@@ -200,8 +220,6 @@ jobs:
200220

201221
- name: Update summary
202222
if: steps.cpr.outputs.pull-request-number != ''
203-
env:
204-
GH_TOKEN: ${{ steps.gh_token.outputs.token }}
205223
run: |
206224
echo "Pull Request Number: ${{ steps.cpr.outputs.pull-request-number }}" | tee -a $GITHUB_STEP_SUMMARY
207225
echo "Pull Request URL: ${{ steps.cpr.outputs.pull-request-url }}" | tee -a $GITHUB_STEP_SUMMARY

0 commit comments

Comments
 (0)