Skip to content

Commit 2711a2d

Browse files
authored
Expand and streamline build action; (#480)
* allow any target branch to sync with upstream branch of the same name * minimize the number of runners * only run create certs if a build is planned * add nice error message if GH_PAT is invalid
1 parent 770c313 commit 2711a2d

File tree

1 file changed

+99
-81
lines changed

1 file changed

+99
-81
lines changed

.github/workflows/build_LoopFollow.yml

Lines changed: 99 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -8,104 +8,97 @@ on:
88
- cron: "17 10 * * 0" # Sunday at UTC 10:17
99

1010
env:
11+
GH_PAT: ${{ secrets.GH_PAT }}
1112
UPSTREAM_REPO: loopandlearn/LoopFollow
1213
UPSTREAM_BRANCH: ${{ github.ref_name }} # branch on upstream repository to sync from (replace with specific branch name if needed)
1314
TARGET_BRANCH: ${{ github.ref_name }} # target branch on fork to be kept in sync
1415

1516
jobs:
16-
17-
# Set a logic flag if this is the second instance of this day-of-week in this month
18-
day_in_month:
19-
runs-on: ubuntu-latest
20-
name: Check day in month
21-
outputs:
22-
IS_SECOND_IN_MONTH: ${{ steps.date-check.outputs.is_second_instance }}
23-
24-
steps:
25-
- id: date-check
26-
name: Check if this is the second time this day-of-week happens this month
27-
run: |
28-
DAY_OF_MONTH=$(date +%-d)
29-
WEEK_OF_MONTH=$(( ($(date +%-d) - 1) / 7 + 1 ))
30-
if [[ $WEEK_OF_MONTH -eq 2 ]]; then
31-
echo "is_second_instance=true" >> "$GITHUB_OUTPUT"
32-
else
33-
echo "is_second_instance=false" >> "$GITHUB_OUTPUT"
34-
fi
35-
36-
# Checks if Distribution certificate is present and valid, optionally nukes and
37-
# creates new certs if the repository variable ENABLE_NUKE_CERTS == 'true'
38-
check_certs:
39-
name: Check certificates
40-
uses: ./.github/workflows/create_certs.yml
41-
secrets: inherit
42-
43-
# Checks if GH_PAT holds workflow permissions
44-
check_permissions:
45-
needs: check_certs
17+
# use a single runner for these sequential steps
18+
check_status:
4619
runs-on: ubuntu-latest
47-
name: Check permissions
20+
name: Check status to decide whether to build
4821
permissions:
4922
contents: write
50-
outputs:
51-
WORKFLOW_PERMISSION: ${{ steps.workflow-permission.outputs.has_permission }}
52-
53-
steps:
54-
- name: Check for workflow permissions
55-
id: workflow-permission
56-
env:
57-
TOKEN_TO_CHECK: ${{ secrets.GH_PAT }}
58-
run: |
59-
PERMISSIONS=$(curl -sS -f -I -H "Authorization: token ${{ env.TOKEN_TO_CHECK }}" https://api.github.com | grep ^x-oauth-scopes: | cut -d' ' -f2-);
60-
61-
if [[ $PERMISSIONS =~ "workflow" || $PERMISSIONS == "" ]]; then
62-
echo "GH_PAT holds workflow permissions or is fine-grained PAT."
63-
echo "has_permission=true" >> $GITHUB_OUTPUT # Set WORKFLOW_PERMISSION to false.
64-
else
65-
echo "GH_PAT lacks workflow permissions."
66-
echo "Automated build features will be skipped!"
67-
echo "has_permission=false" >> $GITHUB_OUTPUT # Set WORKFLOW_PERMISSION to false.
68-
fi
69-
70-
# Checks for changes in upstream repository; if changes exist prompts sync for build
71-
check_latest_from_upstream:
72-
needs: [check_certs, check_permissions]
73-
runs-on: ubuntu-latest
74-
name: Check upstream
7523
outputs:
7624
NEW_COMMITS: ${{ steps.sync.outputs.has_new_commits }}
77-
ABORT_SYNC: ${{ steps.check_branch.outputs.ABORT_SYNC }}
25+
IS_SECOND_IN_MONTH: ${{ steps.date-check.outputs.is_second_instance }}
7826

27+
# Check GH_PAT, sync repository, check day in month
7928
steps:
80-
- name: Check if running on main or dev branch
81-
if: |
82-
needs.check_permissions.outputs.WORKFLOW_PERMISSION == 'true' &&
83-
(vars.SCHEDULED_BUILD != 'false' || vars.SCHEDULED_SYNC != 'false')
84-
id: check_branch
29+
30+
- name: Access
31+
id: workflow-permission
8532
run: |
86-
if [ "${GITHUB_REF##*/}" = "main" ]; then
87-
echo "Running on main branch"
88-
echo "ABORT_SYNC=false" >> $GITHUB_OUTPUT
89-
elif [ "${GITHUB_REF##*/}" = "dev" ]; then
90-
echo "Running on dev branch"
91-
echo "ABORT_SYNC=false" >> $GITHUB_OUTPUT
33+
# Validate Access Token
34+
35+
# Ensure that gh exit codes are handled when output is piped.
36+
set -o pipefail
37+
38+
# Define patterns to validate the access token (GH_PAT) and distinguish between classic and fine-grained tokens.
39+
GH_PAT_CLASSIC_PATTERN='^ghp_[a-zA-Z0-9]{36}$'
40+
GH_PAT_FINE_GRAINED_PATTERN='^github_pat_[a-zA-Z0-9]{22}_[a-zA-Z0-9]{59}$'
41+
42+
# Validate Access Token (GH_PAT)
43+
if [ -z "$GH_PAT" ]; then
44+
failed=true
45+
echo "::error::The GH_PAT secret is unset or empty. Set it and try again."
9246
else
93-
echo "Not running on main or dev branch"
94-
echo "ABORT_SYNC=true" >> $GITHUB_OUTPUT
47+
if [[ $GH_PAT =~ $GH_PAT_CLASSIC_PATTERN ]]; then
48+
provides_scopes=true
49+
echo "The GH_PAT secret is a structurally valid classic token."
50+
elif [[ $GH_PAT =~ $GH_PAT_FINE_GRAINED_PATTERN ]]; then
51+
echo "The GH_PAT secret is a structurally valid fine-grained token."
52+
else
53+
unknown_format=true
54+
echo "The GH_PAT secret does not have a known token format."
55+
fi
56+
57+
# Attempt to capture the x-oauth-scopes scopes of the token.
58+
if ! scopes=$(curl -sS -f -I -H "Authorization: token $GH_PAT" https://api.github.com | { grep -i '^x-oauth-scopes:' || true; } | cut -d ' ' -f2- | tr -d '\r'); then
59+
failed=true
60+
if [ $unknown_format ]; then
61+
echo "::error::Unable to connect to GitHub using the GH_PAT secret. Verify that it is set correctly (including the 'ghp_' or 'github_pat_' prefix) and try again."
62+
else
63+
echo "::error::Unable to connect to GitHub using the GH_PAT secret. Verify that the token exists and has not expired at https://github.com/settings/tokens. If necessary, regenerate or create a new token (and update the secret), then try again."
64+
fi
65+
elif [[ $scopes =~ workflow ]]; then
66+
echo "The GH_PAT secret has repo and workflow permissions."
67+
echo "has_permission=true" >> $GITHUB_OUTPUT
68+
elif [[ $scopes =~ repo ]]; then
69+
echo "The GH_PAT secret has repo (but not workflow) permissions."
70+
elif [ $provides_scopes ]; then
71+
failed=true
72+
if [ -z "$scopes" ]; then
73+
echo "The GH_PAT secret is valid and can be used to connect to GitHub, but it does not provide any permission scopes."
74+
else
75+
echo "The GH_PAT secret is valid and can be used to connect to GitHub, but it only provides the following permission scopes: $scopes"
76+
fi
77+
echo "::error::The GH_PAT secret is lacking at least the 'repo' permission scope required to access the Match-Secrets repository. Update the token permissions at https://github.com/settings/tokens (to include the 'repo' and 'workflow' scopes) and try again."
78+
else
79+
echo "The GH_PAT secret is valid and can be used to connect to GitHub, but it does not provide inspectable scopes. Assuming that the 'repo' and 'workflow' permission scopes required to access the Match-Secrets repository and perform automations are present."
80+
echo "has_permission=true" >> $GITHUB_OUTPUT
81+
fi
82+
fi
83+
84+
# Exit unsuccessfully if secret validation failed.
85+
if [ $failed ]; then
86+
exit 2
9587
fi
9688
9789
- name: Checkout target repo
9890
if: |
99-
needs.check_permissions.outputs.WORKFLOW_PERMISSION == 'true' &&
91+
steps.workflow-permission.outputs.has_permission == 'true' &&
10092
(vars.SCHEDULED_BUILD != 'false' || vars.SCHEDULED_SYNC != 'false')
10193
uses: actions/checkout@v4
10294
with:
10395
token: ${{ secrets.GH_PAT }}
10496

97+
# This syncs any target branch to upstream branch of the same name
10598
- name: Sync upstream changes
10699
if: | # do not run the upstream sync action on the upstream repository
107-
needs.check_permissions.outputs.WORKFLOW_PERMISSION == 'true' &&
108-
vars.SCHEDULED_SYNC != 'false' && github.repository_owner != 'loopandlearn' && steps.check_branch.outputs.ABORT_SYNC == 'false'
100+
steps.workflow-permission.outputs.has_permission == 'true' &&
101+
vars.SCHEDULED_SYNC != 'false' && github.repository_owner != 'loopandlearn'
109102
id: sync
110103
uses: aormsby/[email protected]
111104
with:
@@ -118,43 +111,68 @@ jobs:
118111
# Display a sample message based on the sync output var 'has_new_commits'
119112
- name: New commits found
120113
if: |
121-
needs.check_permissions.outputs.WORKFLOW_PERMISSION == 'true' &&
114+
steps.workflow-permission.outputs.has_permission == 'true' &&
122115
vars.SCHEDULED_SYNC != 'false' && steps.sync.outputs.has_new_commits == 'true'
123116
run: echo "New commits were found to sync."
124117

125118
- name: No new commits
126119
if: |
127-
needs.check_permissions.outputs.WORKFLOW_PERMISSION == 'true' &&
120+
steps.workflow-permission.outputs.has_permission == 'true' &&
128121
vars.SCHEDULED_SYNC != 'false' && steps.sync.outputs.has_new_commits == 'false'
129122
run: echo "There were no new commits."
130123

131124
- name: Show value of 'has_new_commits'
132-
if: needs.check_permissions.outputs.WORKFLOW_PERMISSION == 'true' && vars.SCHEDULED_SYNC != 'false' && steps.check_branch.outputs.ABORT_SYNC == 'false'
125+
if: steps.workflow-permission.outputs.has_permission == 'true' && vars.SCHEDULED_SYNC != 'false'
133126
run: |
134127
echo ${{ steps.sync.outputs.has_new_commits }}
135128
echo "NEW_COMMITS=${{ steps.sync.outputs.has_new_commits }}" >> $GITHUB_OUTPUT
136129
137130
- name: Show scheduled build configuration message
138-
if: needs.check_permissions.outputs.WORKFLOW_PERMISSION != 'true'
131+
if: steps.workflow-permission.outputs.has_permission != 'true'
139132
run: |
140133
echo "### :calendar: Scheduled Sync and Build Disabled :mobile_phone_off:" >> $GITHUB_STEP_SUMMARY
141134
echo "You have not yet configured the scheduled sync and build for LoopFollow's browser build." >> $GITHUB_STEP_SUMMARY
142135
echo "Synchronizing your fork of <code>LoopFollow</code> with the upstream repository <code>loopandlearn/LoopFollow</code> will be skipped." >> $GITHUB_STEP_SUMMARY
143136
echo "If you want to enable automatic builds and updates for your LoopFollow, please follow the instructions \
144137
under the following path <code>LoopFollow/fastlane/testflight.md</code>." >> $GITHUB_STEP_SUMMARY
145-
138+
139+
# Set a logic flag if this is the second instance of this day-of-week in this month
140+
- name: Check if this is the second time this day-of-week happens this month
141+
id: date-check
142+
run: |
143+
DAY_OF_MONTH=$(date +%-d)
144+
WEEK_OF_MONTH=$(( ($(date +%-d) - 1) / 7 + 1 ))
145+
if [[ $WEEK_OF_MONTH -eq 2 ]]; then
146+
echo "is_second_instance=true" >> "$GITHUB_OUTPUT"
147+
else
148+
echo "is_second_instance=false" >> "$GITHUB_OUTPUT"
149+
fi
150+
151+
# Checks if Distribution certificate is present and valid, optionally nukes and
152+
# creates new certs if the repository variable ENABLE_NUKE_CERTS == 'true'
153+
# only run if a build is planned
154+
check_certs:
155+
needs: [check_status]
156+
name: Check certificates
157+
uses: ./.github/workflows/create_certs.yml
158+
secrets: inherit
159+
if: |
160+
github.event_name == 'workflow_dispatch' ||
161+
(vars.SCHEDULED_BUILD != 'false' && needs.check_status.outputs.IS_SECOND_IN_MONTH == 'true') ||
162+
(vars.SCHEDULED_SYNC != 'false' && needs.check_status.outputs.NEW_COMMITS == 'true' )
163+
146164
# Builds LoopFollow
147165
build:
148166
name: Build
149-
needs: [check_certs, check_latest_from_upstream, day_in_month]
167+
needs: [check_certs, check_status]
150168
runs-on: macos-15
151169
permissions:
152170
contents: write
153171
if:
154172
| # builds with manual start; if scheduled: once a month or when new commits are found
155173
github.event_name == 'workflow_dispatch' ||
156-
(vars.SCHEDULED_BUILD != 'false' && needs.day_in_month.outputs.IS_SECOND_IN_MONTH == 'true') ||
157-
(vars.SCHEDULED_SYNC != 'false' && needs.check_latest_from_upstream.outputs.NEW_COMMITS == 'true' )
174+
(vars.SCHEDULED_BUILD != 'false' && needs.check_status.outputs.IS_SECOND_IN_MONTH == 'true') ||
175+
(vars.SCHEDULED_SYNC != 'false' && needs.check_status.outputs.NEW_COMMITS == 'true' )
158176
steps:
159177
- name: Select Xcode version
160178
run: "sudo xcode-select --switch /Applications/Xcode_16.4.app/Contents/Developer"

0 commit comments

Comments
 (0)