-
Notifications
You must be signed in to change notification settings - Fork 2
/
action.yml
488 lines (469 loc) · 20.9 KB
/
action.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
name: Tag and release
description: GitHub Action to check if a patch release can be tagged, and then create a tag and an optional release
inputs:
latest_local_sha:
description: The latest local sha. Used to gauge the release
required: false
type: string
skip_gauge_release:
description: If true, skip straight to tagging the release
required: false
default: false
type: boolean
# Note: the following inputs should usually be left as default if using gauge release
# Note: there is an explicit reason why there is no sha input parameter - see the readme
tag:
description: The name of the tag. Required if skipping gauge release. Cannot use if not skipping gauge release
required: false
default: ''
type: string
delete_existing:
required: false
default: false
type: boolean
release:
description: Whether to create a GitHub Release as well as a tag
required: false
default: true
type: boolean
release_description:
description: The description for the GitHub Release if creating one
required: false
default: ''
type: string
release_auto_notes:
description: If true, the GitHub release description is automatically generated
required: false
default: true
type: boolean
# Only set this to true in action-ci.yml
# We need this to avoid race conditions, i.e. if we dispatched this action and autotag from action-ci, autotag would likely finish first.
dispatch_gha_autotag:
description: If true, the auto-tag.yml workflow will be dispatched after tagging is successful
required: false
default: false
type: boolean
runs:
using: composite
steps:
- name: Validate inputs
env:
LATEST_LOCAL_SHA: ${{ inputs.latest_local_sha }}
SKIP_GAUGE_RELEASE: ${{ inputs.skip_gauge_release }}
TAG: ${{ inputs.tag }}
DELETE_EXISTING: ${{ inputs.delete_existing }}
shell: bash
run: |
VALID=1
# If we're not gauging release, there MUST be a tag.
if [[ $SKIP_GAUGE_RELEASE == 'true' && $TAG == '' ]]; then
echo "Must provide a tag when skip_gauge_release is true"
VALID=0
fi
# If there's a tag, it must be a valid git ref for this repo
if [[ $SKIP_GAUGE_RELEASE != 'true' && $TAG != '' ]]; then
git check-ref-format "tags/$TAG" > /dev/null
if [[ $? != "0" ]]; then
echo "Invalid tag"
VALID=0
fi
fi
# gauge release requires the latest local sha
if [[ $SKIP_GAUGE_RELEASE != 'true' && $LATEST_LOCAL_SHA == '' ]]; then
echo "Must include latest_local_sha when skip_gauge_release is false"
VALID=0
fi
# Can't delete existing tag if using gauge release
if [[ $SKIP_GAUGE_RELEASE != 'true' && $DELETE_EXISTING == 'true' ]]; then
echo "Cannot set delete_existing to true when skip_gauge_release is false"
VALID=0
fi
if [[ $VALID != 1 ]]; then
exit 1
fi
- name: Check unreleased changes
id: gauge-release
env:
GITHUB_REPOSITORY: ${{ github.repository }}
GITHUB_REF_NAME: ${{ github.ref_name }}
GITHUB_SHA: ${{ github.sha }}
LATEST_LOCAL_SHA: ${{ inputs.latest_local_sha }}
SKIP_GAUGE_RELEASE: ${{ inputs.skip_gauge_release }}
shell: bash
run: |
DO_RELEASE=1
if [[ $SKIP_GAUGE_RELEASE == 'true' ]]; then
echo "skipping gauge release"
echo "do_release output is $DO_RELEASE"
echo "do_release=$DO_RELEASE" >> $GITHUB_OUTPUT
exit 0
fi
# Double check that LATEST_LOCAL_SHA matches GITHUB_SHA
echo "LATEST_LOCAL_SHA is $LATEST_LOCAL_SHA"
echo "GITHUB_SHA is $GITHUB_SHA"
if [[ $LATEST_LOCAL_SHA != $GITHUB_SHA ]]; then
echo "Not patch releasing because GITHUB_SHA is not equal to latest local sha"
DO_RELEASE=0
fi
# Must be on a minor branch to do a patch release
if [[ $DO_RELEASE == "1" ]]; then
if ! [[ $GITHUB_REF_NAME =~ ^[0-9]+\.[0-9]+$ ]]; then
echo "Not patch releasing because not on a minor branch"
DO_RELEASE=0
fi
fi
# Validate that this commit is that latest commit for the branch using GitHub API
# We need to check this in case re-rerunning an old job and there have been new commits since
if [[ $DO_RELEASE == "1" ]]; then
# https://docs.github.com/en/rest/commits/commits?apiVersion=2022-11-28
RESP_CODE=$(curl -w %{http_code} -s -o __response.json \
-X GET "https://api.github.com/repos/${GITHUB_REPOSITORY}/commits?sha=${GITHUB_REF_NAME}&per_page=1" \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${{ github.token }}" \
-H "X-GitHub-Api-Version: 2022-11-28" \
)
if [[ $RESP_CODE != "200" ]]; then
echo "Unable to read list of commits - HTTP response code was $RESP_CODE"
exit 1
fi
LATEST_REMOTE_SHA=$(jq -r '.[0].sha' __response.json)
echo "LATEST_REMOTE_SHA is $LATEST_REMOTE_SHA"
echo "LATEST_LOCAL_SHA is $LATEST_LOCAL_SHA"
if [[ $LATEST_REMOTE_SHA != $LATEST_LOCAL_SHA ]]; then
echo "Not patch releasing because latest remote sha is not equal to latest local sha"
DO_RELEASE=0
fi
# Also validate the sha matches GITHUB_SHA, which is what gha-tag-release will use
if [[ $GITHUB_SHA != $LATEST_LOCAL_SHA ]]; then
echo "Not patch releasing because GITHUB_SHA is not equal to latest local sha"
DO_RELEASE=0
fi
fi
# Check is there is an existing tag on the branch using GitHub API
# Note cannot use local `git tag` because actions/checkout by default will not checkout tags
# and you need to checkout full history in order to get them
LATEST_TAG=""
NEXT_TAG=""
if [[ $DO_RELEASE == "1" ]]; then
# https://docs.github.com/en/rest/git/refs?apiVersion=2022-11-28#list-matching-references
RESP_CODE=$(curl -w %{http_code} -s -o __response.json \
-X GET https://api.github.com/repos/${GITHUB_REPOSITORY}/git/matching-refs/tags/${GITHUB_REF_NAME} \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${{ github.token }}" \
-H "X-GitHub-Api-Version: 2022-11-28" \
)
if [[ $RESP_CODE != "200" ]]; then
echo "Unable to read list of tags - HTTP response code was $RESP_CODE"
exit 1
fi
# Get the latest tag
LATEST_TAG=$(jq -r '.[].ref' __response.json | grep -Po '(?<=^refs\/tags\/)[0-9]+\.[0-9]+\.[0-9]+$' | sort -V -r | head -n 1) || true
echo "LATEST_TAG is $LATEST_TAG"
echo "latest_tag=$LATEST_TAG" >> $GITHUB_OUTPUT
if ! [[ $LATEST_TAG =~ ([0-9]+)\.([0-9]+)\.([0-9]+) ]]; then
echo "Not patch releasing because cannot find a matching semver tag on the branch"
DO_RELEASE=0
else
MAJOR=${BASH_REMATCH[1]}
MINOR=${BASH_REMATCH[2]}
PATCH=${BASH_REMATCH[3]}
NEXT_TAG="$MAJOR.$MINOR.$((PATCH+1))"
echo "NEXT_TAG is $NEXT_TAG"
echo "next_tag=$NEXT_TAG" >> $GITHUB_OUTPUT
fi
fi
# Check if there is anything relevant commits to release using GitHub API using the tripe-dot compoare endpoint
# which will show things that are in the next-patch branch that are not in the latest tag
# Note: unlike CLI, the API endpoint results include all merged pull-requests, not commits
# Pull-requests prefixed with MNT or DOC will not be considered relevant for releasing
if [[ $DO_RELEASE == "1" ]]; then
# Check on github release notes api if there's anything worth releasing
# Compare commits between current sha with latest tag to see if there is anything worth releasing
# https://docs.github.com/en/rest/commits/commits?apiVersion=2022-11-28#compare-two-commits
RESP_CODE=$(curl -w %{http_code} -s -o __response.json \
-X GET https://api.github.com/repos/$GITHUB_REPOSITORY/compare/$LATEST_TAG...$GITHUB_SHA?per_page=100 \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${{ github.token }}" \
-H "X-GitHub-Api-Version: 2022-11-28" \
)
if [[ $RESP_CODE != "200" ]]; then
echo "Unable to fetch compare two commits - HTTP response code was $RESP_CODE"
exit 1
fi
# Get commits for text parsing
jq -r '.commits[].commit.message' __response.json > __commits.json
# Parse comits one line at a time
HAS_THINGS_TO_RELEASE=0
while IFS="" read -r line || [[ -n $line ]]; do
# Remove any leading bullet points
line="${line#\* }"
line="${line#\-}"
line="${line# }"
if ! [[ "$line" =~ ^(Merge|MNT|DOC) ]] && ! [[ $line =~ ^[[:space:]]*$ ]]; then
HAS_THINGS_TO_RELEASE=1
break
fi
done < __commits.json
if [[ $HAS_THINGS_TO_RELEASE == "0" ]]; then
echo "Not patch releasing because there is nothing relevant to release"
DO_RELEASE=0
fi
fi
# Check again, this time using the double-dot syntax which will show the raw diff between the latest tag
# and the next-patch branch
# This isn't available via the github api, so screen scrape this instead. Screen scraping isn't
# great because it's brittle, however if this fails then all that happens is we tag a release that
# has no actual changes, which isn't the end of the world.
# Here we are only detecting if there are no actual changes to release, which can happen in a couple of scenarios:
# a) A change is made and tagged, and then backported to an older branch and then merged-up
# b) A change made in a previous major that we don't want to keep in current major, so
# it's reverted during the merge-up
if [[ $DO_RELEASE == "1" ]]; then
RESP_CODE=$(curl -w %{http_code} -s -o __compare.html \
-X GET https://github.com/$GITHUB_REPOSITORY/compare/$LATEST_TAG..$GITHUB_SHA
)
if [[ $RESP_CODE != "200" ]]; then
echo "Unable to fetch compare html - HTTP response code was $RESP_CODE"
exit 1
fi
PARSED=$(php -r '
$s = file_get_contents("__compare.html");
$s = strip_tags($s);
$s = str_replace("[\r\n]", " ", $s);
$s = preg_replace("# {2,}#", " ", $s);
echo $s;
')
# `|| true` needs to be suffixed otherwise an error code of 1 will be omitted when there is no grep match
IDENTICAL=$(echo $PARSED | grep "$LATEST_TAG and $GITHUB_SHA are identical") || true
if [[ $IDENTICAL != "" ]]; then
echo "Not patch releasing because there are no actual changes to release"
DO_RELEASE=0
fi
fi
echo "do_release output is $DO_RELEASE"
echo "do_release=$DO_RELEASE" >> $GITHUB_OUTPUT
- name: Get clean tag name
id: tag-name
if: ${{ steps.gauge-release.outputs.do_release == '1' }}
env:
DISCOVERED_TAG: ${{ steps.gauge-release.outputs.next_tag }}
INPUT_TAG: ${{ inputs.tag }}
shell: bash
run: |
# Provide an output with the tag to use
TAG="$DISCOVERED_TAG"
if [[ $INPUT_TAG != '' ]]; then
TAG="$INPUT_TAG"
fi
echo "tag output is $TAG"
echo "tag=$TAG" >> $GITHUB_OUTPUT
- name: Delete existing release if one exists
if: ${{ steps.gauge-release.outputs.do_release == '1' && inputs.release == 'true' && inputs.delete_existing == 'true' }}
env:
TAG: ${{ steps.tag-name.outputs.tag }}
GITHUB_REPOSITORY: ${{ github.repository }}
shell: bash
run: |
# Get id for an existing release matching $TAG
# https://docs.github.com/en/rest/releases/releases#get-a-release-by-tag-name
RESP_CODE=$(curl -w %{http_code} -s -o __response.json \
-X GET https://api.github.com/repos/$GITHUB_REPOSITORY/releases/tags/$TAG \
-H "Accept: application/vnd.github.v3+json")
if [[ $RESP_CODE != "200" ]]; then
RESP_MSG=$(jq -r .message __response.json)
if [[ $RESP_MSG != "Not Found" ]]; then
echo "Unable to check tag status for $TAG - HTTP response code was $RESP_CODE and message was $RESP_MSG"
cat __response.json
exit 1
fi
fi
RELEASE_ID=$(jq .id __response.json)
if [[ $RESP_CODE == "404" || $RELEASE_ID == "null" ]]; then
echo "Did not find an existing release for tag $TAG"
else
# https://docs.github.com/en/rest/releases/releases#delete-a-release
RESP_CODE=$(curl -w %{http_code} -s -o __response.json \
-X DELETE https://api.github.com/repos/$GITHUB_REPOSITORY/releases/$RELEASE_ID \
-H "Accept: application/vnd.github.v3+json" \
-H "Authorization: token ${{ github.token }}")
if [[ $RESP_CODE != "204" ]]; then
echo "Unable to delete release $RELEASE_ID for tag $TAG - HTTP response code was $RESP_CODE"
cat __response.json
exit 1
fi
echo "Deleted existing release $RELEASE_ID for tag $TAG"
fi
- name: Delete existing tag if one exists
if: ${{ steps.gauge-release.outputs.do_release == '1' && inputs.delete_existing == 'true' }}
env:
TAG: ${{ steps.tag-name.outputs.tag }}
GITHUB_REPOSITORY: ${{ github.repository }}
shell: bash
run: |
# Check if tag currently exists
# Note: not using https://api.github.com/repos/$GITHUB_REPOSITORY/git/refs/tags/<tag>
# because that uses a "starts-with" filter, so it will also match -beta1 and -rc1 tags
# https://docs.github.com/en/rest/git/refs#get-a-reference
RESP_CODE=$(curl -w %{http_code} -s -o __response.json \
-X GET https://api.github.com/repos/$GITHUB_REPOSITORY/git/refs/tags \
-H "Accept: application/vnd.github.v3+json")
if [[ $RESP_CODE != "200" ]]; then
echo "Unable to check tag status - HTTP response code was $RESP_CODE"
cat __response.json
exit 1
fi
FOUND="true"
# Check there are any tags so we can use jq array selector later
if [[ $(jq 'map(type)' __response.json) =~ object ]]; then
EXISTING_TAG=$(jq ".[] | select(.ref == \"refs/tags/$TAG\")" __response.json)
if [[ $EXISTING_TAG == "" ]]; then
FOUND="false"
fi
fi
if [[ $FOUND == "false" ]]; then
echo "Did not find an existing tag for $TAG"
else
# Delete tag via GitHub API
# https://docs.github.com/en/rest/reference/git#delete-a-reference
RESP_CODE=$(curl -w %{http_code} -s -o __response.json \
-X DELETE https://api.github.com/repos/$GITHUB_REPOSITORY/git/refs/tags/$TAG \
-H "Accept: application/vnd.github.v3+json" \
-H "Authorization: token ${{ github.token }}")
if [[ $RESP_CODE != "204" ]]; then
echo "Unable to delete existing TAG $TAG - HTTP response code was $RESP_CODE"
cat __response.json
exit 1
fi
echo "Deleted existing tag $TAG"
fi
- name: Create tag
# Creating a release will also create a tag, so only create explicitly create tag if not creating release
if: ${{ steps.gauge-release.outputs.do_release == '1' && inputs.release == 'false' }}
env:
TAG: ${{ steps.tag-name.outputs.tag }}
GITHUB_REPOSITORY: ${{ github.repository }}
GITHUB_SHA: ${{ github.sha }}
shell: bash
run: |
# Create new tag via GitHub API
# https://docs.github.com/en/rest/reference/git#create-a-reference
RESP_CODE=$(curl -w %{http_code} -s -o __response.json \
-X POST https://api.github.com/repos/$GITHUB_REPOSITORY/git/refs \
-H "Accept: application/vnd.github.v3+json" \
-H "Authorization: token ${{ github.token }}" \
-d @- << EOF
{
"sha": "$GITHUB_SHA",
"ref": "refs/tags/$TAG"
}
EOF
)
if [[ $RESP_CODE != "201" ]]; then
echo "Unable to create tag $TAG for sha ${{ github.sha }} - HTTP response code was $RESP_CODE"
cat __response.json
exit 1
fi
echo "New tag $TAG created for sha ${{ github.sha }}"
- name: Create release
if: ${{ steps.gauge-release.outputs.do_release == '1' && inputs.release == 'true' }}
env:
TAG: ${{ steps.tag-name.outputs.tag }}
RELEASE_DESCRIPTION: ${{ inputs.release_description }}
GITHUB_REPOSITORY: ${{ github.repository }}
GITHUB_SHA: ${{ github.sha }}
shell: bash
run: |
# Work out if release should be marked as the latest
# Only do the for stable semver tags
# Note = not using "legacy" as GitHub seems to only consider if it's the latest tag in a particular major line
# whereas we only want to mark it the latest if it's the latest tag overall
MAKE_LATEST="false"
if [[ $TAG =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
# Gets latest 100 tags across all majors from via GitHub API
# https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#list-repository-tags
RESP_CODE=$(curl -w %{http_code} -s -o __response.json \
-X GET "https://api.github.com/repos/${GITHUB_REPOSITORY}/tags?per_page=100" \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${{ github.token }}" \
-H "X-GitHub-Api-Version: 2022-11-28" \
)
if [[ $RESP_CODE != "200" ]]; then
echo "Unable to read list of tags - HTTP response code was $RESP_CODE"
cat __response.json
exit 1
fi
# Get the latest stable tag
LATEST_TAG=$(jq -r '.[].name' __response.json | grep -Po '^[0-9]+\.[0-9]+\.[0-9]+$' | sort -V -r | head -n 1)
echo "LATEST_TAG is $LATEST_TAG"
if [[ $LATEST_TAG =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
MAJOR=${BASH_REMATCH[1]}
MINOR=${BASH_REMATCH[2]}
PATCH=${BASH_REMATCH[3]}
NEXT_TAG="$MAJOR.$MINOR.$((PATCH+1))"
if [[ $TAG == $NEXT_TAG ]]; then
MAKE_LATEST="true"
fi
fi
fi
echo "MAKE_LATEST is $MAKE_LATEST"
# Escape double quotes '"' => '\"'
RELEASE_DESCRIPTION=${RELEASE_DESCRIPTION//\"/\\\"}
# Create new release via GitHub API
# https://docs.github.com/en/rest/releases/releases#create-a-release
RESP_CODE=$(curl -w %{http_code} -s -o __response.json \
-X POST https://api.github.com/repos/$GITHUB_REPOSITORY/releases \
-H "Accept: application/vnd.github.v3+json" \
-H "Authorization: token ${{ github.token }}" \
-d @- << EOF
{
"tag_name": "$TAG",
"target_commitish": "$GITHUB_SHA",
"name": "$TAG",
"body": "$RELEASE_DESCRIPTION",
"draft": false,
"prerelease": false,
"generate_release_notes": ${{ inputs.release_auto_notes }},
"make_latest": "$MAKE_LATEST"
}
EOF
)
if [[ $RESP_CODE != "201" ]]; then
echo "Unable to create release for tag $TAG - HTTP response code was $RESP_CODE"
cat __response.json
exit 1
fi
echo "New release $TAG created"
- name: Dispatch auto tag
if: ${{ steps.gauge-release.outputs.do_release == '1' && inputs.dispatch_gha_autotag == 'true' }}
env:
GITHUB_REPOSITORY: ${{ github.repository }}
BRANCH: ${{ github.ref_name }}
shell: bash
run: |
# https://docs.github.com/en/rest/actions/workflows?apiVersion=2022-11-28#create-a-workflow-dispatch-event
RESP_CODE=$(curl -w %{http_code} -s -L -o __response.json \
-X POST \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${{ github.token }}" \
-H "X-GitHub-Api-Version: 2022-11-28" \
https://api.github.com/repos/$GITHUB_REPOSITORY/actions/workflows/auto-tag.yml/dispatches \
-d "{\"ref\":\"$BRANCH\"}"
)
if [[ $RESP_CODE != "204" ]]; then
echo "Failed to dispatch workflow - HTTP response code was $RESP_CODE"
cat __response.json
exit 1
fi
- name: Delete temporary files
if: always()
shell: bash
run: |
if [[ -f __response.json ]]; then
rm __response.json
fi
if [[ -f __commits.json ]]; then
rm __commits.json
fi
if [[ -f __compare.html ]]; then
rm __compare.html
fi