|
14 | 14 | # print_status / print_success / print_warning / print_error |
15 | 15 | # require_env_var <VAR> [description] — exit if variable is unset/empty |
16 | 16 | # require_command <cmd> — exit if command not found |
| 17 | +# configure_gh_auth [scope_hint] — bridge GITHUB_TOKEN→GH_TOKEN or verify gh session |
17 | 18 | # validate_github_token [bearer] — verify token via /user endpoint |
| 19 | +# |
| 20 | +# Token auto-resolution (at source time): |
| 21 | +# If GITHUB_TOKEN is unset and gh CLI is available, the token is automatically |
| 22 | +# resolved from the active gh auth session so curl-based scripts work with |
| 23 | +# either a GITHUB_TOKEN env var or a gh CLI session. |
18 | 24 | # validate_token <VAR_NAME> — verify a secondary token variable |
19 | 25 | # validate_slug <value> [label] — exit if value contains unsafe chars |
20 | 26 | # gh_api <path|url> [curl args...] — Bearer-auth REST helper with retry |
| 27 | +# gh_api_paginate <path> [filter] [version] — paginated REST, follows Link headers |
21 | 28 | # _paginate_orgs_endpoint <filter> <url_tpl> — page through an org list |
22 | 29 | # _graphql_enterprise_orgs — GraphQL cursor-based enterprise orgs |
23 | 30 | # get_enterprise_orgs — three-tier enterprise org resolver |
@@ -68,6 +75,29 @@ require_command() { |
68 | 75 | fi |
69 | 76 | } |
70 | 77 |
|
| 78 | +### |
| 79 | +## configure_gh_auth [scope_hint] |
| 80 | +## Bridges GITHUB_TOKEN into the gh CLI so scripts can accept either a token |
| 81 | +## or an active gh auth session interchangeably. |
| 82 | +## |
| 83 | +## When GITHUB_TOKEN is set: exports it as GH_TOKEN (gh CLI reads this env var). |
| 84 | +## When GITHUB_TOKEN is not set: verifies gh auth status and exits with an error |
| 85 | +## if no session is active. scope_hint is appended to the error message. |
| 86 | +## |
| 87 | +## Usage: |
| 88 | +## configure_gh_auth "gh auth login" |
| 89 | +## configure_gh_auth 'gh auth refresh --scopes "read:enterprise,manage_billing:enterprise"' |
| 90 | +### |
| 91 | +configure_gh_auth() { |
| 92 | + local scope_hint="${1:-gh auth login}" |
| 93 | + if [[ -n "${GITHUB_TOKEN:-}" ]]; then |
| 94 | + export GH_TOKEN="$GITHUB_TOKEN" |
| 95 | + elif ! gh auth status >/dev/null 2>&1; then |
| 96 | + print_error "Not authenticated. Set GITHUB_TOKEN or run: ${scope_hint}" |
| 97 | + exit 1 |
| 98 | + fi |
| 99 | +} |
| 100 | + |
71 | 101 | ### |
72 | 102 | ## validate_token <TOKEN_VAR_NAME> [bearer] |
73 | 103 | ## Validates the token stored in the named variable by calling the /user endpoint. |
@@ -174,6 +204,73 @@ gh_api() { |
174 | 204 | return 1 |
175 | 205 | } |
176 | 206 |
|
| 207 | +### |
| 208 | +## gh_api_paginate <path_or_url> [jq_filter] [api_version] |
| 209 | +## Paginated GitHub REST API helper using Link-header following. |
| 210 | +## Outputs each page's items (filtered by jq_filter) to stdout, one item per |
| 211 | +## line. Pipe the output to: jq -s '.' to get a JSON array of all items. |
| 212 | +## jq -s '. // []' to get [] when the endpoint 404s. |
| 213 | +## jq_filter defaults to .[] (one item per array element). |
| 214 | +## api_version defaults to 2022-11-28. |
| 215 | +## Returns 0 silently on 404/422 (empty output); returns 1 after 5 failed attempts. |
| 216 | +### |
| 217 | +gh_api_paginate() { |
| 218 | + local url="$1" |
| 219 | + local jq_filter="${2:-.[]}" |
| 220 | + local api_version="${3:-2022-11-28}" |
| 221 | + [[ "${url}" == http* ]] || url="${API_URL_PREFIX}${url}" |
| 222 | + |
| 223 | + local _tmp_headers _tmp_body _http_code _attempt _next_url |
| 224 | + _tmp_headers=$(mktemp) |
| 225 | + _tmp_body=$(mktemp) |
| 226 | + |
| 227 | + while [[ -n "${url}" ]]; do |
| 228 | + _http_code="" |
| 229 | + for _attempt in 1 2 3 4 5; do |
| 230 | + _http_code=$(curl -s \ |
| 231 | + -D "${_tmp_headers}" \ |
| 232 | + -o "${_tmp_body}" \ |
| 233 | + -w "%{http_code}" \ |
| 234 | + -H "Authorization: Bearer ${GITHUB_TOKEN}" \ |
| 235 | + -H "Accept: application/vnd.github+json" \ |
| 236 | + -H "X-GitHub-Api-Version: ${api_version}" \ |
| 237 | + "${url}") |
| 238 | + case "${_http_code}" in |
| 239 | + 200) break ;; |
| 240 | + 404|422) |
| 241 | + rm -f "${_tmp_headers}" "${_tmp_body}" |
| 242 | + return 0 |
| 243 | + ;; |
| 244 | + 403|429) |
| 245 | + print_warning "Rate limited (HTTP ${_http_code}). Sleeping 60s before retry ${_attempt}/5..." |
| 246 | + sleep 60 |
| 247 | + ;; |
| 248 | + *) |
| 249 | + print_warning "HTTP ${_http_code} for ${url} (attempt ${_attempt}/5)" |
| 250 | + sleep 5 |
| 251 | + ;; |
| 252 | + esac |
| 253 | + done |
| 254 | + |
| 255 | + if [[ "${_http_code}" != "200" ]]; then |
| 256 | + rm -f "${_tmp_headers}" "${_tmp_body}" |
| 257 | + print_error "Failed to fetch ${url} after 5 attempts" |
| 258 | + return 1 |
| 259 | + fi |
| 260 | + |
| 261 | + jq -rc "${jq_filter}" "${_tmp_body}" 2>/dev/null || true |
| 262 | + |
| 263 | + # Follow Link: <next-url>; rel="next" to the next page |
| 264 | + _next_url=$(grep -i "^link:" "${_tmp_headers}" \ |
| 265 | + | grep -o '<[^>]*>; rel="next"' \ |
| 266 | + | sed 's/<\([^>]*\)>.*/\1/' \ |
| 267 | + || true) |
| 268 | + url="${_next_url}" |
| 269 | + done |
| 270 | + |
| 271 | + rm -f "${_tmp_headers}" "${_tmp_body}" |
| 272 | +} |
| 273 | + |
177 | 274 | ### |
178 | 275 | ## _paginate_orgs_endpoint <jq_filter> <url_template> |
179 | 276 | ## Internal helper: pages through an org-list REST endpoint, printing one |
@@ -293,3 +390,21 @@ get_enterprise_orgs() { |
293 | 390 | '.[].login' \ |
294 | 391 | "/user/orgs?per_page=100&page=PAGE" |
295 | 392 | } |
| 393 | + |
| 394 | +### |
| 395 | +## Token auto-resolution (runs once at source time) |
| 396 | +## If GITHUB_TOKEN is not set, attempt to derive it from an active gh CLI |
| 397 | +## auth session. This allows scripts that use GITHUB_TOKEN with curl to work |
| 398 | +## with gh CLI auth as an alternative to an explicit token. |
| 399 | +## Scripts should still call require_env_var GITHUB_TOKEN or |
| 400 | +## validate_github_token to fail fast with a clear message if neither source |
| 401 | +## provides a token. |
| 402 | +### |
| 403 | +if [[ -z "${GITHUB_TOKEN:-}" ]] && command -v gh &>/dev/null; then |
| 404 | + _gh_resolved_token=$(gh auth token 2>/dev/null) || true |
| 405 | + if [[ -n "${_gh_resolved_token:-}" ]]; then |
| 406 | + GITHUB_TOKEN="$_gh_resolved_token" |
| 407 | + export GITHUB_TOKEN |
| 408 | + fi |
| 409 | + unset _gh_resolved_token |
| 410 | +fi |
0 commit comments