2.3: CI/CD Pipeline Setup

What You'll Learn

By the end of this chapter, you'll be able to:

  • Build a CI/CD pipeline that generates ASPICE work products automatically
  • Set up quality gates that catch problems before they become assessment findings
  • Configure artifact retention for audit trails
  • Automate release processes with proper evidence packaging

Introduction

Here's a scenario I've witnessed more than once: it's 2 AM before an ASPICE assessment, and the team is frantically generating test reports, compiling coverage data, and trying to prove that yes, they did run MISRA checks on that code from three months ago.

There's a better way.

A well-designed CI/CD pipeline doesn't just build and test your code—it generates ASPICE evidence automatically, enforces quality gates in real-time, and maintains a complete audit trail. When the assessor asks for your SWE.4 test reports, you don't scramble. You point them at your artifact store.

Let's build that pipeline.


The ASPICE-Compliant CI/CD Architecture

The following diagram shows the complete ASPICE-compliant CI/CD pipeline architecture, with quality gates enforcing build, test, analysis, and deployment standards at each stage.

CI/CD Pipeline

The Quality Gates (Your Safety Net):

  • ✅ Build succeeds
  • ✅ All unit tests pass
  • ✅ Coverage ≥ 80% (or your ASIL-specific target)
  • ✅ Zero MISRA mandatory violations
  • ✅ SonarQube quality gate passed
  • ✅ Security scan clean

If any gate fails, the PR doesn't merge. Period.


The Complete GitHub Actions Pipeline

This isn't a toy example—it's a production-ready pipeline you can copy and adapt. Here's the whole thing:

.github/workflows/aspice-ci.yml

name: ASPICE CI/CD Pipeline

on:
  push:
    branches: [main, develop, 'release/**']
  pull_request:
    branches: [main, develop]
  schedule:
    - cron: '0 2 * * *'  # Nightly full analysis

env:
  BUILD_TYPE: Release
  TARGET_PLATFORM: arm-none-eabi

