kubernix 0.3.1

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@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
        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@7627fb428e65e78e2ec9a24ae5c5bd5f8553f182 # v2.69.10
        with:
          tool: cross
      - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
        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@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
        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@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1
        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@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
        with:
          path: |
            ~/.cargo/registry
            ~/.cargo/git
            target
          key: ${{ runner.os }}-cargo-docs-${{ hashFiles('**/Cargo.lock') }}
      - run: make docs
      - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
        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@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4.0.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@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
        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@7627fb428e65e78e2ec9a24ae5c5bd5f8553f182 # v2.69.10
        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@3fd3802e88374d3fe9159b834c7714ec57d6c979 # v2.0.15

  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@7627fb428e65e78e2ec9a24ae5c5bd5f8553f182 # v2.69.10
        with:
          tool: cargo-llvm-cov
      - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
        with:
          path: |
            ~/.cargo/registry
            ~/.cargo/git
            target
          key: ${{ runner.os }}-cargo-test-${{ hashFiles('**/Cargo.lock') }}
      - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
        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@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5.5.3
        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@51f3067b56fe8ae331890c77d4e454f6d60615ff # v31
        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