You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
synctv/.github/workflows/ci.yml

340 lines
9.8 KiB
YAML

name: CI Pipeline
on:
push:
branches: ['**']
pull_request:
branches: ['**']
schedule:
# Run security audit daily at 00:00 UTC
- cron: '0 0 * * *'
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
permissions:
contents: read
env:
CARGO_TERM_COLOR: always
SYNCTV_LOGGING_BACKTRACE: true
jobs:
# Check code formatting
format:
name: Format Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt
- name: Check formatting
run: cargo fmt --all -- --check
# Build
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Install protobuf compiler
run: sudo apt-get update && sudo apt-get install -y protobuf-compiler
- name: Cache Rust build artifacts
uses: Swatinem/rust-cache@v2
- name: Build
run: cargo build --workspace --verbose --locked
# Test non-ignored cases
test-default:
name: Test Default (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os:
- ubuntu-latest
- macos-latest
- windows-latest
env:
SYNCTV_TEST_DOCKER_STARTUP_TIMEOUT_SECS: 600
steps:
- uses: actions/checkout@v4
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- name: Install cargo-nextest
uses: taiki-e/install-action@nextest
- name: Install protobuf compiler on Linux
if: runner.os == 'Linux'
run: sudo apt-get update && sudo apt-get install -y protobuf-compiler
- name: Install protobuf compiler on macOS
if: runner.os == 'macOS'
run: brew install protobuf
- name: Install protobuf compiler on Windows
if: runner.os == 'Windows'
run: choco install protoc --yes
- name: Cache Rust build artifacts
uses: Swatinem/rust-cache@v2
- name: Run clippy
timeout-minutes: 60
run: |
cargo clippy --workspace --all-targets --locked
- name: Run non-ignored tests with nextest
timeout-minutes: 60
run: |
cargo nextest run --workspace --locked --run-ignored default --nff
# Test ignored cases
test-ignored:
name: Test Ignored (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os:
- ubuntu-latest
- macos-latest
- windows-latest
env:
SYNCTV_TEST_DOCKER_STARTUP_TIMEOUT_SECS: 600
steps:
- uses: actions/checkout@v4
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Install cargo-nextest
uses: taiki-e/install-action@nextest
- name: Install protobuf compiler on Linux
if: runner.os == 'Linux'
run: sudo apt-get update && sudo apt-get install -y protobuf-compiler
- name: Install protobuf compiler on macOS
if: runner.os == 'macOS'
run: brew install protobuf
- name: Install protobuf compiler on Windows
if: runner.os == 'Windows'
run: choco install protoc --yes
- name: Cache Rust build artifacts
uses: Swatinem/rust-cache@v2
- name: Run ignored tests with nextest
timeout-minutes: 60
run: |
cargo nextest run --workspace --locked --run-ignored only --nff -j$(nproc)
# Security audit
security-audit:
name: Security Audit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Install cargo-audit
run: cargo install cargo-audit --locked
- name: Run security audit
run: cargo audit --deny warnings
# SECURITY: Audit failures should block the build
- name: Run security audit (advisories)
run: cargo audit
continue-on-error: true # Informational output
# Dependency policy enforcement with cargo-deny
cargo-deny:
name: Dependency Policy Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Install cargo-deny
run: cargo install cargo-deny --locked
- name: Run cargo-deny check
run: cargo deny check
- name: Run cargo-deny check (advisories only - fail on vulnerabilities)
run: cargo deny check advisories
continue-on-error: false
- name: Run cargo-deny check (licenses)
run: cargo deny check licenses
continue-on-error: true # Warn but don't fail
- name: Run cargo-deny check (bans)
run: cargo deny check bans
continue-on-error: true # Warn about multiple versions
- name: Run cargo-deny check (sources)
run: cargo deny check sources
continue-on-error: false # Fail on untrusted sources
# Check for unused dependencies
udeps:
name: Unused Dependencies
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust nightly
uses: dtolnay/rust-toolchain@nightly
- name: Install protobuf compiler
run: sudo apt-get update && sudo apt-get install -y protobuf-compiler
- name: Install cargo-udeps
run: cargo install cargo-udeps --locked
- name: Check for unused dependencies
run: cargo +nightly udeps --locked --all-targets
continue-on-error: true # Don't fail the build, just report
# Prepare Docker image publishing inputs
docker-prepare:
name: Docker Prepare
if: github.event_name == 'push'
runs-on: ubuntu-latest
outputs:
ghcr_image: ${{ steps.images.outputs.ghcr_image }}
dockerhub_image: ${{ steps.images.outputs.dockerhub_image }}
dockerhub_enabled: ${{ steps.images.outputs.dockerhub_enabled }}
env:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
steps:
- name: Compute image targets
id: images
run: |
owner="$(printf '%s' "${GITHUB_REPOSITORY_OWNER}" | tr '[:upper:]' '[:lower:]')"
repo="$(printf '%s' "${GITHUB_REPOSITORY#*/}" | tr '[:upper:]' '[:lower:]')"
ghcr_image="ghcr.io/${owner}/${repo}"
echo "ghcr_image=${ghcr_image}" >> "${GITHUB_OUTPUT}"
if [ -n "${DOCKER_USERNAME}" ] && [ -n "${DOCKER_PASSWORD}" ]; then
dockerhub_user="$(printf '%s' "${DOCKER_USERNAME}" | tr '[:upper:]' '[:lower:]')"
echo "dockerhub_image=docker.io/${dockerhub_user}/${repo}" >> "${GITHUB_OUTPUT}"
echo "dockerhub_enabled=true" >> "${GITHUB_OUTPUT}"
else
echo "dockerhub_image=" >> "${GITHUB_OUTPUT}"
echo "dockerhub_enabled=false" >> "${GITHUB_OUTPUT}"
fi
# Build and publish the GHCR image with native per-platform runners
docker-build:
name: Docker Build
if: github.event_name == 'push'
needs: docker-prepare
permissions:
contents: read
packages: write
id-token: write
uses: docker/github-builder/.github/workflows/build.yml@v1
with:
output: image
push: true
runner: auto
distribute: true
platforms: linux/amd64,linux/arm64
cache: true
cache-scope: synctv-image
meta-images: ${{ needs.docker-prepare.outputs.ghcr_image }}
meta-tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha
set-meta-labels: true
secrets:
registry-auths: |
- registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# Optionally copy the published GHCR image to additional registries
docker-publish-registries:
name: Docker Publish Additional Registries
if: >-
github.event_name == 'push' &&
needs.docker-prepare.outputs.dockerhub_enabled == 'true'
needs:
- docker-prepare
- docker-build
runs-on: ubuntu-latest
permissions:
contents: read
packages: read
steps:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Log in to GHCR
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Log in to Docker Hub
uses: docker/login-action@v4
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Extract GHCR metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ needs.docker-prepare.outputs.ghcr_image }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha
- name: Copy published tags to additional registries
env:
GHCR_IMAGE: ${{ needs.docker-prepare.outputs.ghcr_image }}
DOCKERHUB_IMAGE: ${{ needs.docker-prepare.outputs.dockerhub_image }}
META_TAGS: ${{ steps.meta.outputs.tags }}
run: |
mapfile -t tags < <(printf '%s\n' "${META_TAGS}")
for source_tag in "${tags[@]}"; do
[ -n "${source_tag}" ] || continue
target_tag="${source_tag/#${GHCR_IMAGE}/${DOCKERHUB_IMAGE}}"
docker buildx imagetools create --tag "${target_tag}" "${source_tag}"
done