Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# Actions are pinned to commit SHAs (not version tags) to prevent supply chain attacks.
# Version comments (e.g., "# v6") allow Dependabot to track and update to newer versions.
name: Release

on:
Expand All @@ -6,13 +8,19 @@ on:
- "v*"

jobs:
test:
uses: ./.github/workflows/test.yml

build:
needs: test
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

- name: Install uv
uses: astral-sh/setup-uv@v7
uses: astral-sh/setup-uv@e06108dd0aef18192324c70427afc47652e63a82 # v7.5.0

- name: Build and publish
env:
Expand Down
103 changes: 81 additions & 22 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
# Actions are pinned to commit SHAs (not version tags) to prevent supply chain attacks.
# Version comments (e.g., "# v6") allow Dependabot to track and update to newer versions.
name: Tests
on:
workflow_dispatch:
pull_request:
push:
workflow_call:

jobs:
security:
name: Security Scan
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

- name: Install uv
uses: astral-sh/setup-uv@v7
uses: astral-sh/setup-uv@e06108dd0aef18192324c70427afc47652e63a82 # v7.5.0
with:
enable-cache: true

Expand All @@ -25,7 +30,7 @@ jobs:
run: bandit -c pyproject.toml -r nac_test/ -ll -f json -o bandit-security-report.json

- name: Upload security report
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
if: always()
with:
name: bandit-security-report
Expand All @@ -39,14 +44,14 @@ jobs:
contents: write # Required for dependabot to push lock file updates
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
token: ${{ secrets.GITHUB_TOKEN }}
# For fork PRs, use merge commit; for same-repo PRs, use head ref
ref: ${{ github.event.pull_request.head.sha || github.head_ref }}

- name: Install uv
uses: astral-sh/setup-uv@v7
uses: astral-sh/setup-uv@e06108dd0aef18192324c70427afc47652e63a82 # v7.5.0
with:
enable-cache: true

Expand All @@ -70,12 +75,14 @@ jobs:
run: bash scripts/license-headers.sh

- name: Pre-commit Checks
uses: pre-commit/action@v3.0.1
uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1

test-linux:
name: Tests (Linux)
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
strategy:
matrix:
python:
Expand Down Expand Up @@ -105,6 +112,8 @@ jobs:
name: Tests (Windows)
runs-on: windows-latest
timeout-minutes: 15
permissions:
contents: read
strategy:
matrix:
python:
Expand All @@ -114,10 +123,10 @@ jobs:
- "3.13"
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

- name: Install uv
uses: astral-sh/setup-uv@v7
uses: astral-sh/setup-uv@e06108dd0aef18192324c70427afc47652e63a82 # v7.5.0
with:
enable-cache: true

Expand All @@ -136,15 +145,17 @@ jobs:
name: Tests (uv tool install)
runs-on: ${{ matrix.os }}
timeout-minutes: 10
permissions:
contents: read
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

- name: Install uv
uses: astral-sh/setup-uv@v7
uses: astral-sh/setup-uv@e06108dd0aef18192324c70427afc47652e63a82 # v7.5.0
with:
enable-cache: true

Expand Down Expand Up @@ -174,31 +185,79 @@ jobs:

notification:
name: Notification
if: always() && github.event_name != 'pull_request'
if: always() && github.event_name != 'pull_request' && github.event_name != 'workflow_call' && github.actor != 'dependabot[bot]'
needs: [security, lint, test-linux, test-windows, test-uv-tool]
runs-on: ubuntu-latest
timeout-minutes: 5
permissions: {}
steps:
- name: Check Job Success
run: |
if [ ${{ needs.security.result }} == 'success' ] && [ ${{ needs.lint.result }} == 'success' ] && [ ${{ needs.test-linux.result }} == 'success' ] && [ ${{ needs.test-windows.result }} == 'success' ] && [ ${{ needs.test-uv-tool.result }} == 'success' ]; then
echo "All jobs succeeded"
echo "jobSuccess=success" >> $GITHUB_ENV
echo "JOB_SUCCESS=success" >> $GITHUB_ENV
else
echo "Not all jobs succeeded"
echo "jobSuccess=fail" >> $GITHUB_ENV
echo "JOB_SUCCESS=fail" >> $GITHUB_ENV
fi
id: print_status

