name: CI

permissions: read-all

on:
  push:
    branches:
      - main
  pull_request:
  merge_group:
    branches:
      - main

jobs:
  e2e-tests:
    runs-on: ubuntu-latest
    env:
      SUDO_UNDER_TEST: ours
      CI: true
    steps:
      - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0

      - name: set up docker buildx
        run: docker buildx create --name builder --use

      - name: cache docker layers
        uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
        with:
          path: /tmp/.buildx-cache
          key: docker-buildx-rs-${{ github.sha }}
          restore-keys: docker-buildx-rs-

      - name: Rust Cache
        uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
        with:
          shared-key: "compliance-tests"
          workspaces: |
            test-framework

      - name: Register rust problem matcher
        run: echo "::add-matcher::.github/problem-matchers/rust.json"

      - name: Run all E2E tests
        working-directory: test-framework
        run: cargo test -p e2e-tests --features apparmor

      - name: prevent the cache from growing too large
        run: |
          rm -rf /tmp/.buildx-cache
          mv /tmp/.buildx-cache-new /tmp/.buildx-cache

  compliance-tests-detect-changes:
    runs-on: ubuntu-latest
    outputs:
      updated: ${{ steps.filter.outputs.test-framework }}
    steps:
      - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0

      - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
        id: filter
        with:
          filters: |
            test-framework:
              - 'test-framework/**'

  compliance-tests-og:
    needs: compliance-tests-detect-changes
    if: ${{ needs.compliance-tests-detect-changes.outputs.updated != 'false' }}
    runs-on: ubuntu-latest
    env:
      CI: true
    steps:
      - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0

      - name: set up docker buildx
        run: docker buildx create --name builder --use

      - name: Rust Cache
        uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
        with:
          shared-key: "compliance-tests"
          workspaces: |
            test-framework

      - name: Register rust problem matcher
        run: echo "::add-matcher::.github/problem-matchers/rust.json"

      - name: Test sudo-test itself
        working-directory: test-framework
        run: cargo test -p sudo-test

      - name: Run all compliance tests against original sudo
        working-directory: test-framework
        run: cargo test -p sudo-compliance-tests -- --include-ignored

  compliance-tests:
    runs-on: ubuntu-latest
    timeout-minutes: 20
    env:
      SUDO_TEST_PROFRAW_DIR: /tmp/profraw
      CI: true
    steps:
      - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0

      - name: set up docker buildx
        run: docker buildx create --name builder --use

      - name: cache docker layers
        uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
        with:
          path: /tmp/.buildx-cache
          key: docker-buildx-rs-${{ github.sha }}
          restore-keys: docker-buildx-rs-

      - name: Rust Cache
        uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
        with:
          shared-key: "compliance-tests"
          workspaces: |
            test-framework

      - name: Register rust problem matcher
        run: echo "::add-matcher::.github/problem-matchers/rust.json"

      - name: Run gated compliance tests against sudo-rs
        working-directory: test-framework
        env:
          SUDO_UNDER_TEST: ours
        run: cargo test -p sudo-compliance-tests --features apparmor

      - name: Check that we didn't forget to gate a passing compliance test
        working-directory: test-framework
        env:
          SUDO_UNDER_TEST: ours
        run: |
          tmpfile="$(mktemp)"
          cargo test -p sudo-compliance-tests -- --ignored | tee "$tmpfile"
          grep 'test result: FAILED. 0 passed' "$tmpfile" || ( echo "expected ALL tests to fail but at least one passed; the passing tests must be un-#[ignore]-d" && exit 1 )

      - name: prevent the cache from growing too large
        run: |
          rm -rf /tmp/.buildx-cache
          mv /tmp/.buildx-cache-new /tmp/.buildx-cache

  compliance-tests-lint:
    needs: compliance-tests-detect-changes
    if: ${{ needs.compliance-tests-detect-changes.outputs.updated != 'false' }}
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0

      - name: Rust Cache
        uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
        with:
          shared-key: "compliance-tests"
          workspaces: |
            test-framework

      - name: Register rust problem matcher
        run: echo "::add-matcher::.github/problem-matchers/rust.json"

      - name: clippy sudo-test
        working-directory: test-framework
        run: cargo clippy -p sudo-test --no-deps -- --deny warnings

      - name: clippy compliance-tests
        working-directory: test-framework
        run: cargo clippy -p sudo-compliance-tests --tests --no-deps -- --deny warnings

      - name: Check that all ignored tests are linked to a GH issue
        working-directory: test-framework/sudo-compliance-tests
        run: |
          grep -r '#\[ignore' ./src | grep -v -e '"gh' -e '"wontfix"' && echo 'found ignored tests not linked to a GitHub issue. please like them using the format #[ignore = "gh123"]' && exit 1; true

  build-and-test:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0

      - name: Correct permissions
        run: sudo chmod -R 755 /usr/share

      - name: Install llvm-tools component
        run: rustup component add llvm-tools

      - name: Add cargo-llvm-cov
        uses: taiki-e/install-action@3575e532701a5fc614b0c842e4119af4cc5fd16d # v2.62.60
        with:
          tool: cargo-llvm-cov

      - name: Install dependencies
        run: |
          sudo apt update
          sudo apt install libpam0g-dev

      - name: Register rust problem matcher
        run: echo "::add-matcher::.github/problem-matchers/rust.json"

      - name: Build
        run: cargo build --workspace --all-targets --release

      - name: Run tests
        run: cargo llvm-cov --workspace --all-targets --release --lcov --output-path lcov.info

      - name: Upload code coverage
        uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1
        with:
          files: lcov.info

  build-and-test-minimal:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0

      - name: Correct permissions
        run: sudo chmod -R 755 /usr/share

      - name: Install nightly rust
        run: |
          rustup set profile minimal
          rustup override set nightly

      - name: Install dependencies
        run: |
          sudo apt update
          sudo apt install libpam0g-dev

      - name: Register rust problem matcher
        run: echo "::add-matcher::.github/problem-matchers/rust.json"

      - name: Update to minimal direct dependencies
        run: cargo update -Zdirect-minimal-versions

      - name: Build
        run: cargo build --workspace --all-targets --release

      - name: Run tests
        run: cargo test --workspace --all-targets --release

  build-and-test-msrv:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0

      - name: Correct permissions
        run: sudo chmod -R 755 /usr/share

      - name: Install rust 1.70
        run: rustup override set 1.70

      - name: Install dependencies
        run: |
          sudo apt update
          sudo apt install libpam0g-dev

      - name: Register rust problem matcher
        run: echo "::add-matcher::.github/problem-matchers/rust.json"

      - name: Build
        run: cargo build --workspace --all-targets --release

      - name: Run tests
        run: cargo test --workspace --all-targets --release

  build-and-test-fedora:
    runs-on: ubuntu-latest
    container: fedora:latest

    steps:
      - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0

      - name: Install dependencies
        run: |
          dnf install -y cargo pam-devel

      - name: Register rust problem matcher
        run: echo "::add-matcher::.github/problem-matchers/rust.json"

      - name: Reduce privileges
        run: |
          useradd builder
          chown builder .

      - name: Build
        run: sudo -ubuilder cargo build --workspace --all-targets --release

      - name: Run tests
        run: sudo -ubuilder cargo test --workspace --all-targets --release

  build-and-test-alpine:
    runs-on: ubuntu-latest
    container: alpine:latest

    steps:
      - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0

      - name: Install dependencies
        run: |
          apk add cargo linux-pam-dev sudo tzdata coreutils-fmt

      - name: Register rust problem matcher
        run: echo "::add-matcher::.github/problem-matchers/rust.json"

      - name: Reduce privileges
        run: |
          adduser --disabled-password builder
          chown builder .

      - name: Build
        run: sudo -ubuilder cargo build --workspace --all-targets --release

      - name: Run tests
        run: |
          # Alpine hasn't done usr-merge yet
          sudo -ubuilder cargo test --workspace --all-targets --release \
            -- --skip canonicalization --skip test_build_run_context

  build-and-test-32bit:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0

      - name: Correct permissions
        run: sudo chmod -R 755 /usr/share

      - name: Add 32-bit target
        run: |
          rustup target add i686-unknown-linux-gnu

      - name: Install dependencies
        run: |
          sudo dpkg --add-architecture i386
          sudo apt update
          sudo apt install libpam0g-dev:i386 gcc-multilib

      - name: Register rust problem matcher
        run: echo "::add-matcher::.github/problem-matchers/rust.json"

      - name: Build
        run: cargo build --target i686-unknown-linux-gnu --workspace --all-targets --release

      - name: Run tests
        run: cargo test --target i686-unknown-linux-gnu --workspace --all-targets --release

  miri:
    needs: build-and-test
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0

      - name: Install nightly rust and miri
        run: |
          rustup set profile minimal
          rustup override set nightly
          rustup component add miri

      - name: Install dependencies
        run: |
          sudo apt update
          sudo apt install libpam0g-dev

      - name: Register rust problem matcher
        run: echo "::add-matcher::.github/problem-matchers/rust.json"

      - name: Run tests
        run: cargo miri test --workspace miri

  check-bindings:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0

      - name: Install dependencies
        run: |
          sudo apt update
          sudo apt install libpam0g-dev

      - name: Install rust-bindgen
        uses: taiki-e/install-action@3575e532701a5fc614b0c842e4119af4cc5fd16d # v2.62.60
        with:
          tool: bindgen-cli@0.70.1

      - name: Install cargo-minify
        run: cargo install --locked --git https://github.com/tweedegolf/cargo-minify cargo-minify

      - name: Regenerate bindings
        run: make -B pam-sys

      - name: Check for differences
        run: git diff --exit-code

  format:
    runs-on: ubuntu-latest
    env:
      RUSTDOCFLAGS: "-D warnings"
    steps:
      - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0

      - name: Run rustfmt
        run: |
          cargo fmt --all -- --check
          cargo fmt --manifest-path test-framework/Cargo.toml --all -- --check

  clippy:
    needs: format
    runs-on: ubuntu-latest
    env:
      RUSTDOCFLAGS: "-D warnings"
    steps:
      - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0

      - name: Register rust problem matcher
        run: echo "::add-matcher::.github/problem-matchers/rust.json"

      - name: Run clippy
        run: cargo clippy --no-deps --all-targets -- --deny warnings

  docs:
    needs: clippy
    runs-on: ubuntu-latest
    env:
      RUSTDOCFLAGS: "-D warnings"
    steps:
      - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0

      - name: Register rust problem matcher
        run: echo "::add-matcher::.github/problem-matchers/rust.json"

      - name: Build docs
        run: cargo doc --no-deps --document-private-items

  audit:
    needs: clippy
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0

      - name: Install cargo-audit
        uses: taiki-e/install-action@3575e532701a5fc614b0c842e4119af4cc5fd16d # v2.62.60
        with:
          tool: cargo-audit

      - name: Run audit
        run: cargo audit
