#!/usr/bin/env bash # ============================================================================= # github-common.sh # # Shared utility functions for GitHub API scripts. Source this library from # any script; do not execute it directly. # # Usage (from a script two directory levels deep): # SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # # shellcheck source=../../lib/github-common.sh # source "${SCRIPT_DIR}/../../lib/github-common.sh" # # Provides: # print_status / print_success / print_warning / print_error # require_env_var [description] — exit if variable is unset/empty # require_command — exit if command not found # configure_gh_auth [scope_hint] — bridge GITHUB_TOKEN→GH_TOKEN or verify gh session # validate_github_token [bearer] — verify GITHUB_TOKEN via /user endpoint # validate_token — verify a secondary token variable # validate_slug [label] — exit if value contains unsafe chars # is_bsd_date — 0 if `date` is BSD-style (macOS), 1 for GNU (Linux) # csv_escape — doubles embedded double-quotes for a quoted CSV field # gh_api [--api-version V] [curl args…] — Bearer-auth REST helper with retry; # returns "__404__"/"__422__" (exit 0) for those codes # gh_api_paginate [filter] [version] — paginated REST, follows Link headers; # silently returns empty output on 404/422 # get_repo_page_count — total page count for a paginated REST endpoint # _paginate_orgs_endpoint — page through an org list (internal) # _graphql_enterprise_orgs — GraphQL cursor-based enterprise orgs (internal) # get_enterprise_orgs — three-tier enterprise org resolver # # Token auto-resolution (at source time): # If GITHUB_TOKEN is unset and gh CLI is available, the token is automatically # resolved from the active gh auth session so curl-based scripts work with # either a GITHUB_TOKEN env var or a gh CLI session. GH_TOKEN is also kept # in sync with GITHUB_TOKEN so gh CLI calls use the same credential. # ============================================================================= ### ## COLOR CODES ### RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' ### ## PRINT FUNCTIONS ### print_status() { echo -e "${BLUE}[INFO]${NC} $1"; } print_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; } print_warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; } print_error() { echo -e "${RED}[ERROR]${NC} $1" >&2; } err() { print_error "$*"; exit 1; } ### ## require_env_var [description] ## Exits with status 1 if the named variable is empty or unset. ### require_env_var() { local var_name="$1" local description="${2:-${var_name}}" local var_value var_value="${!var_name}" if [ -z "${var_value}" ]; then print_error "${description} is empty. Please set ${var_name} and try again" exit 1 fi } ### ## require_command [install_hint] ## Exits with status 1 if the command is not found in PATH. ### require_command() { local cmd="$1" local hint="${2:-${cmd}}" if ! command -v "${cmd}" > /dev/null 2>&1; then print_error "${cmd} is not installed. Please install ${hint} and try again" exit 1 fi } ### ## configure_gh_auth [scope_hint] ## Bridges GITHUB_TOKEN into the gh CLI so scripts can accept either a token ## or an active gh auth session interchangeably. ## ## When GITHUB_TOKEN is set: exports it as GH_TOKEN (gh CLI reads this env var). ## When GITHUB_TOKEN is not set: verifies gh auth status and exits with an error ## if no session is active. scope_hint is appended to the error message. ## ## Usage: ## configure_gh_auth "gh auth login" ## configure_gh_auth 'gh auth refresh --scopes "read:enterprise,manage_billing:enterprise"' ### configure_gh_auth() { local scope_hint="${1:-gh auth login}" if [[ -n "${GITHUB_TOKEN:-}" ]]; then export GH_TOKEN="$GITHUB_TOKEN" elif ! gh auth status >/dev/null 2>&1; then print_error "Not authenticated. Set GITHUB_TOKEN or run: ${scope_hint}" exit 1 fi } ### ## validate_token [bearer] ## Validates the token stored in the named variable by calling the /user endpoint. ## Pass "bearer" as second argument to use Bearer auth scheme (default: token). ## Requires API_URL_PREFIX to be set. ### validate_token() { local token_var="$1" local auth_scheme="token" [ "${2:-}" = "bearer" ] && auth_scheme="Bearer" local token_value token_value="${!token_var}" local response response=$(curl -s -o /dev/null -w "%{http_code}" \ -H "Authorization: ${auth_scheme} ${token_value}" \ "${API_URL_PREFIX}/user") if [ "${response}" -ne 200 ]; then print_error "${token_var} is invalid or does not have required permissions (HTTP ${response})" exit 1 fi } ### ## validate_github_token [bearer] ## Convenience wrapper: validates GITHUB_TOKEN via the /user endpoint. ## Also warns if API_URL_PREFIX does not look like a GitHub endpoint. ### validate_github_token() { case "${API_URL_PREFIX}" in https://api.github.com*|https://*.github.com*) ;; *) print_warning "API_URL_PREFIX '${API_URL_PREFIX}' does not look like a GitHub API endpoint." ;; esac validate_token GITHUB_TOKEN "${1:-}" } ### ## get_repo_page_count ## Returns the total number of pages for a paginated GitHub API list endpoint. ## Example: get_repo_page_count "${API_URL_PREFIX}/orgs/${ORG}/repos?per_page=100" ### get_repo_page_count() { local url="$1" local pages pages=$(curl -s -H "Authorization: token ${GITHUB_TOKEN}" -I "${url}" \ | grep -Eo '&page=[0-9]+' | grep -Eo '[0-9]+' | tail -1) echo "${pages:-1}" } ### ## is_bsd_date ## Returns 0 if the system `date` is BSD-style (macOS), 1 for GNU (Linux). ## Use to branch between `date -v` and `date -d` syntax. ### is_bsd_date() { date -v-1d > /dev/null 2>&1 } ### ## csv_escape ## Doubles embedded double-quotes so a value is safe to wrap in a quoted ## CSV field (e.g. printf '"%s"' "$(csv_escape "$value")"). ### csv_escape() { echo "$1" | sed 's/"/""/g' } ### ## validate_slug