From 13c10b65b476c962708af6c2538a4533ca7b0c60 Mon Sep 17 00:00:00 2001 From: Uday Beswal <71140717+UdBe@users.noreply.github.com> Date: Wed, 6 Nov 2024 10:44:38 +0530 Subject: [PATCH] Update Zulip Bot to manage image creation run alerts (#888) * Update Zulip Bot to manage image creation run alerts Signed-off-by: Uday Beswal * Add Debugging Steps Signed-off-by: Uday Beswal * Remove Debugging Steps Signed-off-by: Uday Beswal --------- Signed-off-by: Uday Beswal --- .github/workflows/zulip_notification_bot.yml | 260 ++++++++++++++++++- 1 file changed, 259 insertions(+), 1 deletion(-) diff --git a/.github/workflows/zulip_notification_bot.yml b/.github/workflows/zulip_notification_bot.yml index e3a3009711..f5b97ed7c2 100644 --- a/.github/workflows/zulip_notification_bot.yml +++ b/.github/workflows/zulip_notification_bot.yml @@ -158,4 +158,262 @@ jobs: echo "Chunk file $chunk_file does not exist." exit 1 fi - done \ No newline at end of file + done + + + image_creation_run_alerts: + name: Image Creation Run Alerts + runs-on: ubuntu-latest + + steps: + - name: Get the last two runs of Image Creation Run workflow + id: get_runs + uses: actions/github-script@v7 + with: + script: | + const workflowName = 'Image Creation Run'; + const owner = context.repo.owner; + const repo = context.repo.repo; + + // Get the workflows in the repo + const workflows = await github.rest.actions.listRepoWorkflows({ + owner, + repo, + }); + + // Find the workflow ID for "Image Creation Run" + const workflow = workflows.data.workflows.find( + (wf) => wf.name === workflowName + ); + if (!workflow) { + core.setFailed(`Workflow "${workflowName}" not found.`); + return; + } + const workflow_id = workflow.id; + + // Get the last two workflow runs + const runsResponse = await github.rest.actions.listWorkflowRuns({ + owner, + repo, + workflow_id, + per_page: 2, + }); + const runs = runsResponse.data.workflow_runs; + if (runs.length < 2) { + core.setFailed('Not enough workflow runs found.'); + return; + } + + // Output the run IDs, conclusions, URLs, and dates + core.setOutput('run_id1', runs[0].id.toString()); + core.setOutput('run_id2', runs[1].id.toString()); + core.setOutput('conclusion1', runs[0].conclusion || 'unknown'); + core.setOutput('url1', runs[0].html_url); + core.setOutput('url2', runs[1].html_url); + + - name: Get failed ironbank images from the last two runs + id: get_failed_jobs + uses: actions/github-script@v7 + env: + RUN_ID1: ${{ steps.get_runs.outputs.run_id1 }} + RUN_ID2: ${{ steps.get_runs.outputs.run_id2 }} + with: + script: | + const owner = context.repo.owner; + const repo = context.repo.repo; + + // Function to get all jobs with pagination + async function getAllJobs(run_id) { + let jobs = []; + let page = 1; + let moreJobs = true; + + while (moreJobs) { + const response = await github.rest.actions.listJobsForWorkflowRun({ + owner, + repo, + run_id: run_id, + per_page: 100, + page: page, + }); + + if (response.data.jobs.length > 0) { + jobs = jobs.concat(response.data.jobs); + page++; + } else { + moreJobs = false; + } + } + + return jobs; + } + + // Function to get failed jobs ending with "-ib" + async function getFailedJobs(run_id) { + const jobs = await getAllJobs(run_id); + const failedJobs = jobs.filter((job) => { + return job.conclusion === 'failure' && job.name.endsWith('-ib'); + }); + return failedJobs.map((job) => job.name); + } + + const run_id1 = process.env.RUN_ID1; + const run_id2 = process.env.RUN_ID2; + + const failedJobs1 = await getFailedJobs(run_id1); + const failedJobs2 = await getFailedJobs(run_id2); + + // Find jobs that failed in both runs + const failedInBoth = failedJobs1.filter((job) => failedJobs2.includes(job)); + + core.setOutput('failed_jobs', failedInBoth.join(',')); + + - name: Get failed non-ironbank images from the last two runs + id: get_failed_jobs_no_ib + uses: actions/github-script@v7 + env: + RUN_ID1: ${{ steps.get_runs.outputs.run_id1 }} + RUN_ID2: ${{ steps.get_runs.outputs.run_id2 }} + with: + script: | + const owner = context.repo.owner; + const repo = context.repo.repo; + + // Function to get all jobs with pagination + async function getAllJobs(run_id) { + let jobs = []; + let page = 1; + let moreJobs = true; + + while (moreJobs) { + const response = await github.rest.actions.listJobsForWorkflowRun({ + owner, + repo, + run_id: run_id, + per_page: 100, + page: page, + }); + + if (response.data.jobs.length > 0) { + jobs = jobs.concat(response.data.jobs); + page++; + } else { + moreJobs = false; + } + } + + return jobs; + } + + // Function to get failed jobs NOT ending with "-ib" + async function getFailedJobs(run_id) { + const jobs = await getAllJobs(run_id); + const failedJobs = jobs.filter((job) => { + return job.conclusion === 'failure' && !job.name.endsWith('-ib'); + }); + return failedJobs.map((job) => job.name); + } + + const run_id1 = process.env.RUN_ID1; + const run_id2 = process.env.RUN_ID2; + + const failedJobs1 = await getFailedJobs(run_id1); + const failedJobs2 = await getFailedJobs(run_id2); + + // Find jobs that failed in both runs + const failedInBoth = failedJobs1.filter((job) => failedJobs2.includes(job)); + + core.setOutput('failed_jobs', failedInBoth.join(',')); + + - name: Extract and Process Log + id: extract_summary + shell: bash {0} + run: | + # Get the failed jobs from outputs + failed_jobs_ib="${{ steps.get_failed_jobs.outputs.failed_jobs }}" + failed_jobs_no_ib="${{ steps.get_failed_jobs_no_ib.outputs.failed_jobs }}" + + # Get the conclusions and URLs + conclusion1="${{ steps.get_runs.outputs.conclusion1 }}" + url1="${{ steps.get_runs.outputs.url1 }}" + url2="${{ steps.get_runs.outputs.url2 }}" + + # Function to get status emoji + get_status_emoji() { + local conclusion="$1" + if [ "$conclusion" = "success" ]; then + echo "✅" + elif [ "$conclusion" = "failure" ]; then + echo "❌" + else + echo "⚠️" + fi + } + + emoji1=$(get_status_emoji "$conclusion1") + + # Initialize message + message="" + + # Add run details + message+="**Date:** $(date -u +"%b %d %Y")\n" + message+="**Status:** $emoji1 $conclusion1\n\n" + + message+="Pipeline Run 1: [View Run]($url1)\n" + message+="Pipeline Run 2: [View Run]($url2)\n\n" + + # Process jobs with '-ib' suffix + if [[ -n "$failed_jobs_ib" ]]; then + message+="**Ironbank Images failed consecutively in last two runs**:\n" + count=1 + IFS=',' read -ra JOBS_IB <<< "$failed_jobs_ib" + for job in "${JOBS_IB[@]}"; do + message+="$count. $job\n" + count=$((count + 1)) + done + fi + + # Process jobs without '-ib' suffix + if [[ -n "$failed_jobs_no_ib" ]]; then + message+="\n**Non-Ironbank Images failed consecutively in last two runs**:\n" + count=1 + IFS=',' read -ra JOBS_NO_IB <<< "$failed_jobs_no_ib" + for job in "${JOBS_NO_IB[@]}"; do + message+="$count. $job\n" + count=$((count + 1)) + done + fi + + # If no failed jobs, set message accordingly + if [[ -z "$failed_jobs_ib" && -z "$failed_jobs_no_ib" ]]; then + message+="All images passed in the last two runs." + fi + + # Save message to summary.txt + echo -e "$message" > summary.txt + + - name: Send to Zulip + if: ${{ always() }} + shell: bash + env: + ZULIP_SERVER_URL: ${{ secrets.ZULIP_SERVER_URL }} + ZULIP_BOT_EMAIL: ${{ secrets.ZULIP_BOT_EMAIL }} + ZULIP_BOT_API_KEY: ${{ secrets.ZULIP_BOT_API_KEY }} + ZULIP_STREAM_NAME: 'community images' + ZULIP_TOPIC_NAME: 'Image Creation Run Alerts 🤖' + run: | + message=$(cat summary.txt) + # Send the message to Zulip + response=$(curl -sSf -X POST "$ZULIP_SERVER_URL/api/v1/messages" \ + -u "$ZULIP_BOT_EMAIL:$ZULIP_BOT_API_KEY" \ + -d type="stream" \ + -d to="$ZULIP_STREAM_NAME" \ + -d topic="$ZULIP_TOPIC_NAME" \ + --data-urlencode content="$message") + + # Check if the curl command was successful + if [ $? -ne 0 ]; then + echo "Failed to send message to Zulip." + echo "Response: $response" + exit 1 + fi