Skip to content

Commit 72a0702

Browse files
authored
ci: add schedule reporter and cb failure reporter files (#1260)
1 parent 5e73fc5 commit 72a0702

File tree

2 files changed

+209
-0
lines changed

2 files changed

+209
-0
lines changed
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
name: Cloud Build Failure Reporter
16+
17+
on:
18+
workflow_call:
19+
inputs:
20+
trigger_names:
21+
required: true
22+
type: string
23+
workflow_dispatch:
24+
inputs:
25+
trigger_names:
26+
description: 'Cloud Build trigger names separated by comma.'
27+
required: true
28+
default: ''
29+
30+
jobs:
31+
report:
32+
33+
permissions:
34+
issues: 'write'
35+
checks: 'read'
36+
contents: 'read'
37+
38+
runs-on: 'ubuntu-latest'
39+
40+
steps:
41+
- uses: 'actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea' # v7
42+
with:
43+
script: |-
44+
// parse test names
45+
const testNameSubstring = '${{ inputs.trigger_names }}';
46+
const testNameFound = new Map(); //keeps track of whether each test is found
47+
testNameSubstring.split(',').forEach(testName => {
48+
testNameFound.set(testName, false);
49+
});
50+
51+
// label for all issues opened by reporter
52+
const periodicLabel = 'periodic-failure';
53+
54+
// check if any reporter opened any issues previously
55+
const prevIssues = await github.paginate(github.rest.issues.listForRepo, {
56+
...context.repo,
57+
state: 'open',
58+
creator: 'github-actions[bot]',
59+
labels: [periodicLabel]
60+
});
61+
62+
// createOrCommentIssue creates a new issue or comments on an existing issue.
63+
const createOrCommentIssue = async function (title, txt) {
64+
if (prevIssues.length < 1) {
65+
console.log('no previous issues found, creating one');
66+
await github.rest.issues.create({
67+
...context.repo,
68+
title: title,
69+
body: txt,
70+
labels: [periodicLabel]
71+
});
72+
return;
73+
}
74+
// only comment on issue related to the current test
75+
for (const prevIssue of prevIssues) {
76+
if (prevIssue.title.includes(title)){
77+
console.log(
78+
`found previous issue ${prevIssue.html_url}, adding comment`
79+
);
80+
81+
await github.rest.issues.createComment({
82+
...context.repo,
83+
issue_number: prevIssue.number,
84+
body: txt
85+
});
86+
return;
87+
}
88+
}
89+
};
90+
91+
// updateIssues comments on any existing issues. No-op if no issue exists.
92+
const updateIssues = async function (checkName, txt) {
93+
if (prevIssues.length < 1) {
94+
console.log('no previous issues found.');
95+
return;
96+
}
97+
// only comment on issue related to the current test
98+
for (const prevIssue of prevIssues) {
99+
if (prevIssue.title.includes(checkName)){
100+
console.log(`found previous issue ${prevIssue.html_url}, adding comment`);
101+
await github.rest.issues.createComment({
102+
...context.repo,
103+
issue_number: prevIssue.number,
104+
body: txt
105+
});
106+
}
107+
}
108+
};
109+
110+
// Find status of check runs.
111+
// We will find check runs for each commit and then filter for the periodic.
112+
// Checks API only allows for ref and if we use main there could be edge cases where
113+
// the check run happened on a SHA that is different from head.
114+
const commits = await github.paginate(github.rest.repos.listCommits, {
115+
...context.repo
116+
});
117+
118+
const relevantChecks = new Map();
119+
for (const commit of commits) {
120+
console.log(
121+
`checking runs at ${commit.html_url}: ${commit.commit.message}`
122+
);
123+
const checks = await github.rest.checks.listForRef({
124+
...context.repo,
125+
ref: commit.sha
126+
});
127+
128+
// Iterate through each check and find matching names
129+
for (const check of checks.data.check_runs) {
130+
console.log(`Handling test name ${check.name}`);
131+
for (const testName of testNameFound.keys()) {
132+
if (testNameFound.get(testName) === true){
133+
//skip if a check is already found for this name
134+
continue;
135+
}
136+
if (check.name.includes(testName)) {
137+
relevantChecks.set(check, commit);
138+
testNameFound.set(testName, true);
139+
}
140+
}
141+
}
142+
// Break out of the loop early if all tests are found
143+
const allTestsFound = Array.from(testNameFound.values()).every(value => value === true);
144+
if (allTestsFound){
145+
break;
146+
}
147+
}
148+
149+
// Handle each relevant check
150+
relevantChecks.forEach((commit, check) => {
151+
if (
152+
check.status === 'completed' &&
153+
check.conclusion === 'success'
154+
) {
155+
updateIssues(
156+
check.name,
157+
`[Tests are passing](${check.html_url}) for commit [${commit.sha}](${commit.html_url}).`
158+
);
159+
} else if (check.status === 'in_progress') {
160+
console.log(
161+
`Check is pending ${check.html_url} for ${commit.html_url}. Retry again later.`
162+
);
163+
} else {
164+
createOrCommentIssue(
165+
`Cloud Build Failure Reporter: ${check.name} failed`,
166+
`Cloud Build Failure Reporter found test failure for [**${check.name}** ](${check.html_url}) at [${commit.sha}](${commit.html_url}). Please fix the error and then close the issue after the **${check.name}** test passes.`
167+
);
168+
}
169+
});
170+
171+
// no periodic checks found across all commits, report it
172+
const noTestFound = Array.from(testNameFound.values()).every(value => value === false);
173+
if (noTestFound){
174+
createOrCommentIssue(
175+
'Missing periodic tests: ${{ inputs.trigger_names }}',
176+
`No periodic test is found for triggers: ${{ inputs.trigger_names }}. Last checked from ${
177+
commits[0].html_url
178+
} to ${commits[commits.length - 1].html_url}.`
179+
);
180+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
name: Schedule Reporter
16+
17+
on:
18+
schedule:
19+
- cron: '0 6 * * *' # Runs at 6 AM every morning
20+
21+
jobs:
22+
run_reporter:
23+
permissions:
24+
issues: 'write'
25+
checks: 'read'
26+
contents: 'read'
27+
uses: ./.github/workflows/cloud_build_failure_reporter.yml
28+
with:
29+
trigger_names: "py-continuous-test-on-merge,py-integration-test-nightly"

0 commit comments

Comments
 (0)