|
1 | 1 | #!/bin/bash |
2 | 2 |
|
3 | | -# gh cli's token needs to be able to admin org - run this first if it can't |
4 | | -# gh auth refresh -h github.com -s admin:org |
| 3 | +# Returns the permission for everyone who can access a repository and how they |
| 4 | +# access it (direct, team, organization) |
| 5 | +# |
| 6 | +# Uses the REST API to get accurate team role names (maintain, triage) since the |
| 7 | +# GraphQL permissionSources API only returns READ, WRITE, and ADMIN. A heuristic |
| 8 | +# is also applied to direct sources to correct MAINTAIN/TRIAGE labels. |
| 9 | +# |
| 10 | +# gh cli's token needs to be able to admin the organization - run this first if needed: |
| 11 | +# gh auth refresh -h github.com -s admin:org |
| 12 | +# |
| 13 | +# Usage: |
| 14 | +# ./get-repository-users-permission-and-source.sh <org> <repo> [affiliation] [hostname] |
| 15 | +# |
| 16 | +# affiliation can be: OUTSIDE, DIRECT, ALL (default: ALL) |
| 17 | +# hostname: GitHub hostname (default: github.com), e.g. github.example.com |
5 | 18 |
|
6 | | -# affiliation can be: OUTSIDE, DIRECT, ALL |
| 19 | +# Example output: |
| 20 | +# |
| 21 | +# USER EFFECTIVE SOURCES |
| 22 | +# joshjohanning ADMIN org-member(ADMIN), team:admin-team(WRITE), team:approver-team(WRITE) |
| 23 | +# FluffyCarlton MAINTAIN direct(MAINTAIN), org-member(READ) |
| 24 | +# joshgoldfishturtle ADMIN org-member(READ), team:compliance-team(ADMIN) |
7 | 25 |
|
8 | | -# returns the permission for everyone who can access the repo and how they access it (direct, team, org) |
9 | 26 |
|
10 | | -gh api graphql --paginate -f owner='joshjohanning-org' -f repo='ghas-demo' -f affiliation='ALL' -f query=' |
| 27 | +if [ -z "$2" ]; then |
| 28 | + echo "Usage: $0 <org> <repo> [affiliation] [hostname]" |
| 29 | + echo " affiliation: OUTSIDE, DIRECT, ALL (default: ALL)" |
| 30 | + echo " hostname: GitHub hostname (default: github.com)" |
| 31 | + exit 1 |
| 32 | +fi |
| 33 | + |
| 34 | +org="$1" |
| 35 | +repo="$2" |
| 36 | +affiliation_input="${3:-ALL}" |
| 37 | +affiliation="$(echo "$affiliation_input" | tr '[:lower:]' '[:upper:]')" |
| 38 | +hostname="${4:-github.com}" |
| 39 | + |
| 40 | +case "$affiliation" in |
| 41 | + OUTSIDE|DIRECT|ALL) |
| 42 | + ;; |
| 43 | + *) |
| 44 | + echo "Error: invalid affiliation '$affiliation_input'. Must be OUTSIDE, DIRECT, or ALL." |
| 45 | + exit 1 |
| 46 | + ;; |
| 47 | +esac |
| 48 | + |
| 49 | +# Map REST permission names (pull/push) to GraphQL-style names (READ/WRITE) |
| 50 | +map_permission() { |
| 51 | + case "$1" in |
| 52 | + pull) echo "READ" ;; |
| 53 | + triage) echo "TRIAGE" ;; |
| 54 | + push) echo "WRITE" ;; |
| 55 | + maintain) echo "MAINTAIN" ;; |
| 56 | + admin) echo "ADMIN" ;; |
| 57 | + *) echo "$1" | tr '[:lower:]' '[:upper:]' ;; |
| 58 | + esac |
| 59 | +} |
| 60 | + |
| 61 | +# Get true team permissions via REST API and build a sed command to fix labels |
| 62 | +sed_cmd="" |
| 63 | +while IFS=$'\t' read -r slug perm; do |
| 64 | + mapped=$(map_permission "$perm") |
| 65 | + sed_cmd="${sed_cmd}s/team:${slug}\([^)]*\)/team:${slug}(${mapped})/g;" |
| 66 | +done <<EOF |
| 67 | +$(gh api --hostname "$hostname" --paginate "/repos/$org/$repo/teams?per_page=100" --jq '.[] | [.slug, .permission] | @tsv') |
| 68 | +EOF |
| 69 | + |
| 70 | +# Get source details via GraphQL |
| 71 | +raw_output=$(gh api graphql --hostname "$hostname" --paginate -f owner="$org" -f repo="$repo" -f affiliation="$affiliation" -f query=' |
11 | 72 | query ($owner: String!, $repo: String!, $affiliation: CollaboratorAffiliation!, $endCursor: String) { |
12 | 73 | repository(owner:$owner, name:$repo) { |
13 | 74 | name |
@@ -44,4 +105,29 @@ query ($owner: String!, $repo: String!, $affiliation: CollaboratorAffiliation!, |
44 | 105 | } |
45 | 106 | } |
46 | 107 | } |
47 | | -}' |
| 108 | +}' --jq ' |
| 109 | + .data.repository.collaborators.edges[] | |
| 110 | + .node.login as $user | |
| 111 | + .permission as $effective | |
| 112 | + (.permissionSources | map(select(.source.type == "Organization") | .permission)) as $org_perms | |
| 113 | + [.permissionSources[] | |
| 114 | + if .source.type == "Organization" then "org-member(\(.permission))" |
| 115 | + elif .source.type == "Team" then "team:\(.source.name)(\(.permission))" |
| 116 | + elif (.permission as $p | $org_perms | any(. == $p)) | not then |
| 117 | + # permissionSources only returns READ/WRITE/ADMIN - use effective for MAINTAIN/TRIAGE |
| 118 | + if .permission == "WRITE" and $effective == "MAINTAIN" then "direct(MAINTAIN)" |
| 119 | + elif .permission == "READ" and $effective == "TRIAGE" then "direct(TRIAGE)" |
| 120 | + else "direct(\(.permission))" |
| 121 | + end |
| 122 | + else empty |
| 123 | + end |
| 124 | + ] | unique | join(", ") | |
| 125 | + "\($user) | \($effective) | \(.)" |
| 126 | +') |
| 127 | + |
| 128 | +# Fix team permission labels using REST data |
| 129 | +if [ -n "$sed_cmd" ]; then |
| 130 | + raw_output=$(echo "$raw_output" | sed -E "$sed_cmd") |
| 131 | +fi |
| 132 | + |
| 133 | +(echo "USER | EFFECTIVE | SOURCES" && echo "$raw_output") | column -t -s '|' |
0 commit comments