iscsi-client-rs 0.0.9

A pure-Rust iSCSI initiator library
Documentation
name: Rust

on:
  push:
    branches: [main]
    tags:
      - "*.*.*"
  pull_request:
    branches: [main]

env:
  CARGO_HTTP_MULTIPLEXING: false
  CARGO_HTTP_TIMEOUT: 120
  CARGO_NET_RETRY: 10

jobs:
  lint:
    name: Lint (clippy + rustfmt)
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7

      - name: Install Rust toolchain
        uses: actions-rs/toolchain@v1
        with:
          toolchain: nightly
          components: clippy, rustfmt

      - &cache-cargo
        name: Cache Cargo registry
        uses: actions/cache@v5
        with:
          path: |
            ~/.cargo/registry
            ~/.cargo/git
          key: cargo-${{ runner.os }}-${{ hashFiles('Cargo.lock') }}

      - &fetch-cargo
        name: Fetch Cargo dependencies
        run: |
          for attempt in 1 2 3 4 5; do
            cargo fetch --locked && exit 0
            if [ "$attempt" -eq 5 ]; then
              exit 1
            fi
            sleep $((attempt * 5))
          done

      - name: rustfmt check
        run: cargo +nightly fmt -- --check

      - name: clippy check
        run: cargo clippy --offline --tests --examples --benches --no-deps --all-targets -- -D warnings

  test:
    name: Run tests
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7

      - name: Install Rust toolchain
        uses: actions-rs/toolchain@v1
        with:
          toolchain: nightly

      - *cache-cargo
      - *fetch-cargo

      - name: Run tests
        run: cargo test --offline --tests unit --no-fail-fast --verbose

  test-tgt:
    name: Integration tests (TGT/${{ matrix.mode }})
    runs-on: ubuntu-latest
    timeout-minutes: 20
    strategy:
      fail-fast: false
      matrix:
        include:
          - mode: plain
            config: tests/configs/tgt/plain.yaml
            service: iscsi-target-plain
          - mode: chap
            config: tests/configs/tgt/chap.yaml
            service: iscsi-target-chap
          - mode: crc
            config: tests/configs/tgt/crc.yaml
            service: iscsi-target-crc
    steps:
      - uses: actions/checkout@v7

      - name: Install Rust toolchain
        uses: actions-rs/toolchain@v1
        with:
          toolchain: stable

      - *cache-cargo
      - *fetch-cargo

      - name: Start TGT
        run: docker compose --profile tgt up -d --build --wait --wait-timeout 60 ${{ matrix.service }}

      - name: Run integration tests
        env:
          TEST_CONFIG: ${{ matrix.config }}
        run: cargo test --offline --test integration -- --nocapture --test-threads=1

      - name: Show TGT logs
        if: failure()
        run: docker compose --profile tgt logs --no-color ${{ matrix.service }}

      - name: Stop TGT
        if: always()
        run: docker compose --profile tgt down -v

  test-lio:
    name: Integration tests (LIO/${{ matrix.mode }})
    runs-on: ubuntu-latest
    timeout-minutes: 45
    strategy:
      fail-fast: false
      matrix:
        include:
          - mode: plain
            config: tests/configs/lio/plain.yaml
            host_port: 3261
          - mode: chap
            config: tests/configs/lio/chap.yaml
            host_port: 3262
          - mode: crc
            config: tests/configs/lio/crc.yaml
            host_port: 3263
    steps:
      - uses: actions/checkout@v7

      - name: Install Rust toolchain
        uses: actions-rs/toolchain@v1
        with:
          toolchain: stable

      - *cache-cargo
      - *fetch-cargo

      - name: Install QEMU and cloud-init tools
        run: |
          sudo apt-get update
          sudo apt-get install -y qemu-system-x86 qemu-utils cloud-image-utils netcat-openbsd
          test -e /dev/kvm
          sudo chmod 666 /dev/kvm

      - name: Cache Ubuntu cloud image
        id: cache-cloud-image
        uses: actions/cache@v5
        with:
          path: ${{ runner.temp }}/ubuntu-24.04-server-cloudimg-amd64.img
          key: ubuntu-24.04-server-cloudimg-amd64-20260627

      - name: Download Ubuntu cloud image
        if: steps.cache-cloud-image.outputs.cache-hit != 'true'
        run: |
          curl --fail --location --retry 3 \
            https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img \
            --output "${RUNNER_TEMP}/ubuntu-24.04-server-cloudimg-amd64.img"

      - name: Start LIO VM
        timeout-minutes: 10
        env:
          LIO_QEMU_ACCEL: kvm
        run: |
          bash ci/lio/start-qemu.sh \
            "${RUNNER_TEMP}/ubuntu-24.04-server-cloudimg-amd64.img" \
            "${{ matrix.mode }}" \
            "${{ matrix.host_port }}"

      - name: Run integration tests
        env:
          TEST_CONFIG: ${{ matrix.config }}
        run: cargo test --offline --test integration -- --nocapture --test-threads=1

      - name: Stop LIO VM
        if: always()
        run: bash ci/lio/stop-qemu.sh

  publish:
    name: Publish to crates.io
    runs-on: ubuntu-latest
    needs: [lint, test, test-tgt, test-lio]
    if: startsWith(github.ref, 'refs/tags/')
    steps:
      - uses: actions/checkout@v7

      - name: Install Rust toolchain
        uses: actions-rs/toolchain@v1
        with:
          toolchain: stable

      - name: Verify version matches tag
        run: |
          TAG=${GITHUB_REF#refs/tags/}
          VERSION=$(grep '^version =' Cargo.toml | head -1 | sed 's/version = "\([^"]*\)"/\1/')
          if [ "$TAG" != "$VERSION" ]; then
            echo "Tag ($TAG) does not match version in Cargo.toml ($VERSION)"
            exit 1
          fi

      - name: Publish to crates.io
        env:
          CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
        run: cargo publish --no-verify

  release_notes:
    name: Generate release notes
    runs-on: ubuntu-latest
    permissions:
      contents: write
    needs: [publish]
    if: startsWith(github.ref, 'refs/tags/')
    steps:
      - uses: actions/checkout@v7
        with:
          fetch-depth: 0

      - name: Install git-cliff
        run: cargo install git-cliff --locked

      - name: Generate RELEASE_NOTES.md
        run: |
          TAG=${GITHUB_REF#refs/tags/}
          git-cliff --tag "$TAG" -o RELEASE_NOTES.md

      - name: Create GitHub Release
        uses: softprops/action-gh-release@v3
        with:
          tag_name: ${{ github.ref_name }}
          body_path: RELEASE_NOTES.md
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}