Skip to content

Commit 0ff5a95

Browse files
committed
Add scripts for GitHub repository management
- Implement `github-enable-issues.sh` to enable issues on all repositories in a GitHub organization that currently have it disabled, with support for dry-run mode. - Create `github-get-public-repos.sh` to list all public repositories across organizations in a GitHub Cloud Enterprise account and generate a timestamped CSV report. - Add `github-get-repo-list.sh` to retrieve and export a list of repositories for a specified organization, including details such as visibility and creation dates. - Introduce `github-migrate-internal-repos-to-private.sh` to convert internal repositories to private within a specified organization.
1 parent f5a2607 commit 0ff5a95

20 files changed

Lines changed: 3213 additions & 29 deletions

File tree

‎.gitignore‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Local environment files
2+
.envrc
3+
4+
# Generated report artifacts
5+
**/reports/*.csv
6+
**/reports/*.txt
7+
8+
# Python bytecode/cache
9+
**/__pycache__/
10+
**/*.pyc
Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
#!/bin/bash
2+
set -euo pipefail
3+
4+
###
5+
## GitHub Enterprise Team Org Role Assignment
6+
## Assigns the built-in "All-repository read" organization role to the
7+
## configured enterprise team in every org in the enterprise.
8+
##
9+
## This org-level role grants read access to all current AND future
10+
## repositories, so the assignment only needs to be made once per org.
11+
###
12+
13+
###
14+
## GLOBAL VARIABLES
15+
###
16+
GITHUB_TOKEN=${GITHUB_TOKEN:-''}
17+
# GitHub Enterprise Cloud slug (visible in your enterprise URL: github.com/enterprises/{slug})
18+
ENTERPRISE=${ENTERPRISE:-''}
19+
API_URL_PREFIX=${API_URL_PREFIX:-'https://api.github.com'}
20+
# Slug used in the org-roles API - enterprise teams use name WITHOUT the "ent:" prefix
21+
ENTERPRISE_TEAM_SLUG=${ENTERPRISE_TEAM_SLUG:-''}
22+
# API name of the built-in org role that grants read on all repositories
23+
ALL_REPO_READ_ROLE_NAME=${ALL_REPO_READ_ROLE_NAME:-'all_repo_read'}
24+
25+
###
26+
## Color codes for output
27+
###
28+
RED='\033[0;31m'
29+
GREEN='\033[0;32m'
30+
YELLOW='\033[1;33m'
31+
BLUE='\033[0;34m'
32+
NC='\033[0m'
33+
34+
print_status() { echo -e "${BLUE}[INFO]${NC} $1"; }
35+
print_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
36+
print_warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; }
37+
print_error() { echo -e "${RED}[ERROR]${NC} $1"; }
38+
39+
# Check if GITHUB_TOKEN is set
40+
if [ -z "${GITHUB_TOKEN}" ]; then
41+
print_error "GITHUB_TOKEN is empty. Please set your token and try again"
42+
exit 1
43+
fi
44+
45+
# Check if ENTERPRISE is set
46+
if [ -z "${ENTERPRISE}" ]; then
47+
print_error "ENTERPRISE is empty. Please set the enterprise slug and try again"
48+
exit 1
49+
fi
50+
51+
# Check if ENTERPRISE_TEAM_SLUG is set
52+
if [ -z "${ENTERPRISE_TEAM_SLUG}" ]; then
53+
print_error "ENTERPRISE_TEAM_SLUG is empty. Please set the enterprise team slug and try again"
54+
exit 1
55+
fi
56+
57+
# Check if jq is installed
58+
if ! command -v jq &> /dev/null; then
59+
print_error "jq is required but not installed. Install it and try again."
60+
exit 1
61+
fi
62+
63+
print_status "Validating GitHub token..."
64+
65+
# Validate GITHUB_TOKEN by calling GitHub API
66+
RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: token $GITHUB_TOKEN" "${API_URL_PREFIX}/user")
67+
68+
if [ "$RESPONSE" -ne 200 ]; then
69+
print_error "GITHUB_TOKEN is invalid or does not have required permissions."
70+
exit 1
71+
fi
72+
73+
print_success "GitHub token validated successfully"
74+
print_status "Enterprise: ${ENTERPRISE}"
75+
print_status "Enterprise team slug: ${ENTERPRISE_TEAM_SLUG}"
76+
print_status "Org role to assign: ${ALL_REPO_READ_ROLE_NAME}"
77+
78+
###
79+
## Fetch all organization logins in the enterprise via GraphQL (cursor-based pagination)
80+
###
81+
get_enterprise_orgs () {
82+
local orgs=()
83+
local cursor=""
84+
local has_next_page="true"
85+
86+
while [ "${has_next_page}" = "true" ]; do
87+
local after_clause=""
88+
if [ -n "${cursor}" ]; then
89+
after_clause=", after: \\\"${cursor}\\\""
90+
fi
91+
92+
local query="{\"query\":\"{ enterprise(slug: \\\"${ENTERPRISE}\\\") { organizations(first: 100${after_clause}) { nodes { login } pageInfo { hasNextPage endCursor } } } }\"}"
93+
94+
local response
95+
response=$(curl -s \
96+
-H "Authorization: token ${GITHUB_TOKEN}" \
97+
-H "Content-Type: application/json" \
98+
"${API_URL_PREFIX}/graphql" \
99+
-d "${query}")
100+
101+
local batch
102+
batch=$(echo "${response}" | jq -r '.data.enterprise.organizations.nodes[].login // empty' 2>/dev/null)
103+
104+
[ -z "${batch}" ] && break
105+
106+
while IFS= read -r org; do
107+
orgs+=("${org}")
108+
done <<< "${batch}"
109+
110+
has_next_page=$(echo "${response}" | jq -r '.data.enterprise.organizations.pageInfo.hasNextPage')
111+
cursor=$(echo "${response}" | jq -r '.data.enterprise.organizations.pageInfo.endCursor')
112+
done
113+
114+
printf '%s\n' "${orgs[@]}"
115+
}
116+
117+
###
118+
## Look up the numeric role_id for the all_repo_read built-in org role
119+
###
120+
get_role_id () {
121+
local org="$1"
122+
123+
local response
124+
response=$(curl -s \
125+
-H "Authorization: token ${GITHUB_TOKEN}" \
126+
-H "Accept: application/vnd.github+json" \
127+
-H "X-GitHub-Api-Version: 2022-11-28" \
128+
"${API_URL_PREFIX}/orgs/${org}/organization-roles")
129+
130+
local status
131+
status=$(echo "${response}" | jq -r '.status // empty' 2>/dev/null)
132+
if [ "${status}" = "403" ]; then
133+
echo "SAML"
134+
return
135+
fi
136+
if [ "${status}" = "404" ]; then
137+
echo "NOT_FOUND"
138+
return
139+
fi
140+
141+
echo "${response}" | jq -r --arg name "${ALL_REPO_READ_ROLE_NAME}" \
142+
'.roles[] | select(.name == $name) | .id' 2>/dev/null
143+
}
144+
145+
###
146+
## Assign the org role to the enterprise team
147+
###
148+
process_org () {
149+
local org="$1"
150+
151+
print_status "========================================="
152+
print_status "Processing organization: ${org}"
153+
print_status "========================================="
154+
155+
print_status "Looking up role ID for '${ALL_REPO_READ_ROLE_NAME}'..."
156+
local role_id
157+
role_id=$(get_role_id "${org}")
158+
159+
case "${role_id}" in
160+
SAML)
161+
print_warning "Skipping ${org}: SAML SSO block — authorize token at github.com/settings/tokens"
162+
return 2
163+
;;
164+
NOT_FOUND)
165+
print_warning "Skipping ${org}: org roles not available (org may not be on Enterprise Cloud)"
166+
return 2
167+
;;
168+
"")
169+
print_error "Could not find role '${ALL_REPO_READ_ROLE_NAME}' in org ${org}. Skipping."
170+
return 1
171+
;;
172+
esac
173+
174+
print_status "Found role ID: ${role_id}"
175+
print_status "Assigning role to team '${ENTERPRISE_TEAM_SLUG}'..."
176+
177+
local http_code
178+
http_code=$(curl -s -o /dev/null -w "%{http_code}" \
179+
-X PUT \
180+
-H "Authorization: token ${GITHUB_TOKEN}" \
181+
-H "Accept: application/vnd.github+json" \
182+
-H "X-GitHub-Api-Version: 2022-11-28" \
183+
"${API_URL_PREFIX}/orgs/${org}/organization-roles/teams/${ENTERPRISE_TEAM_SLUG}/${role_id}")
184+
185+
if [ "${http_code}" -eq 204 ]; then
186+
print_success "'${ALL_REPO_READ_ROLE_NAME}' org role assigned to '${ENTERPRISE_TEAM_SLUG}' in ${org}"
187+
else
188+
print_error "Failed to assign role in ${org} (HTTP ${http_code})"
189+
return 1
190+
fi
191+
}
192+
193+
###
194+
## Main
195+
###
196+
print_status "Starting org role assignment..."
197+
print_status "Fetching organizations in enterprise: ${ENTERPRISE}..."
198+
199+
mapfile -t ORG_LIST < <(get_enterprise_orgs)
200+
201+
if [ "${#ORG_LIST[@]}" -eq 0 ]; then
202+
print_error "No organizations found in enterprise '${ENTERPRISE}'. Check the enterprise slug and token permissions."
203+
exit 1
204+
fi
205+
206+
print_status "Found ${#ORG_LIST[@]} organization(s)"
207+
echo ""
208+
209+
SUCCESS_COUNT=0
210+
SKIP_COUNT=0
211+
FAIL_COUNT=0
212+
SKIPPED_ORGS=()
213+
FAILED_ORGS=()
214+
215+
for ORG in "${ORG_LIST[@]}"; do
216+
rc=0
217+
process_org "${ORG}" || rc=$?
218+
case $rc in
219+
0) SUCCESS_COUNT=$((SUCCESS_COUNT + 1)) ;;
220+
2) SKIP_COUNT=$((SKIP_COUNT + 1)); SKIPPED_ORGS+=("${ORG}") ;;
221+
*) FAIL_COUNT=$((FAIL_COUNT + 1)); FAILED_ORGS+=("${ORG}") ;;
222+
esac
223+
done
224+
225+
echo ""
226+
print_success "Done! ${SUCCESS_COUNT} assigned, ${SKIP_COUNT} skipped (SAML/unsupported), ${FAIL_COUNT} failed."
227+
228+
if [ "${#SKIPPED_ORGS[@]}" -gt 0 ]; then
229+
echo ""
230+
print_warning "Skipped orgs (SAML SSO block or org roles unsupported):"
231+
for org in "${SKIPPED_ORGS[@]}"; do
232+
echo " - ${org}"
233+
done
234+
fi
235+
236+
if [ "${#FAILED_ORGS[@]}" -gt 0 ]; then
237+
echo ""
238+
print_error "Failed orgs (role not found or assignment error):"
239+
for org in "${FAILED_ORGS[@]}"; do
240+
echo " - ${org}"
241+
done
242+
fi
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
#!/bin/bash
2+
set -euo pipefail
3+
4+
###
5+
## GLOBAL VARIABLES
6+
###
7+
GITHUB_TOKEN=${GITHUB_TOKEN:-''}
8+
ORG=${ORG:-''}
9+
API_URL_PREFIX=${API_URL_PREFIX:-'https://api.github.com'}
10+
COLLABORATORS=${COLLABORATORS:-''} # Comma-separated usernames, e.g. alice,bob
11+
PERMISSION=${PERMISSION:-'push'} # pull|triage|push|maintain|admin
12+
REPO_NAME_REGEX=${REPO_NAME_REGEX:-''}
13+
REPO_EXCLUDE_REGEX=${REPO_EXCLUDE_REGEX:-''}
14+
15+
###
16+
## FUNCTIONS
17+
###
18+
usage() {
19+
echo "Usage: ORG=<org> GITHUB_TOKEN=<token> COLLABORATORS=<u1,u2> REPO_NAME_REGEX=<regex> $0"
20+
echo "Optional: PERMISSION=push REPO_EXCLUDE_REGEX=<regex> API_URL_PREFIX=<url>"
21+
}
22+
23+
get_repo_pagination() {
24+
local repo_pages
25+
repo_pages=$(curl -s -H "Authorization: token ${GITHUB_TOKEN}" -I "${API_URL_PREFIX}/orgs/${ORG}/repos?per_page=100" | grep -Eo '&page=[0-9]+' | grep -Eo '[0-9]+' | tail -1)
26+
echo "${repo_pages:-1}"
27+
}
28+
29+
limit_repo_pagination() {
30+
seq "$(get_repo_pagination)"
31+
}
32+
33+
validate() {
34+
if [ -z "${GITHUB_TOKEN}" ] || [ -z "${ORG}" ] || [ -z "${COLLABORATORS}" ] || [ -z "${REPO_NAME_REGEX}" ]; then
35+
echo "Error: missing required variable(s)."
36+
usage
37+
exit 1
38+
fi
39+
40+
if ! command -v jq > /dev/null 2>&1; then
41+
echo "Error: jq is required but not installed."
42+
exit 1
43+
fi
44+
45+
local auth_status
46+
auth_status=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: token ${GITHUB_TOKEN}" "${API_URL_PREFIX}/user")
47+
if [ "${auth_status}" -ne 200 ]; then
48+
echo "Error: invalid GITHUB_TOKEN or insufficient permissions (HTTP ${auth_status})."
49+
exit 1
50+
fi
51+
}
52+
53+
get_matching_repos() {
54+
local page repos_json
55+
for page in $(limit_repo_pagination); do
56+
repos_json=$(curl -s -H "Authorization: token ${GITHUB_TOKEN}" -H "Accept: application/json" "${API_URL_PREFIX}/orgs/${ORG}/repos?type=all&per_page=100&page=${page}&sort=full_name")
57+
58+
if [ -n "${REPO_EXCLUDE_REGEX}" ]; then
59+
while IFS= read -r repo_name; do
60+
[ -n "${repo_name}" ] && echo "${repo_name}"
61+
done < <(echo "${repos_json}" | jq -r --arg include "${REPO_NAME_REGEX}" --arg exclude "${REPO_EXCLUDE_REGEX}" '.[] | select(has("name") and (.name | test($include)) and (.name | test($exclude) | not)) | .name')
62+
else
63+
while IFS= read -r repo_name; do
64+
[ -n "${repo_name}" ] && echo "${repo_name}"
65+
done < <(echo "${repos_json}" | jq -r --arg include "${REPO_NAME_REGEX}" '.[] | select(has("name") and (.name | test($include))) | .name')
66+
fi
67+
done
68+
}
69+
70+
add_collaborators_to_repos() {
71+
local repo collaborator response
72+
local collaborators_space
73+
collaborators_space=$(echo "${COLLABORATORS}" | tr ',' ' ')
74+
75+
while IFS= read -r repo; do
76+
[ -z "${repo}" ] && continue
77+
for collaborator in ${collaborators_space}; do
78+
response=$(curl -s -o /dev/null -w "%{http_code}" -X PUT -H "Authorization: token ${GITHUB_TOKEN}" -H "Accept: application/vnd.github.v3+json" "${API_URL_PREFIX}/repos/${ORG}/${repo}/collaborators/${collaborator}" -d "{\"permission\": \"${PERMISSION}\"}")
79+
80+
if [[ "${response}" =~ ^2 ]]; then
81+
echo "Added '${collaborator}' to '${repo}' with '${PERMISSION}' permission"
82+
else
83+
echo "Failed to add '${collaborator}' to '${repo}' (HTTP ${response})"
84+
fi
85+
done
86+
done < <(get_matching_repos)
87+
}
88+
89+
###
90+
## MAIN PROGRAM
91+
###
92+
validate
93+
echo "Matching repositories:"
94+
get_matching_repos
95+
96+
echo "Applying collaborator permissions..."
97+
add_collaborators_to_repos
98+
99+
echo "Done"

0 commit comments

Comments
 (0)