-
Notifications
You must be signed in to change notification settings - Fork 49
203 lines (190 loc) · 8.94 KB
/
export_cloud_build_logs.yml
File metadata and controls
203 lines (190 loc) · 8.94 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
name: Export Cloud Build Failure Logs
permissions:
contents: read
checks: read
issues: write
pull-requests: write
on:
check_suite:
types: [completed]
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
detect-build-failure:
name: Detect Cloud Build Failures
runs-on: ubuntu-latest
outputs:
failure_detected: ${{ steps.detect_failures.outputs.failure_detected }}
failed_checks: ${{ steps.detect_failures.outputs.failed_checks }}
pr_number: ${{ steps.detect_pr_number.outputs.pr_number }}
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Install GitHub CLI
run: sudo apt-get update && sudo apt-get install -y gh
- name: Detect PR number using gh CLI
id: detect_pr_number
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_EVENT_CHECK_SUITE_HEAD_SHA: ${{ github.event.check_suite.head_sha }}
run: |
PR_NUMBER=$(gh pr list --search "${GITHUB_EVENT_CHECK_SUITE_HEAD_SHA}" --state open --json number --jq '.[0].number')
if [ -z "$PR_NUMBER" ]; then
echo "No associated PR found."
echo "failure_detected=false" >> $GITHUB_OUTPUT
exit 0
else
echo "PR Number is $PR_NUMBER"
echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT
fi
- name: Detect all failed Cloud Build checks
id: detect_failures
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
env:
PR_NUMBER: ${{ steps.detect_pr_number.outputs.pr_number }}
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const prefixes = [
'core-python-sdk-pr-',
'langchain-python-sdk-pr-',
'llamaindex-python-sdk-pr-',
];
const pr_number = process.env.PR_NUMBER;
if (!pr_number) {
core.setOutput('failure_detected', 'false');
return;
}
const { owner, repo } = context.repo;
const sha = context.payload.check_suite.head_sha;
const { data: checks } = await github.rest.checks.listForRef({ owner, repo, ref: sha, per_page: 100 });
const failed = checks.check_runs.filter(
c =>
prefixes.some(prefix => c.name.startsWith(prefix)) &&
c.status === 'completed' &&
c.conclusion === 'failure'
);
if (failed.length === 0) {
core.setOutput('failure_detected', 'false');
return;
}
core.setOutput('failure_detected', 'true');
core.setOutput('failed_checks', JSON.stringify(failed.map(f => ({ name: f.name, id: f.id, html_url: f.html_url, details_url: f.details_url, external_id: f.external_id || '' }))));
core.setOutput('pr_number', pr_number);
process-failed-builds:
needs: detect-build-failure
if: needs.detect-build-failure.outputs.failure_detected == 'true'
runs-on: ubuntu-latest
env:
GCLOUD_SERVICE_KEY: ${{ secrets.GCLOUD_SERVICE_KEY }}
strategy:
matrix:
include: ${{ fromJson(needs.detect-build-failure.outputs.failed_checks) }}
steps:
- name: Parse build ID and set project ID
id: parse_build_info
run: |
details_url="${{ matrix.details_url }}"
build_id=$(echo "$details_url" | sed -n 's#.*/builds/\([^?]*\).*#\1#p')
project_id=$(echo '${GCLOUD_SERVICE_KEY}' | jq -r '.project_id')
echo "Build ID: $build_id"
echo "build_id=$build_id" >> $GITHUB_OUTPUT
echo "project_id=$project_id" >> $GITHUB_OUTPUT
- name: Authenticate to Google Cloud
uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3
with:
credentials_json: ${{ env.GCLOUD_SERVICE_KEY }}
- name: Fetch Cloud Build logs
id: fetch_cloud_build_logs
run: |
LOG_FILE="cloudbuild_error_logs.txt"
GCLOUD_OUTPUT=$(gcloud builds log "${STEPS_PARSE_BUILD_INFO_OUTPUTS_BUILD_ID}" --project="${STEPS_PARSE_BUILD_INFO_OUTPUTS_PROJECT_ID}" 2>&1)
if echo "$GCLOUD_OUTPUT" | grep -q "ERROR"; then
echo "Failed to fetch logs from gcloud builds log." > "${LOG_FILE}"
echo "--- gcloud command output ---" >> "${LOG_FILE}"
echo "$GCLOUD_OUTPUT" >> "${LOG_FILE}"
echo "-----------------------------" >> "${LOG_FILE}"
echo "Log URL: ${{ matrix.details_url }}" >> "${LOG_FILE}"
elif [ -z "$GCLOUD_OUTPUT" ]; then
echo "Warning: No logs found or gcloud command returned empty output." > "${LOG_FILE}"
echo "Log URL: ${{ matrix.details_url }}" >> "${LOG_FILE}"
else
echo "$GCLOUD_OUTPUT" > "${LOG_FILE}"
fi
echo "log_file_name=${LOG_FILE}" >> $GITHUB_OUTPUT
env:
STEPS_PARSE_BUILD_INFO_OUTPUTS_BUILD_ID: ${{ steps.parse_build_info.outputs.build_id }}
STEPS_PARSE_BUILD_INFO_OUTPUTS_PROJECT_ID: ${{ steps.parse_build_info.outputs.project_id }}
- name: Upload Logs as Artifact
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
name: logs-${{ matrix.name }}-${{ steps.parse_build_info.outputs.build_id }}
path: ${{ steps.fetch_cloud_build_logs.outputs.log_file_name }}
retention-days: 15
post-pr-comment:
needs: [detect-build-failure, process-failed-builds]
runs-on: ubuntu-latest
if: always()
steps:
- name: Compose and post PR comment with artifact links
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
env:
PR_NUMBER: ${{ needs.detect-build-failure.outputs.pr_number }}
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const prNumber = process.env.PR_NUMBER;
if (!prNumber) {
core.info('No PR number was passed from the detection job. Skipping.');
return;
}
const { owner, repo } = context.repo;
const run_id = context.runId;
const { data: artifacts } = await github.rest.actions.listWorkflowRunArtifacts({ owner, repo, run_id });
const logArtifacts = artifacts.artifacts.filter(a => a.name.startsWith('logs-'));
const { data: comments } = await github.rest.issues.listComments({ owner, repo, issue_number: prNumber });
// The botLogin is 'github-actions' when using GITHUB_TOKEN
// You should filter comments by user.login for robustness
const botLogin = 'github-actions[bot]';
const existing = comments.find(c => c.user.login === 'github-actions[bot]' && c.body && c.body.includes('**Download Error logs:**'));
if (logArtifacts.length > 0) {
let body = '🔗 **Download Error logs:**\n\n';
for (const artifact of logArtifacts) {
const url = `https://github.com/${owner}/${repo}/actions/runs/${run_id}/artifacts/${artifact.id}`;
const openParenIndex = artifact.name.indexOf('(');
let displayName;
if (openParenIndex !== -1) {
displayName = artifact.name.substring(0, openParenIndex);
} else {
displayName = artifact.name.replace(/^logs-/, '');
}
displayName = displayName.trim();
body += `- [${displayName}](${url})\n`;
}
if (existing) {
await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body });
core.info(`Updated existing comment in PR #${prNumber}.`);
} else {
await github.rest.issues.createComment({ owner, repo, issue_number: prNumber, body });
core.info(`Posted new comment in PR #${prNumber}.`);
}
} else if (existing) {
await github.rest.issues.deleteComment({ owner, repo, comment_id: existing.id });
core.info(`Deleted previous failure comment in PR #${prNumber} as all tests now pass.`);
} else {
core.info('No failures found and no existing comment to delete.');
}