jobs:
  # ============================================================================
  # STAGE 1: BUILD
  # ============================================================================
  build:
    name: Build (SWE.3 BP2)
    runs-on: ubuntu-latest
    container:
      image: gcc-arm-none-eabi:latest
    
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0  # Full history for traceability
      
      - name: Install dependencies
        run: |
          apt-get update
          apt-get install -y cmake ninja-build
      
      - name: Configure CMake
        run: |
          cmake -B build \
                -G Ninja \
                -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} \
                -DCMAKE_TOOLCHAIN_FILE=cmake/arm-toolchain.cmake \
                -DENABLE_COVERAGE=ON \
                -DENABLE_MISRA=ON
      
      - name: Build
        run: cmake --build build --parallel 4
      
      - name: Generate build artifacts
        run: |
          mkdir -p artifacts
          cp build/door_lock_ctrl.elf artifacts/
          cp build/door_lock_ctrl.hex artifacts/
          cp build/door_lock_ctrl.map artifacts/
          
          # Generate build report (ASPICE work product)
          echo "# Build Report" > artifacts/build_report.md
          echo "Date: $(date)" >> artifacts/build_report.md
          echo "Commit: $GITHUB_SHA" >> artifacts/build_report.md
          echo "Branch: $GITHUB_REF_NAME" >> artifacts/build_report.md
          arm-none-eabi-size build/door_lock_ctrl.elf >> artifacts/build_report.md
      
      - name: Upload build artifacts
        uses: actions/upload-artifact@v3
        with:
          name: build-artifacts
          path: artifacts/
          retention-days: 90  # Keep for ASPICE audit trail

  # ============================================================================
  # STAGE 2: UNIT TESTS (SWE.4)
  # ============================================================================
  unit-tests:
    name: Unit Tests (SWE.4 BP3)
    needs: build
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      
      - name: Install test framework
        run: |
          sudo apt-get update
          sudo apt-get install -y ruby
          gem install ceedling
      
      - name: Run unit tests
        run: |
          cd tests/unit
          ceedling test:all
          ceedling gcov:all
      
      - name: Generate coverage report
        run: |
          lcov --capture --directory build --output-file coverage.info
          lcov --remove coverage.info '/usr/*' '*/tests/*' --output-file coverage_filtered.info
          genhtml coverage_filtered.info --output-directory coverage_html
      
      - name: Check coverage threshold
        run: |
          COVERAGE=$(lcov --summary coverage_filtered.info | grep lines | awk '{print $2}' | sed 's/%//')
          echo "Coverage: $COVERAGE%"
          
          if (( $(echo "$COVERAGE < 80" | bc -l) )); then
            echo "❌ ERROR: Coverage $COVERAGE% below 80% threshold"
            exit 1
          fi
          echo "✅ Coverage threshold met!"
      
      - name: Upload coverage report
        uses: actions/upload-artifact@v3
        with:
          name: coverage-report
          path: coverage_html/
      
      - name: Upload to Codecov
        uses: codecov/codecov-action@v3
        with:
          files: ./coverage_filtered.info
          flags: unittests
          name: codecov-umbrella

  # ============================================================================
  # STAGE 3: STATIC ANALYSIS (SWE.3 BP7)
  # ============================================================================
  static-analysis:
    name: Static Analysis (MISRA C)
    needs: build
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      
      - name: Run Cppcheck with MISRA addon
        run: |
          cppcheck --addon=misra \
                   --enable=all \
                   --std=c11 \
                   --platform=unix32 \
                   --suppress=missingIncludeSystem \
                   --xml \
                   --xml-version=2 \
                   src/ 2> cppcheck-misra.xml
      
      - name: Check MISRA violations
        run: |
          # Count mandatory rule violations
          MANDATORY_VIOLATIONS=$(grep -c 'misra-c2012.*mandatory' cppcheck-misra.xml || true)
          
          if [ "$MANDATORY_VIOLATIONS" -gt 0 ]; then
            echo "❌ ERROR: $MANDATORY_VIOLATIONS MISRA mandatory violations found"
            exit 1
          fi
          echo "✅ MISRA mandatory rules: PASS"
      
      - name: Upload MISRA report
        uses: actions/upload-artifact@v3
        with:
          name: misra-report
          path: cppcheck-misra.xml
  
  sonarqube:
    name: SonarQube Analysis
    needs: [build, unit-tests]
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0  # Shallow clones disable blame
      
      - name: Download coverage
        uses: actions/download-artifact@v3
        with:
          name: coverage-report
      
      - name: SonarQube Scan
        uses: sonarsource/sonarqube-scan-action@master
        env:
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
          SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
      
      - name: SonarQube Quality Gate
        uses: sonarsource/sonarqube-quality-gate-action@master
        timeout-minutes: 5
        env:
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

  # ============================================================================
  # STAGE 4: INTEGRATION TESTS (SWE.5)
  # ============================================================================
  integration-tests:
    name: Integration Tests (SWE.5 BP3)
    needs: [build, unit-tests]
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      
      - name: Download build artifacts
        uses: actions/download-artifact@v3
        with:
          name: build-artifacts
      
      - name: Setup Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.11'
      
      - name: Install test dependencies
        run: |
          pip install pytest pytest-html robotframework cantools
      
      - name: Run integration tests
        run: |
          cd tests/integration
          pytest --html=report.html --self-contained-html
      
      - name: Upload integration test report
        uses: actions/upload-artifact@v3
        with:
          name: integration-test-report
          path: tests/integration/report.html

  # ============================================================================
  # STAGE 5: SECURITY SCAN
  # ============================================================================
  security-scan:
    name: Security Vulnerability Scan
    needs: build
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      
      - name: Run Bandit (Python)
        run: |
          pip install bandit
          bandit -r tools/ scripts/ -f json -o bandit-report.json || true
      
      - name: Run Trivy (Container/Dependencies)
        uses: aquasecurity/trivy-action@master
        with:
          scan-type: 'fs'
          scan-ref: '.'
          format: 'sarif'
          output: 'trivy-results.sarif'
      
      - name: Upload security reports
        uses: github/codeql-action/upload-sarif@v2
        with:
          sarif_file: trivy-results.sarif

  # ============================================================================
  # STAGE 6: TRACEABILITY GENERATION (SUP.10 BP5)
  # ============================================================================
  traceability:
    name: Generate Traceability Matrix
    needs: [build, unit-tests]
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0  # Need full history
      
      - name: Generate traceability matrix
        run: |
          python tools/scripts/generate_traceability.py \
            --since v2.0.0 \
            --output traceability_matrix.csv
      
      - name: Upload traceability matrix
        uses: actions/upload-artifact@v3
        with:
          name: traceability-matrix
          path: traceability_matrix.csv

  # ============================================================================
  # STAGE 7: RELEASE (SUP.8 BP5)
  # ============================================================================
  release:
    name: Create Release
    needs: [build, unit-tests, integration-tests, static-analysis, security-scan]
    if: startsWith(github.ref, 'refs/tags/v')
    runs-on: ubuntu-latest
    permissions:
      contents: write
    
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      
      - name: Download all artifacts
        uses: actions/download-artifact@v3
      
      - name: Generate release notes
        id: changelog
        run: |
          echo "## Release ${{ github.ref_name }}" > RELEASE_NOTES.md
          echo "" >> RELEASE_NOTES.md
          echo "### Build Artifacts" >> RELEASE_NOTES.md
          echo "- door_lock_ctrl.hex - Production firmware" >> RELEASE_NOTES.md
          echo "- door_lock_ctrl.elf - Debug symbols" >> RELEASE_NOTES.md
          echo "" >> RELEASE_NOTES.md
          echo "### Quality Metrics" >> RELEASE_NOTES.md
          echo "- Unit Test Coverage: $(grep 'lines' coverage_filtered.info)" >> RELEASE_NOTES.md
          echo "- MISRA Compliance: $(grep -c 'misra' cppcheck-misra.xml) violations" >> RELEASE_NOTES.md
          echo "" >> RELEASE_NOTES.md
          echo "### Traceability" >> RELEASE_NOTES.md
          echo "See attached traceability_matrix.csv" >> RELEASE_NOTES.md
      
      - name: Create GitHub Release
        uses: softprops/action-gh-release@v1
        with:
          body_path: RELEASE_NOTES.md
          files: |
            build-artifacts/door_lock_ctrl.hex
            build-artifacts/door_lock_ctrl.elf
            build-artifacts/build_report.md
            coverage-report/index.html
            misra-report/cppcheck-misra.xml
            traceability-matrix/traceability_matrix.csv
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

