[TEAM] TEAM-Name #107
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Route Issue to Repo or Team PR | |
| on: | |
| issues: | |
| types: [opened] | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| jobs: | |
| process_repo_or_team_request: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout Repository | |
| uses: actions/checkout@v4 | |
| - name: Determine Request Type | |
| id: classify | |
| run: | | |
| TITLE="${{ github.event.issue.title }}" | |
| echo "Issue Title: $TITLE" | |
| if [[ "$TITLE" == *"[Repo Request]"* ]]; then | |
| echo "type=repo" >> $GITHUB_OUTPUT | |
| elif [[ "$TITLE" == *"[TEAM]"* ]]; then | |
| echo "type=team" >> $GITHUB_OUTPUT | |
| else | |
| echo "Unsupported issue type, skipping." | |
| echo "type=skip" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Stop if unsupported issue | |
| if: steps.classify.outputs.type == 'skip' | |
| run: echo "No supported issue type found. Exiting." | |
| - name: Process Repo Request | |
| if: steps.classify.outputs.type == 'repo' | |
| run: | | |
| ISSUE_BODY=$(jq -r '.issue.body' "$GITHUB_EVENT_PATH") | |
| # Print the full issue body to debug formatting issues | |
| echo "Full ISSUE_BODY content:" | |
| echo "$ISSUE_BODY" | |
| - name: Parse Issue Details | |
| if: steps.classify.outputs.type == 'repo' | |
| id: parse | |
| run: | | |
| ISSUE_BODY=$(jq -r '.issue.body' "$GITHUB_EVENT_PATH") | |
| # Extract values by looking for the header and getting the next non-empty line | |
| REPO_NAME=$(echo "$ISSUE_BODY" | awk -F'\n' '/^### Repository Name$/{getline; getline; print $0}' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') | |
| VISIBILITY=$(echo "$ISSUE_BODY" | awk -F'\n' '/^### Visibility$/{getline; getline; print $0}' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') | |
| BRANCH=$(echo "$ISSUE_BODY" | awk -F'\n' '/^### Default Branch$/{getline; getline; print $0}' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') | |
| INCLUDE_README=$(echo "$ISSUE_BODY" | awk -F'\n' '/^### Include README$/{getline; getline; print $0}' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') | |
| # Debugging Output | |
| echo "Extracted Repository Name: '$REPO_NAME'" | |
| echo "Extracted Visibility: '$VISIBILITY'" | |
| echo "Extracted Branch: '$BRANCH'" | |
| echo "Extracted Include README: '$INCLUDE_README'" | |
| # Ensure default values if missing | |
| if [ -z "$REPO_NAME" ]; then echo "Error: Repository Name is required"; exit 1; fi | |
| if [ -z "$BRANCH" ]; then BRANCH="main"; fi | |
| echo "REPO_NAME=$REPO_NAME" >> $GITHUB_ENV | |
| echo "VISIBILITY=$VISIBILITY" >> $GITHUB_ENV | |
| echo "BRANCH=$BRANCH" >> $GITHUB_ENV | |
| echo "INCLUDE_README=$INCLUDE_README" >> $GITHUB_ENV | |
| - name: Set Git Identity | |
| if: steps.classify.outputs.type == 'repo' | |
| run: | | |
| git config --global user.name "github-actions" | |
| git config --global user.email "github-actions@github.com" | |
| - name: Create Repository Structure | |
| if: steps.classify.outputs.type == 'repo' | |
| run: | | |
| mkdir -p repos/${{ env.REPO_NAME }} | |
| cd repos/${{ env.REPO_NAME }} | |
| git init -b ${{ env.BRANCH }} | |
| # Ensure at least one file exists before committing | |
| if [[ "${{ env.INCLUDE_README }}" == "Yes" ]]; then | |
| echo "# ${{ env.REPO_NAME }}" > README.md | |
| else | |
| touch .gitkeep # Create a placeholder file if README is not included | |
| fi | |
| git add . | |
| git commit -m "Initial commit for ${{ env.REPO_NAME }}" | |
| - name: Commit Changes | |
| if: steps.classify.outputs.type == 'repo' | |
| run: | | |
| cd repos/${{ env.REPO_NAME }} | |
| git add . | |
| git commit -m "Add repository structure for ${{ env.REPO_NAME }}" || echo "No changes to commit" | |
| - name: Create Pull Request for Repository | |
| if: steps.classify.outputs.type == 'repo' | |
| uses: peter-evans/create-pull-request@v6 | |
| with: | |
| token: ${{ secrets.GH_PAT }} | |
| title: "Create new repository: ${{ env.REPO_NAME }}" | |
| body: | | |
| Repository Details: | |
| - **Name**: ${{ env.REPO_NAME }} | |
| - **Visibility**: ${{ env.VISIBILITY }} | |
| - **Default Branch**: ${{ env.BRANCH }} | |
| - **Include README**: ${{ env.INCLUDE_README }} | |
| branch: "create-${{ env.REPO_NAME }}" | |
| - name: Process Team Request | |
| if: steps.classify.outputs.type == 'team' | |
| run: | | |
| # Extract issue body from GitHub event | |
| ISSUE_BODY="${{ github.event.issue.body }}" | |
| # Debugging: Print the issue body | |
| echo "Issue Body:" | |
| echo "$ISSUE_BODY" | |
| TEAM_NAME=$(echo "$ISSUE_BODY" | awk '/^### Team Name$/{getline; getline; print}' | sed 's/[`]//g' | xargs) | |
| PARENT_TEAM=$(echo "$ISSUE_BODY" | awk '/^### Parent Team \(optional\)$/{getline; getline; print}' | sed 's/[`]//g' | xargs) | |
| VISIBILITY=$(echo "$ISSUE_BODY" | awk '/^### Visibility$/{getline; getline; print}' | sed 's/[`]//g' | xargs) | |
| PURPOSE=$(echo "$ISSUE_BODY" | awk '/^### Purpose$/{getline; getline; print}' | sed 's/[`]//g' | xargs) | |
| MAINTAINERS=$(echo "$ISSUE_BODY" | awk '/^### Maintainers \(comma-separated\)$/{getline; getline; print}' | sed 's/[`]//g' | xargs) | |
| MAINTAINER_FULL_NAMES=$(echo "$ISSUE_BODY" | awk '/^### Maintainer Full Names \(comma-separated\)$/{getline; getline; print}' | sed 's/[`]//g' | xargs) | |
| TEAM_MEMBERS=$(echo "$ISSUE_BODY" | awk '/^### Team Members \(comma-separated\)$/{getline; getline; print}' | sed 's/[`]//g' | xargs) | |
| MEMBER_FULL_NAMES=$(echo "$ISSUE_BODY" | awk '/^### Team Member Full Names \(comma-separated\)$/{getline; getline; print}' | sed 's/[`]//g' | xargs) | |
| echo "Extracted Values:" | |
| echo "Team Name: '$TEAM_NAME'" | |
| echo "Visibility: '$VISIBILITY'" | |
| echo "Purpose: '$PURPOSE'" | |
| echo "Maintainers: '$MAINTAINERS'" | |
| echo "Maintainer Full Names: '$MAINTAINER_FULL_NAMES'" | |
| echo "Team Members: '$TEAM_MEMBERS'" | |
| echo "Member Full Names: '$MEMBER_FULL_NAMES'" | |
| echo "parent_team=$PARENT_TEAM" >> "$GITHUB_ENV" | |
| if [[ ! "$TEAM_NAME" =~ ^TEAM-[a-zA-Z0-9_-]{1,20}$ ]]; then | |
| echo "::error::Invalid team name format: '$TEAM_NAME'" | |
| exit 1 | |
| fi | |
| VISIBILITY=${VISIBILITY:-Visible} | |
| PURPOSE=${PURPOSE:-Team created via automation} | |
| MAINTAINERS=${MAINTAINERS:-none} | |
| TEAM_MEMBERS=${TEAM_MEMBERS:-none} | |
| MAINTAINER_FULL_NAMES=${MAINTAINER_FULL_NAMES:-none} | |
| MEMBER_FULL_NAMES=${MEMBER_FULL_NAMES:-none} | |
| PARENT_TEAM=${PARENT_TEAM:-none} | |
| echo "$TEAM_NAME" > team-name.txt | |
| echo "team_name=$TEAM_NAME" >> "$GITHUB_ENV" | |
| echo "visibility=$VISIBILITY" >> "$GITHUB_ENV" | |
| echo "purpose=$PURPOSE" >> "$GITHUB_ENV" | |
| echo "maintainers=$MAINTAINERS" >> "$GITHUB_ENV" | |
| echo "maintainer_full_names=$MAINTAINER_FULL_NAMES" >> "$GITHUB_ENV" | |
| echo "team_members=$TEAM_MEMBERS" >> "$GITHUB_ENV" | |
| echo "member_full_names=$MEMBER_FULL_NAMES" >> "$GITHUB_ENV" | |
| echo "parent_team=$PARENT_TEAM" >> "$GITHUB_ENV" | |
| - name: Debug team info | |
| run: | | |
| echo "Maintainers to validate: $MAINTAINERS" | |
| echo "Team members to validate: $TEAM_MEMBERS" | |
| echo "Maintainer full names: $MAINTAINER_FULL_NAMES" | |
| echo "Member full names: $MEMBER_FULL_NAMES" | |
| env: | |
| MAINTAINERS: ${{ env.maintainers }} | |
| TEAM_MEMBERS: ${{ env.team_members }} | |
| MAINTAINER_FULL_NAMES: ${{ env.maintainer_full_names }} | |
| MEMBER_FULL_NAMES: ${{ env.member_full_names }} | |
| - name: Validate GitHub usernames and full names | |
| if: env.maintainers != '_No response_' || env.team_members != '_No response_' | |
| shell: bash | |
| run: | | |
| # Function to validate GitHub username format | |
| validate_username_format() { | |
| local username=$1 | |
| # GitHub username regex: between 1-39 alphanumeric or hyphen characters | |
| # Cannot start or end with hyphen, no consecutive hyphens | |
| # GitHub usernames are case-insensitive but can contain uppercase letters | |
| local username_regex='^[a-zA-Z0-9][a-zA-Z0-9-]{0,38}$' | |
| if [[ ! "$username" =~ $username_regex ]]; then | |
| echo "::error::Invalid GitHub username format: '$username'" | |
| echo "GitHub usernames must:" | |
| echo "- Be 1-39 characters long" | |
| echo "- Contain only alphanumeric characters or hyphens" | |
| echo "- Not start or end with a hyphen" | |
| echo "- Not contain consecutive hyphens" | |
| exit 1 | |
| fi | |
| } | |
| # Function to check if username exists on GitHub | |
| validate_username_existence() { | |
| local username=$1 | |
| response=$(curl -s -H "Authorization: token $GITHUB_TOKEN" "https://api.github.com/users/$username") | |
| if echo "$response" | grep -q '"login":'; then | |
| echo "✅ Valid GitHub user: $username" | |
| else | |
| echo "::error::❌ GitHub user does not exist: $username" | |
| echo "API Response: $response" | |
| exit 1 | |
| fi | |
| } | |
| # Function to validate full name format | |
| validate_full_name_format() { | |
| local full_name=$1 | |
| # Full name regex: 3-100 characters, letters, spaces, and some punctuation | |
| local full_name_regex='^[A-Za-z ,.'\''\\-]{3,100}$' | |
| if [[ ! "$full_name" =~ $full_name_regex ]]; then | |
| echo "::error::Invalid full name format: '$full_name'" | |
| echo "Full names must:" | |
| echo "- Be 3-100 characters long" | |
| echo "- Contain only letters, spaces, and basic punctuation (,.'-)" | |
| exit 1 | |
| fi | |
| } | |
| # Function to validate that the number of usernames matches the number of full names | |
| validate_count_match() { | |
| local user_type=$1 | |
| local usernames=$2 | |
| local full_names=$3 | |
| if [[ "$usernames" == "_No response_" || "$usernames" == "none" || -z "$usernames" ]]; then | |
| if [[ "$full_names" != "_No response_" && "$full_names" != "none" && -n "$full_names" ]]; then | |
| echo "::error::Full names provided for $user_type but no usernames were specified" | |
| exit 1 | |
| fi | |
| return | |
| fi | |
| if [[ "$full_names" == "_No response_" || "$full_names" == "none" || -z "$full_names" ]]; then | |
| echo "::error::No full names provided for $user_type" | |
| exit 1 | |
| fi | |
| IFS=',' read -ra USERNAME_ARRAY <<< "$usernames" | |
| IFS=',' read -ra FULLNAME_ARRAY <<< "$full_names" | |
| username_count=${#USERNAME_ARRAY[@]} | |
| fullname_count=${#FULLNAME_ARRAY[@]} | |
| if [[ $username_count != $fullname_count ]]; then | |
| echo "::error::Mismatch between number of $user_type usernames ($username_count) and full names ($fullname_count)" | |
| exit 1 | |
| fi | |
| } | |
| # Main validation logic | |
| validate_users() { | |
| local user_type=$1 | |
| local users=$2 | |
| local full_names=$3 | |
| echo "Processing $user_type..." | |
| if [[ "$users" == "_No response_" || "$users" == "none" || -z "$users" ]]; then | |
| echo "No $user_type to validate" | |
| return | |
| fi | |
| # Validate count match first | |
| validate_count_match "$user_type" "$users" "$full_names" | |
| IFS=',' read -ra USER_ARRAY <<< "$users" | |
| for user in "${USER_ARRAY[@]}"; do | |
| trimmed=$(echo "$user" | xargs) # Trim whitespace | |
| if [ -z "$trimmed" ]; then | |
| echo "::warning::Empty username found in $user_type list" | |
| continue | |
| fi | |
| echo "Validating $user_type username: '$trimmed'" | |
| validate_username_format "$trimmed" | |
| validate_username_existence "$trimmed" | |
| done | |
| IFS=',' read -ra FULLNAME_ARRAY <<< "$full_names" | |
| for full_name in "${FULLNAME_ARRAY[@]}"; do | |
| trimmed=$(echo "$full_name" | xargs) # Trim whitespace | |
| if [ -z "$trimmed" ]; then | |
| echo "::warning::Empty full name found in $user_type list" | |
| continue | |
| fi | |
| echo "Validating $user_type full name: '$trimmed'" | |
| validate_full_name_format "$trimmed" | |
| done | |
| } | |
| # Validate both maintainers and team members | |
| validate_users "maintainers" "$MAINTAINERS" "$MAINTAINER_FULL_NAMES" | |
| validate_users "team members" "$TEAM_MEMBERS" "$MEMBER_FULL_NAMES" | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| MAINTAINERS: ${{ env.maintainers }} | |
| TEAM_MEMBERS: ${{ env.team_members }} | |
| MAINTAINER_FULL_NAMES: ${{ env.maintainer_full_names }} | |
| MEMBER_FULL_NAMES: ${{ env.member_full_names }} | |
| - name: Create Pull Request for Team | |
| if: steps.classify.outputs.type == 'team' | |
| uses: peter-evans/create-pull-request@v5 | |
| with: | |
| token: ${{ secrets.GH_PAT }} | |
| commit-message: "Add team: ${{ env.team_name }}" | |
| title: "Create team: ${{ env.team_name }}" | |
| body: | | |
| ### Team Creation Request | |
| **Team Name:** ${{ env.team_name }} | |
| **Parent Team:** ${{ env.parent_team }} | |
| **Visibility:** ${{ env.visibility }} | |
| **Purpose:** ${{ env.purpose }} | |
| **Maintainers:** ${{ env.maintainers }} | |
| **Maintainer Full Names:** ${{ env.maintainer_full_names }} | |
| **Team Members:** ${{ env.team_members }} | |
| **Team Member Full Names:** ${{ env.member_full_names }} | |
| Generated from issue #${{ github.event.issue.number }} | |
| branch: "team-request/${{ github.event.issue.number }}" | |
| labels: "team-creation" | |
| paths: | | |
| team-name.txt | |
| - name: Post a message in Slack | |
| uses: slackapi/slack-github-action@v2.0.0 | |
| with: | |
| webhook: ${{ secrets.SLACK_TOKEN }} | |
| webhook-type: incoming-webhook | |
| payload: | | |
| text: "*New Team PR Created: `${{ env.team_name }}`*" | |
| blocks: | |
| - type: section | |
| text: | |
| type: mrkdwn | |
| text: ":tada: A new PR has been created for team *`${{ env.team_name }}`*!\n*Purpose:* ${{ env.purpose }}\n*Maintainers:* ${{ env.maintainers }}\n*Parent:* `${{ env.parent_team }}`\n<https://github.com/${{ github.repository }}/pull/${{ steps.create_pr.outputs.pull-request-number }}|View Pull Request>" | |