lhotari commented on code in PR #24940:
URL: https://github.com/apache/pulsar/pull/24940#discussion_r2491000210


##########
.github/workflows/ci-pulsarbot.yaml:
##########
@@ -34,6 +34,233 @@ jobs:
     steps:
       - name: Execute pulsarbot command
         id: pulsarbot
-        env:
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-        uses: apache/pulsar-test-infra/pulsarbot@master
+        uses: actions/github-script@v7
+        with:
+          github-token: ${{ secrets.GITHUB_TOKEN }}
+          script: |
+            // Supported commands:
+            // - /pulsarbot rerun
+            //   Reruns all completed workflows with conclusions of 
failure/timed_out/skipped/cancelled
+            //   If workflow is still running, cannot rerun whole workflow, 
just suggest using "/pulsarbot rerun jobname"
+            // - /pulsarbot rerun jobname
+            //   Matches job.name by keyword, reruns matching jobs (regardless 
of current state, failures are logged)
+            // - /pulsarbot stop or /pulsarbot cancel
+            //   Cancels all still running (queued/in_progress) workflow runs 
associated with the current PR
+            const commentBody = context.payload.comment.body.trim();
+            const prefix = '/pulsarbot';
+            if (!commentBody.startsWith(prefix)) {
+              console.log('Not a pulsarbot command, skipping ...');
+              return;
+            }
+            if (!context.payload.issue || !context.payload.issue.pull_request) 
{
+              console.error('This comment is not on a Pull Request. pulsarbot 
only works on PRs.');
+              return;
+            }
+            const parts = commentBody.split(/\s+/);
+            const sub = (parts[1] || '').toLowerCase();
+            const arg = parts.length > 2 ? parts.slice(2).join(' ') : '';
+            const supported = ['rerun', 'stop', 'cancel', 
'rerun-failure-checks'];
+            if (!supported.includes(sub)) {
+              console.log(`Unsupported command '${sub}'. Supported: 
'/pulsarbot rerun [jobName?]', '/pulsarbot stop', '/pulsarbot cancel'.`);
+              return;
+            }
+            const prNum = context.payload.issue.number;
+            // Get PR info
+            let pr;
+            try {
+              ({ data: pr } = await github.rest.pulls.get({
+                owner: context.repo.owner,
+                repo: context.repo.repo,
+                pull_number: prNum
+              }));
+            } catch (e) {
+              console.error(`Failed to fetch PR #${prNum}: ${e.message}`);
+              return;
+            }
+            const headSha = pr.head.sha;
+            const prBranch = pr.head.ref;
+            const prUser = pr.user.login;
+            const prUrl = pr.html_url;
+            console.log(`pulsarbot handling PR #${prNum} ${prUrl}`);
+            console.log(`PR branch='${prBranch}', headSha='${headSha}', 
author='${prUser}'`);
+            console.log(`Command parsed => sub='${sub}', arg='${arg || ''}'`);
+            // Fetch workflow runs in this repo triggered by this user on this 
branch, then filter by headSha
+            let page = 1;
+            const allRunsRaw = [];
+            while (true) {
+              const { data } = await 
github.rest.actions.listWorkflowRunsForRepo({
+                owner: context.repo.owner,
+                repo: context.repo.repo,
+                actor: prUser,
+                branch: prBranch,
+                per_page: 100,
+                page
+              });
+              const wr = data.workflow_runs || [];
+              if (wr.length === 0) break;
+              allRunsRaw.push(...wr);
+              if (wr.length < 100) break;
+              page++;
+            }
+            const runsAtHead = allRunsRaw.filter(r => r.head_sha === headSha);
+            if (runsAtHead.length === 0) {
+              console.error(`No workflow runs found for head SHA ${headSha} on 
branch ${prBranch}.`);
+              return;
+            }
+            // Only keep the latest run for each workflow_id
+            runsAtHead.sort((a, b) => {
+              if (a.workflow_id !== b.workflow_id) return a.workflow_id - 
b.workflow_id;
+              return new Date(b.created_at) - new Date(a.created_at);
+            });
+            const latestRuns = [];
+            const seen = new Set();
+            for (const r of runsAtHead) {
+              if (!seen.has(r.workflow_id)) {
+                seen.add(r.workflow_id);
+                latestRuns.push(r);
+              }
+            }
+            function runKey(r) {
+              return `[run_id=${r.id}] ${r.name || '(unnamed)'} | 
status=${r.status} | conclusion=${r.conclusion || '-'} | ${r.html_url}`;
+            }
+            console.log('--- Latest workflow runs for this PR headSHA (one per 
workflow) ---');
+            for (const r of latestRuns) console.log('- ' + runKey(r));
+            // Utility: list all jobs in a run
+            async function listAllJobs(runId) {
+              let jobs = [];
+              let p = 1;
+              while (true) {
+                const { data } = await 
github.rest.actions.listJobsForWorkflowRun({
+                  owner: context.repo.owner,
+                  repo: context.repo.repo,
+                  run_id: runId,
+                  per_page: 100,
+                  page: p
+                });
+                const js = data.jobs || [];
+                if (js.length === 0) break;
+                jobs.push(...js);
+                if (js.length < 100) break;
+                p++;
+              }
+              return jobs;
+            }
+            // Utility: rerun a single job
+            async function rerunJob(job, run) {
+              try {
+                if (github.rest.actions.reRunJobForWorkflowRun) {
+                  await github.rest.actions.reRunJobForWorkflowRun({
+                    owner: context.repo.owner,
+                    repo: context.repo.repo,
+                    job_id: job.id
+                  });
+                } else {
+                  await github.request('POST 
/repos/{owner}/{repo}/actions/jobs/{job_id}/rerun', {
+                    owner: context.repo.owner,
+                    repo: context.repo.repo,
+                    job_id: job.id
+                  });
+                }
+                console.log(`Re-ran job '${job.name}' (job_id=${job.id}) in 
run '${run.name}' | ${run.html_url}`);
+                return true;
+              } catch (e) {
+                console.log(`Failed to re-run job '${job.name}' 
(job_id=${job.id}) in run '${run.name}': ${e.message}`);
+                return false;
+              }
+            }
+            // Command 1: /pulsarbot rerun
+            if ((sub === 'rerun' || sub === 'rerun-failure-checks') && !arg) {
+              const targetConclusions = new Set(['failure', 'timed_out', 
'cancelled', 'skipped']);
+              let fullRerunCount = 0;
+              let skippedRunning = 0;
+              let skippedConclusion = 0;
+              console.log('Mode: full workflow re-run for completed runs with 
conclusions in [failure,timed_out,cancelled,skipped].');
+              for (const r of latestRuns) {
+                if (r.status !== 'completed') {
+                  console.log(`Skip (still running) ${runKey(r)}. Cannot 
re-run whole workflow. Consider '/pulsarbot rerun <jobName>' for single job.`);
+                  skippedRunning++;
+                  continue;
+                }
+                if (!targetConclusions.has(r.conclusion)) {
+                  console.log(`Skip (conclusion not eligible) ${runKey(r)}`);
+                  skippedConclusion++;
+                  continue;
+                }
+                try {
+                  await github.rest.actions.reRunWorkflow({
+                    owner: context.repo.owner,
+                    repo: context.repo.repo,
+                    run_id: r.id
+                  });
+                  console.log(`Triggered full re-run for ${runKey(r)}`);
+                  fullRerunCount++;
+                } catch (e) {
+                  console.log(`Failed to trigger full re-run for ${runKey(r)}: 
${e.message}`);
+                }
+              }
+              if (fullRerunCount === 0) {
+                console.error(`No eligible workflow runs to re-run. Skipped 
running=${skippedRunning}, skipped by conclusion=${skippedConclusion}.`);
+              } else {
+                console.log(`Finished. Triggered full re-run for 
${fullRerunCount} workflow run(s). Skipped running=${skippedRunning}, skipped 
by conclusion=${skippedConclusion}.`);

Review Comment:
   full re-run: 
https://docs.github.com/en/rest/actions/workflow-runs?apiVersion=2022-11-28#re-run-a-workflow
 / https://octokit.github.io/rest.js/v22/#actions-re-run-workflow
   re-run failed jobs: 
https://docs.github.com/en/rest/actions/workflow-runs?apiVersion=2022-11-28#re-run-failed-jobs-from-a-workflow-run
 / https://octokit.github.io/rest.js/v22/#actions-re-run-workflow-failed-jobs



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to