Skip to content

Commit 900f394

Browse files
Add sumary to workflow that compare vulnerabilities betwen two branches #TASK-7908
1 parent cbb62d7 commit 900f394

File tree

1 file changed

+112
-127
lines changed

1 file changed

+112
-127
lines changed

.github/workflows/compare-vulnerabilities.yml

Lines changed: 112 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ on:
66
branch_a:
77
description: 'First branch to compare (e.g. main)'
88
required: true
9-
default: 'main'
9+
default: 'develop'
1010
branch_b:
1111
description: 'Second branch to compare (e.g. feature/fix-branch)'
1212
required: true
@@ -93,7 +93,8 @@ jobs:
9393
echo "branchB-grype.json missing"
9494
fi
9595
96-
- name: Generate robust comparison report (by VulnerabilityID and by package:version)
96+
- name: Generate comparison report (table + full MD)
97+
id: gen_report
9798
run: |
9899
set -euo pipefail
99100
A_BRANCH="${{ github.event.inputs.branch_a }}"
@@ -103,7 +104,7 @@ jobs:
103104
OUT="${REPORT_DIR}/comparison-report.md"
104105
mkdir -p "$(dirname "$OUT")"
105106
106-
# --- basic presence checks ---
107+
# comprueba que los JSON existen
107108
if [ ! -s "$A_GRYPE" ]; then
108109
echo "ERROR: ${A_GRYPE} not found or empty" >&2
109110
exit 1
@@ -113,147 +114,82 @@ jobs:
113114
exit 1
114115
fi
115116
117+
# --- extraer entradas formateadas: ID|pkg:version|SEVERITY ---
118+
jq -r '[ .matches[]? as $m |
119+
($m.vulnerability.id // "-") as $id |
120+
( if ($m.artifact | type) == "object"
121+
then (($m.artifact.name // $m.artifact.id // "-") + ":" + ($m.artifact.version // "-"))
122+
else (($m.artifact // "-") + ":" + "-")
123+
end) as $pv |
124+
(($id + "|" + $pv + "|" + (($m.vulnerability.severity // "") | ascii_upcase)))
125+
] | .[]' "$A_GRYPE" | sort -u > /tmp/a_entries.txt || true
126+
127+
jq -r '[ .matches[]? as $m |
128+
($m.vulnerability.id // "-") as $id |
129+
( if ($m.artifact | type) == "object"
130+
then (($m.artifact.name // $m.artifact.id // "-") + ":" + ($m.artifact.version // "-"))
131+
else (($m.artifact // "-") + ":" + "-")
132+
end) as $pv |
133+
(($id + "|" + $pv + "|" + (($m.vulnerability.severity // "") | ascii_upcase)))
134+
] | .[]' "$B_GRYPE" | sort -u > /tmp/b_entries.txt || true
135+
136+
# unimos y normalizamos
137+
cat /tmp/a_entries.txt /tmp/b_entries.txt | sort -u > /tmp/all_entries.txt || true
138+
139+
# --- START MD FILE ---
116140
echo "# Vulnerability comparison: ${A_BRANCH} **vs** ${B_BRANCH}" > "${OUT}"
117141
echo "" >> "${OUT}"
118142
119-
# Totals (unique vulnerability IDs)
143+
# --- TABLE requested: VulnerabilityID | package:version | Severity | branches ---
144+
echo "| VulnerabilityID | package:version | Severity | branches |" >> "${OUT}"
145+
echo "|---|---|---|---|" >> "${OUT}"
146+
147+
if [ -s /tmp/all_entries.txt ]; then
148+
while IFS= read -r line; do
149+
# line format: ID|pkg:version|SEVERITY
150+
id=$(echo "$line" | awk -F'|' '{print $1}')
151+
pv=$(echo "$line" | awk -F'|' '{print $2}')
152+
sev=$(echo "$line" | awk -F'|' '{print $3}')
153+
154+
inA=0; inB=0
155+
if grep -Fxq "$line" /tmp/a_entries.txt 2>/dev/null; then inA=1; fi
156+
if grep -Fxq "$line" /tmp/b_entries.txt 2>/dev/null; then inB=1; fi
157+
158+
if [ "$inA" -eq 1 ] && [ "$inB" -eq 1 ]; then
159+
branches="**BOTH**"
160+
elif [ "$inA" -eq 1 ]; then
161+
branches="${A_BRANCH}"
162+
else
163+
branches="${B_BRANCH}"
164+
fi
165+
166+
# salida segura (escapa pipes en campos si hay)
167+
# aquí asumimos que id/pv/sev no contienen pipes adicionales
168+
echo "| ${id} | ${pv} | ${sev} | ${branches} |" >> "${OUT}"
169+
done < /tmp/all_entries.txt
170+
else
171+
echo "| - | - | - | - |" >> "${OUT}"
172+
echo "" >> "${OUT}"
173+
fi
174+
175+
echo "" >> "${OUT}"
176+
# --- Totals y resto del MD (se mantienen para contexto) ---
120177
totalA=$(jq -r '[ .matches[]?.vulnerability?.id ] | unique | length' "${A_GRYPE}" 2>/dev/null || echo 0)
121178
totalB=$(jq -r '[ .matches[]?.vulnerability?.id ] | unique | length' "${B_GRYPE}" 2>/dev/null || echo 0)
122179
echo "- **Total unique vulnerability IDs**: ${totalA} (${A_BRANCH}) | ${totalB} (${B_BRANCH})" >> "${OUT}"
123180
echo "" >> "${OUT}"
124181
125-
# Totals by package:version (unique vulnerable packages)
126-
# Use a robust expression: handle .artifact being object or string
127-
jq -r '[ .matches[]? |
128-
( if (.artifact | type) == "object"
129-
then ((.artifact.name // .artifact.id // "-") + ":" + (.artifact.version // "-"))
130-
else ((.artifact // "-") + ":" + ("-"))
131-
end)
132-
] | unique | .[]' "${A_GRYPE}" 2>/dev/null | sort > /tmp/a_pkgs.txt || true
133-
jq -r '[ .matches[]? |
134-
( if (.artifact | type) == "object"
135-
then ((.artifact.name // .artifact.id // "-") + ":" + (.artifact.version // "-"))
136-
else ((.artifact // "-") + ":" + ("-"))
137-
end)
138-
] | unique | .[]' "${B_GRYPE}" 2>/dev/null | sort > /tmp/b_pkgs.txt || true
139-
140-
pkgsA=$(wc -l < /tmp/a_pkgs.txt 2>/dev/null || echo 0)
141-
pkgsB=$(wc -l < /tmp/b_pkgs.txt 2>/dev/null || echo 0)
142-
echo "- **Total unique vulnerable package:version**: ${pkgsA} (${A_BRANCH}) | ${pkgsB} (${B_BRANCH})" >> "${OUT}"
143-
echo "" >> "${OUT}"
144-
145-
# Severity table (counts by unique vulnerability ID) — normalize severity uppercase
146-
echo "## Vulnerabilities by severity (counted by unique VulnerabilityID)" >> "${OUT}"
147-
echo "" >> "${OUT}"
182+
# tabla de severidad (como antes)
148183
echo "| Severity | ${A_BRANCH} | ${B_BRANCH} |" >> "${OUT}"
149184
echo "|---:|---:|---:|" >> "${OUT}"
150185
for sev in CRITICAL HIGH MEDIUM LOW UNKNOWN; do
151186
ca=$(jq --arg s "$sev" '[ .matches[]?.vulnerability? | select((.severity // "") | ascii_upcase == $s) | .id ] | unique | length' "${A_GRYPE}" 2>/dev/null || echo 0)
152187
cb=$(jq --arg s "$sev" '[ .matches[]?.vulnerability? | select((.severity // "") | ascii_upcase == $s) | .id ] | unique | length' "${B_GRYPE}" 2>/dev/null || echo 0)
153188
echo "| $sev | $ca | $cb |" >> "${OUT}"
154189
done
155-
echo "" >> "${OUT}"
156-
157-
# Prepare lists of unique vulnerability IDs
158-
jq -r '[ .matches[]?.vulnerability?.id ] | unique | .[]' "${A_GRYPE}" 2>/dev/null | sort > /tmp/a_ids.txt || true
159-
jq -r '[ .matches[]?.vulnerability?.id ] | unique | .[]' "${B_GRYPE}" 2>/dev/null | sort > /tmp/b_ids.txt || true
160-
161-
# New vulnerabilities in A not in B (by ID)
162-
echo "## VulnerabilityIDs present in ${A_BRANCH} but NOT in ${B_BRANCH}" >> "${OUT}"
163-
echo "" >> "${OUT}"
164-
if [ -s /tmp/a_ids.txt ]; then
165-
comm -23 /tmp/a_ids.txt /tmp/b_ids.txt > /tmp/new_in_a_ids.txt || true
166-
if [ -s /tmp/new_in_a_ids.txt ]; then
167-
while read -r id; do
168-
jq --arg id "$id" -r '
169-
.matches[]? | select(.vulnerability?.id==$id) |
170-
("- " + (.vulnerability.id // "-") + " | " + ((.vulnerability.severity // "") | ascii_upcase // "-") + " | " +
171-
( if (.artifact | type) == "object" then (.artifact.name // .artifact.id // "-") else (.artifact // "-") end ) + " | " +
172-
( if (.artifact | type) == "object" then (.artifact.version // "-") else "-" end ) + " | " +
173-
((.vulnerability.description // "") | gsub("\n"; " ") | .[0:250] )
174-
)
175-
' "${A_GRYPE}" | head -n 1 >> "${OUT}"
176-
done < /tmp/new_in_a_ids.txt
177-
else
178-
echo "No unique VulnerabilityIDs in ${A_BRANCH} vs ${B_BRANCH}." >> "${OUT}"
179-
fi
180-
else
181-
echo "No vulnerabilities found in ${A_BRANCH}." >> "${OUT}"
182-
fi
183-
echo "" >> "${OUT}"
184-
185-
# New vulnerabilities in B not in A (by ID)
186-
echo "## VulnerabilityIDs present in ${B_BRANCH} but NOT in ${A_BRANCH}" >> "${OUT}"
187-
echo "" >> "${OUT}"
188-
if [ -s /tmp/b_ids.txt ]; then
189-
comm -13 /tmp/a_ids.txt /tmp/b_ids.txt > /tmp/new_in_b_ids.txt || true
190-
if [ -s /tmp/new_in_b_ids.txt ]; then
191-
while read -r id; do
192-
jq --arg id "$id" -r '
193-
.matches[]? | select(.vulnerability?.id==$id) |
194-
("- " + (.vulnerability.id // "-") + " | " + ((.vulnerability.severity // "") | ascii_upcase // "-") + " | " +
195-
( if (.artifact | type) == "object" then (.artifact.name // .artifact.id // "-") else (.artifact // "-") end ) + " | " +
196-
( if (.artifact | type) == "object" then (.artifact.version // "-") else "-" end ) + " | " +
197-
((.vulnerability.description // "") | gsub("\n"; " ") | .[0:250] )
198-
)
199-
' "${B_GRYPE}" | head -n 1 >> "${OUT}"
200-
done < /tmp/new_in_b_ids.txt
201-
else
202-
echo "No unique VulnerabilityIDs in ${B_BRANCH} vs ${A_BRANCH}." >> "${OUT}"
203-
fi
204-
else
205-
echo "No vulnerabilities found in ${B_BRANCH}." >> "${OUT}"
206-
fi
207-
echo "" >> "${OUT}"
208190
209-
# New vulnerable package:version in A not in B
210-
echo "## package:version present in ${A_BRANCH} but NOT in ${B_BRANCH}" >> "${OUT}"
211191
echo "" >> "${OUT}"
212-
if [ -s /tmp/a_pkgs.txt ]; then
213-
comm -23 /tmp/a_pkgs.txt /tmp/b_pkgs.txt > /tmp/new_in_a_pkgs.txt || true
214-
if [ -s /tmp/new_in_a_pkgs.txt ]; then
215-
while read -r pv; do
216-
jq -r --arg pv "$pv" '
217-
.matches[]? | select(
218-
( if (.artifact|type) == "object" then ((.artifact.name // .artifact.id // "-") + ":" + (.artifact.version // "-")) else ((.artifact // "-") + ":" + "-") end) == $pv
219-
) |
220-
("- " + $pv + " | " + (.vulnerability.id // "-") + " | " + ((.vulnerability.severity // "") | ascii_upcase // "-") + " | " +
221-
((.vulnerability.description // "") | gsub("\n"; " ") | .[0:200])
222-
)
223-
' "${A_GRYPE}" | head -n 1 >> "${OUT}"
224-
done < /tmp/new_in_a_pkgs.txt
225-
else
226-
echo "No unique package:version in ${A_BRANCH} vs ${B_BRANCH}." >> "${OUT}"
227-
fi
228-
else
229-
echo "No vulnerable packages found in ${A_BRANCH}." >> "${OUT}"
230-
fi
231-
echo "" >> "${OUT}"
232-
233-
# New vulnerable package:version in B not in A
234-
echo "## package:version present in ${B_BRANCH} but NOT in ${A_BRANCH}" >> "${OUT}"
235-
echo "" >> "${OUT}"
236-
if [ -s /tmp/b_pkgs.txt ]; then
237-
comm -13 /tmp/a_pkgs.txt /tmp/b_pkgs.txt > /tmp/new_in_b_pkgs.txt || true
238-
if [ -s /tmp/new_in_b_pkgs.txt ]; then
239-
while read -r pv; do
240-
jq -r --arg pv "$pv" '
241-
.matches[]? | select(
242-
( if (.artifact|type) == "object" then ((.artifact.name // .artifact.id // "-") + ":" + (.artifact.version // "-")) else ((.artifact // "-") + ":" + "-") end) == $pv
243-
) |
244-
("- " + $pv + " | " + (.vulnerability.id // "-") + " | " + ((.vulnerability.severity // "") | ascii_upcase // "-") + " | " +
245-
((.vulnerability.description // "") | gsub("\n"; " ") | .[0:200])
246-
)
247-
' "${B_GRYPE}" | head -n 1 >> "${OUT}"
248-
done < /tmp/new_in_b_pkgs.txt
249-
else
250-
echo "No unique package:version in ${B_BRANCH} vs ${A_BRANCH}." >> "${OUT}"
251-
fi
252-
else
253-
echo "No vulnerable packages found in ${B_BRANCH}." >> "${OUT}"
254-
fi
255-
echo "" >> "${OUT}"
256-
192+
# añadimos secciones de detalle (opcionales) - mantenidas o puedes comentarlas si no quieres
257193
echo "----" >> "${OUT}"
258194
echo "Artifacts included:" >> "${OUT}"
259195
echo "- ${REPORT_DIR}/branchA-sbom.cdx.json" >> "${OUT}"
@@ -268,6 +204,55 @@ jobs:
268204
cd "${REPORT_DIR}"
269205
zip -r comparison-artifacts.zip . || true
270206
207+
- name: "Publish table to GitHub Actions summary (only the table)"
208+
if: always()
209+
run: |
210+
set -euo pipefail
211+
SUMMARY="$GITHUB_STEP_SUMMARY"
212+
A_BRANCH="${{ github.event.inputs.branch_a }}"
213+
B_BRANCH="${{ github.event.inputs.branch_b }}"
214+
215+
# Comprueba que exista la tabla (en /tmp/all_entries o en reports MD)
216+
if [ ! -f /tmp/all_entries.txt ] || [ ! -s /tmp/all_entries.txt ]; then
217+
# Si all_entries no tiene datos, intentamos extraer del MD
218+
if [ -f "${REPORT_DIR}/comparison-report.md" ]; then
219+
# extraemos la tabla completa entre el header y la línea en blanco que sigue
220+
awk '
221+
BEGIN {found=0}
222+
/^\\| VulnerabilityID \\| package:version \\| Severity \\| branches \\|/ {found=1; print; next}
223+
found==1 { if (/^$/) exit; print }
224+
' "${REPORT_DIR}/comparison-report.md" >> "$SUMMARY" || true
225+
else
226+
echo "No table available to show in summary." >> "$SUMMARY"
227+
fi
228+
exit 0
229+
fi
230+
231+
# Build the table header in the summary
232+
echo "| VulnerabilityID | package:version | Severity | branches |" >> "$SUMMARY"
233+
echo "|---|---|---|---|" >> "$SUMMARY"
234+
235+
# For each entry in /tmp/all_entries.txt, build row with branches decision (same logic as in MD)
236+
while IFS= read -r line; do
237+
id=$(echo "$line" | awk -F'|' '{print $1}')
238+
pv=$(echo "$line" | awk -F'|' '{print $2}')
239+
sev=$(echo "$line" | awk -F'|' '{print $3}')
240+
241+
inA=0; inB=0
242+
if grep -Fxq "$line" /tmp/a_entries.txt 2>/dev/null; then inA=1; fi
243+
if grep -Fxq "$line" /tmp/b_entries.txt 2>/dev/null; then inB=1; fi
244+
245+
if [ "$inA" -eq 1 ] && [ "$inB" -eq 1 ]; then
246+
branches="**BOTH**"
247+
elif [ "$inA" -eq 1 ]; then
248+
branches="${A_BRANCH}"
249+
else
250+
branches="${B_BRANCH}"
251+
fi
252+
253+
echo "| ${id} | ${pv} | ${sev} | ${branches} |" >> "$SUMMARY"
254+
done < /tmp/all_entries.txt
255+
271256
- name: Upload artifacts (reports)
272257
uses: actions/upload-artifact@v4
273258
with:

0 commit comments

Comments
 (0)