kubernix 0.3.2

Kubernetes development cluster bootstrapping with Nix packages
Documentation
name: ci
on:
  pull_request: {}
  push:
    tags:
      - "v*"
    branches:
      - main

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

env:
  CARGO_TERM_COLOR: always

jobs:
  build:
    name: build / ${{ matrix.profile }}
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        include:
          - profile: debug
            make_target: build
            cache_suffix: ""
          - profile: release
            make_target: build-release
            cache_suffix: "-release"
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      - uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
      - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
        with:
          path: |
            ~/.cargo/registry
            ~/.cargo/git
            target
          key: ${{ runner.os }}-cargo${{ matrix.cache_suffix }}-${{ hashFiles('**/Cargo.lock') }}
      - run: make ${{ matrix.make_target }}

  build-cross:
    name: build / cross / ${{ matrix.arch }}
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        include:
          - arch: x86_64
            target: x86_64-unknown-linux-gnu
            strip: strip
          - arch: aarch64
            target: aarch64-unknown-linux-gnu
            strip: aarch64-linux-gnu-strip
          - arch: armv7
            target: armv7-unknown-linux-gnueabihf
            strip: arm-linux-gnueabihf-strip
          - arch: ppc64le
            target: powerpc64le-unknown-linux-gnu
            strip: powerpc64le-linux-gnu-strip
          - arch: s390x
            target: s390x-unknown-linux-gnu
            strip: s390x-linux-gnu-strip
          - arch: riscv64
            target: riscv64gc-unknown-linux-gnu
            strip: riscv64-linux-gnu-strip
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      - uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
      - uses: taiki-e/install-action@65851e10cd6c377f11a60e600abc07cb08643468 # v2.79.3
        with:
          tool: cross
      - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
        with:
          path: |
            ~/.cargo/registry
            ~/.cargo/git
            target
          key: ${{ runner.os }}-cargo-${{ matrix.target }}-${{ hashFiles('**/Cargo.lock') }}
      - run: make build-cross CROSS_TARGET=${{ matrix.target }}
      - name: Strip binary
        run: |
          docker run --rm \
            -v $PWD/target/${{ matrix.target }}/release:/target \
            ghcr.io/cross-rs/${{ matrix.target }}:main \
            ${{ matrix.strip }} -s /target/kubernix
      - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: kubernix-${{ matrix.arch }}
          path: target/${{ matrix.target }}/release/kubernix

  deploy:
    name: deploy / release
    runs-on: ubuntu-latest
    needs: build-cross
    if: startsWith(github.ref, 'refs/tags/v')
    permissions:
      contents: write
    steps:
      - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          path: artifacts
      - name: Prepare release binaries
        run: |
          mkdir -p release
          for dir in artifacts/kubernix-*; do
            target=$(basename "$dir" | sed 's/^kubernix-//')
            cp "$dir/kubernix" "release/kubernix-$target"
          done
          ls -la release/
      - name: Upload release binaries
        uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
        with:
          files: release/*

  docs:
    name: docs / build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      - uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
      - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
        with:
          path: |
            ~/.cargo/registry
            ~/.cargo/git
            target
          key: ${{ runner.os }}-cargo-docs-${{ hashFiles('**/Cargo.lock') }}
      - run: make docs
      - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: docs
          path: target/doc

  docs-publish:
    name: docs / publish
    runs-on: ubuntu-latest
    needs: docs
    if: github.ref == 'refs/heads/main'
    permissions:
      contents: write
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          name: docs
          path: doc
      - uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453 # v4.1.0
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: ./doc

  lint-clippy:
    name: lint / clippy
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      - uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
        with:
          components: clippy
      - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
        with:
          path: |
            ~/.cargo/registry
            ~/.cargo/git
            target
          key: ${{ runner.os }}-cargo-clippy-${{ hashFiles('**/Cargo.lock') }}
      - run: make lint-clippy

  lint-rustfmt:
    name: lint / rustfmt
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      - uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
        with:
          components: rustfmt
      - run: make lint-rustfmt

  lint-audit:
    name: lint / audit
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      - uses: taiki-e/install-action@65851e10cd6c377f11a60e600abc07cb08643468 # v2.79.3
        with:
          tool: cargo-audit
      - run: make lint-audit

  lint-deny:
    name: lint / deny
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      - uses: EmbarkStudios/cargo-deny-action@6c8f9facfa5047ec02d8485b6bf52b587b7777d1 # v2.0.18

  lint-dependencies:
    name: lint / dependencies
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      - run: make lint-dependencies

  test-unit:
    name: test / unit
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      - uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
      - uses: taiki-e/install-action@65851e10cd6c377f11a60e600abc07cb08643468 # v2.79.3
        with:
          tool: cargo-llvm-cov
      - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
        with:
          path: |
            ~/.cargo/registry
            ~/.cargo/git
            target
          key: ${{ runner.os }}-cargo-test-${{ hashFiles('**/Cargo.lock') }}
      - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
        with:
          path: |
            ~/go/bin
            ~/go/pkg
            ~/bin
          key: ${{ runner.os }}-test-tools-cfssl-1.6.5
      - name: Install test dependencies
        run: |
          go install github.com/cloudflare/cfssl/cmd/cfssl@v1.6.5
          go install github.com/cloudflare/cfssl/cmd/cfssljson@v1.6.5
          echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
      - name: Generate code coverage
        run: cargo llvm-cov --all-features --lib --lcov --output-path lcov.info
      - name: Upload Results
        uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
        with:
          files: lcov.info
          token: ${{ secrets.CODECOV_TOKEN }}

  test-integration:
    name: test / integration / ${{ matrix.name }}
    runs-on: ubuntu-latest
    needs: build-cross
    strategy:
      fail-fast: false
      matrix:
        include:
          - name: single
            nodes: 1
          - name: multi
            nodes: 2
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      - uses: cachix/install-nix-action@8aa03977d8d733052d78f4e008a241fd1dbf36b3 # v31.10.6
        with:
          extra_nix_config: |
            experimental-features = nix-command flakes
      - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          name: kubernix-x86_64
      - run: chmod +x kubernix
      - name: Set hostname
        run: |
          echo "127.0.0.1 test" | sudo tee -a /etc/hosts
          sudo hostnamectl set-hostname test
      - name: Prepare the system
        run: sudo contrib/prepare-system
      - name: Run ${{ matrix.name }} integration test
        run: |
          sudo -E env "PATH=$PATH" ./kubernix --log-level=debug --no-shell --addons --nodes=${{ matrix.nodes }} &
          KUBERNIX_PID=$!
          timeout 300 bash -c 'while [ ! -f kubernix-run/kubernix.pid ]; do sleep 1; done'
          echo "Cluster is up, running checks"
          export KUBECONFIG=$PWD/kubernix-run/kubeconfig/admin.kubeconfig

          echo "Asserting all nodes are Ready..."
          READY_COUNT=$(kubectl get nodes -o go-template='{{range .items}}{{range .status.conditions}}{{if eq .type "Ready"}}{{if eq .status "True"}}x{{end}}{{end}}{{end}}{{end}}' | wc -c)
          if [ "$READY_COUNT" -ne "${{ matrix.nodes }}" ]; then
            echo "FAIL: expected ${{ matrix.nodes }} ready nodes, got $READY_COUNT"
            exit 1
          fi

          echo "Asserting all pods are healthy..."
          timeout 60 bash -c 'until [ "$(kubectl get pods -A -o go-template="{{range .items}}{{if and (ne .status.phase \"Running\") (ne .status.phase \"Succeeded\")}}x{{end}}{{end}}" | wc -c)" -eq 0 ]; do sleep 2; done'
          UNHEALTHY=$(kubectl get pods -A -o go-template='{{range .items}}{{if and (ne .status.phase "Running") (ne .status.phase "Succeeded")}}{{.metadata.name}}({{.status.phase}}) {{end}}{{end}}')
          if [ -n "$UNHEALTHY" ]; then
            echo "FAIL: unhealthy pods: $UNHEALTHY"
            exit 1
          fi

          echo "All assertions passed"
          sudo kill $KUBERNIX_PID
          wait $KUBERNIX_PID || true
        timeout-minutes: 10