Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
341 changes: 341 additions & 0 deletions .github/workflows/setup-new-repo.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,341 @@
# .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

env:
TEMPLATE_REPO_NAME: Template_API_Repository

steps:

- name: Checkout template repository
uses: actions/checkout@v4
with:
repository: camaraproject/${{ env.TEMPLATE_REPO_NAME }}
token: ${{ secrets.GH_REPO_CREATE_TOKEN }}

- 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 template repository as a template
gh repo create "$OWNER/$REPO_NAME" --public --template "$OWNER/${{ env.TEMPLATE_REPO_NAME }}"

# 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="${{ env.TEMPLATE_REPO_NAME }}"
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 template files from new repository
run: |
# Explicit list of files to remove after setup from the newly created repository
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 template file $file from new 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
Loading