Skip to content

Commit a84ef6d

Browse files
authored
ci(scan-logs): attach pipeline annotation if concerning logs are found (#46)
1 parent 56aa100 commit a84ef6d

4 files changed

Lines changed: 244 additions & 0 deletions

File tree

.github/workflows/ci.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,5 +118,11 @@ jobs:
118118
echo "One or more required jobs failed or was cancelled. Marking finalizer as failed."
119119
exit 1
120120
fi
121+
- name: Checkout the repository
122+
uses: actions/checkout@v4
123+
- name: Scan workflow logs for warnings and errors
124+
run: scripts/scan_workflow_logs.sh ${{ github.run_id }}
125+
env:
126+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
121127
- name: Finalize
122128
run: echo "Pipeline complete!"

scripts/scan_workflow_logs.sh

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
# Check if run ID is provided as argument
5+
if [[ $# -eq 1 ]]; then
6+
GITHUB_RUN_ID="$1"
7+
elif [[ -z "${GITHUB_RUN_ID:-}" ]]; then
8+
echo "Usage: $0 <run_id>"
9+
echo "Or set GITHUB_RUN_ID environment variable"
10+
exit 1
11+
fi
12+
13+
# Ensure required environment variables
14+
: "${GITHUB_REPOSITORY:?Missing GITHUB_REPOSITORY}"
15+
: "${GITHUB_TOKEN:?Missing GITHUB_TOKEN}"
16+
17+
# Use the GH CLI with auth
18+
export GH_TOKEN="$GITHUB_TOKEN"
19+
20+
# Create a temp dir for logs
21+
tmpdir=$(mktemp -d)
22+
trap 'rm -rf "$tmpdir"' EXIT
23+
24+
cd "$tmpdir"
25+
26+
# Get list of jobs in the run
27+
echo "Fetching jobs for run $GITHUB_RUN_ID..."
28+
jobs_json=$(gh api \
29+
-H "Accept: application/vnd.github+json" \
30+
"/repos/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/jobs" \
31+
--paginate)
32+
33+
# Extract completed job IDs (excluding the current job if GITHUB_JOB is set and always excluding "Test Scripts")
34+
if [[ -n "${GITHUB_JOB:-}" ]]; then
35+
completed_jobs=$(echo "$jobs_json" | jq -r --arg current_job "$GITHUB_JOB" '.jobs[] | select(.status == "completed") | select(.name != $current_job) | select(.name != "Test Scripts") | .id')
36+
else
37+
completed_jobs=$(echo "$jobs_json" | jq -r '.jobs[] | select(.status == "completed") | select(.name != "Test Scripts") | .id')
38+
fi
39+
40+
if [[ -z "$completed_jobs" ]]; then
41+
echo "::warning::No completed jobs found to scan"
42+
exit 0
43+
fi
44+
45+
# Download logs for each completed job
46+
echo "Downloading logs for completed jobs..."
47+
for job_id in $completed_jobs; do
48+
job_name=$(echo "$jobs_json" | jq -r --arg id "$job_id" '.jobs[] | select(.id == ($id | tonumber)) | .name')
49+
echo " Downloading logs for job: $job_name (ID: $job_id)"
50+
51+
# Create directory for this job's logs
52+
mkdir -p "$job_name"
53+
54+
# Download the job's log
55+
if gh api \
56+
-H "Accept: application/vnd.github+json" \
57+
"/repos/${GITHUB_REPOSITORY}/actions/jobs/${job_id}/logs" \
58+
--method GET > "$job_name/log.txt" 2>/dev/null; then
59+
echo " ✓ Downloaded $(wc -l < "$job_name/log.txt") lines"
60+
else
61+
echo " ✗ Failed to download logs"
62+
rm -rf "$job_name"
63+
fi
64+
done
65+
66+
# Scan for lines with "deprecated", "warning:", or "error"
67+
echo "Scanning logs for warnings, deprecations, and errors..."
68+
69+
# Process each log file and collect results
70+
results_file=$(mktemp)
71+
72+
find . -type f -name "*.txt" | while IFS= read -r logfile; do
73+
# Get the job name from the directory structure
74+
job_name=$(dirname "$logfile" | sed 's|^\./||' | cut -d'/' -f1)
75+
76+
# Search for patterns with context
77+
while IFS= read -r line; do
78+
# Extract line number and content
79+
line_num=$(echo "$line" | cut -d: -f2)
80+
content=$(echo "$line" | cut -d: -f3-)
81+
82+
# Sanitize content to prevent command injection and log poisoning
83+
sanitized_content=$(echo "$content" | tr -d '\n\r' | cut -c1-200)
84+
85+
# Determine the type of issue and output both annotation and count
86+
if echo "$content" | grep -qiE '\berror\b'; then
87+
echo "::error file=$job_name,line=$line_num::$sanitized_content"
88+
echo "error" >> "$results_file"
89+
elif echo "$content" | grep -qiE '\bwarning:'; then
90+
echo "::warning file=$job_name,line=$line_num::$sanitized_content"
91+
echo "warning" >> "$results_file"
92+
elif echo "$content" | grep -qiE '\bdeprecated\b'; then
93+
echo "::warning file=$job_name,line=$line_num::$sanitized_content"
94+
echo "deprecated" >> "$results_file"
95+
fi
96+
done < <(grep -niE '(\berror\b|warning:|deprecated)' "$logfile" 2>/dev/null || true)
97+
done
98+
99+
# Count results
100+
error_count=$(grep -c "error" "$results_file" 2>/dev/null || echo 0)
101+
warning_count=$(grep -c "warning" "$results_file" 2>/dev/null || echo 0)
102+
deprecated_count=$(grep -c "deprecated" "$results_file" 2>/dev/null || echo 0)
103+
104+
# Summary
105+
echo
106+
echo "=== Log Scan Summary ==="
107+
echo "Errors found: $error_count"
108+
echo "Warnings found: $warning_count"
109+
echo "Deprecation notices found: $deprecated_count"
110+
echo "Total issues: $((error_count + warning_count + deprecated_count))"
111+
112+
# Clean up temp file
113+
rm -f "$results_file"
114+
115+
# Exit with success even if issues were found
116+
exit 0

{{cookiecutter.project_name|replace(" ", "")}}/.github/workflows/ci.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,5 +146,11 @@ jobs:
146146
echo "One or more required jobs failed or was cancelled. Marking finalizer as failed."
147147
exit 1
148148
fi
149+
- name: Checkout the repository
150+
uses: actions/checkout@v4
151+
- name: Scan workflow logs for warnings and errors
152+
run: scripts/scan_workflow_logs.sh {% raw %}${{ github.run_id }}{% endraw %}
153+
env:
154+
GITHUB_TOKEN: "{% raw %}${{ secrets.GITHUB_TOKEN }}{% endraw %}"
149155
- name: Finalize
150156
run: echo "Pipeline complete!"
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
# Check if run ID is provided as argument
5+
if [[ $# -eq 1 ]]; then
6+
GITHUB_RUN_ID="$1"
7+
elif [[ -z "${GITHUB_RUN_ID:-}" ]]; then
8+
echo "Usage: $0 <run_id>"
9+
echo "Or set GITHUB_RUN_ID environment variable"
10+
exit 1
11+
fi
12+
13+
# Ensure required environment variables
14+
: "${GITHUB_REPOSITORY:?Missing GITHUB_REPOSITORY}"
15+
: "${GITHUB_TOKEN:?Missing GITHUB_TOKEN}"
16+
17+
# Use the GH CLI with auth
18+
export GH_TOKEN="$GITHUB_TOKEN"
19+
20+
# Create a temp dir for logs
21+
tmpdir=$(mktemp -d)
22+
trap 'rm -rf "$tmpdir"' EXIT
23+
24+
cd "$tmpdir"
25+
26+
# Get list of jobs in the run
27+
echo "Fetching jobs for run $GITHUB_RUN_ID..."
28+
jobs_json=$(gh api \
29+
-H "Accept: application/vnd.github+json" \
30+
"/repos/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/jobs" \
31+
--paginate)
32+
33+
# Extract completed job IDs (excluding the current job if GITHUB_JOB is set and always excluding "Test Scripts")
34+
if [[ -n "${GITHUB_JOB:-}" ]]; then
35+
completed_jobs=$(echo "$jobs_json" | jq -r --arg current_job "$GITHUB_JOB" '.jobs[] | select(.status == "completed") | select(.name != $current_job) | select(.name != "Test Scripts") | .id')
36+
else
37+
completed_jobs=$(echo "$jobs_json" | jq -r '.jobs[] | select(.status == "completed") | select(.name != "Test Scripts") | .id')
38+
fi
39+
40+
if [[ -z "$completed_jobs" ]]; then
41+
echo "::warning::No completed jobs found to scan"
42+
exit 0
43+
fi
44+
45+
# Download logs for each completed job
46+
echo "Downloading logs for completed jobs..."
47+
for job_id in $completed_jobs; do
48+
job_name=$(echo "$jobs_json" | jq -r --arg id "$job_id" '.jobs[] | select(.id == ($id | tonumber)) | .name')
49+
echo " Downloading logs for job: $job_name (ID: $job_id)"
50+
51+
# Create directory for this job's logs
52+
mkdir -p "$job_name"
53+
54+
# Download the job's log
55+
if gh api \
56+
-H "Accept: application/vnd.github+json" \
57+
"/repos/${GITHUB_REPOSITORY}/actions/jobs/${job_id}/logs" \
58+
--method GET > "$job_name/log.txt" 2>/dev/null; then
59+
echo " ✓ Downloaded $(wc -l < "$job_name/log.txt") lines"
60+
else
61+
echo " ✗ Failed to download logs"
62+
rm -rf "$job_name"
63+
fi
64+
done
65+
66+
# Scan for lines with "deprecated", "warning:", or "error"
67+
echo "Scanning logs for warnings, deprecations, and errors..."
68+
69+
# Process each log file and collect results
70+
results_file=$(mktemp)
71+
72+
find . -type f -name "*.txt" | while IFS= read -r logfile; do
73+
# Get the job name from the directory structure
74+
job_name=$(dirname "$logfile" | sed 's|^\./||' | cut -d'/' -f1)
75+
76+
# Search for patterns with context
77+
while IFS= read -r line; do
78+
# Extract line number and content
79+
line_num=$(echo "$line" | cut -d: -f2)
80+
content=$(echo "$line" | cut -d: -f3-)
81+
82+
# Sanitize content to prevent command injection and log poisoning
83+
sanitized_content=$(echo "$content" | tr -d '\n\r' | cut -c1-200)
84+
85+
# Determine the type of issue and output both annotation and count
86+
if echo "$content" | grep -qiE '\berror\b'; then
87+
echo "::error file=$job_name,line=$line_num::$sanitized_content"
88+
echo "error" >> "$results_file"
89+
elif echo "$content" | grep -qiE '\bwarning:'; then
90+
echo "::warning file=$job_name,line=$line_num::$sanitized_content"
91+
echo "warning" >> "$results_file"
92+
elif echo "$content" | grep -qiE '\bdeprecated\b'; then
93+
echo "::warning file=$job_name,line=$line_num::$sanitized_content"
94+
echo "deprecated" >> "$results_file"
95+
fi
96+
done < <(grep -niE '(\berror\b|warning:|deprecated)' "$logfile" 2>/dev/null || true)
97+
done
98+
99+
# Count results
100+
error_count=$(grep -c "error" "$results_file" 2>/dev/null || echo 0)
101+
warning_count=$(grep -c "warning" "$results_file" 2>/dev/null || echo 0)
102+
deprecated_count=$(grep -c "deprecated" "$results_file" 2>/dev/null || echo 0)
103+
104+
# Summary
105+
echo
106+
echo "=== Log Scan Summary ==="
107+
echo "Errors found: $error_count"
108+
echo "Warnings found: $warning_count"
109+
echo "Deprecation notices found: $deprecated_count"
110+
echo "Total issues: $((error_count + warning_count + deprecated_count))"
111+
112+
# Clean up temp file
113+
rm -f "$results_file"
114+
115+
# Exit with success even if issues were found
116+
exit 0

0 commit comments

Comments
 (0)