Skip to content

ci: add comprehensive GitHub Actions pipeline #6

ci: add comprehensive GitHub Actions pipeline

ci: add comprehensive GitHub Actions pipeline #6

Workflow file for this run

# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# 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: CI
on:
pull_request:
push:
branches:
- develop
- "release/**"
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
PYTHONDONTWRITEBYTECODE: "1"
PYTHONUNBUFFERED: "1"
UV_CACHE_DIR: ${{ github.workspace }}/.cache/uv
UV_LINK_MODE: copy
jobs:
validate:
name: Validate lockfile and configuration
runs-on: ubuntu-latest
steps:
- name: Checkout with submodules
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
with:
fetch-depth: 0
persist-credentials: false
submodules: recursive
- name: Set up uv
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78
with:
version: "0.9.15"
enable-cache: true
- name: Install Python and dependencies
run: |
uv python install 3.13
uv sync --locked --group dev
- name: Check lockfile
run: uv lock --check
- name: Compile Python sources
run: uv run python -m compileall -q src tests scripts ci
- name: Validate NAT configurations
env:
NVIDIA_API_KEY: dummy # pragma: allowlist secret
TAVILY_API_KEY: dummy # pragma: allowlist secret
run: |
for config_file in src/vuln_analysis/configs/*.yml; do
uv run nat validate --config_file "${config_file}"
done
lint:
name: Lint and repository checks
runs-on: ubuntu-latest
steps:
- name: Checkout history
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
with:
fetch-depth: 0
persist-credentials: false
- name: Set up uv
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78
with:
version: "0.9.15"
enable-cache: true
- name: Run Flake8
run: |
uvx --python 3.13 --from flake8==7.3.0 --with flake8-pyproject==1.2.3 \
flake8 src/vuln_analysis
- name: Run ShellCheck
run: shellcheck deploy/*.sh scripts/*.sh
- name: Check copyright headers on changed files
env:
BEFORE_SHA: ${{ github.event.before }}
PR_BASE_SHA: ${{ github.event.pull_request.base.sha }}
run: |
base_sha="${PR_BASE_SHA:-${BEFORE_SHA:-}}"
if [[ -z "${base_sha}" || "${base_sha}" =~ ^0+$ ]]; then
base_sha="$(git rev-parse HEAD^)"
fi
uv run --no-project --python 3.13 python ci/scripts/copyright.py \
--git-diff-commits "${base_sha}" "${GITHUB_SHA}" \
--verify-apache-v2 \
--exclude '^aiq/' \
--exclude '^\.secrets\.baseline$'
unit_tests:
name: Unit tests
needs: validate
runs-on: ubuntu-latest
steps:
- name: Checkout with submodules
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
with:
persist-credentials: false
submodules: recursive
- name: Set up uv
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78
with:
version: "0.9.15"
enable-cache: true
- name: Install Python and dependencies
run: |
uv python install 3.13
uv sync --locked --group dev
- name: Run tests with coverage
run: |
mkdir -p reports/junit
uv run python -X faulthandler -m pytest tests -v \
--cov=src/vuln_analysis \
--cov-fail-under=70 \
--cov-report=term \
--cov-report=xml:coverage.xml \
--cov-report=html:htmlcov \
--junitxml=reports/junit/all.xml
- name: Upload test reports
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with:
name: unit-test-reports
path: |
reports/junit/all.xml
coverage.xml
htmlcov/
retention-days: 14
security:
name: Source security checks
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
with:
persist-credentials: false
- name: Set up uv
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78
with:
version: "0.9.15"
enable-cache: true
- name: Scan tracked files
run: |
git ls-files -z | xargs -0 \
uvx --from detect-secrets==1.5.0 detect-secrets-hook --baseline .secrets.baseline --exclude-files '^aiq$' --exclude-files '^\.env\.example$' --
- name: Run Bandit
run: uvx --from 'bandit[toml]==1.8.6' bandit -c pyproject.toml -r src/vuln_analysis
dependency_audit:
name: Dependency audit (advisory)
runs-on: ubuntu-latest
steps:
- name: Checkout with submodules
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
with:
persist-credentials: false
submodules: recursive
- name: Set up uv
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78
with:
version: "0.9.15"
enable-cache: true
- name: Export locked runtime dependencies
run: |
uv export --locked --no-dev --no-emit-project --no-hashes \
--output-file requirements-audit.txt
# The current lockfile has known findings that need remediation before this
# can become a blocking gate. Keep producing a reviewable report meanwhile.
- name: Audit dependencies
id: audit
continue-on-error: true
run: |
uvx --from pip-audit==2.9.0 pip-audit \
--requirement requirements-audit.txt \
--no-deps --disable-pip --progress-spinner off \
--format json --output dependency-audit.json
- name: Upload dependency report
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with:
name: dependency-audit
path: dependency-audit.json
if-no-files-found: warn
retention-days: 14
- name: Summarize advisory result
if: always()
env:
AUDIT_OUTCOME: ${{ steps.audit.outcome }}
run: |
echo "Dependency audit outcome (currently advisory): ${AUDIT_OUTCOME}" >> "${GITHUB_STEP_SUMMARY}"
infrastructure:
name: Container and Compose validation
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
with:
persist-credentials: false
- name: Run Hadolint
run: |
docker run --rm \
--volume "$PWD:/workspace" \
--workdir /workspace \
--entrypoint "" \
hadolint/hadolint:v2.14.0-alpine@sha256:7aba693c1442eb31c0b015c129697cb3b6cb7da589d85c7562f9deb435a6657c \
hadolint --ignore DL3008 --ignore DL3013 deploy/Dockerfile
- name: Render base Compose configuration
run: docker compose -f deploy/docker-compose.yml config > compose.yml
- name: Render Compose configuration with NIM overlay
run: |
docker compose \
-f deploy/docker-compose.yml \
-f deploy/docker-compose.nim.yml \
config > compose-nim.yml
- name: Upload rendered Compose configurations
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with:
name: compose-rendered
path: |
compose.yml
compose-nim.yml
retention-days: 14
package:
name: Build package
needs: validate
runs-on: ubuntu-latest
steps:
- name: Checkout with submodules
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
with:
fetch-depth: 0
persist-credentials: false
submodules: recursive
- name: Set up uv
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78
with:
version: "0.9.15"
enable-cache: true
- name: Install Python
run: uv python install 3.13
- name: Build wheel and source distribution
run: uv build --out-dir dist
- name: Smoke-test wheel metadata and import
run: |
uv venv --python 3.13 /tmp/vuln-analysis-wheel-smoke
uv pip install --python /tmp/vuln-analysis-wheel-smoke/bin/python \
--no-deps dist/*.whl
/tmp/vuln-analysis-wheel-smoke/bin/python - <<'PY'
import importlib.metadata
import vuln_analysis
scripts = importlib.metadata.entry_points(group="console_scripts")
assert any(entry.name == "vuln-analysis" for entry in scripts)
PY
- name: Upload package
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with:
name: python-package
path: dist/
retention-days: 14
container_build:
name: Build and scan container
if: github.event_name != 'pull_request'
needs:
- unit_tests
- infrastructure
- package
- security
runs-on: ubuntu-latest
steps:
- name: Checkout with submodules
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
with:
fetch-depth: 0
persist-credentials: false
submodules: recursive
- name: Build runtime image
run: |
docker build \
--file deploy/Dockerfile \
--target runtime \
--tag vuln-analysis:ci .
- name: Generate image SBOM
run: |
docker run --rm \
--volume /var/run/docker.sock:/var/run/docker.sock \
--volume "$PWD:/workspace" \
aquasec/trivy:0.70.0@sha256:be1190afcb28352bfddc4ddeb71470835d16462af68d310f9f4bca710961a41e image \
--format cyclonedx \
--output /workspace/vuln-analysis-sbom.cdx.json \
vuln-analysis:ci
# Report the existing image findings without blocking releases until an
# image-specific allowlist and remediation baseline are established.
- name: Scan image vulnerabilities
id: image_scan
continue-on-error: true
run: |
docker run --rm \
--volume /var/run/docker.sock:/var/run/docker.sock \
--volume "$PWD:/workspace" \
aquasec/trivy:0.70.0@sha256:be1190afcb28352bfddc4ddeb71470835d16462af68d310f9f4bca710961a41e image \
--severity HIGH,CRITICAL \
--ignore-unfixed \
--format json \
--output /workspace/vuln-analysis-trivy.json \
--exit-code 1 \
vuln-analysis:ci
- name: Upload image security reports
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with:
name: image-security-reports
path: |
vuln-analysis-sbom.cdx.json
vuln-analysis-trivy.json
if-no-files-found: warn
retention-days: 14