[TEAM] TEAM-devops-5 #116
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: | | |
| validate_username_format() { | |
| local username=$1 | |
| 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'" | |
| exit 1 | |
| fi | |
| } | |
| 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 "✅ GitHub user exists: $username" | |
| else | |
| echo "::error::GitHub user does not exist: $username" | |
| echo "Response: $response" | |
| exit 1 | |
| fi | |
| } | |
| validate_full_name_with_api() { | |
| local username=$1 | |
| local expected_full_name=$2 | |
| user_info=$(curl -s -H "Authorization: token $GITHUB_TOKEN" "https://api.github.com/users/$username") | |
| actual_name=$(echo "$user_info" | jq -r '.name') | |
| if [[ "$actual_name" == "null" || -z "$actual_name" ]]; then | |
| echo "::warning::No public full name for '$username'. Skipping name match." | |
| return | |
| fi | |
| expected_full_name=$(echo "$expected_full_name" | xargs) | |
| actual_name=$(echo "$actual_name" | xargs) | |
| if [[ "${expected_full_name,,}" != "${actual_name,,}" ]]; then | |
| echo "::error::Full name mismatch for '$username': expected '$expected_full_name', found '$actual_name'" | |
| exit 1 | |
| fi | |
| echo "✅ Full name matches for '$username'" | |
| } | |
| validate_user_block() { | |
| local user_type=$1 | |
| local usernames=$2 | |
| local full_names=$3 | |
| if [[ "$usernames" == "_No response_" || "$usernames" == "none" || -z "$usernames" ]]; then | |
| echo "No $user_type to validate." | |
| return | |
| fi | |
| if [[ "$full_names" == "_No response_" || "$full_names" == "none" || -z "$full_names" ]]; then | |
| echo "::error::Full names required for $user_type but missing." | |
| exit 1 | |
| fi | |
| IFS=',' read -ra USERNAME_ARRAY <<< "$usernames" | |
| IFS=',' read -ra FULLNAME_ARRAY <<< "$full_names" | |
| if [[ ${#USERNAME_ARRAY[@]} -ne ${#FULLNAME_ARRAY[@]} ]]; then | |
| echo "::error::Mismatch in number of $user_type usernames and full names." | |
| exit 1 | |
| fi | |
| for i in "${!USERNAME_ARRAY[@]}"; do | |
| username=$(echo "${USERNAME_ARRAY[$i]}" | xargs) | |
| full_name=$(echo "${FULLNAME_ARRAY[$i]}" | xargs) | |
| echo "🔍 Validating $user_type: '$username' -> '$full_name'" | |
| validate_username_format "$username" | |
| validate_username_existence "$username" | |
| validate_full_name_with_api "$username" "$full_name" | |
| done | |
| } | |
| validate_user_block "maintainers" "$MAINTAINERS" "$MAINTAINER_FULL_NAMES" | |
| validate_user_block "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 | |
| maintainers_mapping.txt | |
| team_members_mapping.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>" | |