bindcar 0.7.0

HTTP REST API for managing BIND9 zones via rndc
# Copyright (c) 2025 Erick Bourgeois, firestoned
# SPDX-License-Identifier: MIT

name: Pull Request CI

on:
  pull_request:
    branches:
      - main

permissions:
  contents: read
  id-token: write
  packages: write
  security-events: write  # For uploading SARIF results

env:
  CARGO_TERM_COLOR: always
  RUST_BACKTRACE: 1
  K8S_OPENAPI_ENABLED_VERSION: "1.32"

jobs:
  license-check:
    name: Verify SPDX License Headers
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

      - name: Check license headers
        uses: firestoned/github-actions/security/license-check@d0d51c638a90bffc2a1567fe7af112b37fe8854c # v1.3.7
        with:
          copyright-holder: "Erick Bourgeois, firestoned"
          license-id: "MIT"

  verify-commits:
    name: Verify Signed Commits
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          fetch-depth: 0  # Fetch all history for verification

      - name: Verify all commits are signed
        uses: firestoned/github-actions/security/verify-signed-commits@d0d51c638a90bffc2a1567fe7af112b37fe8854c # v1.3.7
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          base-ref: origin/${{ github.base_ref }}
          verify-mode: pr

  format:
    name: Check Formatting
    runs-on: ubuntu-latest
    needs: [license-check, verify-commits]
    steps:
      - name: Checkout code
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

      - name: Install Rust toolchain
        uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
        with:
          components: rustfmt

      - name: Check formatting
        run: make fmt

  clippy:
    name: Clippy
    runs-on: ubuntu-latest
    needs: format
    steps:
      - name: Checkout code
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

      - name: Install Rust toolchain
        uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
        with:
          components: clippy

      - name: Cache cargo dependencies
        uses: firestoned/github-actions/rust/cache-cargo@d0d51c638a90bffc2a1567fe7af112b37fe8854c # v1.3.7

      - name: Run clippy
        run: make clippy

  build:
    name: Build - ${{ matrix.platform.name }}
    runs-on: ${{ matrix.platform.os }}
    needs: [format, clippy]
    strategy:
      fail-fast: false
      matrix:
        platform:
          - name: Linux x86_64
            os: ubuntu-latest
            target: x86_64-unknown-linux-gnu
            artifact_name: bindcar-linux-amd64
            binary_name: bindcar
          - name: Linux ARM64
            os: ubuntu-latest
            target: aarch64-unknown-linux-gnu
            artifact_name: bindcar-linux-arm64
            binary_name: bindcar
    steps:
      - name: Checkout code
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

      - name: Setup Rust build environment
        uses: firestoned/github-actions/rust/setup-rust-build@d0d51c638a90bffc2a1567fe7af112b37fe8854c # v1.3.7
        with:
          target: ${{ matrix.platform.target }}

      - name: Build binary
        uses: firestoned/github-actions/rust/build-binary@d0d51c638a90bffc2a1567fe7af112b37fe8854c # v1.3.7
        with:
          target: ${{ matrix.platform.target }}

      - name: Generate SBOM
        uses: firestoned/github-actions/rust/generate-sbom@d0d51c638a90bffc2a1567fe7af112b37fe8854c # v1.3.7
        with:
          target: ${{ matrix.platform.target }}

      - name: Upload binary and SBOM artifacts
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
        with:
          name: ${{ matrix.platform.artifact_name }}
          path: |
            target/${{ matrix.platform.target }}/release/${{ matrix.platform.binary_name }}
            *.cdx.*
          retention-days: 1
          if-no-files-found: error

  test:
    name: Test
    runs-on: ubuntu-latest
    needs: build
    steps:
      - name: Checkout code
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

      - name: Install Rust toolchain
        uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable

      - name: Download x86_64 build artifact
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          name: bindcar-linux-amd64
          path: target/x86_64-unknown-linux-gnu/release/

      - name: Cache cargo dependencies
        uses: firestoned/github-actions/rust/cache-cargo@d0d51c638a90bffc2a1567fe7af112b37fe8854c # v1.3.7

      - name: Run tests
        run: make test

  extract-version:
    name: Extract Version Information
    runs-on: ubuntu-latest
    needs: format
    outputs:
      image-tag-chainguard: ${{ steps.version-chainguard.outputs.image-tag }}
      image-tag-distroless: ${{ steps.version-distroless.outputs.image-tag }}
      image-repository-chainguard: ${{ steps.version-chainguard.outputs.image-repository }}
      image-repository-distroless: ${{ steps.version-distroless.outputs.image-repository }}
      short-sha: ${{ steps.version-chainguard.outputs.short-sha }}
    steps:
      - name: Checkout code
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

      - name: Extract version for Chainguard
        id: version-chainguard
        uses: firestoned/github-actions/versioning/extract-version@d0d51c638a90bffc2a1567fe7af112b37fe8854c # v1.3.7
        with:
          repository: firestoned/bindcar
          workflow-type: pr
          pr-number: ${{ github.event.pull_request.number }}
          image-suffix: ""

      - name: Extract version for Distroless
        id: version-distroless
        uses: firestoned/github-actions/versioning/extract-version@d0d51c638a90bffc2a1567fe7af112b37fe8854c # v1.3.7
        with:
          repository: firestoned/bindcar
          workflow-type: pr
          pr-number: ${{ github.event.pull_request.number }}
          image-suffix: "-distroless"

  docker:
    name: Build and Push Docker Image - ${{ matrix.variant.name }}
    runs-on: ubuntu-latest
    needs: [test, extract-version]
    permissions:
      contents: read
      packages: write
    strategy:
      fail-fast: false
      matrix:
        variant:
          - name: Chainguard
            dockerfile: docker/Dockerfile.chainguard
            suffix: ""
            description: "BIND9 RNDC API Server - Chainguard Zero-CVE"
            image-tag: ${{ needs.extract-version.outputs.image-tag-chainguard }}
            image-repository: ${{ needs.extract-version.outputs.image-repository-chainguard }}
          - name: Distroless
            dockerfile: docker/Dockerfile
            suffix: "-distroless"
            description: "BIND9 RNDC API Server - Google Distroless"
            image-tag: ${{ needs.extract-version.outputs.image-tag-distroless }}
            image-repository: ${{ needs.extract-version.outputs.image-repository-distroless }}
    steps:
      - name: Checkout code
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

      - name: Prepare Docker binaries
        uses: ./.github/actions/prepare-docker-binaries

      - name: Setup Docker environment
        uses: firestoned/github-actions/docker/setup-docker@d0d51c638a90bffc2a1567fe7af112b37fe8854c # v1.3.7
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0
        with:
          images: ghcr.io/${{ matrix.variant.image-repository }}
          tags: |
            type=raw,value=${{ matrix.variant.image-tag }}
            type=raw,value=sha-${{ needs.extract-version.outputs.short-sha }}
          flavor: |
            latest=false

      - name: Build and push Docker image (${{ matrix.variant.name }})
        uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
        with:
          context: .
          file: ${{ matrix.variant.dockerfile }}
          platforms: linux/amd64,linux/arm64
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: |
            ${{ steps.meta.outputs.labels }}
            org.opencontainers.image.description=${{ matrix.variant.description }}
          cache-from: type=gha,scope=${{ matrix.variant.name }}
          cache-to: type=gha,mode=max,scope=${{ matrix.variant.name }}
          sbom: true
          provenance: true

  drone-integration-test:
    name: Drone Integration Test
    runs-on: ubuntu-latest
    needs: build
    steps:
      - name: Checkout code
        uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4

      - name: Install DNS tools
        run: sudo apt-get update && sudo apt-get install -y dnsutils bind9-utils

      - name: Download x86_64 build artifact
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
        with:
          name: bindcar-linux-amd64
          path: /tmp/bindcar-bin

      - name: Make binary executable
        run: chmod +x /tmp/bindcar-bin/target/x86_64-unknown-linux-gnu/release/bindcar

      - name: Run drone integration test
        env:
          BINDCAR_BIN: /tmp/bindcar-bin/target/x86_64-unknown-linux-gnu/release/bindcar
        run: make drone-integration-test-ci

  security:
    name: Security Vulnerability Scan
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

      - name: Run security scan
        uses: firestoned/github-actions/rust/security-scan@d0d51c638a90bffc2a1567fe7af112b37fe8854c # v1.3.7
        with:
          cargo-audit-version: "0.22.0"

  coverage:
    name: Code Coverage
    runs-on: ubuntu-latest
    needs: test
    permissions:
      contents: write
      pull-requests: write
    steps:
      - name: Checkout code
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

      - name: Install Rust toolchain
        uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable

      - name: Setup Rust cache
        uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1

      - name: Install cargo-llvm-cov
        uses: taiki-e/install-action@9bcaee1dcae34154180f412e2fa69355a7cda9f6 # v2.82.6
        with:
          tool: cargo-llvm-cov

      - name: Generate coverage report
        run: cargo llvm-cov --lib --lcov --output-path lcov.info

      - name: Upload coverage to Codecov
        uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
        with:
          files: lcov.info
          fail_ci_if_error: false
          token: ${{ secrets.CODECOV_TOKEN }}
          verbose: true

      - name: Generate HTML coverage report
        run: cargo llvm-cov --lib --html

      - name: Upload HTML coverage as artifact
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
        with:
          name: coverage-report
          path: target/llvm-cov/html/