Skip to content

Commit 2cc4391

Browse files
Merge branch 'develop' into feature/parallelize-mt-integration-tests
2 parents d6c2ac0 + 1cf22d0 commit 2cc4391

3 files changed

Lines changed: 270 additions & 0 deletions

File tree

.github/scripts/analyze-failure.js

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
const { getOctokit } = require("@actions/github");
2+
const { GoogleGenerativeAI } = require("@google/generative-ai");
3+
4+
// Configuration
5+
const MAX_LOG_LINES = 500; // Lines to analyze from the tail of the log
6+
const MAX_TOKENS = 15000;
7+
8+
async function run() {
9+
try {
10+
const token = process.env.GITHUB_TOKEN;
11+
const apiKey = process.env.GEMINI_API_KEY;
12+
const runId = process.env.WORKFLOW_RUN_ID;
13+
const workflowName = process.env.WORKFLOW_NAME;
14+
const owner = process.env.REPO_OWNER;
15+
const repo = process.env.REPO_NAME;
16+
17+
if (!token || !apiKey || !runId) {
18+
throw new Error("Missing required environment variables.");
19+
}
20+
21+
const octokit = getOctokit(token);
22+
const genAI = new GoogleGenerativeAI(apiKey);
23+
const model = genAI.getGenerativeModel({ model: "gemini-2.5-flash" });
24+
25+
console.log(`Analyzing failure for workflow run ${runId} (${workflowName})...`);
26+
27+
// 1. Get Jobs for the Run
28+
const jobs = await octokit.rest.actions.listJobsForWorkflowRun({
29+
owner,
30+
repo,
31+
run_id: runId,
32+
});
33+
34+
// 2. Find the Failed Job(s)
35+
const failedJob = jobs.data.jobs.find(j => j.conclusion === 'failure');
36+
if (!failedJob) {
37+
console.log("No failed job found (or it was cancelled). Exiting.");
38+
return;
39+
}
40+
41+
console.log(`Found failed job: ${failedJob.name} (ID: ${failedJob.id})`);
42+
43+
// 3. Download Logs for the Failed Job
44+
console.log("Downloading logs...");
45+
let logContent = "";
46+
try {
47+
const logs = await octokit.rest.actions.downloadJobLogsForWorkflowRun({
48+
owner,
49+
repo,
50+
job_id: failedJob.id,
51+
});
52+
logContent = logs.data;
53+
} catch (error) {
54+
// Sometimes logs redirect, handle if necessary, but octokit usually handles it.
55+
// If raw text is returned, use it.
56+
logContent = error.response?.data || "";
57+
if (!logContent && typeof logs === 'string') logContent = logs;
58+
if (!logContent) {
59+
console.error("Could not retrieve logs:", error.message);
60+
return;
61+
}
62+
}
63+
64+
// 4. Pre-process Logs (Tail & Token Limit)
65+
const lines = logContent.split('\n');
66+
// Simple heuristic: Take the last N lines, as errors are usually at the end.
67+
// For better results, one could scan for "ERROR" or "FAILED" and take surrounding context.
68+
const tailLogs = lines.slice(-MAX_LOG_LINES).join('\n');
69+
70+
console.log(`Extracted log tail (${tailLogs.length} characters). Generating analysis...`);
71+
72+
// 5. Generate Analysis with Gemini
73+
// const prompt = `
74+
// You are a DevOps Expert and "Build Doctor".
75+
// A GitHub Actions workflow '${workflowName}' failed.
76+
77+
// Analyze the following log snippet (last ${MAX_LOG_LINES} lines) to identify the root cause.
78+
79+
// Log Snippet:
80+
// \`\`\`
81+
// ${tailLogs}
82+
// \`\`\`
83+
84+
// Your response must be a concise Markdown comment suitable for a developer.
85+
// Structure:
86+
87+
// ## 🩺 Build Doctor Diagnosis
88+
89+
// **1. Root Cause:**
90+
// (Explain what went wrong in 1-2 senteces. Be specific: Syntax error, Dependencies, Test failure, Infra, etc.)
91+
92+
// **2. Relevant Log Lines:**
93+
// (Quote the specific error message from the logs)
94+
95+
// **3. Suggested Fix:**
96+
// (Actionable advice. If it's a code fix, show the snippet. If it's a config tweak, show the command or yaml change.)
97+
98+
// **Confidence:** (High/Medium/Low)
99+
// `;
100+
const prompt = `
101+
You are an expert DevOps engineer and "Build Doctor".
102+
A GitHub Actions workflow '${workflowName}' has failed.
103+
104+
Your job:
105+
- Read the log snippet carefully.
106+
- Identify the **single most likely root cause** of the failure.
107+
- Map it to a clear category (Syntax, Dependency, Test, Infra, Config, Credential, Network, Tooling, etc.).
108+
- Propose **one** primary fix that a developer can apply quickly.
109+
110+
Context:
111+
- These are the last ${MAX_LOG_LINES} lines of the job log.
112+
- They may contain retries, warnings, and noisy stack traces.
113+
- The **true error** is usually near the first occurrence of "error", "exception", "failed", "fatal", non‑zero exit codes, or GitHub Actions step failure messages.
114+
115+
Log Snippet:
116+
\`\`\`
117+
${tailLogs}
118+
\`\`\`
119+
120+
First, think step by step (do NOT include this reasoning in the final answer):
121+
1. Skim for the first real failure signal (error/exception/exit code/failed step).
122+
2. Summarize in your own words what actually went wrong.
123+
3. Decide which category it belongs to: Syntax, Dependency, Test, Infra, Config, Credential, Network, Tooling, Other.
124+
4. Decide the most likely fix a DevOps/dev engineer should try first.
125+
5. If there is not enough information, state that clearly and suggest what extra logs/checks are needed.
126+
127+
Then, output **only** the following Markdown comment, nothing else:
128+
129+
# 🩺 Build Doctor Diagnosis
130+
131+
### Diagnosis (${workflowName})
132+
**1. Root Cause:**
133+
(Explain what went wrong in 1–2 sentences. Be specific: e.g. "Maven dependency not found", "JUnit test failure", "Docker login failed", "Kubernetes connection timeout". If unsure, say "Most likely ..." and why.)
134+
135+
**2. Relevant Log Lines:**
136+
(Quote the *minimal* most important 1–3 lines from the logs that show the error. Do not paste large blocks.)
137+
138+
**3. Suggested Fix:**
139+
(Give concrete, copy‑pasteable steps.
140+
- For code issues: show the key code or config snippet to change.
141+
- For pipeline/config issues: show the YAML or command to adjust.
142+
- For infra/credentials: describe exact checks or commands to run, and what to update.)
143+
144+
**Confidence:** High | Medium | Low
145+
(Choose one based on how clear the error is. If logs are ambiguous or truncated, use Medium or Low and say what’s missing.)
146+
`;
147+
148+
149+
const result = await model.generateContent(prompt);
150+
const analysis = result.response.text();
151+
152+
console.log("Analysis generated. Posting comment...");
153+
154+
// 6. Post Comment
155+
// We need to find where to post.
156+
// If triggered by PR, we post to the PR.
157+
// If triggered by Push, we post to the Commit.
158+
159+
// Context is tricky in 'workflow_run'. We have to look at the 'workflow_run' event payload.
160+
// We can get the PRs associated with the run.
161+
const runDetails = await octokit.rest.actions.getWorkflowRun({
162+
owner,
163+
repo,
164+
run_id: runId
165+
});
166+
167+
const prs = runDetails.data.pull_requests;
168+
169+
if (prs && prs.length > 0) {
170+
// Post to the first associated PR
171+
const prNumber = prs[0].number;
172+
await octokit.rest.issues.createComment({
173+
owner,
174+
repo,
175+
issue_number: prNumber,
176+
body: analysis
177+
});
178+
console.log(`Posted analysis to PR #${prNumber}`);
179+
} else {
180+
// Post to the Commit
181+
const headSha = runDetails.data.head_sha;
182+
await octokit.rest.repos.createCommitComment({
183+
owner,
184+
repo,
185+
commit_sha: headSha,
186+
body: analysis
187+
});
188+
console.log(`Posted analysis to Commit ${headSha}`);
189+
}
190+
191+
} catch (error) {
192+
console.error("Build Doctor failed:", error);
193+
process.exit(1);
194+
}
195+
}
196+
197+
run();