What This Pipeline Does For You

The ASPICE value of each pipeline stage breaks down as follows:

Pipeline Stage ASPICE Process Evidence Generated
Build SWE.3 BP2 Build report, .elf, .hex, .map
Unit Tests SWE.4 BP3 Test logs, coverage report
Static Analysis SWE.3 BP7 MISRA violation report
Integration Tests SWE.5 BP3 Integration test report
Security Scan SUP.1 Security vulnerability report
Traceability SUP.10 BP5 Traceability matrix
Release SUP.8 BP5 Release notes, artifact package

Every push generates evidence. Every release packages it neatly.


Summary

Your CI/CD pipeline is now an ASPICE evidence factory:

  • Automated Quality Gates: Build, test, coverage, MISRA, security—all enforced
  • Work Product Generation: Reports generated automatically, not manually
  • Fail Fast: Bad code gets stopped at the PR, not discovered at assessment
  • Audit Trail: 90-day artifact retention for complete traceability
  • Release Automation: Tag → evidence package, ready for auditors

Your Best Practices Checklist:

  1. 🏃 Run lightweight checks first (build, unit tests) before expensive ones
  2. Parallelize independent jobs for faster feedback
  3. 💾 Cache dependencies to speed up builds
  4. 🚫 Enforce quality gates—don't let bad code merge, ever
  5. 📦 Retain artifacts for at least 90 days (check your ASPICE retention requirements)

With your pipeline in place, let's talk about choosing the right tools to feed into it.