Skip to content

Commit ed624e3

Browse files
feat: add script to export repository variables from repositories in an organization (#123)
1 parent 7da82bf commit ed624e3

File tree

2 files changed

+183
-0
lines changed

2 files changed

+183
-0
lines changed

gh-cli/README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,21 @@ Generates a list of users from a team in the organization - has many uses, but t
597597

598598
Gets the status of Actions on a repository (ie, if Actions are disabled)
599599

600+
### get-actions-repository-variables-in-organization.sh
601+
602+
Exports repository variables and their values from all repositories in an organization to a CSV file.
603+
604+
Usage:
605+
606+
```shell
607+
./get-actions-repository-variables-in-organization.sh my-org
608+
./get-actions-repository-variables-in-organization.sh my-org repo-variables.csv
609+
./get-actions-repository-variables-in-organization.sh my-org output.csv --repos-file=repos.txt # optionally specify an input file instead
610+
```
611+
612+
> [!NOTE]
613+
> Requires `write` access to repositories to retrieve variables
614+
600615
### get-actions-usage-in-organization.sh
601616

602617
Returns a list of all actions used in an organization using the SBOM API
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
#!/bin/bash
2+
3+
# Exports all repository variables from all repositories in an organization to a CSV file
4+
5+
# Usage function
6+
usage() {
7+
echo "Usage: $0 <organization> [output-file] [--repos-file=FILE]"
8+
echo ""
9+
echo "Exports all repository variables from all repositories in an organization to CSV"
10+
echo ""
11+
echo "Examples:"
12+
echo " ./get-actions-repository-variables-in-organization.sh my-org"
13+
echo " ./get-actions-repository-variables-in-organization.sh my-org repo-variables.csv"
14+
echo " ./get-actions-repository-variables-in-organization.sh my-org output.csv --repos-file=repos.txt"
15+
echo ""
16+
echo "Notes:"
17+
echo " - Default output file is 'actions-repository-variables-ORGANIZATION-TIMESTAMP.csv'"
18+
echo " - Requires write access to repositories to retrieve variables and their values"
19+
echo " - --repos-file: Optional file containing repository names (one per line, format: owner/repo)"
20+
exit 1
21+
}
22+
23+
# Check if organization is provided
24+
if [ -z "$1" ]; then
25+
usage
26+
fi
27+
28+
org="$1"
29+
output_file=""
30+
repos_file=""
31+
32+
# Parse arguments
33+
shift # Remove organization from arguments
34+
while [[ $# -gt 0 ]]; do
35+
case $1 in
36+
--repos-file=*)
37+
repos_file="${1#*=}"
38+
shift
39+
;;
40+
*)
41+
# If no output file set yet, this is the output file
42+
if [ -z "$output_file" ]; then
43+
output_file="$1"
44+
fi
45+
shift
46+
;;
47+
esac
48+
done
49+
50+
# Set default output file if not provided
51+
if [ -z "$output_file" ]; then
52+
timestamp=$(date +"%Y%m%d_%H%M%S")
53+
output_file="actions-repository-variables-${org}-${timestamp}.csv"
54+
fi
55+
56+
echo "🔍 Fetching all repositories in organization: $org"
57+
echo "📄 Output file: $output_file"
58+
echo ""
59+
60+
# Get list of repositories - either from file or from organization
61+
if [ -n "$repos_file" ]; then
62+
if [ ! -f "$repos_file" ]; then
63+
echo "❌ Repository file '$repos_file' not found"
64+
exit 1
65+
fi
66+
echo "📁 Using repository list from file: $repos_file"
67+
repos=$(cat "$repos_file" | grep -v '^#' | grep -v '^[[:space:]]*$')
68+
else
69+
# Get list of all repositories in the organization
70+
# Capture both output and errors to check for authentication issues
71+
repos_output=$(gh api graphql --paginate -f org="$org" -f query='
72+
query($org: String!, $endCursor: String) {
73+
organization(login: $org) {
74+
repositories(first: 100, after: $endCursor) {
75+
pageInfo {
76+
hasNextPage
77+
endCursor
78+
}
79+
nodes {
80+
owner {
81+
login
82+
}
83+
name
84+
}
85+
}
86+
}
87+
}' --template '{{range .data.organization.repositories.nodes}}{{printf "%s/%s\n" .owner.login .name}}{{end}}' 2>&1)
88+
89+
# Check for authentication errors
90+
if echo "$repos_output" | grep -q "Bad credentials\|HTTP 401"; then
91+
echo "🔐❌ Authentication failed! Please check your GitHub CLI authentication."
92+
echo "Run 'gh auth login' to authenticate or 'gh auth status' to check current authentication."
93+
exit 1
94+
fi
95+
96+
repos="$repos_output"
97+
fi
98+
99+
# Check if repositories were found or if there were other errors
100+
if [ -z "$repos" ] || echo "$repos" | grep -q "message.*status.*401"; then
101+
if echo "$repos" | grep -q "401"; then
102+
echo "🔐❌ Authentication failed! Please check your GitHub CLI authentication."
103+
echo "Run 'gh auth login' to authenticate or 'gh auth status' to check current authentication."
104+
else
105+
echo "❌ No repositories found in organization '$org' or insufficient permissions"
106+
fi
107+
exit 1
108+
fi
109+
110+
repo_count=$(echo "$repos" | wc -l | tr -d ' ')
111+
echo "📊 Found $repo_count repositories"
112+
echo ""
113+
114+
# Create CSV header
115+
echo "Repository,Variable Name,Value,Created At,Updated At" > "$output_file"
116+
117+
processed_repos=0
118+
119+
# Process each repository
120+
echo "$repos" | while IFS= read -r repo; do
121+
if [ -n "$repo" ]; then
122+
((processed_repos++))
123+
echo "🔄 Processing repository $processed_repos/$repo_count: $repo"
124+
125+
# Get list of variables for this repository (names only)
126+
variable_names=$(gh api repos/"$repo"/actions/variables --paginate 2>/dev/null | jq -r '.variables[]? | .name' 2>/dev/null)
127+
128+
if [ -n "$variable_names" ]; then
129+
repo_var_count=$(echo "$variable_names" | wc -l | tr -d ' ')
130+
echo " ✅ Found $repo_var_count variables"
131+
132+
# Get each variable individually to retrieve its value
133+
echo "$variable_names" | while IFS= read -r var_name; do
134+
if [ -n "$var_name" ]; then
135+
# Get individual variable details including value
136+
var_details=$(gh api repos/"$repo"/actions/variables/"$var_name" 2>/dev/null)
137+
138+
if [ -n "$var_details" ]; then
139+
name=$(echo "$var_details" | jq -r '.name // ""')
140+
value=$(echo "$var_details" | jq -r '.value // ""')
141+
created_at=$(echo "$var_details" | jq -r '.created_at // ""')
142+
updated_at=$(echo "$var_details" | jq -r '.updated_at // ""')
143+
144+
# Sanitize value for CSV: remove CR/LF, escape quotes/commas, and guard against CSV injection
145+
escaped_value=$(printf "%s" "$value" | tr '\r\n' ' ' | sed 's/"/\\"/g; s/,/\\,/g')
146+
if [[ "$escaped_value" =~ ^[=+\-@] ]]; then
147+
escaped_value="'$escaped_value'"
148+
fi
149+
150+
echo "\"$repo\",\"$name\",\"$escaped_value\",\"$created_at\",\"$updated_at\"" >> "$output_file"
151+
fi
152+
fi
153+
done
154+
else
155+
echo " ℹ️ No variables found"
156+
fi
157+
fi
158+
done
159+
160+
# Get final count of variables
161+
final_total=$(tail -n +2 "$output_file" | wc -l | tr -d ' ')
162+
163+
echo ""
164+
echo "✅ Export completed successfully!"
165+
echo "📊 Summary:"
166+
echo " - Repositories processed: $repo_count"
167+
echo " - Total variables exported: $final_total"
168+
echo " - Output file: $output_file"

0 commit comments

Comments
 (0)