Skip to content

Commit 1c627a6

Browse files
committed
Support Authenticating as a GitHub App installation
Provide credentials via env variables: GITHUB_APP_CLIENT_ID GITHUB_APP_INSTALLATION_ID GITHUB_APP_PRIVATE_KEY https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/authenticating-as-a-github-app-installation
1 parent 89651e2 commit 1c627a6

File tree

2 files changed

+100
-79
lines changed

2 files changed

+100
-79
lines changed

README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,20 +46,20 @@ Finally extract and strip (-s) the two leading directories:
4646

4747
Usage:
4848

49-
github-latest-release [-b <BEARER_TOKEN>] -r <OWNER/REPO> -l -p [REGEX]
50-
github-latest-release [-b <BEARER_TOKEN>] -r <OWNER/REPO> -f -p <REGEX> -o [OUTPUT]
51-
github-latest-release [-b <BEARER_TOKEN>] -r <OWNER/REPO> -t -p <REGEX> \
49+
github-latest-release [-a <BEARER_TOKEN>] -r <OWNER/REPO> -l -p [REGEX]
50+
github-latest-release [-a <BEARER_TOKEN>] -r <OWNER/REPO> -f -p <REGEX> -o [OUTPUT]
51+
github-latest-release [-a <BEARER_TOKEN>] -r <OWNER/REPO> -t -p <REGEX> \
5252
-i [INCLUDE_GLOB]
53-
github-latest-release [-b <BEARER_TOKEN>] -r <OWNER/REPO> -x -p <REGEX> \
53+
github-latest-release [-a <BEARER_TOKEN>] -r <OWNER/REPO> -x -p <REGEX> \
5454
-i [INCLUDE_GLOB] -s [STRIP_COMPONENTS] -c [DIRECTORY]
5555

5656
Options:
5757

58-
-b <BEARER_TOKEN>
58+
-a <BEARER_TOKEN>
5959
Authorization token.
6060

6161
Example:
62-
github-latest-release -b "$(gh auth token)" -r enterprise/repo -l
62+
github-latest-release -a "$(gh auth token)" -r enterprise/repo -l
6363

6464
-r <OWNER/REPO>
6565
The github repo identifier. The 'owner/repo' part of 'https://github.com/owner/repo'

github-latest-release

Lines changed: 94 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,127 +1,137 @@
11
#!/bin/bash
22

3-
# prerequisite: curl, jq, bsdtar (libarchive-tools)
3+
# prerequisite: openssl, curl, jq, bsdtar (libarchive-tools)
44

55
set -euo pipefail
66

7-
87
function curl() {
9-
local bearer_token="$1"
8+
local auth_token="$1"
109
local accept="$2"
1110
shift 2
1211
command curl --fail-with-body -sSL \
13-
${bearer_token:+ -H "Authorization: Bearer ${bearer_token}"} \
12+
${auth_token:+ -H "Authorization: Bearer ${auth_token}"} \
1413
${accept:+ -H "Accept: ${accept}"} \
1514
"$@"
1615
}
1716

17+
base64() { openssl base64 -e -A | tr -- '+/' '-_' | tr -d = ; }
18+
19+
sign() {
20+
local message="$1"
21+
local private_key="$2"
22+
printf '%s' "$message" | openssl dgst -binary -sha256 -sign <(printf '%s' "$private_key") | base64
23+
}
24+
25+
jwt() {
26+
local client_id="$1"
27+
local private_key="$2"
28+
29+
local now iat exp header claims jwt
30+
now=$(date +%s)
31+
iat=$((now - 60))
32+
exp=$((now + 600))
33+
header=$(printf '%s' '{ "typ":"JWT", "alg": "RS256" }' | base64)
34+
claims=$(printf '{ "iss": "%s", "iat": %d, "exp": %d }' "$client_id" "$iat" "$exp" | base64)
35+
jwt="$header.$claims.$(sign "$header.$claims" "$private_key")"
36+
37+
printf '%s' "$jwt"
38+
}
39+
40+
fetch_installation_access_token() {
41+
local installation_id="$1"
42+
local jwt_token="$2"
43+
44+
curl "$jwt_token" "application/vnd.github+json" -X POST \
45+
"https://api.github.com/app/installations/$installation_id/access_tokens" \
46+
| jq -r '.token'
47+
}
48+
1849
list_download_urls() {
19-
local bearer_token="$1"
50+
local auth_token="$1"
2051
local repo="$2"
2152

22-
curl "$bearer_token" "" "https://api.github.com/repos/${repo}/releases/latest" \
53+
curl "$auth_token" "" "https://api.github.com/repos/${repo}/releases/latest" \
2354
| jq --unbuffered -r '.assets[].browser_download_url'
2455
}
2556