- name: Sanitize notification variables
env:
# Pass through env to avoid shell injection from server-side expression expansion
RAW_COMMIT_MSG: ${{ github.event.head_commit.message }}
RAW_PR_TITLE: ${{ github.event.pull_request.title }}
run: |
# Truncate to stay within Webex message size limits (500 chars for commit
# messages, 200 chars for PR titles are generous practical limits)
COMMIT_MSG=$(echo "$RAW_COMMIT_MSG" | head -c 500)
PR_TITLE=$(echo "$RAW_PR_TITLE" | head -c 200)
echo "SAFE_COMMIT_MSG=${COMMIT_MSG}" >> "$GITHUB_ENV"
echo "SAFE_PR_TITLE=${PR_TITLE}" >> "$GITHUB_ENV"

# Using direct curl to Webex API instead of a GitHub Action because:
# - qsnyder/action-wxt is unmaintained (last commit 2022, no releases)
# - Direct API call has no third-party action dependency to maintain
# - Gives full control over error handling and message format
- name: Webex Notification
if: always()
uses: qsnyder/action-wxt@master
env:
TOKEN: ${{ secrets.WEBEX_TOKEN }}
ROOMID: ${{ secrets.WEBEX_ROOM_ID }}
MESSAGE: |
[**[${{ env.jobSuccess }}] ${{ github.repository }} #${{ github.run_number }}**](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})
* Commit: [${{ github.event.head_commit.message }}](${{ github.event.head_commit.url }})[${{ github.event.pull_request.title }}](${{ github.event.pull_request.html_url }})
* Author: ${{ github.event.sender.login }}
* Branch: ${{ github.ref }} ${{ github.head_ref }}
* Event: ${{ github.event_name }}
# Pass sanitized env vars and static context values through env to avoid
# server-side expression expansion inside the shell script
JOB_SUCCESS: ${{ env.JOB_SUCCESS }}
SAFE_COMMIT_MSG: ${{ env.SAFE_COMMIT_MSG }}
SAFE_PR_TITLE: ${{ env.SAFE_PR_TITLE }}
WEBEX_TOKEN: ${{ secrets.WEBEX_TOKEN }}
WEBEX_ROOM_ID: ${{ secrets.WEBEX_ROOM_ID }}
COMMIT_URL: ${{ github.event.head_commit.url }}
AUTHOR: ${{ github.event.sender.login }}
REF: ${{ github.ref }}
HEAD_REF: ${{ github.head_ref }}
EVENT: ${{ github.event_name }}
REPO: ${{ github.repository }}
RUN_NUM: ${{ github.run_number }}
RUN_ID: ${{ github.run_id }}
run: |
# jq -n --arg handles all JSON escaping; avoids double-encoding from jq -Rs
PAYLOAD=$(jq -n \
--arg room_id "$WEBEX_ROOM_ID" \
--arg status "$JOB_SUCCESS" \
--arg repo "$REPO" \
--arg run_num "$RUN_NUM" \
--arg run_url "https://github.com/$REPO/actions/runs/$RUN_ID" \
--arg commit_msg "$SAFE_COMMIT_MSG" \
--arg commit_url "$COMMIT_URL" \
--arg pr_title "$SAFE_PR_TITLE" \
--arg author "$AUTHOR" \
--arg ref "$REF" \
--arg head_ref "$HEAD_REF" \
--arg event "$EVENT" \
'{roomId: $room_id, markdown: ("[**[\($status)] \($repo) #\($run_num)**](\($run_url))\n* Commit: [\($commit_msg)](\($commit_url))\($pr_title)\n* Author: \($author)\n* Branch: \($ref) \($head_ref)\n* Event: \($event)")}')
# --fail-with-body: exit non-zero on HTTP 4xx/5xx so the || warning fires
curl --silent --fail-with-body --max-time 30 \
--request POST \
--url https://webexapis.com/v1/messages \
--header "Authorization: Bearer $WEBEX_TOKEN" \
--header "Content-Type: application/json" \
--data "$PAYLOAD" \
|| echo "::warning::Webex notification failed"
Loading