Skip to content

Commit 55b6ed9

Browse files
committed
🐛 Fix post-merge workflow bugs + sync security self-assessment
Workflow fixes (addresses Copilot review on #4351): - Fix null PR number: use `// empty` jq fallback, guard against "null" string - Fix whitespace in spec dedup: use printf instead of indented heredoc append - Fix exit code masking: capture Playwright exit code, propagate to job status - Fix result propagation: add job-level outputs mapping - Fix PCRE regex: use -oE (ERE) instead of -oP (PCRE, not available on all CI) - Fix report logic: require both output=passed AND job=success Security self-assessment sync (addresses TAG-Security review on cncf/toc#2106): - Add scope statement: kubestellar/console only, not KubeStellar Core - Add deployment architecture diagram with security boundaries - Expand JWT details: HS256-only, WithValidMethods, revocation, cookie attrs - Explain RBAC inheritance: no privilege escalation, kubeconfig as-is - Add GitHub Security Advisories as primary disclosure channel Signed-off-by: Andrew Anderson <andy@clubanderson.com>
1 parent 2aa52b0 commit 55b6ed9

File tree

2 files changed

+119
-35
lines changed

2 files changed

+119
-35
lines changed

.github/workflows/post-merge-verify.yml

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -86,16 +86,16 @@ jobs:
8686
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
8787
run: |
8888
# Find the PR that produced this merge commit
89-
PR_NUMBER=$(gh pr list --state merged --search "${{ github.sha }}" --json number --jq '.[0].number' 2>/dev/null || echo "")
90-
if [ -z "$PR_NUMBER" ]; then
89+
PR_NUMBER=$(gh pr list --state merged --search "${{ github.sha }}" --json number --jq '.[0].number // empty' 2>/dev/null || echo "")
90+
if [ -z "$PR_NUMBER" ] || [ "$PR_NUMBER" = "null" ]; then
9191
# Fallback: search by commit message
92-
PR_NUMBER=$(git log -1 --format='%s' | grep -oP '#\K\d+' | head -1 || echo "")
92+
PR_NUMBER=$(git log -1 --format='%s' | grep -oE '#[0-9]+' | head -1 | tr -d '#' || echo "")
9393
fi
9494
echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT
9595
9696
if [ -n "$PR_NUMBER" ]; then
9797
# Extract "Fixes #NNN" from PR body
98-
ISSUE=$(gh pr view "$PR_NUMBER" --json body --jq '.body' 2>/dev/null | grep -oiP '(?:fixes|closes|resolves)\s+#\K\d+' | head -1 || echo "")
98+
ISSUE=$(gh pr view "$PR_NUMBER" --json body --jq '.body' 2>/dev/null | grep -oiE '(fixes|closes|resolves)\s+#[0-9]+' | head -1 | grep -oE '[0-9]+' || echo "")
9999
echo "issue_number=$ISSUE" >> $GITHUB_OUTPUT
100100
echo "PR: #$PR_NUMBER, Issue: #$ISSUE"
101101
else
@@ -122,27 +122,25 @@ jobs:
122122
for LABEL in $LABELS; do
123123
MATCHED=$(jq -r --arg l "$LABEL" '.label_map[$l] // [] | .[]' "$SPEC_MAP" 2>/dev/null)
124124
if [ -n "$MATCHED" ]; then
125-
SPECS="$SPECS
126-
$MATCHED"
125+
SPECS=$(printf '%s\n%s' "$SPECS" "$MATCHED")
127126
fi
128127
done
129128
fi
130129
131130
# From changed file paths
132131
PR="${{ steps.find-pr.outputs.pr_number }}"
133-
if [ -n "$PR" ]; then
132+
if [ -n "$PR" ] && [ "$PR" != "null" ]; then
134133
CHANGED_FILES=$(gh pr view "$PR" --json files --jq '.files[].path' 2>/dev/null || echo "")
135134
for PREFIX in $(jq -r '.path_map | keys[]' "$SPEC_MAP"); do
136135
if echo "$CHANGED_FILES" | grep -q "^$PREFIX"; then
137136
MATCHED=$(jq -r --arg p "$PREFIX" '.path_map[$p][]' "$SPEC_MAP")
138-
SPECS="$SPECS
139-
$MATCHED"
137+
SPECS=$(printf '%s\n%s' "$SPECS" "$MATCHED")
140138
fi
141139
done
142140
fi
143141
144-
# Deduplicate and format
145-
SPEC_LIST=$(echo "$SPECS" | sort -u | grep -v '^$' | tr '\n' ' ')
142+
# Trim whitespace, deduplicate, format as space-separated list
143+
SPEC_LIST=$(printf '%s\n' "$SPECS" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | sort -u | grep -v '^$' | tr '\n' ' ')
146144
echo "Selected specs: $SPEC_LIST"
147145
echo "spec_files=$SPEC_LIST" >> $GITHUB_OUTPUT
148146
@@ -153,6 +151,8 @@ jobs:
153151
if: needs.extract-context.outputs.spec_files != ''
154152
runs-on: ubuntu-latest
155153
timeout-minutes: 15
154+
outputs:
155+
result: ${{ steps.test.outputs.result }}
156156
steps:
157157
- name: Checkout
158158
uses: actions/checkout@v4
@@ -188,19 +188,23 @@ jobs:
188188
done
189189
190190
# Run with JSON + HTML reporters, Chromium only
191+
# Capture exit code without masking it
192+
EXIT_CODE=0
191193
npx playwright test $SPEC_ARGS \
192194
--project=chromium \
193195
--reporter=json,html \
194196
--output=test-results \
195-
2>&1 | tee playwright-output.txt || true
197+
2>&1 | tee playwright-output.txt || EXIT_CODE=$?
196198
197-
# Check exit code from the test run
198-
if grep -q '"status":"passed"' test-results/.last-run.json 2>/dev/null; then
199+
if [ "$EXIT_CODE" -eq 0 ]; then
199200
echo "result=passed" >> $GITHUB_OUTPUT
200201
else
201202
echo "result=failed" >> $GITHUB_OUTPUT
202203
fi
203204
205+
# Propagate the real exit code so the job status reflects test outcome
206+
exit $EXIT_CODE
207+
204208
- name: Upload Playwright report
205209
if: always()
206210
uses: actions/upload-artifact@v4
@@ -232,13 +236,14 @@ jobs:
232236
PR="${{ needs.extract-context.outputs.pr_number }}"
233237
ISSUE="${{ needs.extract-context.outputs.issue_number }}"
234238
SPECS="${{ needs.extract-context.outputs.spec_files }}"
235-
RESULT="${{ needs.run-tests.outputs.result || needs.run-tests.result }}"
239+
RESULT="${{ needs.run-tests.outputs.result }}"
240+
JOB_RESULT="${{ needs.run-tests.result }}"
236241
RUN_URL="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
237242
238-
if [ "$RESULT" = "passed" ] || [ "${{ needs.run-tests.result }}" = "success" ]; then
243+
if [ "$RESULT" = "passed" ] && [ "$JOB_RESULT" = "success" ]; then
239244
ICON="✅"
240245
STATUS="passed"
241-
elif [ "${{ needs.run-tests.result }}" = "skipped" ]; then
246+
elif [ "$JOB_RESULT" = "skipped" ]; then
242247
ICON="⏭️"
243248
STATUS="skipped (no matching specs)"
244249
else

0 commit comments

Comments
 (0)