26-
fetch_asset_metadata() {
27-
local bearer_token="$1"
28-
local repo="$2"
29-
local regex="$3"
57+
asset_name() { jq -r '.[] | .name' <<< "$1" ; }
3058

31-
curl "$bearer_token" "" "https://api.github.com/repos/${repo}/releases/latest" \
32-
| jq --unbuffered -r --arg regex "$regex" '.assets[] | select(.browser_download_url|test($regex)) | { url, name }'
33-
}
59+
asset_url() { jq -r '.[] | .url' <<< "$1" ; }
60+
61+
asset_print_browser_download_url() { jq -r '.[] | .browser_download_url' <<< "$1" ; }
3462

3563
select_single_download_url() {
36-
local bearer_token="$1"
64+
local auth_token="$1"
3765
local repo="$2"
3866
local regex="$3"
3967

40-
matches="$(curl "$bearer_token" "" "https://api.github.com/repos/${repo}/releases/latest" \
41-
| jq --unbuffered -r --arg regex "$regex" '.assets[].browser_download_url | select(.|test($regex))')"
68+
matches="$(curl "$auth_token" "" "https://api.github.com/repos/${repo}/releases/latest" \
69+
| jq --unbuffered -r --arg regex "$regex" '[ .assets[] | select(.browser_download_url|test($regex)) | { url, name, browser_download_url } ]')"
4270

71+
local length
4372
if [[ -z $matches ]]; then
44-
lines="0"
73+
length="0"
4574
else
46-
lines="$(wc -l <<< "$matches" | tr -d '[:blank:]')"
75+
length="$(jq length <<< "$matches")"
4776
fi
4877

49-
if (( lines != 1 )); then
50-
echo "found $lines matches, refine the pattern '$regex' until exactly one match is found" >&2
51-
if (( lines > 0 )); then
52-
printf "\n%s\n" "$matches" >&2
78+
if (( length != 1 )); then
79+
echo "found $length matches, refine the pattern '$regex' until exactly one match is found" >&2
80+
if (( length > 0 )); then
81+
asset_print_browser_download_url "$matches" >&2
5382
fi
5483
exit 1
5584
else
56-
printf "%s\n" "$matches"
85+
printf '%s' "$matches"
5786
fi
5887
}
5988

6089
fetch_release() {
61-
local bearer_token="$1"
62-
local repo="$2"
63-
local regex_pattern="$3"
64-
local output="$4"
65-
66-
local asset asset_url
67-
asset="$(fetch_asset_metadata "$bearer_token" "$repo" "$regex_pattern")"
68-
asset_url="$(jq -r .url <<< "$asset")"
69-
70-
if [[ -z $output ]]; then
71-
output="$(jq -r .name <<< "$asset")"
72-
fi
90+
local auth_token="$1"
91+
local asset="$2"
92+
local output="$3"
7393

74-
curl "$bearer_token" "application/octet-stream" -o "$output" "$asset_url"
94+
curl "$auth_token" "application/octet-stream" -o "${output:-$(asset_name "$asset")}" "$(asset_url "$asset")"
7595
}
7696

7797
list_files() {
78-
local bearer_token="$1"
79-
local repo="$2"
80-
local regex_pattern="$3"
81-
local include_glob_pattern="$4"
82-
83-
local asset asset_url
84-
asset="$(fetch_asset_metadata "$bearer_token" "$repo" "$regex_pattern")"
85-
asset_url="$(jq -r .url <<< "$asset")"
98+
local auth_token="$1"
99+
local asset="$2"
100+
local include_glob_pattern="$3"
86101

87-
curl "$bearer_token" "application/octet-stream" "$asset_url" \
102+
curl "$auth_token" "application/octet-stream" "$(asset_url "$asset")" \
88103
| bsdtar -t --include "$include_glob_pattern"
89104
}
90105

91106
extract_files() {
92-
local bearer_token="$1"
93-
local repo="$2"
94-
local regex_pattern="$3"
95-
local include_glob_pattern="$4"
96-
local strip_components="$5"
97-
local directory="$6"
107+
local auth_token="$1"
108+
local asset="$2"
109+
local include_glob_pattern="$3"
110+
local strip_components="$4"
111+
local directory="$5"
98112

99-
local asset asset_url
100-
asset="$(fetch_asset_metadata "$bearer_token" "$repo" "$regex_pattern")"
101-
asset_url="$(jq -r .url <<< "$asset")"
102-
103-
curl "$bearer_token" "application/octet-stream" "$asset_url" \
113+
curl "$auth_token" "application/octet-stream" "$(asset_url "$asset")" \
104114
| bsdtar -x --include "$include_glob_pattern" --strip-components "$strip_components" -C "$directory"
105115
}
106116

