mirror of
https://github.com/trailofbits/buttercup
synced 2026-06-21 14:11:39 +00:00
ci: consolidate GitHub Actions workflows (#427)
Consolidate and improve GitHub Actions workflows: Deleted redundant/outdated workflows: - claude-code-review.yml - No longer used - claude.yml - No longer used - pre-commit.yml - Merged into static-checks.yml Renamed for clarity: - comp-integration.yml → component-integration.yml - integration.yml → system-integration.yml New workflows: - static-checks.yml - Comprehensive static analysis (shellcheck, hadolint, actionlint) Improvements: - Add actionlint configuration (.github/actionlint.yaml) - Update dependabot configuration with cooldown settings - Improve workflow concurrency settings - Pin actions to SHA hashes for security - Add better job dependencies and caching Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
# actionlint configuration
|
||||
# https://github.com/rhysd/actionlint/blob/main/docs/config.md
|
||||
|
||||
self-hosted-runner:
|
||||
labels:
|
||||
- gha-ubuntu-8
|
||||
@@ -8,7 +8,10 @@ updates:
|
||||
patterns:
|
||||
- "*"
|
||||
schedule:
|
||||
interval: daily
|
||||
interval: weekly
|
||||
# Supply chain security: delay updates to allow community vetting
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
@@ -17,4 +20,7 @@ updates:
|
||||
patterns:
|
||||
- "*"
|
||||
schedule:
|
||||
interval: daily
|
||||
interval: weekly
|
||||
# Supply chain security: delay updates to allow community vetting
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
name: Claude Code Review
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [labeled, synchronize]
|
||||
# Optional: Only run on specific file changes
|
||||
# paths:
|
||||
# - "src/**/*.ts"
|
||||
# - "src/**/*.tsx"
|
||||
# - "src/**/*.js"
|
||||
# - "src/**/*.jsx"
|
||||
|
||||
jobs:
|
||||
claude-review:
|
||||
# Only run when PR has the 'claude-review' label
|
||||
if: contains(github.event.pull_request.labels.*.name, 'claude-review')
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
issues: read
|
||||
id-token: write # Required for OIDC authentication (used by Codecov and other services)
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 1
|
||||
submodules: true
|
||||
|
||||
- name: Run Claude Code Review
|
||||
id: claude-review
|
||||
uses: anthropics/claude-code-action@beta
|
||||
with:
|
||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
timeout_minutes: "60"
|
||||
|
||||
# Optional: Specify model (defaults to Claude Sonnet 4, uncomment for Claude Opus 4.1)
|
||||
# model: "claude-opus-4-1-20250805"
|
||||
|
||||
# Direct prompt for automated review (no @claude mention needed)
|
||||
direct_prompt: |
|
||||
Please review this pull request and provide feedback on:
|
||||
- Code quality and best practices
|
||||
- Potential bugs or issues
|
||||
- Performance considerations
|
||||
- Security concerns
|
||||
- Test coverage
|
||||
|
||||
Be constructive and helpful in your feedback.
|
||||
|
||||
# Use sticky comments to make Claude reuse the same comment on subsequent pushes to the same PR
|
||||
use_sticky_comment: true
|
||||
|
||||
# Optional: Customize review based on file types
|
||||
# direct_prompt: |
|
||||
# Review this PR focusing on:
|
||||
# - For TypeScript files: Type safety and proper interface usage
|
||||
# - For API endpoints: Security, input validation, and error handling
|
||||
# - For React components: Performance, accessibility, and best practices
|
||||
# - For tests: Coverage, edge cases, and test quality
|
||||
|
||||
# Optional: Different prompts for different authors
|
||||
# direct_prompt: |
|
||||
# ${{ github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR' &&
|
||||
# 'Welcome! Please review this PR from a first-time contributor. Be encouraging and provide detailed explanations for any suggestions.' ||
|
||||
# 'Please provide a thorough code review focusing on our coding standards and best practices.' }}
|
||||
|
||||
# Add specific tools for running tests or linting
|
||||
allowed_tools: |
|
||||
Bash(make *),
|
||||
Bash(cd * && make *),
|
||||
Bash(uv *),
|
||||
Bash(cd * && uv *),
|
||||
Bash(pytest*),
|
||||
Bash(ruff check*),
|
||||
Bash(ruff format*),
|
||||
Bash(mypy*),
|
||||
Bash(git status*),
|
||||
Bash(git diff*),
|
||||
Bash(git log*),
|
||||
Bash(git branch*),
|
||||
Bash(git show*),
|
||||
Bash(ls*),
|
||||
Bash(cat *),
|
||||
Bash(head *),
|
||||
Bash(tail *),
|
||||
Bash(grep *),
|
||||
Bash(find * -name *),
|
||||
Bash(pwd),
|
||||
Bash(echo *),
|
||||
Bash(which *),
|
||||
Bash(pip list),
|
||||
Bash(pip show *),
|
||||
Bash(python --version),
|
||||
Bash(python -m *)
|
||||
|
||||
# Optional: Skip review for certain conditions
|
||||
# if: |
|
||||
# !contains(github.event.pull_request.title, '[skip-review]') &&
|
||||
# !contains(github.event.pull_request.title, '[WIP]')
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
name: Claude Code
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
pull_request_review_comment:
|
||||
types: [created]
|
||||
issues:
|
||||
types: [opened, assigned]
|
||||
pull_request_review:
|
||||
types: [submitted]
|
||||
|
||||
jobs:
|
||||
claude:
|
||||
if: |
|
||||
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
|
||||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
|
||||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
|
||||
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
issues: read
|
||||
id-token: write # Required for OIDC authentication (used by Codecov and other services)
|
||||
actions: read # Required for Claude to read CI results on PRs
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 1
|
||||
submodules: true
|
||||
|
||||
- name: Run Claude Code
|
||||
id: claude
|
||||
uses: anthropics/claude-code-action@beta
|
||||
with:
|
||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
timeout_minutes: "60"
|
||||
|
||||
# This is an optional setting that allows Claude to read CI results on PRs
|
||||
additional_permissions: |
|
||||
actions: read
|
||||
|
||||
# Optional: Specify model (defaults to Claude Sonnet 4, uncomment for Claude Opus 4.1)
|
||||
# model: "claude-opus-4-1-20250805"
|
||||
|
||||
# Optional: Customize the trigger phrase (default: @claude)
|
||||
# trigger_phrase: "/claude"
|
||||
|
||||
# Optional: Trigger when specific user is assigned to an issue
|
||||
# assignee_trigger: "claude-bot"
|
||||
|
||||
# Allow Claude to run specific commands
|
||||
allowed_tools: |
|
||||
Bash(make *),
|
||||
Bash(cd * && make *),
|
||||
Bash(uv *),
|
||||
Bash(cd * && uv *),
|
||||
Bash(pytest*),
|
||||
Bash(ruff check*),
|
||||
Bash(ruff format*),
|
||||
Bash(mypy*),
|
||||
Bash(git status*),
|
||||
Bash(git diff*),
|
||||
Bash(git log*),
|
||||
Bash(git branch*),
|
||||
Bash(git show*),
|
||||
Bash(git add*),
|
||||
Bash(git commit*),
|
||||
Bash(git push),
|
||||
Bash(ls*),
|
||||
Bash(cat *),
|
||||
Bash(head *),
|
||||
Bash(tail *),
|
||||
Bash(grep *),
|
||||
Bash(find * -name *),
|
||||
Bash(pwd),
|
||||
Bash(echo *),
|
||||
Bash(which *),
|
||||
Bash(pip list),
|
||||
Bash(pip show *),
|
||||
Bash(python --version),
|
||||
Bash(python -m *),
|
||||
Bash(npm run*),
|
||||
Bash(pnpm run*),
|
||||
Bash(./scripts/setup-local.sh),
|
||||
Bash(./scripts/setup-azure.sh),
|
||||
Bash(./scripts/validate-setup.sh),
|
||||
Bash(./deployment/collect-logs.sh),
|
||||
Bash(./deployment/collect-data.sh),
|
||||
Bash(./orchestrator/scripts/task_crs.sh),
|
||||
Bash(./orchestrator/scripts/send_sarif.sh)
|
||||
|
||||
# Optional: Add custom instructions for Claude to customize its behavior for your project
|
||||
# custom_instructions: |
|
||||
# Follow our coding standards
|
||||
# Ensure all new code has tests
|
||||
# Use TypeScript for new files
|
||||
|
||||
# Optional: Custom environment variables for Claude
|
||||
# claude_env: |
|
||||
# NODE_ENV: test
|
||||
|
||||
+15
-10
@@ -30,15 +30,18 @@ concurrency:
|
||||
jobs:
|
||||
# Integration tests - run on schedule, manual trigger, or labeled PRs
|
||||
test-integration:
|
||||
permissions:
|
||||
contents: read
|
||||
# Run if:
|
||||
# - Scheduled run
|
||||
# - Manual dispatch with run_integration=true
|
||||
# - Push to main branch
|
||||
# - PR with 'integration-tests' label
|
||||
if: |
|
||||
github.event_name == 'push' ||
|
||||
github.event_name == 'schedule' ||
|
||||
(github.event_name == 'workflow_dispatch' && github.event.inputs.run_integration == 'true') ||
|
||||
contains(github.event.pull_request.labels.*.name, 'integration-tests')
|
||||
(github.event_name == 'push' && github.ref == 'refs/heads/main') ||
|
||||
(github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'integration-tests'))
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -60,7 +63,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
redis:
|
||||
image: redis
|
||||
image: redis@sha256:e647cfe134bf5e8e74e620f66346f93418acfc240b71dd85640325cb7cd01402 # 7.4
|
||||
options: >-
|
||||
--health-cmd "redis-cli ping"
|
||||
--health-interval 10s
|
||||
@@ -70,7 +73,7 @@ jobs:
|
||||
- 6379:6379
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: true
|
||||
@@ -78,9 +81,11 @@ jobs:
|
||||
- name: Check if component should be tested
|
||||
id: should_test
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
env:
|
||||
INPUT_COMPONENTS: ${{ github.event.inputs.components }}
|
||||
MATRIX_COMPONENT: ${{ matrix.component }}
|
||||
run: |
|
||||
components="${{ github.event.inputs.components }}"
|
||||
if [[ -z "$components" ]] || [[ "$components" == *"${{ matrix.component }}"* ]]; then
|
||||
if [[ -z "$INPUT_COMPONENTS" ]] || [[ "$INPUT_COMPONENTS" == *"$MATRIX_COMPONENT"* ]]; then
|
||||
echo "test=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "test=false" >> $GITHUB_OUTPUT
|
||||
@@ -88,11 +93,11 @@ jobs:
|
||||
|
||||
- name: Install uv
|
||||
if: steps.should_test.outputs.test != 'false'
|
||||
uses: astral-sh/setup-uv@v6
|
||||
uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
|
||||
|
||||
- name: Setup uv cache
|
||||
if: steps.should_test.outputs.test != 'false'
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
||||
with:
|
||||
path: |
|
||||
~/.cache/uv
|
||||
@@ -126,7 +131,7 @@ jobs:
|
||||
if: steps.should_test.outputs.test != 'false'
|
||||
run: |
|
||||
uv sync --all-extras --frozen
|
||||
uv pip install --isolated pytest-html>=4.1.1 pytest-cov>=6.0.0
|
||||
uv pip install --isolated 'pytest-html>=4.1.1' 'pytest-cov>=6.0.0'
|
||||
working-directory: ${{ matrix.component }}
|
||||
|
||||
- name: Run program-model libpng integration test
|
||||
@@ -189,7 +194,7 @@ jobs:
|
||||
|
||||
- name: Upload integration test results
|
||||
if: always() && steps.should_test.outputs.test != 'false'
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: integration-test-results-${{ matrix.component }}-py${{ matrix.python }}
|
||||
path: |
|
||||
@@ -45,16 +45,21 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: true
|
||||
|
||||
- name: Lint Dockerfile
|
||||
uses: hadolint/hadolint-action@2332a7b74a6de0dda2e2221d575162eba76ba5e5 # v3.3.0
|
||||
with:
|
||||
dockerfile: ${{ matrix.dockerfile }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.repository_owner }}
|
||||
@@ -62,7 +67,7 @@ jobs:
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.BASE_IMAGE_NAME }}-${{ matrix.component }}
|
||||
tags: |
|
||||
@@ -73,7 +78,7 @@ jobs:
|
||||
type=semver,pattern={{major}}.{{minor}},suffix=${{ inputs.suffix }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v6
|
||||
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
|
||||
with:
|
||||
context: .
|
||||
file: ${{ matrix.dockerfile }}
|
||||
|
||||
+29
-11
@@ -11,6 +11,7 @@ on:
|
||||
- "common/**"
|
||||
- "orchestrator/**"
|
||||
- "fuzzer/**"
|
||||
- "fuzzer_runner/**"
|
||||
- "program-model/**"
|
||||
- "seed-gen/**"
|
||||
- "patcher/**"
|
||||
@@ -26,21 +27,29 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
# Detect which components need linting based on changed files
|
||||
changes:
|
||||
permissions:
|
||||
contents: read
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
common: ${{ steps.filter.outputs.common }}
|
||||
orchestrator: ${{ steps.filter.outputs.orchestrator }}
|
||||
fuzzer: ${{ steps.filter.outputs.fuzzer }}
|
||||
fuzzer_runner: ${{ steps.filter.outputs.fuzzer_runner }}
|
||||
program-model: ${{ steps.filter.outputs.program-model }}
|
||||
seed-gen: ${{ steps.filter.outputs.seed-gen }}
|
||||
patcher: ${{ steps.filter.outputs.patcher }}
|
||||
run-all: ${{ steps.filter.outputs.workflow || steps.filter.outputs.global }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: dorny/paths-filter@v3
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
|
||||
id: filter
|
||||
with:
|
||||
filters: |
|
||||
@@ -50,6 +59,8 @@ jobs:
|
||||
- 'orchestrator/**'
|
||||
fuzzer:
|
||||
- 'fuzzer/**'
|
||||
fuzzer_runner:
|
||||
- 'fuzzer_runner/**'
|
||||
program-model:
|
||||
- 'program-model/**'
|
||||
seed-gen:
|
||||
@@ -64,6 +75,8 @@ jobs:
|
||||
- '**/*.proto'
|
||||
|
||||
matrix-generator:
|
||||
permissions:
|
||||
contents: read
|
||||
runs-on: ubuntu-latest
|
||||
needs: changes
|
||||
outputs:
|
||||
@@ -74,22 +87,25 @@ jobs:
|
||||
run: |
|
||||
components=()
|
||||
if [[ "${{ needs.changes.outputs.run-all }}" == "true" ]]; then
|
||||
components=("common" "orchestrator" "fuzzer" "program-model" "seed-gen" "patcher")
|
||||
components=("common" "orchestrator" "fuzzer" "fuzzer_runner" "program-model" "seed-gen" "patcher")
|
||||
else
|
||||
[[ "${{ needs.changes.outputs.common }}" == "true" ]] && components+=("common")
|
||||
[[ "${{ needs.changes.outputs.orchestrator }}" == "true" ]] && components+=("orchestrator")
|
||||
[[ "${{ needs.changes.outputs.fuzzer }}" == "true" ]] && components+=("fuzzer")
|
||||
[[ "${{ needs.changes.outputs.fuzzer_runner }}" == "true" ]] && components+=("fuzzer_runner")
|
||||
[[ "${{ needs.changes.outputs.program-model }}" == "true" ]] && components+=("program-model")
|
||||
[[ "${{ needs.changes.outputs.seed-gen }}" == "true" ]] && components+=("seed-gen")
|
||||
[[ "${{ needs.changes.outputs.patcher }}" == "true" ]] && components+=("patcher")
|
||||
fi
|
||||
if [[ ${#components[@]} -eq 0 ]]; then
|
||||
echo 'matrix={"component":[]}' >> $GITHUB_OUTPUT
|
||||
echo 'matrix={"component":[]}' >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "matrix={\"component\":[$(printf '"%s",' "${components[@]}" | sed 's/,$//')]}" >> $GITHUB_OUTPUT
|
||||
echo "matrix={\"component\":[$(printf '"%s",' "${components[@]}" | sed 's/,$//')]}" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
lint:
|
||||
permissions:
|
||||
contents: read
|
||||
needs: [changes, matrix-generator]
|
||||
strategy:
|
||||
fail-fast: false # Continue running other components even if one fails
|
||||
@@ -97,15 +113,15 @@ jobs:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v6
|
||||
uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
|
||||
|
||||
- name: Setup uv cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
||||
with:
|
||||
path: |
|
||||
~/.cache/uv
|
||||
@@ -125,17 +141,19 @@ jobs:
|
||||
uv run ruff check --output-format=github
|
||||
working-directory: ${{ matrix.component }}
|
||||
|
||||
- name: Run mypy on ${{ matrix.component }}
|
||||
run: uv run mypy
|
||||
- name: Run ty on ${{ matrix.component }}
|
||||
run: uv run ty check src/
|
||||
working-directory: ${{ matrix.component }}
|
||||
|
||||
check-protobuf:
|
||||
permissions:
|
||||
contents: read
|
||||
needs: changes
|
||||
# Run if protobuf files changed or we need to run all
|
||||
if: needs.changes.outputs.run-all == 'true' || contains(github.event.head_commit.modified, '.proto') || contains(github.event.head_commit.modified, 'protoc.sh')
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
name: Pre-commit
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
pre-commit:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.12'
|
||||
- uses: astral-sh/setup-uv@v6
|
||||
- uses: pre-commit/action@v3.0.1
|
||||
@@ -0,0 +1,82 @@
|
||||
name: Static Checks
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
static-checks:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
|
||||
with:
|
||||
enable-cache: true
|
||||
|
||||
- name: Set up Python
|
||||
run: uv python install 3.13
|
||||
|
||||
- name: Lint GitHub Actions
|
||||
run: |
|
||||
# Install actionlint
|
||||
bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash)
|
||||
./actionlint -color
|
||||
|
||||
- name: Security audit GitHub Actions
|
||||
run: |
|
||||
# Install zizmor
|
||||
uv pip install --isolated zizmor
|
||||
zizmor .github/workflows/
|
||||
|
||||
- name: Check YAML files
|
||||
run: |
|
||||
python3 -c "
|
||||
import yaml
|
||||
from pathlib import Path
|
||||
for f in Path('.').rglob('*.yaml'):
|
||||
if 'deployment/k8s' not in str(f):
|
||||
list(yaml.safe_load_all(f.read_text()))
|
||||
for f in Path('.').rglob('*.yml'):
|
||||
if 'deployment/k8s' not in str(f):
|
||||
list(yaml.safe_load_all(f.read_text()))
|
||||
"
|
||||
|
||||
- name: Check TOML files
|
||||
run: |
|
||||
python3 -c "
|
||||
import tomllib
|
||||
from pathlib import Path
|
||||
for f in Path('.').rglob('*.toml'):
|
||||
tomllib.load(f.open('rb'))
|
||||
"
|
||||
|
||||
- name: Check JSON files
|
||||
run: |
|
||||
python3 -c "
|
||||
import json
|
||||
from pathlib import Path
|
||||
for f in Path('.').rglob('*.json'):
|
||||
json.load(f.open())
|
||||
"
|
||||
|
||||
- name: Check for merge conflicts
|
||||
run: |
|
||||
! grep -rE '^(<{7}|>{7}|={7})' --include='*.py' --include='*.yaml' --include='*.yml' --include='*.toml' --include='*.json' . || exit 1
|
||||
|
||||
- name: Lint shell scripts
|
||||
run: |
|
||||
shellcheck --version
|
||||
find . -name '*.sh' -type f ! -path './.git/*' ! -path '*/.venv/*' -print0 | xargs -0 shellcheck
|
||||
|
||||
- name: Lint Dockerfiles
|
||||
run: |
|
||||
hadolint --version
|
||||
find . -name 'Dockerfile*' -type f ! -path './.git/*' -print0 | xargs -0 hadolint
|
||||
@@ -58,7 +58,6 @@ env:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
integration:
|
||||
@@ -69,7 +68,7 @@ jobs:
|
||||
contains(github.event.pull_request.labels.*.name, 'full-integration')
|
||||
runs-on: gha-ubuntu-8
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: true
|
||||
@@ -88,20 +87,41 @@ jobs:
|
||||
echo "BUTTERCUP_NAMESPACE=${BUTTERCUP_NAMESPACE}" >> $GITHUB_ENV
|
||||
|
||||
- name: Configure env file for minikube
|
||||
env:
|
||||
BUTTERCUP_NS: ${{ env.BUTTERCUP_NAMESPACE }}
|
||||
OPENAI_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
ANTHROPIC_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
GEMINI_KEY: ${{ secrets.GEMINI_API_KEY }}
|
||||
LF_HOST: ${{ secrets.LANGFUSE_HOST }}
|
||||
LF_PUBLIC_KEY: ${{ secrets.LANGFUSE_PUBLIC_KEY }}
|
||||
LF_SECRET_KEY: ${{ secrets.LANGFUSE_SECRET_KEY }}
|
||||
OTEL_EP: ${{ secrets.OTEL_ENDPOINT }}
|
||||
OTEL_TK: ${{ secrets.OTEL_TOKEN }}
|
||||
run: |
|
||||
cp ../.github/ci-env.template env
|
||||
|
||||
sed -i "/GHCR_AUTH=.*/d" env
|
||||
sed -i "s|BUTTERCUP_NAMESPACE=.*|BUTTERCUP_NAMESPACE=${{ env.BUTTERCUP_NAMESPACE }}|" env
|
||||
sed -i "s|OPENAI_API_KEY=.*|OPENAI_API_KEY=${{ secrets.OPENAI_API_KEY }}|" env
|
||||
sed -i "s|ANTHROPIC_API_KEY=.*|ANTHROPIC_API_KEY=${{ secrets.ANTHROPIC_API_KEY }}|" env
|
||||
sed -i "s|GEMINI_API_KEY=.*|GEMINI_API_KEY=${{ secrets.GEMINI_API_KEY }}|" env
|
||||
sed -i "s|LANGFUSE_HOST=.*|LANGFUSE_HOST=${{ secrets.LANGFUSE_HOST }}|" env
|
||||
sed -i "s|LANGFUSE_PUBLIC_KEY=.*|LANGFUSE_PUBLIC_KEY=${{ secrets.LANGFUSE_PUBLIC_KEY }}|" env
|
||||
sed -i "s|LANGFUSE_SECRET_KEY=.*|LANGFUSE_SECRET_KEY=${{ secrets.LANGFUSE_SECRET_KEY }}|" env
|
||||
sed -i "s|OTEL_ENDPOINT=.*|OTEL_ENDPOINT=${{ secrets.OTEL_ENDPOINT }}|" env
|
||||
sed -i "s|OTEL_TOKEN=.*|OTEL_TOKEN=\"${{ secrets.OTEL_TOKEN }}\"|" env
|
||||
|
||||
# Remove GHCR_AUTH line and lines for values we'll append
|
||||
sed -i "/^GHCR_AUTH=/d" env
|
||||
sed -i "/^BUTTERCUP_NAMESPACE=/d" env
|
||||
sed -i "/^OPENAI_API_KEY=/d" env
|
||||
sed -i "/^ANTHROPIC_API_KEY=/d" env
|
||||
sed -i "/^GEMINI_API_KEY=/d" env
|
||||
sed -i "/^LANGFUSE_HOST=/d" env
|
||||
sed -i "/^LANGFUSE_PUBLIC_KEY=/d" env
|
||||
sed -i "/^LANGFUSE_SECRET_KEY=/d" env
|
||||
sed -i "/^OTEL_ENDPOINT=/d" env
|
||||
sed -i "/^OTEL_TOKEN=/d" env
|
||||
# Append values safely using echo (avoids sed injection with special chars)
|
||||
{
|
||||
echo "BUTTERCUP_NAMESPACE=${BUTTERCUP_NS}"
|
||||
echo "OPENAI_API_KEY=${OPENAI_KEY}"
|
||||
echo "ANTHROPIC_API_KEY=${ANTHROPIC_KEY}"
|
||||
echo "GEMINI_API_KEY=${GEMINI_KEY}"
|
||||
echo "LANGFUSE_HOST=${LF_HOST}"
|
||||
echo "LANGFUSE_PUBLIC_KEY=${LF_PUBLIC_KEY}"
|
||||
echo "LANGFUSE_SECRET_KEY=${LF_SECRET_KEY}"
|
||||
echo "OTEL_ENDPOINT=${OTEL_EP}"
|
||||
echo "OTEL_TOKEN=${OTEL_TK}"
|
||||
} >> "env"
|
||||
working-directory: deployment
|
||||
|
||||
- name: Run CRS
|
||||
+40
-18
@@ -11,8 +11,13 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test:
|
||||
permissions:
|
||||
contents: read
|
||||
strategy:
|
||||
fail-fast: false # Continue running other components even if one fails
|
||||
matrix:
|
||||
@@ -35,12 +40,15 @@ jobs:
|
||||
- component: fuzzer
|
||||
coverage_module: buttercup.fuzzer
|
||||
python: "3.12"
|
||||
- component: fuzzer_runner
|
||||
coverage_module: buttercup.fuzzer_runner
|
||||
python: "3.12"
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
# Removed if: matrix.should_run since we're not using path filtering right now
|
||||
services:
|
||||
redis:
|
||||
image: redis
|
||||
image: redis@sha256:e647cfe134bf5e8e74e620f66346f93418acfc240b71dd85640325cb7cd01402 # 7.4
|
||||
options: >-
|
||||
--health-cmd "redis-cli ping"
|
||||
--health-interval 10s
|
||||
@@ -50,16 +58,16 @@ jobs:
|
||||
- 6379:6379
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: true
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v6
|
||||
uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
|
||||
|
||||
- name: Setup uv cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
||||
with:
|
||||
path: |
|
||||
~/.cache/uv
|
||||
@@ -82,14 +90,14 @@ jobs:
|
||||
make install-cscope
|
||||
|
||||
- name: Install minimal dependencies
|
||||
if: matrix.component != 'program-model' && matrix.component != 'seed-gen' && matrix.component != 'patcher' && matrix.component != 'fuzzer'
|
||||
if: matrix.component != 'program-model' && matrix.component != 'seed-gen' && matrix.component != 'patcher' && matrix.component != 'fuzzer' && matrix.component != 'fuzzer_runner'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y ripgrep
|
||||
|
||||
# Fuzzer only needs ripgrep, no codequery
|
||||
|
||||
# Fuzzer and fuzzer_runner only need ripgrep, no codequery
|
||||
- name: Install fuzzer dependencies
|
||||
if: matrix.component == 'fuzzer'
|
||||
if: matrix.component == 'fuzzer' || matrix.component == 'fuzzer_runner'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y ripgrep
|
||||
@@ -106,12 +114,12 @@ jobs:
|
||||
uv sync --all-extras --frozen
|
||||
# Install test reporting tools in isolated environment
|
||||
# This avoids adding them to every component's dependencies
|
||||
uv pip install --isolated pytest-html>=4.1.1 pytest-cov>=6.0.0
|
||||
uv pip install --isolated 'pytest-html>=4.1.1' 'pytest-cov>=6.0.0'
|
||||
working-directory: ${{ matrix.component }}
|
||||
|
||||
- name: Run tests on ${{ matrix.component }} component
|
||||
run: |
|
||||
uv run --frozen pytest -svv ${{ matrix.pytest_args }} \
|
||||
uv run --frozen pytest -svv \
|
||||
--junit-xml=test-results.xml \
|
||||
--html=test-report.html \
|
||||
--self-contained-html \
|
||||
@@ -123,12 +131,24 @@ jobs:
|
||||
PYTHON_WASM_BUILD_PATH: "python-3.12.0.wasm"
|
||||
working-directory: ${{ matrix.component }}
|
||||
|
||||
- name: Audit dependencies for vulnerabilities
|
||||
if: always()
|
||||
run: |
|
||||
uv pip install --isolated pip-audit
|
||||
# Ignore CVEs with no available fix:
|
||||
# - CVE-2025-67221: orjson DoS via deeply nested JSON (no fix available)
|
||||
# - CVE-2026-0994: protobuf DoS via json_format.ParseDict (no fix available)
|
||||
uv run pip-audit --strict --desc \
|
||||
--ignore-vuln CVE-2025-67221 \
|
||||
--ignore-vuln CVE-2026-0994
|
||||
working-directory: ${{ matrix.component }}
|
||||
|
||||
- name: Generate test summary
|
||||
if: always()
|
||||
run: |
|
||||
echo "### Test Results: ${{ matrix.component }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
if [ -f ${{ matrix.component }}/test-results.xml ]; then
|
||||
echo "### Test Results: ${{ matrix.component }}" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "" >> "$GITHUB_STEP_SUMMARY"
|
||||
if [ -f "${{ matrix.component }}/test-results.xml" ]; then
|
||||
python -c "
|
||||
import xml.etree.ElementTree as ET
|
||||
tree = ET.parse('${{ matrix.component }}/test-results.xml')
|
||||
@@ -144,14 +164,14 @@ jobs:
|
||||
print(f'- **Errors**: {errors}')
|
||||
print(f'- **Skipped**: {skipped}')
|
||||
print(f'- **Duration**: {float(time):.2f}s')
|
||||
" >> $GITHUB_STEP_SUMMARY
|
||||
" >> "$GITHUB_STEP_SUMMARY"
|
||||
else
|
||||
echo "No test results found" >> $GITHUB_STEP_SUMMARY
|
||||
echo "No test results found" >> "$GITHUB_STEP_SUMMARY"
|
||||
fi
|
||||
|
||||
- name: Upload test results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: test-results-${{ matrix.component }}-py${{ matrix.python }}
|
||||
path: |
|
||||
@@ -165,18 +185,20 @@ jobs:
|
||||
|
||||
# Consolidated coverage upload after all tests complete
|
||||
coverage-upload:
|
||||
permissions:
|
||||
contents: read
|
||||
needs: [test]
|
||||
if: always()
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download all coverage reports
|
||||
uses: actions/download-artifact@v5
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
||||
with:
|
||||
pattern: test-results-*
|
||||
path: coverage-reports
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v5
|
||||
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
||||
with:
|
||||
directory: coverage-reports
|
||||
files: '*/coverage.xml,**/coverage.xml'
|
||||
|
||||
Reference in New Issue
Block a user