.github/workflows/build-doctor.yml

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
name: Build Doctor 🩺
2+
3+
on:
4+
workflow_run:
5+
workflows: [
6+
"Choose and Deploy 🚀",
7+
"Demo Build",
8+
"Blackduck analysis",
9+
"CodeQL Analysis",
10+
"Deploy to Maven Central",
11+
"Main build and snapshot deploy",
12+
"Multi Tenancy Deploy & Integration Test🚀",
13+
"Multi Tenancy Deploy & Integration Test LatestVersion🚀",
14+
"Pull Request Builder",
15+
"Single Tenant Deploy and Integration Test",
16+
"Single Tenant Deploy and Integration Test LatestVersion",
17+
"Single Tenant Integration Test 🚀",
18+
"SonarQube Analysis",
19+
"UnitTestsWithCodeCoverage"
20+
]
21+
types:
22+
- completed
23+
24+
jobs:
25+
diagnose-failure:
26+
runs-on: ubuntu-latest
27+
if: ${{ github.event.workflow_run.conclusion == 'failure' }}
28+
permissions:
29+
actions: read
30+
contents: write
31+
issues: write
32+
pull-requests: write
33+
steps:
34+
- name: Checkout repository
35+
uses: actions/checkout@v6
36+
37+
- name: Set up Node.js
38+
uses: actions/setup-node@v4
39+
with:
40+
node-version: '20'
41+
42+
- name: Install dependencies
43+
run: npm install @actions/github@5 @google/generative-ai @octokit/core
44+
45+
- name: Run Build Doctor Analysis
46+
env:
47+
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
48+
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
49+
WORKFLOW_RUN_ID: ${{ github.event.workflow_run.id }}
50+
WORKFLOW_NAME: ${{ github.event.workflow_run.name }}
51+
REPO_OWNER: ${{ github.repository_owner }}
52+
REPO_NAME: ${{ github.event.repository.name }}
53+
run: node .github/scripts/analyze-failure.js

.github/workflows/demo-build.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
name: Demo Build
2+
3+
on:
4+
push:
5+
branches:
6+
- 'demo/**'
7+
8+
jobs:
9+
build:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: actions/checkout@v4
13+
- name: Set up JDK 17
14+
uses: actions/setup-java@v4
15+
with:
16+
java-version: '17'
17+
distribution: 'temurin'
18+
cache: maven
19+
- name: Build with Maven
20+
run: mvn clean install

0 commit comments

Comments
 (0)