bindcar 0.7.0

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

name: Main Branch CI/CD

on:
  push:
    branches:
      - main
  workflow_dispatch:

permissions:
  contents: read
  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@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4

      - 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@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
        with:
          fetch-depth: 0  # Fetch all history for verification

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

  build:
    name: Build - ${{ matrix.platform.name }}
    runs-on: ${{ matrix.platform.os }}
    needs: [license-check, verify-commits]
    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@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4

      - 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@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
        with:
          name: ${{ matrix.platform.artifact_name }}
          path: |
            target/${{ matrix.platform.target }}/release/${{ matrix.platform.binary_name }}
            *.cdx.*
          retention-days: 1

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

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

      - name: Download x86_64 build artifact
        uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
        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

  lint:
    name: Lint
    runs-on: ubuntu-latest
    needs: [license-check, verify-commits]
    steps:
      - name: Checkout code
        uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4

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

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

      - name: Check formatting
        run: make fmt

      - name: Run clippy
        run: make clippy

  extract-version:
    name: Extract Version Information
    runs-on: ubuntu-latest
    needs: [license-check, verify-commits]
    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@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4

      - 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: main
          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: main
          image-suffix: "-distroless"

  docker:
    name: Build and Push Docker Image - ${{ matrix.variant.name }}
    runs-on: ubuntu-latest
    needs: [build, 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@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4

      - 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=main-{{date 'YYYY-MM-DD'}}
            type=raw,value=sha-${{ needs.extract-version.outputs.short-sha }}
            type=raw,value=latest
          flavor: |
            latest=false

      - name: Build and push Docker image (${{ matrix.variant.name }})
        id: docker_build
        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

  coverage:
    name: Code Coverage
    runs-on: ubuntu-latest
    needs: test
    permissions:
      contents: write
      pull-requests: write
    steps:
      - name: Checkout code
        uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4

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

      - name: Setup Rust cache
        uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2

      - name: Install cargo-llvm-cov
        uses: taiki-e/install-action@9bcaee1dcae34154180f412e2fa69355a7cda9f6 # v2
        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@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
        with:
          name: coverage-report
          path: target/llvm-cov/html/

  security:
    name: Security Vulnerability Scan
    runs-on: ubuntu-latest
    needs: [license-check, verify-commits]
    steps:
      - name: Checkout code
        uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4

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