-
Notifications
You must be signed in to change notification settings - Fork 5
335 lines (298 loc) · 15.6 KB
/
setup-new-repo.yml
File metadata and controls
335 lines (298 loc) · 15.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
# .github/workflows/setup-new-repo.yml
#
# Prerequisites:
# - A GitHub environment named `repo-setup` must exist.
# - The environment must contain a secret `GH_REPO_CREATE_TOKEN`.
# - This token must be a fine-grained personal access token (FGPAT) with the following:
# • Repository access: Allow access to the template repo and target org repos.
# • Permissions:
# - Contents: Read and write
# - Issues: Read and write
# - Metadata: Read-only
# - Administration: Read and write (for repo settings, team setup)
#
# See: https://github.com/settings/tokens for generating tokens
#
name: Setup New Repository
on:
workflow_dispatch:
inputs:
repo_name:
description: 'Name of the new repository to create'
required: true
subproject_name:
description: 'Optional subproject/working group name (leave empty for independent sandbox repo)'
required: false
repo_wiki_page:
description: 'URL of the repository wiki page'
required: true
subproject_wiki_page:
description: 'Optional URL of the subproject wiki page'
required: false
mailinglist_name:
description: 'Mailing list name'
required: true
initial_codeowners:
description: 'Space-separated GitHub usernames (with @) for initial CODEOWNERS'
required: true
jobs:
setup:
runs-on: ubuntu-latest
environment: repo-setup
permissions:
issues: write
contents: write
actions: write
pull-requests: write
steps:
- name: Checkout template repository
uses: actions/checkout@v4
- name: Set up GitHub CLI token with personal fine grained access token
run: |
# Use GH_REPO_CREATE_TOKEN as the authentication token for all GitHub CLI commands
echo "GH_TOKEN=${{ secrets.GH_REPO_CREATE_TOKEN }}" >> $GITHUB_ENV
if [ -z "${{ secrets.GH_REPO_CREATE_TOKEN }}" ]; then
echo "::error::GH_REPO_CREATE_TOKEN is not set. Please configure the secret in the 'repo-setup' environment."
exit 1
fi
- name: Create new repository and set variables
run: |
REPO_NAME=${{ github.event.inputs.repo_name }}
# Extract the owner (user or organization) part of the current repository (before the slash)
OWNER=$(echo '${{ github.repository }}' | cut -d'/' -f1)
echo "Creating new repository: https://github.com/$OWNER/$REPO_NAME"
# Creates a new public repository using the current repository as a template
gh repo create "$OWNER/$REPO_NAME" --public --template "$OWNER/$(basename '${{ github.repository }}')"
# Wait until the repository is fully accessible
for i in {1..5}; do
if gh api repos/$OWNER/$REPO_NAME > /dev/null 2>&1; then
echo "::notice::Repository $OWNER/$REPO_NAME is now available."
break
else
echo "Waiting for repository $OWNER/$REPO_NAME to become available..."
sleep 4
fi
done
if ! gh api repos/$OWNER/$REPO_NAME > /dev/null 2>&1; then
echo "::warning::Repository $OWNER/$REPO_NAME did not become available after 5 attempts. Continuing anyway."
fi
echo "REPO_NAME=$REPO_NAME" >> $GITHUB_ENV
echo "OWNER=$OWNER" >> $GITHUB_ENV
echo "MAINTAINERS_TEAM=${REPO_NAME}_maintainers" >> $GITHUB_ENV
echo "CODEOWNERS_TEAM=${REPO_NAME}_codeowners" >> $GITHUB_ENV
echo "CODEOWNERS_LIST=${{ github.event.inputs.initial_codeowners }}" >> $GITHUB_ENV
- name: Create teams
run: |
if gh api orgs/$OWNER/teams > /dev/null 2>&1; then
if ! gh api orgs/$OWNER/teams/$MAINTAINERS_TEAM > /dev/null 2>&1; then
# Retrieve the numeric ID of the 'maintainers' team to use as the parent_team_id
MAINTAINERS_PARENT_ID=$(gh api orgs/$OWNER/teams/maintainers | jq -r '.id')
MAINTAINERS_PARENT_ID_NUM=$(echo "$MAINTAINERS_PARENT_ID" | grep -o '[0-9]*')
echo "Debug: MAINTAINERS_PARENT_ID_NUM=$MAINTAINERS_PARENT_ID_NUM"
if [ -z "$MAINTAINERS_PARENT_ID_NUM" ]; then
echo "::error::Invalid team ID format for maintainers. Expected numeric ID."
exit 1
fi
echo "{\"name\": \"$MAINTAINERS_TEAM\", \"description\": \"Maintainers for $REPO_NAME repository\", \"parent_team_id\": $MAINTAINERS_PARENT_ID_NUM}" > maintainer_payload.json
gh api orgs/$OWNER/teams \
-X POST \
-H "Accept: application/vnd.github+json" \
--input maintainer_payload.json
else
echo "Team $MAINTAINERS_TEAM already exists. Skipping creation."
fi
if ! gh api orgs/$OWNER/teams/$CODEOWNERS_TEAM > /dev/null 2>&1; then
CODEOWNERS_PARENT_ID=$(gh api orgs/$OWNER/teams/codeowners | jq -r '.id')
CODEOWNERS_PARENT_ID_NUM=$(echo "$CODEOWNERS_PARENT_ID" | grep -o '[0-9]*')
echo "Debug: CODEOWNERS_PARENT_ID_NUM=$CODEOWNERS_PARENT_ID_NUM"
if [ -z "$CODEOWNERS_PARENT_ID_NUM" ]; then
echo "::error::Invalid team ID format for codeowners. Expected numeric ID."
exit 1
fi
echo "{\"name\": \"$CODEOWNERS_TEAM\", \"description\": \"Codeowners for $REPO_NAME repository\", \"parent_team_id\": $CODEOWNERS_PARENT_ID_NUM}" > codeowners_payload.json
gh api orgs/$OWNER/teams \
-X POST \
-H "Accept: application/vnd.github+json" \
--input codeowners_payload.json
else
echo "Team $CODEOWNERS_TEAM already exists. Skipping creation."
fi
CODEOWNERS_LIST=$(echo "$CODEOWNERS_LIST" | xargs)
# Loop through each provided GitHub username and invite them to the codeowners team
# Note: users who are not yet members of the organization will be invited to join
# and will need to accept the invitation before they are part of the team and the CODEOWNERS file get fully valid
echo "Inviting users to team $CODEOWNERS_TEAM: [$CODEOWNERS_LIST]"
for username in $CODEOWNERS_LIST; do
clean_user=$(echo "$username" | sed 's/^@//')
echo "Checking if @$clean_user is a valid GitHub user..."
if gh api users/$clean_user > /dev/null 2>&1; then
echo "Inviting @$clean_user to team $CODEOWNERS_TEAM"
gh api orgs/$OWNER/teams/$CODEOWNERS_TEAM/memberships/$clean_user -X PUT -f role=member || echo "Failed to invite $clean_user"
else
echo "::warning::User @$clean_user does not exist or cannot be looked up. Skipping."
fi
done
else
echo "Skipping team creation — not running in an organization."
fi
- name: Configure repository settings
run: |
gh repo edit $OWNER/$REPO_NAME \
--description "Sandbox API Repository for $REPO_NAME API(s)" \
--homepage "${{ github.event.inputs.repo_wiki_page }}" \
--add-topic sandbox-api-repository
gh api -X PATCH repos/$OWNER/$REPO_NAME \
-F has_discussions=true \
-F has_issues=true \
-F has_wiki=false
- name: Update README.md placeholders
run: |
# Replace placeholders in the template README.md and push the updated version to the new repository
# Note: the README.md file is expected to be in the root of the repository
sed -i "s/{{repo_name}}/$REPO_NAME/g" README.md
sed -i "s|{{repo_wiki_page}}|${{ github.event.inputs.repo_wiki_page }}|g" README.md
sed -i "s|{{subproject_name}}|${{ github.event.inputs.subproject_name }}|g" README.md
sed -i "s|{{subproject_wiki_page}}|${{ github.event.inputs.subproject_wiki_page }}|g" README.md
sed -i "s|{{mailinglist_name}}|${{ github.event.inputs.mailinglist_name }}|g" README.md
sed -i "s|{{initial_codeowners}}|${{ github.event.inputs.initial_codeowners }}|g" README.md
# Retry loop: waits for README.md to appear in the new repo (max 5 attempts)
SHA=""
for i in {1..5}; do
SHA=$(gh api repos/$OWNER/$REPO_NAME/contents/README.md 2>/dev/null | jq -r '.sha')
if [ "$SHA" != "null" ] && [ -n "$SHA" ]; then
echo "Found README.md sha: $SHA"
break
else
echo "README.md not yet available, retrying in 2s..."
sleep 2
fi
done
gh api repos/$OWNER/$REPO_NAME/contents/README.md \
-X PUT \
-F message='Update README.md with project metadata' \
-F content="$(base64 -w 0 README.md)" \
-F sha="$SHA"
- name: Update issue template config placeholders
run: |
# Update .github/ISSUE_TEMPLATE/config.yml placeholders
CONFIG_FILE=".github/ISSUE_TEMPLATE/config.yml"
if [ -f "$CONFIG_FILE" ]; then
echo "Updating $CONFIG_FILE placeholders..."
sed -i "s/{{repo_name}}/$REPO_NAME/g" $CONFIG_FILE
# Wait for the config.yml file to be available in the new repository
CONFIG_SHA=""
for i in {1..5}; do
CONFIG_SHA=$(gh api repos/$OWNER/$REPO_NAME/contents/$CONFIG_FILE 2>/dev/null | jq -r '.sha')
if [ "$CONFIG_SHA" != "null" ] && [ -n "$CONFIG_SHA" ]; then
echo "Found $CONFIG_FILE sha: $CONFIG_SHA"
break
else
echo "$CONFIG_FILE not yet available, retrying in 2s..."
sleep 2
fi
done
if [ "$CONFIG_SHA" != "null" ] && [ -n "$CONFIG_SHA" ]; then
gh api repos/$OWNER/$REPO_NAME/contents/$CONFIG_FILE \
-X PUT \
-F message="Update $CONFIG_FILE with project metadata" \
-F content="$(base64 -w 0 $CONFIG_FILE)" \
-F sha="$CONFIG_SHA"
echo "Successfully updated $CONFIG_FILE"
else
echo "::warning::Could not find $CONFIG_FILE in the repository. Skipping update."
fi
else
echo "::warning::$CONFIG_FILE not found in template. Skipping update."
fi
- name: Set team permissions
run: |
if gh api orgs/$OWNER/teams > /dev/null 2>&1; then
if gh api orgs/$OWNER/teams/$MAINTAINERS_TEAM > /dev/null 2>&1; then
gh api orgs/$OWNER/teams/$MAINTAINERS_TEAM/repos/$OWNER/$REPO_NAME \
-X PUT -H "Accept: application/vnd.github+json" -f permission=triage || \
echo "::error::Failed to set permissions for $MAINTAINERS_TEAM. Please check team and repository availability."
else
echo "::error::Team $MAINTAINERS_TEAM does not exist. Cannot assign permissions."
exit 1
fi
if gh api orgs/$OWNER/teams/$CODEOWNERS_TEAM > /dev/null 2>&1; then
echo "Assigning permissions to CODEOWNERS_TEAM: [$CODEOWNERS_TEAM]"
gh api orgs/$OWNER/teams/$CODEOWNERS_TEAM/repos/$OWNER/$REPO_NAME \
-X PUT -H "Accept: application/vnd.github+json" -f permission=push || \
echo "::error::Failed to set permissions for $CODEOWNERS_TEAM. Please check team and repository availability."
else
echo "::error::Team $CODEOWNERS_TEAM does not exist. Cannot assign permissions."
exit 1
fi
gh api orgs/$OWNER/teams/admins/repos/$OWNER/$REPO_NAME \
-X PUT -H "Accept: application/vnd.github+json" -f permission=maintain || \
echo "::error::Failed to set permissions for admin team. Please check team and repository availability."
else
echo "Skipping team permission assignment — not running in an organization."
fi
- name: Update CODEOWNERS file
run: |
# Replace placeholder in CODEOWNERS template with actual list of codeowners
# Note: also users who are skipped during team invitation will be added to the CODEOWNERS file and
# might need to be corrected manually later and invited manually to the CODEOWNERS team
sed "s|{{initial_codeowners}}|$CODEOWNERS_LIST|g" templates/CODEOWNERS_TEMPLATE > CODEOWNERS
CODEOWNERS_SHA=$(gh api repos/$OWNER/$REPO_NAME/contents/CODEOWNERS | jq -r '.sha')
gh api repos/$OWNER/$REPO_NAME/contents/CODEOWNERS \
-X PUT \
-F message='Update CODEOWNERS from template' \
-F content="$(base64 -w 0 CODEOWNERS)" \
-F sha="$CODEOWNERS_SHA"
- name: Sync rulesets from template repository
run: |
TEMPLATE_REPO=$(basename "${{ github.repository }}")
echo "Fetching rulesets from $OWNER/$TEMPLATE_REPO"
# Fetch all rulesets defined in the template repository for later replication
RULESETS=$(gh api repos/$OWNER/$TEMPLATE_REPO/rulesets \
-H "Accept: application/vnd.github+json" 2>/dev/null || echo "[]")
if ! echo "$RULESETS" | jq -e 'type == "array" and length > 0' > /dev/null; then
echo "No valid rulesets array found in template repository. Skipping."
exit 0
fi
echo "$RULESETS" | jq -r '.[].id' | while read -r ruleset_id; do
RULESET=$(gh api repos/$OWNER/$TEMPLATE_REPO/rulesets/$ruleset_id \
-H "Accept: application/vnd.github+json")
NAME=$(echo "$RULESET" | jq -r '.name')
echo "Syncing full ruleset: $NAME"
PAYLOAD=$(echo "$RULESET" | jq 'del(.id, .repository_id, .creator, .created_at, .updated_at)')
echo "$PAYLOAD" > ruleset.json
gh api repos/$OWNER/$REPO_NAME/rulesets \
-X POST \
-H "Accept: application/vnd.github+json" \
--input ruleset.json || echo "Warning: Failed to apply ruleset $NAME"
done
- name: Create initial issues
run: |
ADMIN_ISSUE_URL=$(gh issue create --repo $OWNER/$REPO_NAME \
--title "New Repository - Initial administrative tasks" \
--body "$(cat templates/issues/initial-admin.md)")
gh issue create --repo $OWNER/$REPO_NAME \
--title "New Repository - Initial tasks for codeowners" \
--body "$(cat templates/issues/initial-codeowners.md)"
gh issue comment "$ADMIN_ISSUE_URL" \
--body "✅ Repository setup has been completed by automation. You may now proceed with the checklist."
- name: Cleanup setup artifacts from template repository
run: |
# Explicit list of files to remove after setup (e.g., templates and placeholders)
FILES_TO_DELETE=(
"templates/CODEOWNERS_TEMPLATE"
"templates/issues/initial-admin.md"
"templates/issues/initial-codeowners.md"
"templates/README.md"
)
for file in "${FILES_TO_DELETE[@]}"; do
if gh api repos/$OWNER/$REPO_NAME/contents/$file > /dev/null 2>&1; then
sha=$(gh api repos/$OWNER/$REPO_NAME/contents/$file | jq -r '.sha')
gh api repos/$OWNER/$REPO_NAME/contents/$file \
-X DELETE \
-F message="Remove $file from template repository" \
-F sha="$sha" || echo "::error::Failed to delete $file"
echo "::notice::Deleted $file"
else
echo "::warning::File $file not found during cleanup. Skipping."
fi
done