11#! /bin/bash
22
3- # prerequisite: curl, jq, bsdtar (libarchive-tools)
3+ # prerequisite: openssl, curl, jq, bsdtar (libarchive-tools)
44
55set -euo pipefail
66
7-
87function 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+
1849list_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
3563select_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
6089fetch_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
7797list_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
91106extract_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
107117usage () {
108118 cat << 'EOF '
109119Usage:
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
118128Options:
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