diff --git a/assign-repo-role-to-team.sh b/assign-repo-role-to-team.sh new file mode 100755 index 00000000..82ed1bdf --- /dev/null +++ b/assign-repo-role-to-team.sh @@ -0,0 +1,70 @@ +#!/bin/bash + +. ./.gh-api-examples.conf + +# https://docs.github.com/en/rest/teams/teams?apiVersion=2022-11-28#add-or-update-team-repository-permissions + +if [ -z "$GITHUB_TOKEN" ]; then + echo "GITHUB_TOKEN is not set, please provide a PAT with admin:enterprise scope." + exit 1 +fi + +if [ -n "$1" ]; then + ending_org=$1 +fi + +if [ -n "$2" ]; then + starting_org=$2 +fi + +list_enterprise_team_members_output=$(./list-enterprise-team-members.sh "$team") +if [ $? -ne 0 ]; then + echo $list_enterprise_team_members_output + exit 1 +fi +usernames=$list_enterprise_team_members_output +app_installs=$(./tiny-list-app-installations.sh) + +repo=${repo:-"private-repo-1"} + +# Sort app installations by account login and iterate +echo "$app_installs" | jq -c '. | sort_by(.account.login) | .[]' | while read -r install; do + install_id=$(echo "$install" | jq -r '.id') + org=$(echo "$install" | jq -r '.account.login') + if [ -z "$org" ] || [ "$org" = "null" ]; then + continue + fi + + if [ -n "$starting_org" ]; then + if [[ "$org" < "$starting_org" ]]; then + continue + fi + fi + + if [ -n "$ending_org" ]; then + if [[ "$org" > "$ending_org" ]]; then + continue + fi + fi + + echo "➡️ Assigning repo permissions for $org (install_id: $install_id)" + GITHUB_TOKEN=$(./ent-call-get-installation-token.sh $install_id | jq -r '.token') + + response=$(curl -s -w "\n%{http_code}" -X PUT \ + -H "Authorization: Bearer $GITHUB_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + -d "{\"permission\":\"triage\"}" \ + "$GITHUB_API_BASE_URL/orgs/$org/teams/$team/repos/$org/$repo") + http_code=$(echo "$response" | tail -n1) + json_body=$(echo "$response" | sed '$d') + + if [[ "$http_code" == "201" ]] || [[ "$http_code" == "204" ]]; then + echo " ✅ Repo permissions assigned to $team" + else + echo " ❌ Failed to assign repo permissions (HTTP $http_code) for $team" + echo " Response: $json_body" + fi + wait +done + +echo "🎉 Done assigning repo permissions." diff --git a/assign-repo-role-to-user.sh b/assign-repo-role-to-user.sh new file mode 100755 index 00000000..425484b8 --- /dev/null +++ b/assign-repo-role-to-user.sh @@ -0,0 +1,75 @@ +#!/bin/bash + +. ./.gh-api-examples.conf + +# https://docs.github.com/en/rest/collaborators/collaborators?apiVersion=2022-11-28#add-a-repository-collaborator + +if [ -z "$GITHUB_TOKEN" ]; then + echo "GITHUB_TOKEN is not set, please provide a PAT with admin:enterprise scope." + exit 1 +fi + +if [ -n "$1" ]; then + ending_org=$1 +fi + +if [ -n "$2" ]; then + starting_org=$2 +fi + +list_enterprise_team_members_output=$(./list-enterprise-team-members.sh "$team") +if [ $? -ne 0 ]; then + echo $list_enterprise_team_members_output + exit 1 +fi +usernames=$list_enterprise_team_members_output +app_installs=$(./tiny-list-app-installations.sh) + +repo=${repo:-"private-repo-1"} + +# Iterate over each installation +echo "$app_installs" | jq -c '. | sort_by(.account.login) | .[]' | while read -r install; do + install_id=$(echo "$install" | jq -r '.id') + org=$(echo "$install" | jq -r '.account.login') + if [ -z "$org" ] || [ "$org" = "null" ]; then + continue + fi + + if [ -n "$starting_org" ]; then + if [[ "$org" < "$starting_org" ]]; then + continue + fi + fi + + if [ -n "$ending_org" ]; then + if [[ "$org" > "$ending_org" ]]; then + continue + fi + fi + + echo "➡️ Assigning repo permissions for $org (install_id: $install_id)" + GITHUB_TOKEN=$(./ent-call-get-installation-token.sh $install_id | jq -r '.token') + + for username in $usernames; do + ( + response=$(curl -s -w "\n%{http_code}" -X PUT \ + -H "Authorization: Bearer $GITHUB_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + -d "{\"permission\":\"triage\"}" \ + "$GITHUB_API_BASE_URL/repos/$org/$repo/collaborators/$username") + http_code=$(echo "$response" | tail -n1) + json_body=$(echo "$response" | sed '$d') + + if [[ "$http_code" == "201" ]] || [[ "$http_code" == "204" ]]; then + echo " ✅ Repo permissions assigned to $username" + else + echo " ❌ Failed to assign repo permissions (HTTP $http_code) for $username" + #echo " Response: $json_body" + fi + ) & + sleep 0.019 + done + wait +done + +echo "🎉 Done assigning repo permissions." diff --git a/assign-roles.sh b/assign-roles.sh new file mode 100755 index 00000000..b1e94aee --- /dev/null +++ b/assign-roles.sh @@ -0,0 +1,101 @@ +#!/bin/bash + +. ./.gh-api-examples.conf + +if [ -z "$GITHUB_TOKEN" ]; then + echo "GITHUB_TOKEN is not set, please provide a PAT with admin:enterprise scope." + exit 1 +fi + +if [ -n "$1" ]; then + ending_org=$1 +fi + +if [ -n "$2" ]; then + starting_org=$2 +fi + +list_enterprise_team_members_output=$(./list-enterprise-team-members.sh "$team") +if [ $? -ne 0 ]; then + echo $list_enterprise_team_members_output + exit 1 +fi +usernames=$list_enterprise_team_members_output +app_installs=$(./tiny-list-app-installations.sh) + +# Iterate over each installation +echo "$app_installs" | jq -c '. | sort_by(.account.login) | .[]' | while read -r install; do + install_id=$(echo "$install" | jq -r '.id') + org=$(echo "$install" | jq -r '.account.login') + + if [ -z "$org" ] || [ "$org" = "null" ]; then + continue + fi + + if [ -n "$starting_org" ]; then + if [[ "$org" < "$starting_org" ]]; then + continue + fi + fi + + if [ -n "$ending_org" ]; then + if [[ "$org" > "$ending_org" ]]; then + continue + fi + fi + + echo "➡️ Assigning roles for $org (install_id: $install_id)" + GITHUB_TOKEN=$(./ent-call-get-installation-token.sh $install_id | jq -r '.token') + + # Get first org role + response=$(curl -s -w "\n%{http_code}" \ + -H "X-GitHub-Api-Version: ${github_api_version}" \ + -H "Accept: application/vnd.github.v3+json" \ + -H "Authorization: Bearer ${GITHUB_TOKEN}" \ + "${GITHUB_API_BASE_URL}/orgs/${org}/organization-roles") + http_code=$(echo "$response" | tail -n1) + json_body=$(echo "$response" | sed '$d') + if [[ "$http_code" -lt 300 ]]; then + first_role_id=$(echo "$json_body" | jq -r '.roles[0].id') + echo " ➡️ Got first role id for $org: $first_role_id" + else + echo " ❌ Failed to get roles for $org (HTTP $http_code)" + fi + + # Assign it to the team + # put /organizations/:organization_id/organization-roles/team/:team_id/:role_id + response=$(curl -s -w "\n%{http_code}" -X PUT \ + -H "X-GitHub-Api-Version: ${github_api_version}" \ + -H "Accept: application/vnd.github.v3+json" \ + -H "Authorization: Bearer ${GITHUB_TOKEN}" \ + "${GITHUB_API_BASE_URL}/orgs/${org}/organization-roles/teams/${team}/${first_role_id}") + http_code=$(echo "$response" | tail -n1) + json_body=$(echo "$response" | sed '$d') + if [[ "$http_code" -lt 300 ]]; then + echo " ✅ Assigned role to team $team in $org" + else + echo " ❌ Failed to assign role to team $team in $org (HTTP $http_code)" + fi + + # Assign to every user, its redundant but we are just using this to scale test performances + for username in $usernames; do + ( + response=$(curl -s -w "\n%{http_code}" -X PUT \ + -H "X-GitHub-Api-Version: ${github_api_version}" \ + -H "Accept: application/vnd.github.v3+json" \ + -H "Authorization: Bearer ${GITHUB_TOKEN}" \ + "${GITHUB_API_BASE_URL}/orgs/${org}/organization-roles/users/${username}/${first_role_id}") + http_code=$(echo "$response" | tail -n1) + json_body=$(echo "$response" | sed '$d') + if [[ "$http_code" -lt 300 ]]; then + echo " ✅ Assigned role to $username in $org" + else + echo " ❌ Failed to assign role to $username in $org (HTTP $http_code)" + fi + ) & + sleep 0.019 + done + wait +done + +echo "🎉 Done assigning roles." diff --git a/assign-security-manager-org-role-to-team.sh b/assign-security-manager-org-role-to-team.sh new file mode 100644 index 00000000..96595622 --- /dev/null +++ b/assign-security-manager-org-role-to-team.sh @@ -0,0 +1,36 @@ +#!/bin/bash +# This script won't work without updates to the Orgs API to support BTs. + +. ./.gh-api-examples.conf + +# https://docs.github.com/en/rest/orgs/organization-roles?apiVersion=2022-11-28#assign-an-organization-role-to-a-team + +APP_INSTALLS=$(./tiny-list-app-installations.sh) +team_slug="testing" +role_id=138 + +# Iterate over each installation +echo "$APP_INSTALLS" | jq -c '.[]' | while read -r install; do + install_id=$(echo "$install" | jq -r '.id') + org=$(echo "$install" | jq -r '.account.login') + if [ -z "$org" ] || [ "$org" = "null" ]; then + continue + fi + + echo "➡️ Assigning Security Manager role for $org (install_id: $install_id)" + GITHUB_TOKEN=$(./ent-call-get-installation-token.sh $install_id | jq -r '.token') + response=$(curl -s -w "\n%{http_code}" -X PUT \ + -H "Authorization: Bearer $GITHUB_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + "$GITHUB_API_BASE_URL/orgs/$org/organization-roles/teams/$team_slug/$role_id") + http_code=$(echo "$response" | tail -n1) + json_body=$(echo "$response" | sed '$d') + if [[ "$http_code" == "204" ]]; then + echo " ✅ Security manager role assigned to $team_slug" + else + echo " ❌ Failed to assign security manager role (HTTP $http_code)" + #echo " Response: $json_body" + fi +done + +echo "🎉 Done assigning security manager role." diff --git a/assign-security-manager-org-role-to-user.sh b/assign-security-manager-org-role-to-user.sh new file mode 100644 index 00000000..5b728c63 --- /dev/null +++ b/assign-security-manager-org-role-to-user.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +. ./.gh-api-examples.conf + +# https://docs.github.com/en/rest/orgs/organization-roles?apiVersion=2022-11-28#assign-an-organization-role-to-a-user + +APP_INSTALLS=$(./tiny-list-app-installations.sh) +user="fmacar3na-alt_tntmd2" +role_id=138 + +# Iterate over each installation +echo "$APP_INSTALLS" | jq -c '.[]' | while read -r install; do + install_id=$(echo "$install" | jq -r '.id') + org=$(echo "$install" | jq -r '.account.login') + if [ -z "$org" ] || [ "$org" = "null" ]; then + continue + fi + + echo "➡️ Assigning Security Manager role for $org (install_id: $install_id)" + GITHUB_TOKEN=$(./ent-call-get-installation-token.sh $install_id | jq -r '.token') + response=$(curl -s -w "\n%{http_code}" -X PUT \ + -H "Authorization: Bearer $GITHUB_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + "$GITHUB_API_BASE_URL/orgs/$org/organization-roles/users/$user/$role_id") + http_code=$(echo "$response" | tail -n1) + json_body=$(echo "$response" | sed '$d') + if [[ "$http_code" == "204" ]]; then + echo " ✅ Security manager role assigned to $user" + else + echo " ❌ Failed to assign security manager role (HTTP $http_code)" + #echo " Response: $json_body" + fi +done + +echo "🎉 Done assigning security manager role." diff --git a/create-repos.sh b/create-repos.sh new file mode 100755 index 00000000..c653e66e --- /dev/null +++ b/create-repos.sh @@ -0,0 +1,37 @@ +#!/bin/bash + +. ./.gh-api-examples.conf + +APP_INSTALLS=$(./tiny-list-app-installations.sh) + +# Iterate over each installation +echo "$APP_INSTALLS" | jq -c '.[]' | while read -r install; do + install_id=$(echo "$install" | jq -r '.id') + org=$(echo "$install" | jq -r '.account.login') + if [ -z "$org" ] || [ "$org" = "null" ]; then + continue + fi + + echo "➡️ Creating repos for $org (install_id: $install_id)" + GITHUB_TOKEN=$(./ent-call-get-installation-token.sh $install_id | jq -r '.token') + for repo_num in $(seq 1 10); do + ( + repo="private-repo-$repo_num" + response=$(curl -s -w "\n%{http_code}" -X POST "$GITHUB_API_BASE_URL/orgs/$org/repos" \ + -H "Authorization: Bearer $GITHUB_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + -d "{\"name\":\"$repo\",\"private\":true,\"auto_init\":true}") + http_code=$(echo "$response" | tail -n1) + json_body=$(echo "$response" | sed '$d') + if [[ "$http_code" == "201" ]]; then + echo " ✅ Created $org/$repo" + else + echo " ❌ Failed to create $org/$repo (HTTP $http_code)" + #echo " Response: $json_body" + fi + ) & + done + wait +done + +echo "🎉 Done creating repositories." diff --git a/ent-call-get-installation-token.sh b/ent-call-get-installation-token.sh index 98801676..a0c5a9a3 100755 --- a/ent-call-get-installation-token.sh +++ b/ent-call-get-installation-token.sh @@ -6,9 +6,15 @@ # rate_limits: https://docs.github.com/en/developers/apps/building-github-apps/rate-limits-for-github-apps + JWT=$(./ent-call-get-jwt.sh ${ent_app_id} 2>/dev/null) -installation_id=${ent_app_installation_id} +# Allow passing installation_id as $1, fallback to ent_app_installation_id +if [ -n "$1" ]; then + installation_id="$1" +else + installation_id="${ent_app_installation_id}" +fi curl --silent ${curl_custom_flags} \ -X POST \ diff --git a/graphql-list-enterprise-organizations.sh b/graphql-list-enterprise-organizations.sh index 386ba818..e851bfd3 100755 --- a/graphql-list-enterprise-organizations.sh +++ b/graphql-list-enterprise-organizations.sh @@ -1,28 +1,48 @@ . ./.gh-api-examples.conf -# https://docs.github.com/en/graphql/reference/objects#enterpriseorganizationmembershipconnection -# -# API Gap: This feature is not currently in the REST API for Enterprise administration https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin?apiVersion=2022-11-28 +orgs=() +after_cursor="" +while :; do -read -r -d '' graphql_script <<- EOF -{ - enterprise(slug: "$enterprise") { - organizations(first: 100) { - nodes { - name + if [ -z "$after_cursor" ]; then + after_clause="" + else + after_clause=", after: \"$after_cursor\"" + fi + + read -r -d '' graphql_script <<- EOF + { + enterprise(slug: "$enterprise") { + organizations(first: 100, after: "$after_cursor") { + nodes { + login + } + pageInfo { + hasNextPage + endCursor + } } } } -} EOF -# Escape quotes and reformat script to a single line -graphql_script="$(echo ${graphql_script//\"/\\\"})" + response=$(jq -n --arg q "$graphql_script" '{query: $q}' | \ + curl -s ${curl_custom_flags} \ + -H "Accept: application/vnd.github.v3+json" \ + -H "Authorization: Bearer ${GITHUB_TOKEN}" \ + "${GITHUB_APIV4_BASE_URL}" -d @-) + + # Extract org logins and append to orgs array + orgs=( $(echo "$response" | jq -r '.data.enterprise.organizations.nodes[].login') ) + printf "%s\n" "${orgs[@]}" + # Get pagination info + has_next=$(echo "$response" | jq -r '.data.enterprise.organizations.pageInfo.hasNextPage') + after_cursor=$(echo "$response" | jq -r '.data.enterprise.organizations.pageInfo.endCursor') -curl ${curl_custom_flags} \ - -H "Accept: application/vnd.github.v3+json" \ - -H "Authorization: Bearer ${GITHUB_TOKEN}" \ - "${GITHUB_APIV4_BASE_URL}" -d "{ \"query\": \"$graphql_script\"}" + if [ "$has_next" != "true" ]; then + break + fi +done diff --git a/install-app-on-all-orgs.sh b/install-app-on-all-orgs.sh new file mode 100755 index 00000000..04cc1d17 --- /dev/null +++ b/install-app-on-all-orgs.sh @@ -0,0 +1,37 @@ +#!/bin/bash + +. ./.gh-api-examples.conf + +repository_selection="all" + +json_file=/tmp/install-a-github-app-on-an-enterprise-owned-organization.json +jq -n \ + --arg client_id "${ent_app_client_id}" \ + --arg repository_selection "${repository_selection}" \ + '{ + client_id : $client_id, + repository_selection : $repository_selection, + }' > ${json_file} + +GITHUB_TOKEN=$(./ent-call-get-installation-token.sh | jq -r '.token') + +orgs=( $(./graphql-list-enterprise-organizations.sh) ) + +for org in "${orgs[@]}"; do + echo "➡️ Installing app for $org" + response=$(curl -s -w "\n%{http_code}" \ + -H "X-GitHub-Api-Version: ${github_api_version}" \ + -H "Accept: application/vnd.github.v3+json" \ + -H "Authorization: Bearer ${GITHUB_TOKEN}" \ + "${GITHUB_API_BASE_URL}/enterprises/${enterprise}/apps/organizations/${org}/installations" --data @${json_file}) + http_code=$(echo "$response" | tail -n1) + json_body=$(echo "$response" | sed '$d') + if [[ "$http_code" -lt 300 ]]; then + echo " ✅ Installed on $org (HTTP $http_code)" + else + echo " ❌ Failed to install on $org (HTTP $http_code)" + echo " Response: $json_body" + fi +done + +echo "🎉 Done installing app on all organizations." \ No newline at end of file diff --git a/list-enterprise-team-members.sh b/list-enterprise-team-members.sh new file mode 100755 index 00000000..c78ed4b1 --- /dev/null +++ b/list-enterprise-team-members.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +. ./.gh-api-examples.conf + +# get "/enterprises/:enterprise_id/teams/:team_id/memberships" +# This requires a PAT being set as an env variable as GITHUB_TOKEN as the API is not compatible with Apps yet + +team_id="$1" +page=1 +per_page=100 +all_logins=() + +while :; do + response=$(curl -s -w "\n%{http_code}" \ + -H "Authorization: Bearer ${GITHUB_TOKEN}" \ + "${GITHUB_API_BASE_URL}/enterprises/${enterprise}/teams/${team_id}/memberships?per_page=${per_page}&page=${page}") + http_code=$(echo "$response" | tail -n1) + json_body=$(echo "$response" | sed '$d') + if [[ "$http_code" -lt 300 ]]; then + logins=( $(echo "$json_body" | jq -r '.[].login') ) + if [ ${#logins[@]} -eq 0 ]; then + break + fi + all_logins+=("${logins[@]}") + if [ ${#logins[@]} -lt $per_page ]; then + break + fi + page=$((page + 1)) + else + echo " ❌ Failed to get enterprise team members (HTTP $http_code) $json_body" + exit 1 + fi +done + +printf "%s\n" "${all_logins[@]}" diff --git a/tiny-list-app-installations.sh b/tiny-list-app-installations.sh index a68b5324..9e14df27 100755 --- a/tiny-list-app-installations.sh +++ b/tiny-list-app-installations.sh @@ -3,7 +3,6 @@ # https://docs.github.com/en/enterprise-cloud@latest/rest/apps/apps?apiVersion=2022-11-28#list-installations-for-the-authenticated-app # GET /app/installations - # This endpoint has to be presented with a jwt # If the script is passed an argument $1 use that as the JWT if [ -z "$1" ] @@ -13,7 +12,31 @@ if [ -z "$1" ] JWT=$1 fi -curl ${curl_custom_flags} \ - -H "Authorization: Bearer ${JWT}" \ - "${GITHUB_API_BASE_URL}/app/installations" +page=1 +per_page=100 +all_results="[]" + +while :; do + response=$(curl -s ${curl_custom_flags} \ + -H "Authorization: Bearer ${JWT}" \ + "${GITHUB_API_BASE_URL}/app/installations?per_page=${per_page}&page=${page}") + # Skip page if error message is present + if [[ "$response" =~ ^\{ ]]; then + error_message=$(echo "$response" | jq -r '.message // empty') + else + error_message="" + fi + if [ "$error_message" = "Unable to complete request that contains suffixed values in the response payloads." ]; then + echo "Skipping page $page due to error: $error_message" >&2 + page=$((page + 1)) + continue + fi + count=$(echo "$response" | jq 'length') + all_results=$(printf '%s\n%s\n' "$all_results" "$response" | jq -s 'add') + if [ "$count" -lt "$per_page" ]; then + break + fi + page=$((page + 1)) +done +echo "$all_results" diff --git a/uninstall-app-on-all-orgs.sh b/uninstall-app-on-all-orgs.sh new file mode 100755 index 00000000..d18a1882 --- /dev/null +++ b/uninstall-app-on-all-orgs.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +. ./.gh-api-examples.conf + +# https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin/organization-installations?apiVersion=2022-11-28#uninstall-a-github-app-from-an-enterprise-owned-organization +# DELETE /enterprises/{enterprise}/apps/organizations/{org}/installations/{installation_id} + +APP_INSTALLS=$(./tiny-list-app-installations.sh) + +# Iterate over each installation +echo "$APP_INSTALLS" | jq -c '.[]' | while read -r install; do + install_id=$(echo "$install" | jq -r '.id') + org=$(echo "$install" | jq -r '.account.login') + if [ -z "$org" ] || [ "$org" = "null" ]; then + continue + fi + + GITHUB_TOKEN=$(./ent-call-get-installation-token.sh $install_id | jq -r '.token') + echo "➡️ Uninstalling app for $org" + response=$(curl -s -w "\n%{http_code}" \ + -X DELETE \ + -H "X-GitHub-Api-Version: ${github_api_version}" \ + -H "Accept: application/vnd.github.v3+json" \ + -H "Authorization: Bearer ${GITHUB_TOKEN}" \ + "${GITHUB_API_BASE_URL}/enterprises/${enterprise}/apps/organizations/${org}/installations/${install_id}") + http_code=$(echo "$response" | tail -n1) + json_body=$(echo "$response" | sed '$d') + if [[ "$http_code" -lt 300 ]]; then + echo " ✅ Uninstalled on $org (HTTP $http_code)" + else + echo " ❌ Failed to uninstall on $org (HTTP $http_code)" + #echo " Response: $json_body" + fi +done + +echo "🎉 Done uninstalling app on all organizations."