107117
usage() {
108118
cat <<'EOF'
109119
Usage:
110120
111-
github-latest-release [-b <BEARER_TOKEN>] -r <OWNER/REPO> -l -p [REGEX]
112-
github-latest-release [-b <BEARER_TOKEN>] -r <OWNER/REPO> -f -p <REGEX> -o [OUTPUT]
113-
github-latest-release [-b <BEARER_TOKEN>] -r <OWNER/REPO> -t -p <REGEX> \
121+
github-latest-release [-a <auth_token>] -r <OWNER/REPO> -l -p [REGEX]
122+
github-latest-release [-a <auth_token>] -r <OWNER/REPO> -f -p <REGEX> -o [OUTPUT]
123+
github-latest-release [-a <auth_token>] -r <OWNER/REPO> -t -p <REGEX> \
114124
-i [INCLUDE_GLOB]
115-
github-latest-release [-b <BEARER_TOKEN>] -r <OWNER/REPO> -x -p <REGEX> \
125+
github-latest-release [-a <auth_token>] -r <OWNER/REPO> -x -p <REGEX> \
116126
-i [INCLUDE_GLOB] -s [STRIP_COMPONENTS] -c [DIRECTORY]
117127
118128
Options:
119129
120-
-b <BEARER_TOKEN>
130+
-a <auth_token>
121131
Authorization token.
122132
123133
Example:
124-
github-latest-release -b "$(gh auth token)" -r enterprise/repo -l
134+
github-latest-release -a "$(gh auth token)" -r enterprise/repo -l
125135
126136
-r <OWNER/REPO>
127137
The github repo identifier. The 'owner/repo' part of 'https://github.com/owner/repo'
@@ -179,10 +189,15 @@ main() {
179189
local include_glob_pattern="*"
180190
local strip_components="0"
181191
local directory="."
182-
local bearer_token=""
192+
local auth_token="${GITHUB_AUTH_TOKEN:-}"
193+
local github_app_client_id="${GITHUB_APP_CLIENT_ID:-}"
194+
local github_app_installation_id="${GITHUB_APP_INSTALLATION_ID:-}"
195+
local github_app_private_key="${GITHUB_APP_PRIVATE_KEY:-}"
196+
local jwt_token=""
183197
local output=""
198+
local asset=""
184199

185-
while getopts ":r:lftxp:i:s:c:b:o:h" opt; do
200+
while getopts ":r:lftxp:i:s:c:a:o:h" opt; do
186201
case $opt in
187202
r) repo="$OPTARG" ;;
188203
l) list_url="1" ;;
@@ -193,7 +208,7 @@ main() {
193208
i) include_glob_pattern="$OPTARG" ;;
194209
s) strip_components="$OPTARG" ;;
195210
c) directory="$OPTARG" ;;
196-
b) bearer_token="$OPTARG" ;;
211+
a) auth_token="$OPTARG" ;;
197212
o) output="$OPTARG" ;;
198213
h) usage; exit 0 ;;
199214
:) echo "Error: -${OPTARG} requires an argument" >&2 ;;
@@ -206,24 +221,30 @@ main() {
206221
exit 1
207222
fi
208223

224+
if [[ -n $github_app_client_id && -n $github_app_installation_id && -n $github_app_private_key ]]; then
225+
jwt_token="$(jwt "$github_app_client_id" "$github_app_private_key")"
226+
auth_token="$(fetch_installation_access_token "$github_app_installation_id" "$jwt_token")"
227+
fi
228+
209229
if [[ -n $list_url ]]; then
210230
if [[ -n $regex_pattern ]]; then
211-
select_single_download_url "$bearer_token" "$repo" "$regex_pattern"
231+
asset="$(select_single_download_url "$auth_token" "$repo" "$regex_pattern")"
232+
asset_print_browser_download_url "$asset"
212233
else
213-
list_download_urls "$bearer_token" "$repo"
234+
list_download_urls "$auth_token" "$repo"
214235
fi
215236

216237
elif [[ -n $fetch ]]; then
217-
select_single_download_url "$bearer_token" "$repo" "$regex_pattern" >/dev/null
218-
fetch_release "$bearer_token" "$repo" "$regex_pattern" "$output"
238+
asset="$(select_single_download_url "$auth_token" "$repo" "$regex_pattern")"
239+
fetch_release "$auth_token" "$asset" "$output"
219240

220241
elif [[ -n $list_files ]]; then
221-
select_single_download_url "$bearer_token" "$repo" "$regex_pattern" >/dev/null
222-
list_files "$bearer_token" "$repo" "$regex_pattern" "$include_glob_pattern"
242+
asset="$(select_single_download_url "$auth_token" "$repo" "$regex_pattern")"
243+
list_files "$auth_token" "$repo" "$regex_pattern" "$include_glob_pattern"
223244

224245
elif [[ -n $extract ]]; then
225-
select_single_download_url "$bearer_token" "$repo" "$regex_pattern" >/dev/null
226-
extract_files "$bearer_token" "$repo" "$regex_pattern" "$include_glob_pattern" "$strip_components" "$directory"
246+
asset="$(select_single_download_url "$auth_token" "$repo" "$regex_pattern")"
247+
extract_files "$auth_token" "$asset" "$include_glob_pattern" "$strip_components" "$directory"
227248
else
228249
usage
229250
exit 1

0 commit comments

Comments
 (0)