From ac4cb293e0b9a248a50d7aeb9a0c9260163b4c76 Mon Sep 17 00:00:00 2001 From: CodeMaster4711 Date: Wed, 17 Jun 2026 20:00:02 +0200 Subject: [PATCH 1/4] ci: consolidate release pipeline into single workflow with reusable modules --- .github/release/build-binaries.yml | 70 +++ .github/release/build-cp.yml | 75 +++ .github/release/build-frontend.yml | 41 ++ .github/release/publish.yml | 69 +++ .../{workflows => release}/release-please.yml | 22 +- .github/release/update-infra.yml | 134 ++++++ .github/workflows/delete-merged-branch.yml | 24 - .github/workflows/docker-build.yml | 429 ------------------ .github/workflows/prerelease.yml | 420 ----------------- .github/workflows/release.yml | 80 ++++ 10 files changed, 484 insertions(+), 880 deletions(-) create mode 100644 .github/release/build-binaries.yml create mode 100644 .github/release/build-cp.yml create mode 100644 .github/release/build-frontend.yml create mode 100644 .github/release/publish.yml rename .github/{workflows => release}/release-please.yml (53%) create mode 100644 .github/release/update-infra.yml delete mode 100644 .github/workflows/delete-merged-branch.yml delete mode 100644 .github/workflows/docker-build.yml delete mode 100644 .github/workflows/prerelease.yml create mode 100644 .github/workflows/release.yml diff --git a/.github/release/build-binaries.yml b/.github/release/build-binaries.yml new file mode 100644 index 00000000..6bd9448b --- /dev/null +++ b/.github/release/build-binaries.yml @@ -0,0 +1,70 @@ +name: Build Binaries + +on: + workflow_call: + inputs: + version: + required: true + type: string + +jobs: + build-binaries: + name: Build ${{ matrix.binary }} (${{ matrix.arch }}) + runs-on: ${{ matrix.runner }} + strategy: + fail-fast: false + matrix: + binary: + - csfx-updater + - csfx-agent + arch: + - amd64 + - arm64 + include: + - arch: amd64 + runner: ubuntu-latest + target: x86_64-unknown-linux-musl + - arch: arm64 + runner: ubuntu-24.04-arm + target: aarch64-unknown-linux-musl + steps: + - uses: actions/checkout@v4 + + - name: Install build dependencies + run: | + sudo apt-get update + if [ "${{ matrix.arch }}" = "amd64" ]; then + sudo apt-get install -y musl-tools protobuf-compiler + else + sudo apt-get install -y gcc-aarch64-linux-gnu musl-tools protobuf-compiler + fi + + - uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.target }} + + - uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ matrix.binary }}-${{ matrix.arch }}-${{ hashFiles('**/Cargo.lock') }} + + - name: Build + env: + CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_LINKER: musl-gcc + CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER: aarch64-linux-gnu-gcc + CSFX_BUILD_VERSION: ${{ inputs.version }} + run: | + cargo build --release --bin ${{ matrix.binary }} --target ${{ matrix.target }} + cp target/${{ matrix.target }}/release/${{ matrix.binary }} ${{ matrix.binary }}-${{ matrix.arch }} + sha256sum ${{ matrix.binary }}-${{ matrix.arch }} > ${{ matrix.binary }}-${{ matrix.arch }}.sha256 + + - uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.binary }}-${{ matrix.arch }} + path: | + ${{ matrix.binary }}-${{ matrix.arch }} + ${{ matrix.binary }}-${{ matrix.arch }}.sha256 + retention-days: 7 diff --git a/.github/release/build-cp.yml b/.github/release/build-cp.yml new file mode 100644 index 00000000..23a743f2 --- /dev/null +++ b/.github/release/build-cp.yml @@ -0,0 +1,75 @@ +name: Build Control Plane + +on: + workflow_call: + inputs: + version: + required: true + type: string + +jobs: + build-cp-binaries: + name: Build CP ${{ matrix.binary }} (${{ matrix.arch }}) + runs-on: ${{ matrix.runner }} + strategy: + fail-fast: false + matrix: + binary: + - api-gateway + - registry + - scheduler + - volume-manager + - failover-controller + - sdn-controller + - csfx-migrate + arch: + - amd64 + - arm64 + include: + - arch: amd64 + runner: ubuntu-latest + target: x86_64-unknown-linux-musl + - arch: arm64 + runner: ubuntu-24.04-arm + target: aarch64-unknown-linux-musl + steps: + - uses: actions/checkout@v4 + + - name: Install build dependencies + run: | + sudo apt-get update + if [ "${{ matrix.arch }}" = "amd64" ]; then + sudo apt-get install -y musl-tools protobuf-compiler libpq-dev + else + sudo apt-get install -y gcc-aarch64-linux-gnu musl-tools protobuf-compiler libpq-dev + fi + + - uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.target }} + + - uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: cp-${{ matrix.binary }}-${{ matrix.arch }}-${{ hashFiles('**/Cargo.lock') }} + + - name: Build + env: + CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_LINKER: musl-gcc + CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER: aarch64-linux-gnu-gcc + CSFX_BUILD_VERSION: ${{ inputs.version }} + run: | + cargo build --release --bin ${{ matrix.binary }} --target ${{ matrix.target }} + cp target/${{ matrix.target }}/release/${{ matrix.binary }} csfx-cp-${{ matrix.binary }}-${{ matrix.arch }} + sha256sum csfx-cp-${{ matrix.binary }}-${{ matrix.arch }} > csfx-cp-${{ matrix.binary }}-${{ matrix.arch }}.sha256 + + - uses: actions/upload-artifact@v4 + with: + name: csfx-cp-${{ matrix.binary }}-${{ matrix.arch }} + path: | + csfx-cp-${{ matrix.binary }}-${{ matrix.arch }} + csfx-cp-${{ matrix.binary }}-${{ matrix.arch }}.sha256 + retention-days: 7 diff --git a/.github/release/build-frontend.yml b/.github/release/build-frontend.yml new file mode 100644 index 00000000..00164553 --- /dev/null +++ b/.github/release/build-frontend.yml @@ -0,0 +1,41 @@ +name: Build Frontend + +on: + workflow_call: + +jobs: + build-frontend: + name: Build frontend + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: "20" + cache: "npm" + cache-dependency-path: app/package-lock.json + + - name: Install dependencies + working-directory: app + run: npm ci + + - name: Build + working-directory: app + env: + VITE_API_URL: "" + run: npm run build + + - name: Archive + run: tar -czf csfx-frontend.tar.gz -C app/build . + + - name: Checksum + run: sha256sum csfx-frontend.tar.gz > csfx-frontend.tar.gz.sha256 + + - uses: actions/upload-artifact@v4 + with: + name: csfx-frontend + path: | + csfx-frontend.tar.gz + csfx-frontend.tar.gz.sha256 + retention-days: 7 diff --git a/.github/release/publish.yml b/.github/release/publish.yml new file mode 100644 index 00000000..0d893c61 --- /dev/null +++ b/.github/release/publish.yml @@ -0,0 +1,69 @@ +name: Publish Release + +on: + workflow_call: + inputs: + version: + required: true + type: string + prerelease: + required: true + type: boolean + +permissions: + contents: write + +jobs: + publish: + name: Publish ${{ inputs.prerelease && 'pre-release' || 'release' }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/download-artifact@v4 + with: + pattern: csfx-* + merge-multiple: true + + - name: Build CP bins list + id: cp-bins + run: | + BINS="" + for svc in api-gateway registry scheduler volume-manager failover-controller sdn-controller csfx-migrate; do + for arch in amd64 arm64; do + BINS="${BINS} csfx-cp-${svc}-${arch} csfx-cp-${svc}-${arch}.sha256" + done + done + echo "bins=${BINS}" >> $GITHUB_OUTPUT + + - name: Create pre-release + if: ${{ inputs.prerelease }} + run: | + VERSION="${{ inputs.version }}" + gh release create "v${VERSION}" \ + --title "v${VERSION}" \ + --prerelease \ + --generate-notes \ + csfx-updater-amd64 csfx-updater-amd64.sha256 \ + csfx-updater-arm64 csfx-updater-arm64.sha256 \ + csfx-agent-amd64 csfx-agent-amd64.sha256 \ + csfx-agent-arm64 csfx-agent-arm64.sha256 \ + csfx-frontend.tar.gz csfx-frontend.tar.gz.sha256 \ + ${{ steps.cp-bins.outputs.bins }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Attach to full release + if: ${{ !inputs.prerelease }} + run: | + VERSION="${{ inputs.version }}" + gh release upload "v${VERSION}" \ + csfx-updater-amd64 csfx-updater-amd64.sha256 \ + csfx-updater-arm64 csfx-updater-arm64.sha256 \ + csfx-agent-amd64 csfx-agent-amd64.sha256 \ + csfx-agent-arm64 csfx-agent-arm64.sha256 \ + csfx-frontend.tar.gz csfx-frontend.tar.gz.sha256 \ + ${{ steps.cp-bins.outputs.bins }} \ + --clobber + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release-please.yml b/.github/release/release-please.yml similarity index 53% rename from .github/workflows/release-please.yml rename to .github/release/release-please.yml index 7b2244ab..d0768472 100644 --- a/.github/workflows/release-please.yml +++ b/.github/release/release-please.yml @@ -1,10 +1,18 @@ name: Release Please on: - push: - branches: - - main - workflow_dispatch: + workflow_call: + inputs: + token: + required: true + type: string + outputs: + release_created: + value: ${{ jobs.release-please.outputs.release_created }} + version: + value: ${{ jobs.release-please.outputs.version }} + tag_name: + value: ${{ jobs.release-please.outputs.tag_name }} permissions: contents: write @@ -16,12 +24,12 @@ jobs: runs-on: ubuntu-latest outputs: release_created: ${{ steps.release.outputs.release_created }} - tag_name: ${{ steps.release.outputs.tag_name }} version: ${{ steps.release.outputs.version }} + tag_name: ${{ steps.release.outputs.tag_name }} steps: - - uses: googleapis/release-please-action@v5 + - uses: googleapis/release-please-action@v4 id: release with: config-file: release-please-config.json manifest-file: .release-please-manifest.json - token: ${{ secrets.RELEASE_PLEASE_TOKEN || secrets.GITHUB_TOKEN }} + token: ${{ inputs.token }} diff --git a/.github/release/update-infra.yml b/.github/release/update-infra.yml new file mode 100644 index 00000000..894620e8 --- /dev/null +++ b/.github/release/update-infra.yml @@ -0,0 +1,134 @@ +name: Update Infra + +on: + workflow_call: + inputs: + version: + required: true + type: string + infra_ref: + required: true + type: string + secrets: + infra_token: + required: true + +jobs: + update-infra: + name: Update CSFX-Infra versions.nix + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + repository: ${{ github.repository_owner }}/CSFX-Infra + token: ${{ secrets.infra_token }} + ref: ${{ inputs.infra_ref }} + path: infra + + - uses: actions/download-artifact@v4 + with: + pattern: csfx-agent-* + path: /tmp/binaries + merge-multiple: true + + - uses: actions/download-artifact@v4 + with: + pattern: csfx-updater-* + path: /tmp/binaries + merge-multiple: true + + - uses: actions/download-artifact@v4 + with: + pattern: csfx-cp-* + path: /tmp/cp-binaries + merge-multiple: true + + - uses: actions/download-artifact@v4 + with: + name: csfx-frontend + path: /tmp/frontend + + - name: Write versions.nix + run: | + VERSION="${{ inputs.version }}" + REPO="${{ github.repository }}" + RELEASE_BASE="https://github.com/${REPO}/releases/download/v${VERSION}" + + get_sha256() { + awk '{print $1}' "/tmp/binaries/${1}.sha256" 2>/dev/null + } + + get_cp_sha256() { + awk '{print $1}' "/tmp/cp-binaries/${1}.sha256" 2>/dev/null + } + + FRONTEND_SHA256=$(awk '{print $1}' /tmp/frontend/csfx-frontend.tar.gz.sha256 2>/dev/null) + + cat > infra/versions.nix < - github.event_name == 'workflow_dispatch' || - (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success') - outputs: - version: ${{ steps.version.outputs.version }} - should_build: ${{ steps.version.outputs.should_build }} - steps: - - uses: actions/checkout@v6 - - - name: Resolve version - id: version - run: | - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - echo "version=${{ inputs.version }}" >> $GITHUB_OUTPUT - echo "should_build=true" >> $GITHUB_OUTPUT - else - TAG=$(gh release list --limit 1 --json tagName -q '.[0].tagName' 2>/dev/null || echo "") - if [ -z "$TAG" ]; then - echo "should_build=false" >> $GITHUB_OUTPUT - else - echo "version=${TAG#v}" >> $GITHUB_OUTPUT - echo "should_build=true" >> $GITHUB_OUTPUT - fi - fi - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - build-binaries: - name: Build ${{ matrix.binary }} (${{ matrix.arch }}) - runs-on: ${{ matrix.runner }} - needs: prepare - if: needs.prepare.outputs.should_build == 'true' - strategy: - fail-fast: false - matrix: - binary: - - csfx-updater - - csfx-agent - arch: - - amd64 - - arm64 - include: - - arch: amd64 - runner: ubuntu-latest - target: x86_64-unknown-linux-musl - - arch: arm64 - runner: ubuntu-24.04-arm - target: aarch64-unknown-linux-musl - steps: - - uses: actions/checkout@v6 - - - name: Install build dependencies - run: | - sudo apt-get update - if [ "${{ matrix.arch }}" = "amd64" ]; then - sudo apt-get install -y musl-tools protobuf-compiler - else - sudo apt-get install -y gcc-aarch64-linux-gnu musl-tools protobuf-compiler - fi - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@stable - with: - targets: ${{ matrix.target }} - - - name: Cache cargo - uses: actions/cache@v5 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - target - key: ${{ matrix.binary }}-${{ matrix.arch }}-${{ hashFiles('**/Cargo.lock') }} - - - name: Build - env: - CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_LINKER: musl-gcc - CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER: aarch64-linux-gnu-gcc - CSFX_BUILD_VERSION: ${{ needs.prepare.outputs.version }} - run: | - cargo build --release --bin ${{ matrix.binary }} --target ${{ matrix.target }} - cp target/${{ matrix.target }}/release/${{ matrix.binary }} ${{ matrix.binary }}-${{ matrix.arch }} - sha256sum ${{ matrix.binary }}-${{ matrix.arch }} > ${{ matrix.binary }}-${{ matrix.arch }}.sha256 - - - uses: actions/upload-artifact@v7 - with: - name: ${{ matrix.binary }}-${{ matrix.arch }} - path: | - ${{ matrix.binary }}-${{ matrix.arch }} - ${{ matrix.binary }}-${{ matrix.arch }}.sha256 - retention-days: 7 - - build-frontend: - name: Build frontend - runs-on: ubuntu-latest - needs: prepare - if: needs.prepare.outputs.should_build == 'true' - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-node@v4 - with: - node-version: "20" - cache: "npm" - cache-dependency-path: app/package-lock.json - - - name: Install dependencies - working-directory: app - run: npm ci - - - name: Build - working-directory: app - env: - VITE_API_URL: "" - run: npm run build - - - name: Archive - run: tar -czf csfx-frontend.tar.gz -C app/build . - - - name: Checksum - run: sha256sum csfx-frontend.tar.gz > csfx-frontend.tar.gz.sha256 - - - uses: actions/upload-artifact@v4 - with: - name: csfx-frontend - path: | - csfx-frontend.tar.gz - csfx-frontend.tar.gz.sha256 - retention-days: 7 - - build-cp-binaries: - name: Build CP ${{ matrix.binary }} (${{ matrix.arch }}) - runs-on: ${{ matrix.runner }} - needs: prepare - if: needs.prepare.outputs.should_build == 'true' - strategy: - fail-fast: false - matrix: - binary: - - api-gateway - - registry - - scheduler - - volume-manager - - failover-controller - - sdn-controller - - csfx-migrate - arch: - - amd64 - - arm64 - include: - - arch: amd64 - runner: ubuntu-latest - target: x86_64-unknown-linux-musl - - arch: arm64 - runner: ubuntu-24.04-arm - target: aarch64-unknown-linux-musl - steps: - - uses: actions/checkout@v6 - - - name: Install build dependencies - run: | - sudo apt-get update - if [ "${{ matrix.arch }}" = "amd64" ]; then - sudo apt-get install -y musl-tools protobuf-compiler libpq-dev - else - sudo apt-get install -y gcc-aarch64-linux-gnu musl-tools protobuf-compiler libpq-dev - fi - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@stable - with: - targets: ${{ matrix.target }} - - - name: Cache cargo - uses: actions/cache@v5 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - target - key: cp-${{ matrix.binary }}-${{ matrix.arch }}-${{ hashFiles('**/Cargo.lock') }} - - - name: Build - env: - CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_LINKER: musl-gcc - CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER: aarch64-linux-gnu-gcc - CSFX_BUILD_VERSION: ${{ needs.prepare.outputs.version }} - run: | - cargo build --release --bin ${{ matrix.binary }} --target ${{ matrix.target }} - cp target/${{ matrix.target }}/release/${{ matrix.binary }} csfx-cp-${{ matrix.binary }}-${{ matrix.arch }} - sha256sum csfx-cp-${{ matrix.binary }}-${{ matrix.arch }} > csfx-cp-${{ matrix.binary }}-${{ matrix.arch }}.sha256 - - - uses: actions/upload-artifact@v7 - with: - name: csfx-cp-${{ matrix.binary }}-${{ matrix.arch }} - path: | - csfx-cp-${{ matrix.binary }}-${{ matrix.arch }} - csfx-cp-${{ matrix.binary }}-${{ matrix.arch }}.sha256 - retention-days: 7 - - attach-binaries-release: - name: Attach binaries to release - runs-on: ubuntu-latest - needs: [prepare, build-binaries, build-cp-binaries, build-frontend] - steps: - - uses: actions/checkout@v6 - - - uses: actions/download-artifact@v8 - with: - pattern: csfx-* - merge-multiple: true - - - name: Upload to release - run: | - VERSION="${{ needs.prepare.outputs.version }}" - TAG="v${VERSION}" - - CP_BINS="" - for svc in api-gateway registry scheduler volume-manager failover-controller sdn-controller csfx-migrate; do - for arch in amd64 arm64; do - CP_BINS="${CP_BINS} csfx-cp-${svc}-${arch} csfx-cp-${svc}-${arch}.sha256" - done - done - - gh release upload "${TAG}" \ - csfx-updater-amd64 \ - csfx-updater-amd64.sha256 \ - csfx-updater-arm64 \ - csfx-updater-arm64.sha256 \ - csfx-agent-amd64 \ - csfx-agent-amd64.sha256 \ - csfx-agent-arm64 \ - csfx-agent-arm64.sha256 \ - csfx-frontend.tar.gz \ - csfx-frontend.tar.gz.sha256 \ - ${CP_BINS} \ - --clobber - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - update-infra: - name: Update CSFX-Infra versions.nix - runs-on: ubuntu-latest - needs: [prepare, build-binaries, build-cp-binaries, build-frontend, attach-binaries-release] - steps: - - uses: actions/checkout@v6 - with: - repository: ${{ github.repository_owner }}/CSFX-Infra - token: ${{ secrets.INFRA_REPO_TOKEN }} - path: infra - - - uses: actions/download-artifact@v8 - with: - pattern: csfx-agent-* - path: /tmp/binaries - merge-multiple: true - - - uses: actions/download-artifact@v8 - with: - pattern: csfx-updater-* - path: /tmp/binaries - merge-multiple: true - - - uses: actions/download-artifact@v8 - with: - pattern: csfx-cp-* - path: /tmp/cp-binaries - merge-multiple: true - - - uses: actions/download-artifact@v4 - with: - name: csfx-frontend - path: /tmp/frontend - - - name: Write versions.nix - run: | - VERSION="${{ needs.prepare.outputs.version }}" - REPO="${{ github.repository }}" - RELEASE_BASE="https://github.com/${REPO}/releases/download/v${VERSION}" - - get_sha256() { - local file=$1 - awk '{print $1}' "/tmp/binaries/${file}.sha256" 2>/dev/null - } - - get_cp_sha256() { - local file=$1 - awk '{print $1}' "/tmp/cp-binaries/${file}.sha256" 2>/dev/null - } - - FRONTEND_SHA256=$(awk '{print $1}' /tmp/frontend/csfx-frontend.tar.gz.sha256 2>/dev/null) - - cat > infra/versions.nix <> $GITHUB_OUTPUT - else - BASE=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)"/\1/') - COUNT=$(git rev-list --count HEAD) - echo "version=${BASE}-alpha.${COUNT}" >> $GITHUB_OUTPUT - fi - - build-binaries: - name: Build ${{ matrix.binary }} (${{ matrix.arch }}) - runs-on: ${{ matrix.runner }} - needs: version - strategy: - fail-fast: false - matrix: - binary: - - csfx-updater - - csfx-agent - arch: - - amd64 - - arm64 - include: - - arch: amd64 - runner: ubuntu-latest - target: x86_64-unknown-linux-musl - - arch: arm64 - runner: ubuntu-24.04-arm - target: aarch64-unknown-linux-musl - steps: - - uses: actions/checkout@v6 - - - name: Install build dependencies - run: | - sudo apt-get update - if [ "${{ matrix.arch }}" = "amd64" ]; then - sudo apt-get install -y musl-tools protobuf-compiler - else - sudo apt-get install -y gcc-aarch64-linux-gnu musl-tools protobuf-compiler - fi - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@stable - with: - targets: ${{ matrix.target }} - - - name: Cache cargo - uses: actions/cache@v5 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - target - key: ${{ matrix.binary }}-${{ matrix.arch }}-${{ hashFiles('**/Cargo.lock') }} - - - name: Build - env: - CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_LINKER: musl-gcc - CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER: aarch64-linux-gnu-gcc - CSFX_BUILD_VERSION: ${{ needs.version.outputs.version }} - run: | - cargo build --release --bin ${{ matrix.binary }} --target ${{ matrix.target }} - cp target/${{ matrix.target }}/release/${{ matrix.binary }} ${{ matrix.binary }}-${{ matrix.arch }} - sha256sum ${{ matrix.binary }}-${{ matrix.arch }} > ${{ matrix.binary }}-${{ matrix.arch }}.sha256 - - - uses: actions/upload-artifact@v7 - with: - name: ${{ matrix.binary }}-${{ matrix.arch }} - path: | - ${{ matrix.binary }}-${{ matrix.arch }} - ${{ matrix.binary }}-${{ matrix.arch }}.sha256 - retention-days: 7 - - build-frontend: - name: Build frontend - runs-on: ubuntu-latest - needs: version - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-node@v4 - with: - node-version: "20" - cache: "npm" - cache-dependency-path: app/package-lock.json - - - name: Install dependencies - working-directory: app - run: npm ci - - - name: Build - working-directory: app - env: - VITE_API_URL: "" - run: npm run build - - - name: Archive - run: tar -czf csfx-frontend.tar.gz -C app/build . - - - name: Checksum - run: sha256sum csfx-frontend.tar.gz > csfx-frontend.tar.gz.sha256 - - - uses: actions/upload-artifact@v4 - with: - name: csfx-frontend - path: | - csfx-frontend.tar.gz - csfx-frontend.tar.gz.sha256 - retention-days: 7 - - build-cp-binaries: - name: Build CP ${{ matrix.binary }} (${{ matrix.arch }}) - runs-on: ${{ matrix.runner }} - needs: version - strategy: - fail-fast: false - matrix: - binary: - - api-gateway - - registry - - scheduler - - volume-manager - - failover-controller - - sdn-controller - - csfx-migrate - arch: - - amd64 - - arm64 - include: - - arch: amd64 - runner: ubuntu-latest - target: x86_64-unknown-linux-musl - - arch: arm64 - runner: ubuntu-24.04-arm - target: aarch64-unknown-linux-musl - steps: - - uses: actions/checkout@v6 - - - name: Install build dependencies - run: | - sudo apt-get update - if [ "${{ matrix.arch }}" = "amd64" ]; then - sudo apt-get install -y musl-tools protobuf-compiler libpq-dev - else - sudo apt-get install -y gcc-aarch64-linux-gnu musl-tools protobuf-compiler libpq-dev - fi - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@stable - with: - targets: ${{ matrix.target }} - - - name: Cache cargo - uses: actions/cache@v5 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - target - key: cp-${{ matrix.binary }}-${{ matrix.arch }}-${{ hashFiles('**/Cargo.lock') }} - - - name: Build - env: - CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_LINKER: musl-gcc - CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER: aarch64-linux-gnu-gcc - CSFX_BUILD_VERSION: ${{ needs.version.outputs.version }} - run: | - cargo build --release --bin ${{ matrix.binary }} --target ${{ matrix.target }} - cp target/${{ matrix.target }}/release/${{ matrix.binary }} csfx-cp-${{ matrix.binary }}-${{ matrix.arch }} - sha256sum csfx-cp-${{ matrix.binary }}-${{ matrix.arch }} > csfx-cp-${{ matrix.binary }}-${{ matrix.arch }}.sha256 - - - uses: actions/upload-artifact@v7 - with: - name: csfx-cp-${{ matrix.binary }}-${{ matrix.arch }} - path: | - csfx-cp-${{ matrix.binary }}-${{ matrix.arch }} - csfx-cp-${{ matrix.binary }}-${{ matrix.arch }}.sha256 - retention-days: 7 - - github-release: - name: GitHub Pre-release - runs-on: ubuntu-latest - needs: [version, build-binaries, build-cp-binaries, build-frontend] - steps: - - uses: actions/checkout@v6 - - - uses: actions/download-artifact@v8 - with: - pattern: csfx-* - merge-multiple: true - - - name: Create pre-release - run: | - VERSION="${{ needs.version.outputs.version }}" - CP_BINS="" - for svc in api-gateway registry scheduler volume-manager failover-controller sdn-controller csfx-migrate; do - for arch in amd64 arm64; do - CP_BINS="${CP_BINS} csfx-cp-${svc}-${arch} csfx-cp-${svc}-${arch}.sha256" - done - done - - gh release create "v${VERSION}" \ - --title "v${VERSION}" \ - --prerelease \ - --generate-notes \ - csfx-updater-amd64 \ - csfx-updater-amd64.sha256 \ - csfx-updater-arm64 \ - csfx-updater-arm64.sha256 \ - csfx-agent-amd64 \ - csfx-agent-amd64.sha256 \ - csfx-agent-arm64 \ - csfx-agent-arm64.sha256 \ - csfx-frontend.tar.gz \ - csfx-frontend.tar.gz.sha256 \ - ${CP_BINS} - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - update-infra: - name: Update CSFX-Infra versions.nix - runs-on: ubuntu-latest - needs: [version, build-binaries, build-cp-binaries, build-frontend, github-release] - steps: - - uses: actions/checkout@v6 - with: - repository: ${{ github.repository_owner }}/CSFX-Infra - token: ${{ secrets.INFRA_REPO_TOKEN }} - path: infra - - - uses: actions/download-artifact@v8 - with: - pattern: csfx-agent-* - path: /tmp/binaries - merge-multiple: true - - - uses: actions/download-artifact@v8 - with: - pattern: csfx-updater-* - path: /tmp/binaries - merge-multiple: true - - - uses: actions/download-artifact@v8 - with: - pattern: csfx-cp-* - path: /tmp/cp-binaries - merge-multiple: true - - - uses: actions/download-artifact@v4 - with: - name: csfx-frontend - path: /tmp/frontend - - - name: Write versions.nix - run: | - VERSION="${{ needs.version.outputs.version }}" - REPO="${{ github.repository }}" - RELEASE_BASE="https://github.com/${REPO}/releases/download/v${VERSION}" - - get_sha256() { - local file=$1 - awk '{print $1}' "/tmp/binaries/${file}.sha256" 2>/dev/null - } - - get_cp_sha256() { - local file=$1 - awk '{print $1}' "/tmp/cp-binaries/${file}.sha256" 2>/dev/null - } - - FRONTEND_SHA256=$(awk '{print $1}' /tmp/frontend/csfx-frontend.tar.gz.sha256 2>/dev/null) - - cat > infra/versions.nix <> $GITHUB_OUTPUT + else + BASE=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)"/\1/') + COUNT=$(git rev-list --count HEAD) + echo "version=${BASE}-alpha.${COUNT}" >> $GITHUB_OUTPUT + fi + + build-binaries: + name: Build Binaries + needs: resolve-version + uses: ./.github/release/build-binaries.yml + with: + version: ${{ needs.resolve-version.outputs.version }} + + build-cp: + name: Build Control Plane + needs: resolve-version + uses: ./.github/release/build-cp.yml + with: + version: ${{ needs.resolve-version.outputs.version }} + + build-frontend: + name: Build Frontend + needs: resolve-version + uses: ./.github/release/build-frontend.yml + + publish: + name: Publish + needs: [release-please, resolve-version, build-binaries, build-cp, build-frontend] + uses: ./.github/release/publish.yml + with: + version: ${{ needs.resolve-version.outputs.version }} + prerelease: ${{ needs.release-please.outputs.release_created != 'true' }} + permissions: + contents: write + + update-infra: + name: Update Infra + needs: [release-please, resolve-version, build-binaries, build-cp, build-frontend, publish] + uses: ./.github/release/update-infra.yml + with: + version: ${{ needs.resolve-version.outputs.version }} + infra_ref: main + secrets: + infra_token: ${{ secrets.INFRA_REPO_TOKEN }} From 09ea2de3779134e7238e2dec35d3ace82c43168d Mon Sep 17 00:00:00 2001 From: CodeMaster4711 Date: Wed, 17 Jun 2026 20:02:41 +0200 Subject: [PATCH 2/4] ci: workflow exprassion fix --- .github/release/release-please.yml | 5 ++--- .github/workflows/release.yml | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/release/release-please.yml b/.github/release/release-please.yml index d0768472..fcd9a2b8 100644 --- a/.github/release/release-please.yml +++ b/.github/release/release-please.yml @@ -2,10 +2,9 @@ name: Release Please on: workflow_call: - inputs: + secrets: token: required: true - type: string outputs: release_created: value: ${{ jobs.release-please.outputs.release_created }} @@ -32,4 +31,4 @@ jobs: with: config-file: release-please-config.json manifest-file: .release-please-manifest.json - token: ${{ inputs.token }} + token: ${{ secrets.token }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9a897db2..e3c508f6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,7 +15,7 @@ jobs: release-please: name: Release Please uses: ./.github/release/release-please.yml - with: + secrets: token: ${{ secrets.RELEASE_PLEASE_TOKEN || github.token }} resolve-version: From 0167ad5abfe96250a2e0c8bd1b2a3c9972243321 Mon Sep 17 00:00:00 2001 From: CodeMaster4711 Date: Wed, 17 Jun 2026 20:04:33 +0200 Subject: [PATCH 3/4] ci: move reusable workflows to .github/workflows/release/ --- .github/workflows/release.yml | 12 ++++++------ .github/{ => workflows}/release/build-binaries.yml | 0 .github/{ => workflows}/release/build-cp.yml | 0 .github/{ => workflows}/release/build-frontend.yml | 0 .github/{ => workflows}/release/publish.yml | 0 .github/{ => workflows}/release/release-please.yml | 0 .github/{ => workflows}/release/update-infra.yml | 0 7 files changed, 6 insertions(+), 6 deletions(-) rename .github/{ => workflows}/release/build-binaries.yml (100%) rename .github/{ => workflows}/release/build-cp.yml (100%) rename .github/{ => workflows}/release/build-frontend.yml (100%) rename .github/{ => workflows}/release/publish.yml (100%) rename .github/{ => workflows}/release/release-please.yml (100%) rename .github/{ => workflows}/release/update-infra.yml (100%) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e3c508f6..da70734d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,7 +14,7 @@ permissions: jobs: release-please: name: Release Please - uses: ./.github/release/release-please.yml + uses: ./.github/workflows/release/release-please.yml secrets: token: ${{ secrets.RELEASE_PLEASE_TOKEN || github.token }} @@ -43,26 +43,26 @@ jobs: build-binaries: name: Build Binaries needs: resolve-version - uses: ./.github/release/build-binaries.yml + uses: ./.github/workflows/release/build-binaries.yml with: version: ${{ needs.resolve-version.outputs.version }} build-cp: name: Build Control Plane needs: resolve-version - uses: ./.github/release/build-cp.yml + uses: ./.github/workflows/release/build-cp.yml with: version: ${{ needs.resolve-version.outputs.version }} build-frontend: name: Build Frontend needs: resolve-version - uses: ./.github/release/build-frontend.yml + uses: ./.github/workflows/release/build-frontend.yml publish: name: Publish needs: [release-please, resolve-version, build-binaries, build-cp, build-frontend] - uses: ./.github/release/publish.yml + uses: ./.github/workflows/release/publish.yml with: version: ${{ needs.resolve-version.outputs.version }} prerelease: ${{ needs.release-please.outputs.release_created != 'true' }} @@ -72,7 +72,7 @@ jobs: update-infra: name: Update Infra needs: [release-please, resolve-version, build-binaries, build-cp, build-frontend, publish] - uses: ./.github/release/update-infra.yml + uses: ./.github/workflows/release/update-infra.yml with: version: ${{ needs.resolve-version.outputs.version }} infra_ref: main diff --git a/.github/release/build-binaries.yml b/.github/workflows/release/build-binaries.yml similarity index 100% rename from .github/release/build-binaries.yml rename to .github/workflows/release/build-binaries.yml diff --git a/.github/release/build-cp.yml b/.github/workflows/release/build-cp.yml similarity index 100% rename from .github/release/build-cp.yml rename to .github/workflows/release/build-cp.yml diff --git a/.github/release/build-frontend.yml b/.github/workflows/release/build-frontend.yml similarity index 100% rename from .github/release/build-frontend.yml rename to .github/workflows/release/build-frontend.yml diff --git a/.github/release/publish.yml b/.github/workflows/release/publish.yml similarity index 100% rename from .github/release/publish.yml rename to .github/workflows/release/publish.yml diff --git a/.github/release/release-please.yml b/.github/workflows/release/release-please.yml similarity index 100% rename from .github/release/release-please.yml rename to .github/workflows/release/release-please.yml diff --git a/.github/release/update-infra.yml b/.github/workflows/release/update-infra.yml similarity index 100% rename from .github/release/update-infra.yml rename to .github/workflows/release/update-infra.yml From 0bcaf7a12da849de3dc8d6fb2cda743311dab8b7 Mon Sep 17 00:00:00 2001 From: CodeMaster4711 Date: Wed, 17 Jun 2026 20:25:42 +0200 Subject: [PATCH 4/4] fix: cargo fmt run --- agent/src/client.rs | 51 ++++--- agent/src/config.rs | 7 +- agent/src/docker.rs | 13 +- agent/src/main.rs | 43 +++--- agent/src/pki.rs | 13 +- agent/src/rbd.rs | 2 +- agent/src/ssh_keys.rs | 5 +- agent/src/system.rs | 19 ++- control-plane/api-gateway/src/auth/crypto.rs | 16 ++- control-plane/api-gateway/src/auth/rbac.rs | 10 +- control-plane/api-gateway/src/auth_service.rs | 2 +- control-plane/api-gateway/src/init.rs | 124 ++++++++++++++---- control-plane/api-gateway/src/main.rs | 7 +- control-plane/api-gateway/src/metrics.rs | 9 +- .../api-gateway/src/routes/events.rs | 13 +- control-plane/api-gateway/src/routes/mod.rs | 3 +- .../api-gateway/src/routes/networks.rs | 91 +++++++++++-- .../api-gateway/src/routes/registry.rs | 56 ++++++-- .../api-gateway/src/routes/ssh_keys.rs | 10 +- .../api-gateway/src/routes/system.rs | 23 +++- .../api-gateway/src/routes/update.rs | 23 ++-- .../api-gateway/src/routes/volumes.rs | 81 ++++++++++-- .../api-gateway/src/routes/workloads.rs | 23 +++- .../api-gateway/src/service_client.rs | 6 +- control-plane/api-gateway/src/telemetry.rs | 4 +- control-plane/api-gateway/src/tls.rs | 8 +- control-plane/csfx-updater/src/config.rs | 6 +- control-plane/csfx-updater/src/git_mirror.rs | 3 +- control-plane/csfx-updater/src/health.rs | 6 +- control-plane/csfx-updater/src/main.rs | 9 +- control-plane/csfx-updater/src/nix_build.rs | 7 +- control-plane/csfx-updater/src/poller.rs | 5 +- control-plane/csfx-updater/src/rollback.rs | 5 +- .../failover-controller/src/db/agents.rs | 10 +- .../failover-controller/src/db/events.rs | 4 +- .../failover-controller/src/logger.rs | 24 ++-- .../failover-controller/src/metrics.rs | 9 +- .../src/services/failover.rs | 5 +- .../src/services/monitor.rs | 15 ++- control-plane/registry/src/db/agents.rs | 27 ++-- control-plane/registry/src/db/api_keys.rs | 4 +- .../registry/src/db/bootstrap_tokens.rs | 8 +- control-plane/registry/src/db/certificates.rs | 4 +- control-plane/registry/src/db/tokens.rs | 4 +- control-plane/registry/src/handlers/admin.rs | 30 +++-- control-plane/registry/src/handlers/agent.rs | 14 +- control-plane/registry/src/handlers/pki.rs | 13 +- control-plane/registry/src/logger.rs | 24 ++-- control-plane/registry/src/metrics.rs | 9 +- control-plane/registry/src/server.rs | 22 ++-- .../registry/src/services/registry.rs | 25 ++-- control-plane/registry/src/services/tokens.rs | 5 +- control-plane/scheduler/src/db/agents.rs | 2 - control-plane/scheduler/src/db/workloads.rs | 9 +- .../scheduler/src/handlers/internal.rs | 7 +- control-plane/scheduler/src/logger.rs | 24 ++-- control-plane/scheduler/src/metrics.rs | 9 +- control-plane/scheduler/src/server.rs | 21 ++- control-plane/scheduler/src/services/etcd.rs | 7 +- .../scheduler/src/services/scheduler.rs | 14 +- .../sdn-controller/src/db/networks.rs | 8 +- .../sdn-controller/src/handlers/networks.rs | 74 +++++++++-- control-plane/sdn-controller/src/logger.rs | 24 ++-- control-plane/sdn-controller/src/metrics.rs | 9 +- control-plane/sdn-controller/src/server.rs | 11 +- .../sdn-controller/src/services/ipam.rs | 13 +- .../shared/entity/src/entities/mod.rs | 24 ++-- .../src/m20260304_000000_pki_certificates.rs | 6 +- .../m20260309_000000_add_bootstrap_tokens.rs | 58 ++++++-- .../src/m20260523_000000_add_ssh_keys.rs | 13 +- .../volume-manager/src/ceph/storage/pool.rs | 32 +++-- .../volume-manager/src/db/volumes.rs | 9 +- .../volume-manager/src/etcd/ha/health.rs | 14 +- .../volume-manager/src/etcd/state/manager.rs | 30 ++++- .../volume-manager/src/handlers/volumes.rs | 5 +- control-plane/volume-manager/src/logger.rs | 24 ++-- control-plane/volume-manager/src/metrics.rs | 9 +- control-plane/volume-manager/src/server.rs | 20 ++- .../volume-manager/src/services/volume.rs | 25 +++- 79 files changed, 1025 insertions(+), 435 deletions(-) diff --git a/agent/src/client.rs b/agent/src/client.rs index bedce01c..4dbd5319 100644 --- a/agent/src/client.rs +++ b/agent/src/client.rs @@ -171,15 +171,31 @@ impl ApiClient { self.gateway_url, agent_id ); - let (cpu_usage_percent, cpu_cores, memory_total_bytes, memory_used_bytes, - disk_total_bytes, disk_used_bytes, network_rx_bytes, network_tx_bytes, - uptime_seconds) = metrics.map(|m| ( - Some(m.cpu_usage_percent), Some(m.cpu_cores), - Some(m.memory_total_bytes), Some(m.memory_used_bytes), - Some(m.disk_total_bytes), Some(m.disk_used_bytes), - Some(m.network_rx_bytes), Some(m.network_tx_bytes), - Some(m.uptime_seconds), - )).unwrap_or_default(); + let ( + cpu_usage_percent, + cpu_cores, + memory_total_bytes, + memory_used_bytes, + disk_total_bytes, + disk_used_bytes, + network_rx_bytes, + network_tx_bytes, + uptime_seconds, + ) = metrics + .map(|m| { + ( + Some(m.cpu_usage_percent), + Some(m.cpu_cores), + Some(m.memory_total_bytes), + Some(m.memory_used_bytes), + Some(m.disk_total_bytes), + Some(m.disk_used_bytes), + Some(m.network_rx_bytes), + Some(m.network_tx_bytes), + Some(m.uptime_seconds), + ) + }) + .unwrap_or_default(); let mut req = self .client @@ -216,10 +232,7 @@ impl ApiClient { .context("Failed to parse heartbeat response") } - pub async fn fetch_assigned_workloads( - &self, - api_key: &str, - ) -> Result> { + pub async fn fetch_assigned_workloads(&self, api_key: &str) -> Result> { let url = format!("{}/api/agents/self/workloads", self.gateway_url); let resp = self @@ -232,7 +245,11 @@ impl ApiClient { if !resp.status().is_success() { let status = resp.status(); - anyhow::bail!("Failed to fetch workloads status={} {}", status, resp.text().await.unwrap_or_default()); + anyhow::bail!( + "Failed to fetch workloads status={} {}", + status, + resp.text().await.unwrap_or_default() + ); } let all: Vec = resp @@ -289,7 +306,11 @@ impl ApiClient { if !resp.status().is_success() { let status = resp.status(); - anyhow::bail!("Failed to fetch volumes status={} {}", status, resp.text().await.unwrap_or_default()); + anyhow::bail!( + "Failed to fetch volumes status={} {}", + status, + resp.text().await.unwrap_or_default() + ); } let all: Vec = resp diff --git a/agent/src/config.rs b/agent/src/config.rs index 82c6d1fb..8db8bc8c 100644 --- a/agent/src/config.rs +++ b/agent/src/config.rs @@ -24,8 +24,7 @@ pub fn is_registered() -> bool { } pub fn load_config() -> Result { - let data = std::fs::read_to_string(CONFIG_FILE) - .context("Failed to read daemon config")?; + let data = std::fs::read_to_string(CONFIG_FILE).context("Failed to read daemon config")?; serde_json::from_str(&data).context("Failed to parse daemon config") } @@ -53,12 +52,10 @@ pub fn save_credentials(api_key: &str) -> Result<()> { fn set_permissions_600(path: &str) -> Result<()> { use std::os::unix::fs::PermissionsExt; let perms = std::fs::Permissions::from_mode(0o600); - std::fs::set_permissions(path, perms) - .context("Failed to set credentials file permissions") + std::fs::set_permissions(path, perms).context("Failed to set credentials file permissions") } #[cfg(not(unix))] fn set_permissions_600(_path: &str) -> Result<()> { Ok(()) } - diff --git a/agent/src/docker.rs b/agent/src/docker.rs index 97e9977c..4e7919b9 100644 --- a/agent/src/docker.rs +++ b/agent/src/docker.rs @@ -30,8 +30,8 @@ pub struct DockerManager { impl DockerManager { pub fn new() -> Result { - let docker = Docker::connect_with_unix_defaults() - .context("Failed to connect to Docker socket")?; + let docker = + Docker::connect_with_unix_defaults().context("Failed to connect to Docker socket")?; Ok(Self { docker }) } @@ -72,11 +72,10 @@ impl DockerManager { pub async fn start_container(&self, spec: &WorkloadSpec) -> Result { let container_name = format!("csfx-{}", spec.workload_id); - let env: Option> = spec.env_vars.as_ref().map(|vars| { - vars.iter() - .map(|(k, v)| format!("{}={}", k, v)) - .collect() - }); + let env: Option> = spec + .env_vars + .as_ref() + .map(|vars| vars.iter().map(|(k, v)| format!("{}={}", k, v)).collect()); let (port_bindings, exposed_ports) = build_port_config(spec.ports.as_deref()); diff --git a/agent/src/main.rs b/agent/src/main.rs index 0010d9ec..4998539b 100644 --- a/agent/src/main.rs +++ b/agent/src/main.rs @@ -27,7 +27,11 @@ async fn main() -> Result<()> { let gateway_url = std::env::var("CSFX_GATEWAY_URL") .context("CSFX_GATEWAY_URL environment variable is required")?; - if let Some(username) = std::env::args().nth(1).filter(|a| a == "--authorized-keys").and_then(|_| std::env::args().nth(2)) { + if let Some(username) = std::env::args() + .nth(1) + .filter(|a| a == "--authorized-keys") + .and_then(|_| std::env::args().nth(2)) + { let agent_id = config::load_config() .context("Failed to load daemon config")? .agent_id; @@ -43,11 +47,10 @@ async fn main() -> Result<()> { .and_then(|v| v.parse().ok()) .unwrap_or(60); - let api_client = client::ApiClient::new(gateway_url.clone()) - .context("Failed to initialize API client")?; + let api_client = + client::ApiClient::new(gateway_url.clone()).context("Failed to initialize API client")?; - let agent_pki = pki::AgentPki::load_or_generate() - .context("Failed to initialize PKI")?; + let agent_pki = pki::AgentPki::load_or_generate().context("Failed to initialize PKI")?; let (agent_id, api_key) = if config::is_registered() { info!("Existing registration found, loading credentials"); @@ -56,8 +59,13 @@ async fn main() -> Result<()> { (cfg.agent_id, creds.api_key) } else { info!("No registration found, starting registration"); - perform_registration(&api_client, &gateway_url, heartbeat_interval_secs, &agent_pki) - .await? + perform_registration( + &api_client, + &gateway_url, + heartbeat_interval_secs, + &agent_pki, + ) + .await? }; let api_client = if pki::AgentPki::has_certificate() { @@ -91,8 +99,7 @@ async fn main() -> Result<()> { let running_containers: Arc>> = Arc::new(Mutex::new(HashMap::new())); - let mounted_volumes: Arc>> = - Arc::new(Mutex::new(HashMap::new())); + let mounted_volumes: Arc>> = Arc::new(Mutex::new(HashMap::new())); run_heartbeat_loop( &api_client, @@ -114,7 +121,10 @@ async fn perform_registration( heartbeat_interval_secs: u64, agent_pki: &pki::AgentPki, ) -> Result<(uuid::Uuid, String)> { - let token = match std::env::var("CSFX_REGISTRATION_TOKEN").ok().filter(|t| !t.is_empty()) { + let token = match std::env::var("CSFX_REGISTRATION_TOKEN") + .ok() + .filter(|t| !t.is_empty()) + { Some(t) => t, None => { info!("CSFX_REGISTRATION_TOKEN not set, fetching bootstrap token from gateway"); @@ -148,8 +158,7 @@ async fn perform_registration( .context("Registration request failed")?; if let (Some(cert_pem), Some(ca_pem)) = (&resp.certificate_pem, &resp.ca_cert_pem) { - pki::AgentPki::save_certificate(cert_pem, ca_pem) - .context("Failed to save certificate")?; + pki::AgentPki::save_certificate(cert_pem, ca_pem).context("Failed to save certificate")?; info!("PKI: certificate received and stored"); } else { warn!("Registry did not issue a certificate during registration"); @@ -247,7 +256,10 @@ async fn run_heartbeat_loop( } if failure_count > 0 { - error!(failures = failure_count, "Agent shutting down with unresolved heartbeat failures"); + error!( + failures = failure_count, + "Agent shutting down with unresolved heartbeat failures" + ); } } @@ -318,10 +330,7 @@ async fn process_workloads( }; for workload in workloads { - let already_running = running_containers - .lock() - .await - .contains_key(&workload.id); + let already_running = running_containers.lock().await.contains_key(&workload.id); if already_running { continue; diff --git a/agent/src/pki.rs b/agent/src/pki.rs index 95ea1fe0..81521869 100644 --- a/agent/src/pki.rs +++ b/agent/src/pki.rs @@ -15,10 +15,8 @@ pub struct AgentPki { impl AgentPki { pub fn load_or_generate() -> Result { if Path::new(KEY_FILE).exists() && Path::new(CSR_FILE).exists() { - let key_pem = std::fs::read_to_string(KEY_FILE) - .context("Failed to read agent key")?; - let csr_pem = std::fs::read_to_string(CSR_FILE) - .context("Failed to read agent CSR")?; + let key_pem = std::fs::read_to_string(KEY_FILE).context("Failed to read agent key")?; + let csr_pem = std::fs::read_to_string(CSR_FILE).context("Failed to read agent CSR")?; tracing::info!("PKI: loaded existing keypair and CSR"); return Ok(Self { key_pem, csr_pem }); @@ -32,7 +30,9 @@ impl AgentPki { .context("Failed to generate ECDSA keypair")?; let mut params = CertificateParams::default(); - params.distinguished_name.push(DnType::OrganizationName, "CS-Foundry"); + params + .distinguished_name + .push(DnType::OrganizationName, "CS-Foundry"); let csr = params .serialize_request(&key_pair) @@ -86,8 +86,7 @@ impl AgentPki { fn set_permissions_600(path: &str) -> Result<()> { use std::os::unix::fs::PermissionsExt; let perms = std::fs::Permissions::from_mode(0o600); - std::fs::set_permissions(path, perms) - .context("Failed to set file permissions") + std::fs::set_permissions(path, perms).context("Failed to set file permissions") } #[cfg(not(unix))] diff --git a/agent/src/rbd.rs b/agent/src/rbd.rs index 7b3aad80..76d00a49 100644 --- a/agent/src/rbd.rs +++ b/agent/src/rbd.rs @@ -1,4 +1,4 @@ -use anyhow::{Context, Result, anyhow}; +use anyhow::{anyhow, Context, Result}; use tokio::process::Command; use tracing::{info, warn}; diff --git a/agent/src/ssh_keys.rs b/agent/src/ssh_keys.rs index 9e481e45..7571bc72 100644 --- a/agent/src/ssh_keys.rs +++ b/agent/src/ssh_keys.rs @@ -8,10 +8,7 @@ struct AuthorizedKeysResponse { } pub async fn fetch_authorized_keys(gateway_url: &str, agent_id: Uuid) -> Result> { - let url = format!( - "{}/api/agents/{}/authorized-keys", - gateway_url, agent_id - ); + let url = format!("{}/api/agents/{}/authorized-keys", gateway_url, agent_id); let client = reqwest::Client::builder() .timeout(std::time::Duration::from_secs(5)) diff --git a/agent/src/system.rs b/agent/src/system.rs index e4679bda..2ae27cae 100644 --- a/agent/src/system.rs +++ b/agent/src/system.rs @@ -40,15 +40,16 @@ fn detect_os() -> (String, String) { .or_else(|| parse_os_release_field(&content, "BUILD_ID")); if let Some(os_type) = id { - let os_version = version.unwrap_or_else(|| { - System::os_version().unwrap_or_else(|| "unknown".to_string()) - }); + let os_version = version + .unwrap_or_else(|| System::os_version().unwrap_or_else(|| "unknown".to_string())); return (os_type.to_lowercase(), os_version); } } ( - System::name().unwrap_or_else(|| "linux".to_string()).to_lowercase(), + System::name() + .unwrap_or_else(|| "linux".to_string()) + .to_lowercase(), System::os_version().unwrap_or_else(|| "unknown".to_string()), ) } @@ -73,9 +74,13 @@ pub fn collect_metrics() -> SystemMetrics { let memory_used_bytes = sys.used_memory(); let disks = Disks::new_with_refreshed_list(); - let (disk_total_bytes, disk_used_bytes) = disks.iter().fold((0u64, 0u64), |(total, used), d| { - (total + d.total_space(), used + (d.total_space() - d.available_space())) - }); + let (disk_total_bytes, disk_used_bytes) = + disks.iter().fold((0u64, 0u64), |(total, used), d| { + ( + total + d.total_space(), + used + (d.total_space() - d.available_space()), + ) + }); let networks = Networks::new_with_refreshed_list(); let (network_rx_bytes, network_tx_bytes) = diff --git a/control-plane/api-gateway/src/auth/crypto.rs b/control-plane/api-gateway/src/auth/crypto.rs index ec1e9b57..02e5fa26 100644 --- a/control-plane/api-gateway/src/auth/crypto.rs +++ b/control-plane/api-gateway/src/auth/crypto.rs @@ -80,7 +80,10 @@ pub fn verify_password(password: &str, salt: &str, hashed: &str) -> CryptoResult } pub fn encrypt_secret(plaintext: &str, key_b64: &str) -> CryptoResult { - use aes_gcm::{aead::{Aead, KeyInit}, Aes256Gcm, Nonce}; + use aes_gcm::{ + aead::{Aead, KeyInit}, + Aes256Gcm, Nonce, + }; use rand::RngCore; let key_bytes = base64::engine::general_purpose::STANDARD @@ -90,7 +93,8 @@ pub fn encrypt_secret(plaintext: &str, key_b64: &str) -> CryptoResult { return Err(CryptoError::InvalidEncryptedData); } - let cipher = Aes256Gcm::new_from_slice(&key_bytes).map_err(|_| CryptoError::InvalidEncryptedData)?; + let cipher = + Aes256Gcm::new_from_slice(&key_bytes).map_err(|_| CryptoError::InvalidEncryptedData)?; let mut nonce_bytes = [0u8; 12]; OsRng.fill_bytes(&mut nonce_bytes); let nonce = Nonce::from_slice(&nonce_bytes); @@ -105,7 +109,10 @@ pub fn encrypt_secret(plaintext: &str, key_b64: &str) -> CryptoResult { } pub fn decrypt_secret(encoded: &str, key_b64: &str) -> CryptoResult { - use aes_gcm::{aead::{Aead, KeyInit}, Aes256Gcm, Nonce}; + use aes_gcm::{ + aead::{Aead, KeyInit}, + Aes256Gcm, Nonce, + }; let key_bytes = base64::engine::general_purpose::STANDARD .decode(key_b64) @@ -122,7 +129,8 @@ pub fn decrypt_secret(encoded: &str, key_b64: &str) -> CryptoResult { } let (nonce_bytes, ciphertext) = combined.split_at(12); - let cipher = Aes256Gcm::new_from_slice(&key_bytes).map_err(|_| CryptoError::InvalidEncryptedData)?; + let cipher = + Aes256Gcm::new_from_slice(&key_bytes).map_err(|_| CryptoError::InvalidEncryptedData)?; let nonce = Nonce::from_slice(nonce_bytes); let plaintext = cipher diff --git a/control-plane/api-gateway/src/auth/rbac.rs b/control-plane/api-gateway/src/auth/rbac.rs index 9b93c49e..ed38157f 100644 --- a/control-plane/api-gateway/src/auth/rbac.rs +++ b/control-plane/api-gateway/src/auth/rbac.rs @@ -42,7 +42,11 @@ async fn extract_claims(parts: &mut Parts, state: &AppState) -> Result, ) -> Result { let mut client = etcd_client().await?; - client - .delete(ETCD_PAUSED_KEY, None) - .await - .map_err(|e| { - tracing::error!(error = %e, "failed to delete update_paused from etcd"); - StatusCode::INTERNAL_SERVER_ERROR - })?; + client.delete(ETCD_PAUSED_KEY, None).await.map_err(|e| { + tracing::error!(error = %e, "failed to delete update_paused from etcd"); + StatusCode::INTERNAL_SERVER_ERROR + })?; tracing::info!("updates resumed"); Ok(StatusCode::NO_CONTENT) } diff --git a/control-plane/api-gateway/src/routes/volumes.rs b/control-plane/api-gateway/src/routes/volumes.rs index 610d629e..077d4f3b 100644 --- a/control-plane/api-gateway/src/routes/volumes.rs +++ b/control-plane/api-gateway/src/routes/volumes.rs @@ -8,7 +8,10 @@ use axum::{ }; use serde_json::json; -use crate::{auth::rbac::{CanManageVolumes, CanViewVolumes}, AppState}; +use crate::{ + auth::rbac::{CanManageVolumes, CanViewVolumes}, + AppState, +}; async fn proxy_to_volume_manager( state: &AppState, @@ -36,7 +39,9 @@ async fn proxy_to_volume_manager( tracing::error!("Failed to forward request to volume-manager: {}", e); Err(( StatusCode::BAD_GATEWAY, - Json(json!({ "error": "Volume Manager service unavailable", "details": e.to_string() })), + Json( + json!({ "error": "Volume Manager service unavailable", "details": e.to_string() }), + ), )) } } @@ -57,7 +62,14 @@ pub async fn create_volume( ) -> Result)> { let body_json: Option = serde_json::from_str(&body).ok(); let header_map = header_vec(&headers); - proxy_to_volume_manager(&state, reqwest::Method::POST, "/volumes", body_json, Some(header_map)).await + proxy_to_volume_manager( + &state, + reqwest::Method::POST, + "/volumes", + body_json, + Some(header_map), + ) + .await } pub async fn list_volumes( @@ -66,7 +78,14 @@ pub async fn list_volumes( headers: HeaderMap, ) -> Result)> { let header_map = header_vec(&headers); - proxy_to_volume_manager(&state, reqwest::Method::GET, "/volumes", None, Some(header_map)).await + proxy_to_volume_manager( + &state, + reqwest::Method::GET, + "/volumes", + None, + Some(header_map), + ) + .await } pub async fn get_volume( @@ -76,7 +95,14 @@ pub async fn get_volume( headers: HeaderMap, ) -> Result)> { let header_map = header_vec(&headers); - proxy_to_volume_manager(&state, reqwest::Method::GET, &format!("/volumes/{}", id), None, Some(header_map)).await + proxy_to_volume_manager( + &state, + reqwest::Method::GET, + &format!("/volumes/{}", id), + None, + Some(header_map), + ) + .await } pub async fn delete_volume( @@ -86,7 +112,14 @@ pub async fn delete_volume( headers: HeaderMap, ) -> Result)> { let header_map = header_vec(&headers); - proxy_to_volume_manager(&state, reqwest::Method::DELETE, &format!("/volumes/{}", id), None, Some(header_map)).await + proxy_to_volume_manager( + &state, + reqwest::Method::DELETE, + &format!("/volumes/{}", id), + None, + Some(header_map), + ) + .await } pub async fn attach_volume( @@ -98,7 +131,14 @@ pub async fn attach_volume( ) -> Result)> { let body_json: Option = serde_json::from_str(&body).ok(); let header_map = header_vec(&headers); - proxy_to_volume_manager(&state, reqwest::Method::POST, &format!("/volumes/{}/attach", id), body_json, Some(header_map)).await + proxy_to_volume_manager( + &state, + reqwest::Method::POST, + &format!("/volumes/{}/attach", id), + body_json, + Some(header_map), + ) + .await } pub async fn detach_volume( @@ -108,7 +148,14 @@ pub async fn detach_volume( headers: HeaderMap, ) -> Result)> { let header_map = header_vec(&headers); - proxy_to_volume_manager(&state, reqwest::Method::POST, &format!("/volumes/{}/detach", id), None, Some(header_map)).await + proxy_to_volume_manager( + &state, + reqwest::Method::POST, + &format!("/volumes/{}/detach", id), + None, + Some(header_map), + ) + .await } pub async fn create_snapshot( @@ -120,7 +167,14 @@ pub async fn create_snapshot( ) -> Result)> { let body_json: Option = serde_json::from_str(&body).ok(); let header_map = header_vec(&headers); - proxy_to_volume_manager(&state, reqwest::Method::POST, &format!("/volumes/{}/snapshots", id), body_json, Some(header_map)).await + proxy_to_volume_manager( + &state, + reqwest::Method::POST, + &format!("/volumes/{}/snapshots", id), + body_json, + Some(header_map), + ) + .await } pub async fn list_snapshots( @@ -130,7 +184,14 @@ pub async fn list_snapshots( headers: HeaderMap, ) -> Result)> { let header_map = header_vec(&headers); - proxy_to_volume_manager(&state, reqwest::Method::GET, &format!("/volumes/{}/snapshots", id), None, Some(header_map)).await + proxy_to_volume_manager( + &state, + reqwest::Method::GET, + &format!("/volumes/{}/snapshots", id), + None, + Some(header_map), + ) + .await } pub fn volumes_routes() -> Router { diff --git a/control-plane/api-gateway/src/routes/workloads.rs b/control-plane/api-gateway/src/routes/workloads.rs index 2746a452..fac9df8f 100644 --- a/control-plane/api-gateway/src/routes/workloads.rs +++ b/control-plane/api-gateway/src/routes/workloads.rs @@ -8,7 +8,10 @@ use axum::{ }; use serde_json::json; -use crate::{auth::rbac::{CanManageWorkloads, CanViewWorkloads}, AppState}; +use crate::{ + auth::rbac::{CanManageWorkloads, CanViewWorkloads}, + AppState, +}; async fn proxy_to_scheduler( state: &AppState, @@ -50,7 +53,14 @@ pub async fn create_workload( ) -> Result)> { let body_json: Option = serde_json::from_str(&body).ok(); let header_map = header_vec(&headers); - proxy_to_scheduler(&state, reqwest::Method::POST, "/workloads", body_json, Some(header_map)).await + proxy_to_scheduler( + &state, + reqwest::Method::POST, + "/workloads", + body_json, + Some(header_map), + ) + .await } pub async fn list_workloads( @@ -59,7 +69,14 @@ pub async fn list_workloads( headers: HeaderMap, ) -> Result)> { let header_map = header_vec(&headers); - proxy_to_scheduler(&state, reqwest::Method::GET, "/workloads", None, Some(header_map)).await + proxy_to_scheduler( + &state, + reqwest::Method::GET, + "/workloads", + None, + Some(header_map), + ) + .await } pub async fn delete_workload( diff --git a/control-plane/api-gateway/src/service_client.rs b/control-plane/api-gateway/src/service_client.rs index b97acfa3..dad81e33 100644 --- a/control-plane/api-gateway/src/service_client.rs +++ b/control-plane/api-gateway/src/service_client.rs @@ -232,7 +232,11 @@ impl ServiceClient { ) -> Result<(StatusCode, Option)> { let url = format!("{}{}", self.failover_controller_url, path); - tracing::debug!("Forwarding {} request to failover-controller: {}", method, url); + tracing::debug!( + "Forwarding {} request to failover-controller: {}", + method, + url + ); let mut request = match method { reqwest::Method::GET => self.client.get(&url), diff --git a/control-plane/api-gateway/src/telemetry.rs b/control-plane/api-gateway/src/telemetry.rs index de6754f0..0614fc30 100644 --- a/control-plane/api-gateway/src/telemetry.rs +++ b/control-plane/api-gateway/src/telemetry.rs @@ -32,9 +32,7 @@ pub fn init_tracing() { let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")); let fmt_layer = fmt::layer().with_target(false); - let registry = tracing_subscriber::registry() - .with(filter) - .with(fmt_layer); + let registry = tracing_subscriber::registry().with(filter).with(fmt_layer); match build_otlp_provider() { Some(provider) => { diff --git a/control-plane/api-gateway/src/tls.rs b/control-plane/api-gateway/src/tls.rs index 6537cb23..bfbd69b9 100644 --- a/control-plane/api-gateway/src/tls.rs +++ b/control-plane/api-gateway/src/tls.rs @@ -17,7 +17,9 @@ pub async fn generate_tls_config() -> anyhow::Result { let mut params = CertificateParams::default(); params.distinguished_name = DistinguishedName::new(); - params.distinguished_name.push(DnType::CommonName, "csfx-gateway"); + params + .distinguished_name + .push(DnType::CommonName, "csfx-gateway"); let san_hosts = std::env::var("TLS_SANS").unwrap_or_else(|_| "localhost,127.0.0.1".to_string()); for san in san_hosts.split(',') { @@ -25,7 +27,9 @@ pub async fn generate_tls_config() -> anyhow::Result { if let Ok(ip) = san.parse::() { params.subject_alt_names.push(SanType::IpAddress(ip)); } else { - params.subject_alt_names.push(SanType::DnsName(san.try_into()?)); + params + .subject_alt_names + .push(SanType::DnsName(san.try_into()?)); } } diff --git a/control-plane/csfx-updater/src/config.rs b/control-plane/csfx-updater/src/config.rs index 5cd02b47..c335b728 100644 --- a/control-plane/csfx-updater/src/config.rs +++ b/control-plane/csfx-updater/src/config.rs @@ -32,10 +32,8 @@ impl Config { .context("INFRA_REPO_MIRROR_URL must be set")?, infra_repo_github: env::var("INFRA_REPO_GITHUB") .context("INFRA_REPO_GITHUB must be set (e.g. csfx-cloud/CSFX-Infra)")?, - infra_repo_branch: env::var("INFRA_REPO_BRANCH") - .unwrap_or_else(|_| "main".to_string()), - nixos_config: env::var("NIXOS_CONFIG") - .unwrap_or_else(|_| "csfx-node".to_string()), + infra_repo_branch: env::var("INFRA_REPO_BRANCH").unwrap_or_else(|_| "main".to_string()), + nixos_config: env::var("NIXOS_CONFIG").unwrap_or_else(|_| "csfx-node".to_string()), gateway_url: env::var("GATEWAY_URL") .unwrap_or_else(|_| "https://localhost:8000".to_string()), health_check_timeout_secs: env::var("HEALTH_CHECK_TIMEOUT_SECS") diff --git a/control-plane/csfx-updater/src/git_mirror.rs b/control-plane/csfx-updater/src/git_mirror.rs index 91532051..c0bccab8 100644 --- a/control-plane/csfx-updater/src/git_mirror.rs +++ b/control-plane/csfx-updater/src/git_mirror.rs @@ -49,6 +49,5 @@ pub async fn rev_exists(mirror_dir: &str, rev: &str) -> Result { .output() .await?; - Ok(output.status.success() - && String::from_utf8_lossy(&output.stdout).trim() == "commit") + Ok(output.status.success() && String::from_utf8_lossy(&output.stdout).trim() == "commit") } diff --git a/control-plane/csfx-updater/src/health.rs b/control-plane/csfx-updater/src/health.rs index d0c0a709..255daf15 100644 --- a/control-plane/csfx-updater/src/health.rs +++ b/control-plane/csfx-updater/src/health.rs @@ -2,7 +2,11 @@ use std::time::Duration; use tokio::time::sleep; use tracing::{info, warn}; -pub async fn wait_for_gateway(gateway_url: &str, timeout_secs: u64, retry_interval_secs: u64) -> bool { +pub async fn wait_for_gateway( + gateway_url: &str, + timeout_secs: u64, + retry_interval_secs: u64, +) -> bool { let deadline = tokio::time::Instant::now() + Duration::from_secs(timeout_secs); let probe_url = format!("{}/api/public-key", gateway_url.trim_end_matches('/')); let client = reqwest::Client::builder() diff --git a/control-plane/csfx-updater/src/main.rs b/control-plane/csfx-updater/src/main.rs index 561c803e..a09231ce 100644 --- a/control-plane/csfx-updater/src/main.rs +++ b/control-plane/csfx-updater/src/main.rs @@ -143,7 +143,14 @@ async fn execute_once(cfg: &config::Config, last_applied: &str) -> anyhow::Resul let (_cancel_tx, cancel_rx) = watch::channel(false); - match nix_build::build(&cfg.infra_repo_mirror_dir, &desired, &cfg.nixos_config, cancel_rx).await { + match nix_build::build( + &cfg.infra_repo_mirror_dir, + &desired, + &cfg.nixos_config, + cancel_rx, + ) + .await + { Ok(()) => {} Err(e) => { tracing::error!(error = %e, flake_rev = %desired, "nix build failed"); diff --git a/control-plane/csfx-updater/src/nix_build.rs b/control-plane/csfx-updater/src/nix_build.rs index 295d999e..64be608e 100644 --- a/control-plane/csfx-updater/src/nix_build.rs +++ b/control-plane/csfx-updater/src/nix_build.rs @@ -3,7 +3,12 @@ use tokio::process::Command; use tokio::sync::watch; use tracing::info; -pub async fn build(mirror_dir: &str, rev: &str, nixos_config: &str, mut cancel: watch::Receiver) -> Result<()> { +pub async fn build( + mirror_dir: &str, + rev: &str, + nixos_config: &str, + mut cancel: watch::Receiver, +) -> Result<()> { let flake_url = format!("git+file://{}?rev={}#{}", mirror_dir, rev, nixos_config); info!(flake_rev = %rev, nixos_config = %nixos_config, "starting nix build"); diff --git a/control-plane/csfx-updater/src/poller.rs b/control-plane/csfx-updater/src/poller.rs index 322a3200..5b52f52e 100644 --- a/control-plane/csfx-updater/src/poller.rs +++ b/control-plane/csfx-updater/src/poller.rs @@ -122,7 +122,10 @@ async fn dereference_tag(cfg: &Config, tag_sha: &str) -> Result { .await?; if !resp.status().is_success() { - bail!("GitHub API returned {} when dereferencing tag", resp.status()); + bail!( + "GitHub API returned {} when dereferencing tag", + resp.status() + ); } let tag: GitHubTag = resp.json().await?; diff --git a/control-plane/csfx-updater/src/rollback.rs b/control-plane/csfx-updater/src/rollback.rs index e1abc17d..ffe6df91 100644 --- a/control-plane/csfx-updater/src/rollback.rs +++ b/control-plane/csfx-updater/src/rollback.rs @@ -11,7 +11,10 @@ pub async fn rollback() -> Result<()> { .await?; if !status.success() { - bail!("nixos-rebuild switch --rollback failed with status {}", status); + bail!( + "nixos-rebuild switch --rollback failed with status {}", + status + ); } info!("rollback complete"); diff --git a/control-plane/failover-controller/src/db/agents.rs b/control-plane/failover-controller/src/db/agents.rs index d8f2acb5..0a9720a5 100644 --- a/control-plane/failover-controller/src/db/agents.rs +++ b/control-plane/failover-controller/src/db/agents.rs @@ -1,16 +1,10 @@ use anyhow::Result; use chrono::Utc; use entity::entities::agents; -use sea_orm::{ - ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter, Set, -}; +use sea_orm::{ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter, Set}; use uuid::Uuid; -pub async fn set_agent_status( - db: &DatabaseConnection, - agent_id: Uuid, - status: &str, -) -> Result<()> { +pub async fn set_agent_status(db: &DatabaseConnection, agent_id: Uuid, status: &str) -> Result<()> { let agent = agents::Entity::find_by_id(agent_id) .one(db) .await? diff --git a/control-plane/failover-controller/src/db/events.rs b/control-plane/failover-controller/src/db/events.rs index 59b8bd37..16c1be5c 100644 --- a/control-plane/failover-controller/src/db/events.rs +++ b/control-plane/failover-controller/src/db/events.rs @@ -13,8 +13,8 @@ pub async fn insert( affected_workloads: Option>, duration_ms: Option, ) -> Result { - let workloads_json = affected_workloads - .map(|ids| serde_json::to_value(ids).unwrap_or(serde_json::Value::Null)); + let workloads_json = + affected_workloads.map(|ids| serde_json::to_value(ids).unwrap_or(serde_json::Value::Null)); let model = failover_events::ActiveModel { id: Set(Uuid::new_v4()), diff --git a/control-plane/failover-controller/src/logger.rs b/control-plane/failover-controller/src/logger.rs index 8d9f9d62..727fe564 100644 --- a/control-plane/failover-controller/src/logger.rs +++ b/control-plane/failover-controller/src/logger.rs @@ -58,9 +58,7 @@ pub fn init_logger_with_service(service_name: &'static str) { let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")); let fmt_layer = fmt::layer().with_target(false).with_thread_ids(true); - let registry = tracing_subscriber::registry() - .with(filter) - .with(fmt_layer); + let registry = tracing_subscriber::registry().with(filter).with(fmt_layer); match build_otlp_provider(service_name) { Some(provider) => { @@ -79,11 +77,21 @@ pub fn log_message(level: LogLevel, module: &str, location: &str, description: & let lvl: Level = level.into(); match lvl { - Level::ERROR => event!(Level::ERROR, module = %module, location = %location, "{}", description), - Level::WARN => event!(Level::WARN, module = %module, location = %location, "{}", description), - Level::INFO => event!(Level::INFO, module = %module, location = %location, "{}", description), - Level::DEBUG => event!(Level::DEBUG, module = %module, location = %location, "{}", description), - Level::TRACE => event!(Level::TRACE, module = %module, location = %location, "{}", description), + Level::ERROR => { + event!(Level::ERROR, module = %module, location = %location, "{}", description) + } + Level::WARN => { + event!(Level::WARN, module = %module, location = %location, "{}", description) + } + Level::INFO => { + event!(Level::INFO, module = %module, location = %location, "{}", description) + } + Level::DEBUG => { + event!(Level::DEBUG, module = %module, location = %location, "{}", description) + } + Level::TRACE => { + event!(Level::TRACE, module = %module, location = %location, "{}", description) + } } } diff --git a/control-plane/failover-controller/src/metrics.rs b/control-plane/failover-controller/src/metrics.rs index 64d068b0..aa5a9792 100644 --- a/control-plane/failover-controller/src/metrics.rs +++ b/control-plane/failover-controller/src/metrics.rs @@ -1,5 +1,7 @@ use axum::response::IntoResponse; -use prometheus::{register_counter_vec, register_histogram_vec, CounterVec, Encoder, HistogramVec, TextEncoder}; +use prometheus::{ + register_counter_vec, register_histogram_vec, CounterVec, Encoder, HistogramVec, TextEncoder, +}; use std::sync::OnceLock; static HTTP_REQUESTS_TOTAL: OnceLock = OnceLock::new(); @@ -46,7 +48,10 @@ pub async fn metrics_handler() -> impl IntoResponse { .encode(&metric_families, &mut buffer) .expect("failed to encode metrics"); ( - [(axum::http::header::CONTENT_TYPE, "text/plain; version=0.0.4")], + [( + axum::http::header::CONTENT_TYPE, + "text/plain; version=0.0.4", + )], buffer, ) } diff --git a/control-plane/failover-controller/src/services/failover.rs b/control-plane/failover-controller/src/services/failover.rs index 98688ffd..993f4b2d 100644 --- a/control-plane/failover-controller/src/services/failover.rs +++ b/control-plane/failover-controller/src/services/failover.rs @@ -132,10 +132,7 @@ impl FailoverService { Err(e) => { crate::log_warn!( "failover", - &format!( - "Reschedule request failed agent_id={} err={}", - agent_id, e - ) + &format!("Reschedule request failed agent_id={} err={}", agent_id, e) ); } } diff --git a/control-plane/failover-controller/src/services/monitor.rs b/control-plane/failover-controller/src/services/monitor.rs index b1929036..a35eda63 100644 --- a/control-plane/failover-controller/src/services/monitor.rs +++ b/control-plane/failover-controller/src/services/monitor.rs @@ -33,7 +33,10 @@ pub async fn run(db: DatabaseConnection) { if let Err(e) = agent_db::set_agent_status(&db, agent.id, "Degraded").await { crate::log_error!( "monitor", - &format!("Failed to mark agent degraded agent_id={} err={}", agent.id, e) + &format!( + "Failed to mark agent degraded agent_id={} err={}", + agent.id, e + ) ); continue; } @@ -49,7 +52,10 @@ pub async fn run(db: DatabaseConnection) { if let Err(e) = events::insert(&db, Some(agent.id), "degraded", None, None).await { crate::log_error!( "monitor", - &format!("Failed to insert degraded event agent_id={} err={}", agent.id, e) + &format!( + "Failed to insert degraded event agent_id={} err={}", + agent.id, e + ) ); } } @@ -58,7 +64,10 @@ pub async fn run(db: DatabaseConnection) { if let Err(e) = agent_db::set_agent_status(&db, agent.id, "Offline").await { crate::log_error!( "monitor", - &format!("Failed to mark agent offline agent_id={} err={}", agent.id, e) + &format!( + "Failed to mark agent offline agent_id={} err={}", + agent.id, e + ) ); continue; } diff --git a/control-plane/registry/src/db/agents.rs b/control-plane/registry/src/db/agents.rs index c6b16edb..c23ce15b 100644 --- a/control-plane/registry/src/db/agents.rs +++ b/control-plane/registry/src/db/agents.rs @@ -1,8 +1,6 @@ use anyhow::Result; use entity::agents; -use sea_orm::{ - ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter, Set, -}; +use sea_orm::{ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter, Set}; use uuid::Uuid; pub async fn get_by_hostname( @@ -118,8 +116,14 @@ pub async fn mark_degraded_by_timeout( ) -> Result { let threshold = chrono::Utc::now().naive_utc() - chrono::Duration::seconds(timeout_seconds); let result = agents::Entity::update_many() - .col_expr(agents::Column::Status, sea_orm::sea_query::Expr::value("Degraded")) - .col_expr(agents::Column::UpdatedAt, sea_orm::sea_query::Expr::value(chrono::Utc::now().naive_utc())) + .col_expr( + agents::Column::Status, + sea_orm::sea_query::Expr::value("Degraded"), + ) + .col_expr( + agents::Column::UpdatedAt, + sea_orm::sea_query::Expr::value(chrono::Utc::now().naive_utc()), + ) .filter(agents::Column::LastHeartbeat.lt(threshold)) .filter(agents::Column::Status.eq("Online")) .exec(db) @@ -127,12 +131,8 @@ pub async fn mark_degraded_by_timeout( Ok(result.rows_affected) } -pub async fn mark_offline_by_timeout( - db: &DatabaseConnection, - timeout_seconds: i64, -) -> Result { - let threshold = - chrono::Utc::now().naive_utc() - chrono::Duration::seconds(timeout_seconds); +pub async fn mark_offline_by_timeout(db: &DatabaseConnection, timeout_seconds: i64) -> Result { + let threshold = chrono::Utc::now().naive_utc() - chrono::Duration::seconds(timeout_seconds); let result = agents::Entity::update_many() .col_expr( @@ -145,8 +145,9 @@ pub async fn mark_offline_by_timeout( ) .filter(agents::Column::LastHeartbeat.lt(threshold)) .filter( - agents::Column::Status.eq("Online") - .or(agents::Column::Status.eq("Degraded")) + agents::Column::Status + .eq("Online") + .or(agents::Column::Status.eq("Degraded")), ) .exec(db) .await?; diff --git a/control-plane/registry/src/db/api_keys.rs b/control-plane/registry/src/db/api_keys.rs index a4e9e042..e26777c9 100644 --- a/control-plane/registry/src/db/api_keys.rs +++ b/control-plane/registry/src/db/api_keys.rs @@ -1,8 +1,6 @@ use anyhow::Result; use entity::{agent_api_keys, agents}; -use sea_orm::{ - ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter, Set, -}; +use sea_orm::{ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter, Set}; use sha2::{Digest, Sha256}; use uuid::Uuid; diff --git a/control-plane/registry/src/db/bootstrap_tokens.rs b/control-plane/registry/src/db/bootstrap_tokens.rs index ec7dbbf2..cb01fed6 100644 --- a/control-plane/registry/src/db/bootstrap_tokens.rs +++ b/control-plane/registry/src/db/bootstrap_tokens.rs @@ -1,8 +1,6 @@ use anyhow::Result; use entity::bootstrap_tokens; -use sea_orm::{ - ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter, Set, -}; +use sea_orm::{ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter, Set}; use uuid::Uuid; pub async fn create( @@ -67,9 +65,7 @@ pub async fn revoke(db: &DatabaseConnection, id: Uuid) -> Result<()> { Ok(()) } -pub async fn get_all_active( - db: &DatabaseConnection, -) -> Result> { +pub async fn get_all_active(db: &DatabaseConnection) -> Result> { Ok(bootstrap_tokens::Entity::find() .filter(bootstrap_tokens::Column::Revoked.eq(false)) .all(db) diff --git a/control-plane/registry/src/db/certificates.rs b/control-plane/registry/src/db/certificates.rs index f6e2dc95..434ae15d 100644 --- a/control-plane/registry/src/db/certificates.rs +++ b/control-plane/registry/src/db/certificates.rs @@ -1,8 +1,6 @@ use anyhow::Result; use entity::{agent_certificates, certificate_revocations}; -use sea_orm::{ - ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter, Set, -}; +use sea_orm::{ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter, Set}; use uuid::Uuid; pub async fn create_certificate( diff --git a/control-plane/registry/src/db/tokens.rs b/control-plane/registry/src/db/tokens.rs index d855b48b..89ff98b5 100644 --- a/control-plane/registry/src/db/tokens.rs +++ b/control-plane/registry/src/db/tokens.rs @@ -1,8 +1,6 @@ use anyhow::Result; use entity::registry_tokens; -use sea_orm::{ - ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter, Set, -}; +use sea_orm::{ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter, Set}; use uuid::Uuid; pub async fn create( diff --git a/control-plane/registry/src/handlers/admin.rs b/control-plane/registry/src/handlers/admin.rs index 25f5f055..59a3a6f7 100644 --- a/control-plane/registry/src/handlers/admin.rs +++ b/control-plane/registry/src/handlers/admin.rs @@ -89,10 +89,7 @@ pub async fn delete_pending_agent( .await { Ok(_) => Ok(StatusCode::NO_CONTENT), - Err(e) => Err(( - StatusCode::NOT_FOUND, - Json(ErrorResponse { error: e }), - )), + Err(e) => Err((StatusCode::NOT_FOUND, Json(ErrorResponse { error: e }))), } } @@ -144,16 +141,29 @@ pub async fn get_statistics( pub async fn create_bootstrap_token( State(state): State, Json(request): Json, -) -> Result, (StatusCode, Json)> { +) -> Result< + Json, + (StatusCode, Json), +> { let ttl_hours = request.ttl_hours.unwrap_or(24 * 30); let max_uses = request.max_uses.unwrap_or(100); state .bootstrap_token_manager - .create(request.description, "admin".to_string(), ttl_hours, max_uses) + .create( + request.description, + "admin".to_string(), + ttl_hours, + max_uses, + ) .await .map(Json) - .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, Json(ErrorResponse { error: e }))) + .map_err(|e| { + ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(ErrorResponse { error: e }), + ) + }) } pub async fn list_bootstrap_tokens( @@ -170,6 +180,10 @@ pub async fn revoke_bootstrap_token( .bootstrap_token_manager .revoke(id) .await - .map(|_| Json(RevokeBootstrapTokenResponse { message: format!("Bootstrap token {} revoked", id) })) + .map(|_| { + Json(RevokeBootstrapTokenResponse { + message: format!("Bootstrap token {} revoked", id), + }) + }) .map_err(|e| (StatusCode::NOT_FOUND, Json(ErrorResponse { error: e }))) } diff --git a/control-plane/registry/src/handlers/agent.rs b/control-plane/registry/src/handlers/agent.rs index 834144f3..c6992bd7 100644 --- a/control-plane/registry/src/handlers/agent.rs +++ b/control-plane/registry/src/handlers/agent.rs @@ -6,7 +6,9 @@ use axum::{ use uuid::Uuid; use crate::{ - models::agent::{ErrorResponse, HeartbeatRequest, HeartbeatResponse, RegisterRequest, RegisterResponse}, + models::agent::{ + ErrorResponse, HeartbeatRequest, HeartbeatResponse, RegisterRequest, RegisterResponse, + }, server::AppState, services::registry::RegisterAgentParams, }; @@ -108,7 +110,10 @@ pub async fn register_agent( if let Err(e) = state.api_key_manager.revoke_all_keys(agent.id).await { crate::log_warn!( "agent_handler", - &format!("Failed to revoke old API keys for agent={}: {}", agent.id, e) + &format!( + "Failed to revoke old API keys for agent={}: {}", + agent.id, e + ) ); } } @@ -300,7 +305,10 @@ async fn forward_container_statuses( if let Err(e) = state.http_client.post(&url).json(&payload).send().await { crate::log_warn!( "agent_handler", - &format!("Failed to forward container statuses to scheduler err={}", e) + &format!( + "Failed to forward container statuses to scheduler err={}", + e + ) ); } } diff --git a/control-plane/registry/src/handlers/pki.rs b/control-plane/registry/src/handlers/pki.rs index ac925853..b087147b 100644 --- a/control-plane/registry/src/handlers/pki.rs +++ b/control-plane/registry/src/handlers/pki.rs @@ -193,14 +193,11 @@ pub async fn get_agent_endpoint( ) })?; - let public_key_pem = crate::db::certificates::get_active_certificate( - &state.db, - agent_id, - ) - .await - .ok() - .flatten() - .map(|c| c.public_key_pem); + let public_key_pem = crate::db::certificates::get_active_certificate(&state.db, agent_id) + .await + .ok() + .flatten() + .map(|c| c.public_key_pem); Ok(Json(AgentEndpointResponse { agent_id, diff --git a/control-plane/registry/src/logger.rs b/control-plane/registry/src/logger.rs index 8d9f9d62..727fe564 100644 --- a/control-plane/registry/src/logger.rs +++ b/control-plane/registry/src/logger.rs @@ -58,9 +58,7 @@ pub fn init_logger_with_service(service_name: &'static str) { let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")); let fmt_layer = fmt::layer().with_target(false).with_thread_ids(true); - let registry = tracing_subscriber::registry() - .with(filter) - .with(fmt_layer); + let registry = tracing_subscriber::registry().with(filter).with(fmt_layer); match build_otlp_provider(service_name) { Some(provider) => { @@ -79,11 +77,21 @@ pub fn log_message(level: LogLevel, module: &str, location: &str, description: & let lvl: Level = level.into(); match lvl { - Level::ERROR => event!(Level::ERROR, module = %module, location = %location, "{}", description), - Level::WARN => event!(Level::WARN, module = %module, location = %location, "{}", description), - Level::INFO => event!(Level::INFO, module = %module, location = %location, "{}", description), - Level::DEBUG => event!(Level::DEBUG, module = %module, location = %location, "{}", description), - Level::TRACE => event!(Level::TRACE, module = %module, location = %location, "{}", description), + Level::ERROR => { + event!(Level::ERROR, module = %module, location = %location, "{}", description) + } + Level::WARN => { + event!(Level::WARN, module = %module, location = %location, "{}", description) + } + Level::INFO => { + event!(Level::INFO, module = %module, location = %location, "{}", description) + } + Level::DEBUG => { + event!(Level::DEBUG, module = %module, location = %location, "{}", description) + } + Level::TRACE => { + event!(Level::TRACE, module = %module, location = %location, "{}", description) + } } } diff --git a/control-plane/registry/src/metrics.rs b/control-plane/registry/src/metrics.rs index 64d068b0..aa5a9792 100644 --- a/control-plane/registry/src/metrics.rs +++ b/control-plane/registry/src/metrics.rs @@ -1,5 +1,7 @@ use axum::response::IntoResponse; -use prometheus::{register_counter_vec, register_histogram_vec, CounterVec, Encoder, HistogramVec, TextEncoder}; +use prometheus::{ + register_counter_vec, register_histogram_vec, CounterVec, Encoder, HistogramVec, TextEncoder, +}; use std::sync::OnceLock; static HTTP_REQUESTS_TOTAL: OnceLock = OnceLock::new(); @@ -46,7 +48,10 @@ pub async fn metrics_handler() -> impl IntoResponse { .encode(&metric_families, &mut buffer) .expect("failed to encode metrics"); ( - [(axum::http::header::CONTENT_TYPE, "text/plain; version=0.0.4")], + [( + axum::http::header::CONTENT_TYPE, + "text/plain; version=0.0.4", + )], buffer, ) } diff --git a/control-plane/registry/src/server.rs b/control-plane/registry/src/server.rs index a84966b9..7d926cd9 100644 --- a/control-plane/registry/src/server.rs +++ b/control-plane/registry/src/server.rs @@ -12,11 +12,8 @@ use crate::{ handlers::{admin, agent, pki}, metrics, services::{ - api_keys::ApiKeyManager, - bootstrap_tokens::BootstrapTokenManager, - pki::PkiService, - registry::AgentRegistry, - tokens::TokenManager, + api_keys::ApiKeyManager, bootstrap_tokens::BootstrapTokenManager, pki::PkiService, + registry::AgentRegistry, tokens::TokenManager, }, }; @@ -43,16 +40,25 @@ pub fn create_router(state: AppState) -> Router { .route("/health", get(health_check)) .route("/metrics", get(metrics::metrics_handler)) // Admin — agent lifecycle - .route("/admin/agents/pre-register", post(admin::pre_register_agent)) + .route( + "/admin/agents/pre-register", + post(admin::pre_register_agent), + ) .route("/admin/agents/pending", get(admin::list_pending_agents)) .route( "/admin/agents/pending/:agent_id", delete(admin::delete_pending_agent), ) .route("/admin/tokens", get(admin::list_tokens)) - .route("/admin/bootstrap-tokens", post(admin::create_bootstrap_token)) + .route( + "/admin/bootstrap-tokens", + post(admin::create_bootstrap_token), + ) .route("/admin/bootstrap-tokens", get(admin::list_bootstrap_tokens)) - .route("/admin/bootstrap-tokens/:id/revoke", post(admin::revoke_bootstrap_token)) + .route( + "/admin/bootstrap-tokens/:id/revoke", + post(admin::revoke_bootstrap_token), + ) .route("/admin/agents", get(admin::list_agents)) .route("/admin/agents/:agent_id", get(admin::get_agent)) .route("/admin/agents/:agent_id", delete(admin::deregister_agent)) diff --git a/control-plane/registry/src/services/registry.rs b/control-plane/registry/src/services/registry.rs index fe2b01a8..3df0ec69 100644 --- a/control-plane/registry/src/services/registry.rs +++ b/control-plane/registry/src/services/registry.rs @@ -156,7 +156,9 @@ impl AgentRegistry { agent_version: db_agent.agent_version, status: AgentStatus::Online, registered_at: db_agent.registered_at.and_utc(), - last_heartbeat: db_agent.last_heartbeat.map(|dt: NaiveDateTime| dt.and_utc()), + last_heartbeat: db_agent + .last_heartbeat + .map(|dt: NaiveDateTime| dt.and_utc()), tags: params.tags, }; @@ -198,7 +200,9 @@ impl AgentRegistry { agent_version: db_agent.agent_version, status: AgentStatus::Online, registered_at: db_agent.registered_at.and_utc(), - last_heartbeat: db_agent.last_heartbeat.map(|dt: NaiveDateTime| dt.and_utc()), + last_heartbeat: db_agent + .last_heartbeat + .map(|dt: NaiveDateTime| dt.and_utc()), tags: params.tags, }; @@ -231,7 +235,9 @@ impl AgentRegistry { agent_version: db_agent.agent_version, status: AgentStatus::from_str(&db_agent.status), registered_at: db_agent.registered_at.and_utc(), - last_heartbeat: db_agent.last_heartbeat.map(|dt: NaiveDateTime| dt.and_utc()), + last_heartbeat: db_agent + .last_heartbeat + .map(|dt: NaiveDateTime| dt.and_utc()), tags: None, }), _ => None, @@ -253,15 +259,14 @@ impl AgentRegistry { agent_version: db_agent.agent_version, status: AgentStatus::from_str(&db_agent.status), registered_at: db_agent.registered_at.and_utc(), - last_heartbeat: db_agent.last_heartbeat.map(|dt: NaiveDateTime| dt.and_utc()), + last_heartbeat: db_agent + .last_heartbeat + .map(|dt: NaiveDateTime| dt.and_utc()), tags: None, }) .collect(), Err(e) => { - crate::log_error!( - "agent_registry", - &format!("Failed to list agents: {}", e) - ); + crate::log_error!("agent_registry", &format!("Failed to list agents: {}", e)); vec![] } } @@ -281,7 +286,9 @@ impl AgentRegistry { } pub async fn check_agent_health(&self, degraded_seconds: i64, offline_seconds: i64) -> usize { - if let Err(e) = crate::db::agents::mark_degraded_by_timeout(&self.db, degraded_seconds).await { + if let Err(e) = + crate::db::agents::mark_degraded_by_timeout(&self.db, degraded_seconds).await + { crate::log_error!("agent_registry", &format!("Failed to mark degraded: {}", e)); } match crate::db::agents::mark_offline_by_timeout(&self.db, offline_seconds).await { diff --git a/control-plane/registry/src/services/tokens.rs b/control-plane/registry/src/services/tokens.rs index 2ad22752..ea89e61b 100644 --- a/control-plane/registry/src/services/tokens.rs +++ b/control-plane/registry/src/services/tokens.rs @@ -177,10 +177,7 @@ impl TokenManager { }) .collect(), Err(e) => { - crate::log_error!( - "token_manager", - &format!("Failed to list tokens: {}", e) - ); + crate::log_error!("token_manager", &format!("Failed to list tokens: {}", e)); vec![] } } diff --git a/control-plane/scheduler/src/db/agents.rs b/control-plane/scheduler/src/db/agents.rs index f449a5a7..fba9b0f7 100644 --- a/control-plane/scheduler/src/db/agents.rs +++ b/control-plane/scheduler/src/db/agents.rs @@ -4,7 +4,6 @@ use uuid::Uuid; use crate::models::workload::AgentResources; - const TOTAL_CPU_MILLICORES: i32 = 4000; const TOTAL_MEMORY_BYTES: i64 = 8 * 1024 * 1024 * 1024; const TOTAL_DISK_BYTES: i64 = 100 * 1024 * 1024 * 1024; @@ -68,6 +67,5 @@ pub async fn get_assigned_workload_resources( let mem: i64 = workloads.iter().map(|w| w.memory_bytes).sum(); let disk: i64 = workloads.iter().map(|w| w.disk_bytes).sum(); - Ok((cpu, mem, disk)) } diff --git a/control-plane/scheduler/src/db/workloads.rs b/control-plane/scheduler/src/db/workloads.rs index edc6f0f3..9b8c6218 100644 --- a/control-plane/scheduler/src/db/workloads.rs +++ b/control-plane/scheduler/src/db/workloads.rs @@ -1,8 +1,6 @@ use chrono::Utc; use entity::entities::workloads; -use sea_orm::{ - ActiveModelTrait, ActiveValue::Set, DatabaseConnection, EntityTrait, ModelTrait, -}; +use sea_orm::{ActiveModelTrait, ActiveValue::Set, DatabaseConnection, EntityTrait, ModelTrait}; use uuid::Uuid; use crate::models::workload::{CreateWorkloadRequest, WorkloadResponse, WorkloadStatus}; @@ -84,10 +82,7 @@ pub async fn update_container_status( Ok(()) } -pub async fn delete( - db: &DatabaseConnection, - workload_id: Uuid, -) -> Result<(), sea_orm::DbErr> { +pub async fn delete(db: &DatabaseConnection, workload_id: Uuid) -> Result<(), sea_orm::DbErr> { let workload = workloads::Entity::find_by_id(workload_id) .one(db) .await? diff --git a/control-plane/scheduler/src/handlers/internal.rs b/control-plane/scheduler/src/handlers/internal.rs index 3e44cfc0..cc852032 100644 --- a/control-plane/scheduler/src/handlers/internal.rs +++ b/control-plane/scheduler/src/handlers/internal.rs @@ -1,9 +1,4 @@ -use axum::{ - extract::State, - http::StatusCode, - response::IntoResponse, - Json, -}; +use axum::{extract::State, http::StatusCode, response::IntoResponse, Json}; use serde::Deserialize; use uuid::Uuid; diff --git a/control-plane/scheduler/src/logger.rs b/control-plane/scheduler/src/logger.rs index 8d9f9d62..727fe564 100644 --- a/control-plane/scheduler/src/logger.rs +++ b/control-plane/scheduler/src/logger.rs @@ -58,9 +58,7 @@ pub fn init_logger_with_service(service_name: &'static str) { let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")); let fmt_layer = fmt::layer().with_target(false).with_thread_ids(true); - let registry = tracing_subscriber::registry() - .with(filter) - .with(fmt_layer); + let registry = tracing_subscriber::registry().with(filter).with(fmt_layer); match build_otlp_provider(service_name) { Some(provider) => { @@ -79,11 +77,21 @@ pub fn log_message(level: LogLevel, module: &str, location: &str, description: & let lvl: Level = level.into(); match lvl { - Level::ERROR => event!(Level::ERROR, module = %module, location = %location, "{}", description), - Level::WARN => event!(Level::WARN, module = %module, location = %location, "{}", description), - Level::INFO => event!(Level::INFO, module = %module, location = %location, "{}", description), - Level::DEBUG => event!(Level::DEBUG, module = %module, location = %location, "{}", description), - Level::TRACE => event!(Level::TRACE, module = %module, location = %location, "{}", description), + Level::ERROR => { + event!(Level::ERROR, module = %module, location = %location, "{}", description) + } + Level::WARN => { + event!(Level::WARN, module = %module, location = %location, "{}", description) + } + Level::INFO => { + event!(Level::INFO, module = %module, location = %location, "{}", description) + } + Level::DEBUG => { + event!(Level::DEBUG, module = %module, location = %location, "{}", description) + } + Level::TRACE => { + event!(Level::TRACE, module = %module, location = %location, "{}", description) + } } } diff --git a/control-plane/scheduler/src/metrics.rs b/control-plane/scheduler/src/metrics.rs index 64d068b0..aa5a9792 100644 --- a/control-plane/scheduler/src/metrics.rs +++ b/control-plane/scheduler/src/metrics.rs @@ -1,5 +1,7 @@ use axum::response::IntoResponse; -use prometheus::{register_counter_vec, register_histogram_vec, CounterVec, Encoder, HistogramVec, TextEncoder}; +use prometheus::{ + register_counter_vec, register_histogram_vec, CounterVec, Encoder, HistogramVec, TextEncoder, +}; use std::sync::OnceLock; static HTTP_REQUESTS_TOTAL: OnceLock = OnceLock::new(); @@ -46,7 +48,10 @@ pub async fn metrics_handler() -> impl IntoResponse { .encode(&metric_families, &mut buffer) .expect("failed to encode metrics"); ( - [(axum::http::header::CONTENT_TYPE, "text/plain; version=0.0.4")], + [( + axum::http::header::CONTENT_TYPE, + "text/plain; version=0.0.4", + )], buffer, ) } diff --git a/control-plane/scheduler/src/server.rs b/control-plane/scheduler/src/server.rs index 6accf762..2c2d513b 100644 --- a/control-plane/scheduler/src/server.rs +++ b/control-plane/scheduler/src/server.rs @@ -4,7 +4,11 @@ use sea_orm::DatabaseConnection; use std::sync::Arc; use tokio::sync::Mutex; -use crate::{handlers::{internal, workloads}, metrics, services::scheduler::SchedulerService}; +use crate::{ + handlers::{internal, workloads}, + metrics, + services::scheduler::SchedulerService, +}; #[derive(Clone)] pub struct AppState { @@ -21,9 +25,18 @@ pub fn create_router(state: AppState) -> Router { Router::new() .route("/health", get(health_check)) .route("/metrics", get(metrics::metrics_handler)) - .route("/workloads", axum::routing::post(workloads::create_workload)) + .route( + "/workloads", + axum::routing::post(workloads::create_workload), + ) .route("/workloads", get(workloads::list_workloads)) - .route("/workloads/:id", axum::routing::delete(workloads::delete_workload)) - .route("/internal/workloads/status", axum::routing::post(internal::update_container_statuses)) + .route( + "/workloads/:id", + axum::routing::delete(workloads::delete_workload), + ) + .route( + "/internal/workloads/status", + axum::routing::post(internal::update_container_statuses), + ) .with_state(state) } diff --git a/control-plane/scheduler/src/services/etcd.rs b/control-plane/scheduler/src/services/etcd.rs index 18db45fb..ec826a65 100644 --- a/control-plane/scheduler/src/services/etcd.rs +++ b/control-plane/scheduler/src/services/etcd.rs @@ -1,7 +1,7 @@ use etcd_client::Client; use serde::{Deserialize, Serialize}; -use tokio::sync::Mutex; use std::sync::Arc; +use tokio::sync::Mutex; use uuid::Uuid; #[derive(Debug, Clone, Serialize, Deserialize)] @@ -32,10 +32,7 @@ pub async fn put_placement( Ok(()) } -pub async fn delete_placement( - etcd: &Arc>, - workload_id: Uuid, -) -> Result<(), String> { +pub async fn delete_placement(etcd: &Arc>, workload_id: Uuid) -> Result<(), String> { let key = format!("/csfx/placements/{}", workload_id); etcd.lock() diff --git a/control-plane/scheduler/src/services/scheduler.rs b/control-plane/scheduler/src/services/scheduler.rs index 1b196624..1835c5fe 100644 --- a/control-plane/scheduler/src/services/scheduler.rs +++ b/control-plane/scheduler/src/services/scheduler.rs @@ -5,7 +5,9 @@ use std::sync::Arc; use tokio::sync::Mutex; use uuid::Uuid; -use crate::models::workload::{AgentResources, CreateWorkloadRequest, CreateWorkloadResponse, WorkloadStatus}; +use crate::models::workload::{ + AgentResources, CreateWorkloadRequest, CreateWorkloadResponse, WorkloadStatus, +}; use crate::services::etcd::{delete_placement, put_placement, PlacementRecord}; pub struct SchedulerService { @@ -18,7 +20,10 @@ impl SchedulerService { Self { db, etcd } } - pub async fn schedule(&self, req: CreateWorkloadRequest) -> Result { + pub async fn schedule( + &self, + req: CreateWorkloadRequest, + ) -> Result { let workload = crate::db::workloads::create(&self.db, &req) .await .map_err(|e| format!("Failed to persist workload: {}", e))?; @@ -120,7 +125,10 @@ impl SchedulerService { delete_placement(&self.etcd, workload_id).await?; - crate::log_info!("scheduler", &format!("Workload deleted workload_id={}", workload_id)); + crate::log_info!( + "scheduler", + &format!("Workload deleted workload_id={}", workload_id) + ); Ok(()) } diff --git a/control-plane/sdn-controller/src/db/networks.rs b/control-plane/sdn-controller/src/db/networks.rs index 722d9f9c..78cee66e 100644 --- a/control-plane/sdn-controller/src/db/networks.rs +++ b/control-plane/sdn-controller/src/db/networks.rs @@ -2,8 +2,7 @@ use anyhow::{Context, Result}; use chrono::Utc; use entity::entities::{network_members, network_policies, networks}; use sea_orm::{ - ActiveModelTrait, ActiveValue::Set, ColumnTrait, DatabaseConnection, EntityTrait, - QueryFilter, + ActiveModelTrait, ActiveValue::Set, ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter, }; use uuid::Uuid; @@ -24,10 +23,7 @@ pub async fn create_network( updated_at: Set(None), }; - model - .insert(db) - .await - .context("Failed to insert network") + model.insert(db).await.context("Failed to insert network") } pub async fn list_networks(db: &DatabaseConnection) -> Result> { diff --git a/control-plane/sdn-controller/src/handlers/networks.rs b/control-plane/sdn-controller/src/handlers/networks.rs index f799f6e3..e4c55176 100644 --- a/control-plane/sdn-controller/src/handlers/networks.rs +++ b/control-plane/sdn-controller/src/handlers/networks.rs @@ -20,7 +20,10 @@ pub async fn create_network( Ok(network) => Ok((StatusCode::CREATED, Json(json!(network)))), Err(e) => { tracing::error!(error = %e, "Failed to create network"); - Err((StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": e.to_string()})))) + Err(( + StatusCode::INTERNAL_SERVER_ERROR, + Json(json!({"error": e.to_string()})), + )) } } } @@ -32,7 +35,10 @@ pub async fn list_networks( Ok(networks) => Ok(Json(json!(networks))), Err(e) => { tracing::error!(error = %e, "Failed to list networks"); - Err((StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": e.to_string()})))) + Err(( + StatusCode::INTERNAL_SERVER_ERROR, + Json(json!({"error": e.to_string()})), + )) } } } @@ -43,10 +49,16 @@ pub async fn get_network( ) -> Result)> { match db::get_network(&state.db, id).await { Ok(Some(network)) => Ok(Json(json!(network))), - Ok(None) => Err((StatusCode::NOT_FOUND, Json(json!({"error": "Network not found"})))), + Ok(None) => Err(( + StatusCode::NOT_FOUND, + Json(json!({"error": "Network not found"})), + )), Err(e) => { tracing::error!(error = %e, "Failed to get network"); - Err((StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": e.to_string()})))) + Err(( + StatusCode::INTERNAL_SERVER_ERROR, + Json(json!({"error": e.to_string()})), + )) } } } @@ -59,7 +71,10 @@ pub async fn delete_network( Ok(_) => Ok(StatusCode::NO_CONTENT), Err(e) => { tracing::error!(error = %e, "Failed to delete network"); - Err((StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": e.to_string()})))) + Err(( + StatusCode::INTERNAL_SERVER_ERROR, + Json(json!({"error": e.to_string()})), + )) } } } @@ -73,7 +88,10 @@ pub async fn create_policy( Ok(policy) => Ok((StatusCode::CREATED, Json(json!(policy)))), Err(e) => { tracing::error!(error = %e, "Failed to create policy"); - Err((StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": e.to_string()})))) + Err(( + StatusCode::INTERNAL_SERVER_ERROR, + Json(json!({"error": e.to_string()})), + )) } } } @@ -86,7 +104,10 @@ pub async fn list_policies( Ok(policies) => Ok(Json(json!(policies))), Err(e) => { tracing::error!(error = %e, "Failed to list policies"); - Err((StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": e.to_string()})))) + Err(( + StatusCode::INTERNAL_SERVER_ERROR, + Json(json!({"error": e.to_string()})), + )) } } } @@ -98,11 +119,25 @@ pub async fn add_member( ) -> Result)> { let network = match db::get_network(&state.db, network_id).await { Ok(Some(n)) => n, - Ok(None) => return Err((StatusCode::NOT_FOUND, Json(json!({"error": "Network not found"})))), - Err(e) => return Err((StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": e.to_string()})))), + Ok(None) => { + return Err(( + StatusCode::NOT_FOUND, + Json(json!({"error": "Network not found"})), + )) + } + Err(e) => { + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + Json(json!({"error": e.to_string()})), + )) + } }; - let allocated_ip = match state.ipam.allocate(network_id, &network.cidr, req.workload_id).await { + let allocated_ip = match state + .ipam + .allocate(network_id, &network.cidr, req.workload_id) + .await + { Ok(ip) => ip, Err(e) => { tracing::error!(error = %e, "IPAM allocation failed"); @@ -114,7 +149,10 @@ pub async fn add_member( Ok(member) => Ok((StatusCode::CREATED, Json(json!(member)))), Err(e) => { tracing::error!(error = %e, "Failed to add network member"); - Err((StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": e.to_string()})))) + Err(( + StatusCode::INTERNAL_SERVER_ERROR, + Json(json!({"error": e.to_string()})), + )) } } } @@ -127,7 +165,10 @@ pub async fn list_members( Ok(members) => Ok(Json(json!(members))), Err(e) => { tracing::error!(error = %e, "Failed to list network members"); - Err((StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": e.to_string()})))) + Err(( + StatusCode::INTERNAL_SERVER_ERROR, + Json(json!({"error": e.to_string()})), + )) } } } @@ -136,7 +177,9 @@ pub async fn remove_member( State(mut state): State, Path((network_id, workload_id)): Path<(Uuid, Uuid)>, ) -> Result)> { - let members = db::list_members(&state.db, network_id).await.unwrap_or_default(); + let members = db::list_members(&state.db, network_id) + .await + .unwrap_or_default(); let ip = members .iter() .find(|m| m.workload_id == workload_id) @@ -150,7 +193,10 @@ pub async fn remove_member( Ok(_) => Ok(StatusCode::NO_CONTENT), Err(e) => { tracing::error!(error = %e, "Failed to remove network member"); - Err((StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": e.to_string()})))) + Err(( + StatusCode::INTERNAL_SERVER_ERROR, + Json(json!({"error": e.to_string()})), + )) } } } diff --git a/control-plane/sdn-controller/src/logger.rs b/control-plane/sdn-controller/src/logger.rs index 8d9f9d62..727fe564 100644 --- a/control-plane/sdn-controller/src/logger.rs +++ b/control-plane/sdn-controller/src/logger.rs @@ -58,9 +58,7 @@ pub fn init_logger_with_service(service_name: &'static str) { let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")); let fmt_layer = fmt::layer().with_target(false).with_thread_ids(true); - let registry = tracing_subscriber::registry() - .with(filter) - .with(fmt_layer); + let registry = tracing_subscriber::registry().with(filter).with(fmt_layer); match build_otlp_provider(service_name) { Some(provider) => { @@ -79,11 +77,21 @@ pub fn log_message(level: LogLevel, module: &str, location: &str, description: & let lvl: Level = level.into(); match lvl { - Level::ERROR => event!(Level::ERROR, module = %module, location = %location, "{}", description), - Level::WARN => event!(Level::WARN, module = %module, location = %location, "{}", description), - Level::INFO => event!(Level::INFO, module = %module, location = %location, "{}", description), - Level::DEBUG => event!(Level::DEBUG, module = %module, location = %location, "{}", description), - Level::TRACE => event!(Level::TRACE, module = %module, location = %location, "{}", description), + Level::ERROR => { + event!(Level::ERROR, module = %module, location = %location, "{}", description) + } + Level::WARN => { + event!(Level::WARN, module = %module, location = %location, "{}", description) + } + Level::INFO => { + event!(Level::INFO, module = %module, location = %location, "{}", description) + } + Level::DEBUG => { + event!(Level::DEBUG, module = %module, location = %location, "{}", description) + } + Level::TRACE => { + event!(Level::TRACE, module = %module, location = %location, "{}", description) + } } } diff --git a/control-plane/sdn-controller/src/metrics.rs b/control-plane/sdn-controller/src/metrics.rs index 64d068b0..aa5a9792 100644 --- a/control-plane/sdn-controller/src/metrics.rs +++ b/control-plane/sdn-controller/src/metrics.rs @@ -1,5 +1,7 @@ use axum::response::IntoResponse; -use prometheus::{register_counter_vec, register_histogram_vec, CounterVec, Encoder, HistogramVec, TextEncoder}; +use prometheus::{ + register_counter_vec, register_histogram_vec, CounterVec, Encoder, HistogramVec, TextEncoder, +}; use std::sync::OnceLock; static HTTP_REQUESTS_TOTAL: OnceLock = OnceLock::new(); @@ -46,7 +48,10 @@ pub async fn metrics_handler() -> impl IntoResponse { .encode(&metric_families, &mut buffer) .expect("failed to encode metrics"); ( - [(axum::http::header::CONTENT_TYPE, "text/plain; version=0.0.4")], + [( + axum::http::header::CONTENT_TYPE, + "text/plain; version=0.0.4", + )], buffer, ) } diff --git a/control-plane/sdn-controller/src/server.rs b/control-plane/sdn-controller/src/server.rs index 86166c32..ea753a80 100644 --- a/control-plane/sdn-controller/src/server.rs +++ b/control-plane/sdn-controller/src/server.rs @@ -1,11 +1,7 @@ use axum::{http::StatusCode, response::IntoResponse, routing::get, Router}; use sea_orm::DatabaseConnection; -use crate::{ - handlers::networks, - metrics, - services::ipam::IpamService, -}; +use crate::{handlers::networks, metrics, services::ipam::IpamService}; #[derive(Clone)] pub struct AppState { @@ -27,7 +23,10 @@ pub fn create_router(state: AppState) -> Router { Router::new() .route("/health", get(health_check)) .route("/metrics", get(metrics::metrics_handler)) - .route("/networks", get(networks::list_networks).post(networks::create_network)) + .route( + "/networks", + get(networks::list_networks).post(networks::create_network), + ) .route( "/networks/:id", get(networks::get_network).delete(networks::delete_network), diff --git a/control-plane/sdn-controller/src/services/ipam.rs b/control-plane/sdn-controller/src/services/ipam.rs index f1753bc7..bc9c875d 100644 --- a/control-plane/sdn-controller/src/services/ipam.rs +++ b/control-plane/sdn-controller/src/services/ipam.rs @@ -15,7 +15,12 @@ impl IpamService { Self { etcd } } - pub async fn allocate(&mut self, network_id: Uuid, cidr: &str, workload_id: Uuid) -> Result { + pub async fn allocate( + &mut self, + network_id: Uuid, + cidr: &str, + workload_id: Uuid, + ) -> Result { let (base, prefix_len) = parse_cidr(cidr)?; let total = 1u32 << (32 - prefix_len); @@ -60,7 +65,11 @@ impl IpamService { ) -> Result<()> { let base_key = format!("/csfx/peers/{}/{}", network_id, node_id); self.etcd - .put(format!("{}/overlay_ip", base_key).as_str(), overlay_ip, None) + .put( + format!("{}/overlay_ip", base_key).as_str(), + overlay_ip, + None, + ) .await .context("etcd put overlay_ip failed")?; diff --git a/control-plane/shared/entity/src/entities/mod.rs b/control-plane/shared/entity/src/entities/mod.rs index c002d753..d3ea77fd 100644 --- a/control-plane/shared/entity/src/entities/mod.rs +++ b/control-plane/shared/entity/src/entities/mod.rs @@ -1,13 +1,16 @@ pub mod agent_api_keys; -pub mod bootstrap_tokens; -pub mod failover_events; pub mod agent_certificates; pub mod agent_metrics; pub mod agents; +pub mod bootstrap_tokens; pub mod certificate_revocations; pub mod config; +pub mod failover_events; pub mod invalid_jwt; pub mod key; +pub mod network_members; +pub mod network_policies; +pub mod networks; pub mod organization; pub mod permission; pub mod registry_tokens; @@ -15,24 +18,24 @@ pub mod role; pub mod role_permission; pub mod user; pub mod user_organization; -pub mod network_members; -pub mod network_policies; -pub mod networks; +pub mod user_ssh_keys; pub mod volume_snapshots; pub mod volumes; -pub mod user_ssh_keys; pub mod workloads; pub use agent_api_keys::Entity as AgentApiKeys; -pub use bootstrap_tokens::Entity as BootstrapTokens; -pub use failover_events::Entity as FailoverEvents; pub use agent_certificates::Entity as AgentCertificates; pub use agent_metrics::Entity as AgentMetrics; pub use agents::Entity as Agents; +pub use bootstrap_tokens::Entity as BootstrapTokens; pub use certificate_revocations::Entity as CertificateRevocations; pub use config::Entity as Config; +pub use failover_events::Entity as FailoverEvents; pub use invalid_jwt::Entity as InvalidJwt; pub use key::Entity as Key; +pub use network_members::Entity as NetworkMembers; +pub use network_policies::Entity as NetworkPolicies; +pub use networks::Entity as Networks; pub use organization::Entity as Organization; pub use permission::Entity as Permission; pub use registry_tokens::Entity as RegistryTokens; @@ -40,10 +43,7 @@ pub use role::Entity as Role; pub use role_permission::Entity as RolePermission; pub use user::Entity as User; pub use user_organization::Entity as UserOrganization; -pub use network_members::Entity as NetworkMembers; -pub use network_policies::Entity as NetworkPolicies; -pub use networks::Entity as Networks; +pub use user_ssh_keys::Entity as UserSshKeys; pub use volume_snapshots::Entity as VolumeSnapshots; pub use volumes::Entity as Volumes; -pub use user_ssh_keys::Entity as UserSshKeys; pub use workloads::Entity as Workloads; diff --git a/control-plane/shared/migration/src/m20260304_000000_pki_certificates.rs b/control-plane/shared/migration/src/m20260304_000000_pki_certificates.rs index 00e6a5e3..322004a9 100644 --- a/control-plane/shared/migration/src/m20260304_000000_pki_certificates.rs +++ b/control-plane/shared/migration/src/m20260304_000000_pki_certificates.rs @@ -114,7 +114,11 @@ impl MigrationTrait for Migration { .await?; manager - .drop_table(Table::drop().table(CertificateRevocations::Table).to_owned()) + .drop_table( + Table::drop() + .table(CertificateRevocations::Table) + .to_owned(), + ) .await?; manager diff --git a/control-plane/shared/migration/src/m20260309_000000_add_bootstrap_tokens.rs b/control-plane/shared/migration/src/m20260309_000000_add_bootstrap_tokens.rs index 0c695eb5..2d689321 100644 --- a/control-plane/shared/migration/src/m20260309_000000_add_bootstrap_tokens.rs +++ b/control-plane/shared/migration/src/m20260309_000000_add_bootstrap_tokens.rs @@ -11,16 +11,56 @@ impl MigrationTrait for Migration { Table::create() .table(BootstrapTokens::Table) .if_not_exists() - .col(ColumnDef::new(BootstrapTokens::Id).uuid().not_null().primary_key()) - .col(ColumnDef::new(BootstrapTokens::Token).string().not_null().unique_key()) + .col( + ColumnDef::new(BootstrapTokens::Id) + .uuid() + .not_null() + .primary_key(), + ) + .col( + ColumnDef::new(BootstrapTokens::Token) + .string() + .not_null() + .unique_key(), + ) .col(ColumnDef::new(BootstrapTokens::Description).string().null()) - .col(ColumnDef::new(BootstrapTokens::CreatedBy).string().not_null()) - .col(ColumnDef::new(BootstrapTokens::CreatedAt).date_time().not_null()) - .col(ColumnDef::new(BootstrapTokens::ExpiresAt).date_time().not_null()) - .col(ColumnDef::new(BootstrapTokens::MaxUses).integer().not_null()) - .col(ColumnDef::new(BootstrapTokens::UseCount).integer().not_null().default(0)) - .col(ColumnDef::new(BootstrapTokens::Revoked).boolean().not_null().default(false)) - .col(ColumnDef::new(BootstrapTokens::RevokedAt).date_time().null()) + .col( + ColumnDef::new(BootstrapTokens::CreatedBy) + .string() + .not_null(), + ) + .col( + ColumnDef::new(BootstrapTokens::CreatedAt) + .date_time() + .not_null(), + ) + .col( + ColumnDef::new(BootstrapTokens::ExpiresAt) + .date_time() + .not_null(), + ) + .col( + ColumnDef::new(BootstrapTokens::MaxUses) + .integer() + .not_null(), + ) + .col( + ColumnDef::new(BootstrapTokens::UseCount) + .integer() + .not_null() + .default(0), + ) + .col( + ColumnDef::new(BootstrapTokens::Revoked) + .boolean() + .not_null() + .default(false), + ) + .col( + ColumnDef::new(BootstrapTokens::RevokedAt) + .date_time() + .null(), + ) .to_owned(), ) .await diff --git a/control-plane/shared/migration/src/m20260523_000000_add_ssh_keys.rs b/control-plane/shared/migration/src/m20260523_000000_add_ssh_keys.rs index 3ded61f5..f24b5d3a 100644 --- a/control-plane/shared/migration/src/m20260523_000000_add_ssh_keys.rs +++ b/control-plane/shared/migration/src/m20260523_000000_add_ssh_keys.rs @@ -11,7 +11,12 @@ impl MigrationTrait for Migration { Table::create() .table(UserSshKeys::Table) .if_not_exists() - .col(ColumnDef::new(UserSshKeys::Id).uuid().not_null().primary_key()) + .col( + ColumnDef::new(UserSshKeys::Id) + .uuid() + .not_null() + .primary_key(), + ) .col(ColumnDef::new(UserSshKeys::UserId).uuid().not_null()) .col(ColumnDef::new(UserSshKeys::Name).string().not_null()) .col(ColumnDef::new(UserSshKeys::PublicKey).text().not_null()) @@ -21,11 +26,7 @@ impl MigrationTrait for Migration { .timestamp() .not_null(), ) - .col( - ColumnDef::new(UserSshKeys::ExpiresAt) - .timestamp() - .null(), - ) + .col(ColumnDef::new(UserSshKeys::ExpiresAt).timestamp().null()) .to_owned(), ) .await diff --git a/control-plane/volume-manager/src/ceph/storage/pool.rs b/control-plane/volume-manager/src/ceph/storage/pool.rs index 3d558860..01df096a 100644 --- a/control-plane/volume-manager/src/ceph/storage/pool.rs +++ b/control-plane/volume-manager/src/ceph/storage/pool.rs @@ -1,5 +1,5 @@ -use crate::ceph::core::CephClient; use super::types::*; +use crate::ceph::core::CephClient; use anyhow::{Context, Result}; pub struct PoolManager { @@ -26,7 +26,9 @@ impl PoolManager { .arg(pool.pg_num.to_string()) .arg(pool.pgp_num.to_string()); - self.client.execute(cmd).await + self.client + .execute(cmd) + .await .context("Failed to create pool")?; // Replikation setzen @@ -37,7 +39,9 @@ impl PoolManager { .arg("size") .arg(pool.size.to_string()); - self.client.execute(cmd).await + self.client + .execute(cmd) + .await .context("Failed to set pool size")?; // Min size setzen @@ -48,7 +52,9 @@ impl PoolManager { .arg("min_size") .arg(pool.min_size.to_string()); - self.client.execute(cmd).await + self.client + .execute(cmd) + .await .context("Failed to set pool min_size")?; // RBD Pool initialisieren @@ -59,7 +65,9 @@ impl PoolManager { .arg(&pool.name) .arg("rbd"); - self.client.execute(cmd).await + self.client + .execute(cmd) + .await .context("Failed to enable RBD application")?; crate::log_info!( @@ -84,7 +92,9 @@ impl PoolManager { .arg(pool_name) // Bestätigung .arg("--yes-i-really-really-mean-it"); - self.client.execute(cmd).await + self.client + .execute(cmd) + .await .context("Failed to delete pool")?; Ok(()) @@ -93,19 +103,17 @@ impl PoolManager { /// Listet alle Pools auf pub async fn list_pools(&self) -> Result> { crate::log_debug!("pool_manager", "Listing all Ceph pools"); - - let cmd = CephCommand::new("osd") - .arg("pool") - .arg("ls"); + + let cmd = CephCommand::new("osd").arg("pool").arg("ls"); let output = self.client.execute(cmd).await?; let pools: Vec = serde_json::from_str(&output)?; - + crate::log_debug!( "pool_manager", &format!("Found {} pools: {}", pools.len(), pools.join(", ")) ); - + Ok(pools) } diff --git a/control-plane/volume-manager/src/db/volumes.rs b/control-plane/volume-manager/src/db/volumes.rs index 44a3d2a1..7e6921a3 100644 --- a/control-plane/volume-manager/src/db/volumes.rs +++ b/control-plane/volume-manager/src/db/volumes.rs @@ -1,8 +1,8 @@ use chrono::Utc; use entity::entities::{volume_snapshots, volumes}; use sea_orm::{ - ActiveModelTrait, ActiveValue::Set, DatabaseConnection, EntityTrait, ModelTrait, - QueryFilter, ColumnTrait, + ActiveModelTrait, ActiveValue::Set, ColumnTrait, DatabaseConnection, EntityTrait, ModelTrait, + QueryFilter, }; use uuid::Uuid; @@ -14,7 +14,10 @@ pub async fn create( db: &DatabaseConnection, req: &CreateVolumeRequest, ) -> Result { - let pool = req.pool.clone().unwrap_or_else(|| "csfx-volumes".to_string()); + let pool = req + .pool + .clone() + .unwrap_or_else(|| "csfx-volumes".to_string()); let image_name = format!("{}-{}", req.name, Uuid::new_v4()); let model = volumes::ActiveModel { diff --git a/control-plane/volume-manager/src/etcd/ha/health.rs b/control-plane/volume-manager/src/etcd/ha/health.rs index f456dd60..c22c65a3 100644 --- a/control-plane/volume-manager/src/etcd/ha/health.rs +++ b/control-plane/volume-manager/src/etcd/ha/health.rs @@ -40,7 +40,14 @@ impl HealthChecker { let is_healthy = time_since_heartbeat < self.timeout; if !is_healthy { - log_warn!("etcd::ha::health", &format!("Node {} is unhealthy ({}s since last heartbeat)", node.node_id, time_since_heartbeat.as_secs())); + log_warn!( + "etcd::ha::health", + &format!( + "Node {} is unhealthy ({}s since last heartbeat)", + node.node_id, + time_since_heartbeat.as_secs() + ) + ); } NodeHealthStatus { @@ -69,7 +76,10 @@ impl HealthChecker { let healthy = health_statuses.iter().filter(|s| s.is_healthy).count(); let unhealthy = total - healthy; - log_info!("etcd::ha::health", &format!("Cluster Health: {}/{} nodes healthy", healthy, total)); + log_info!( + "etcd::ha::health", + &format!("Cluster Health: {}/{} nodes healthy", healthy, total) + ); ClusterHealthSummary { total_nodes: total, diff --git a/control-plane/volume-manager/src/etcd/state/manager.rs b/control-plane/volume-manager/src/etcd/state/manager.rs index 8cd5cdfc..1b162f32 100644 --- a/control-plane/volume-manager/src/etcd/state/manager.rs +++ b/control-plane/volume-manager/src/etcd/state/manager.rs @@ -26,7 +26,10 @@ impl StateManager { encrypted: bool, ) -> Result { let volume = VolumeState::new(name, size_gb, pool, encrypted); - log_info!("etcd::state::manager", &format!("Creating volume: {} ({})", volume.name, volume.id)); + log_info!( + "etcd::state::manager", + &format!("Creating volume: {} ({})", volume.name, volume.id) + ); self.storage.save_volume(&volume).await?; Ok(volume) } @@ -45,7 +48,10 @@ impl StateManager { volume.update_status(status); self.storage.save_volume(&volume).await?; - log_info!("etcd::state::manager", &format!("Updated volume {} status to {:?}", id, volume.status)); + log_info!( + "etcd::state::manager", + &format!("Updated volume {} status to {:?}", id, volume.status) + ); Ok(()) } @@ -84,7 +90,10 @@ impl StateManager { volumes: Vec::new(), }; - log_info!("etcd::state::manager", &format!("Registering node: {}", node_id)); + log_info!( + "etcd::state::manager", + &format!("Registering node: {}", node_id) + ); self.storage.save_node(&node).await?; Ok(node) } @@ -110,7 +119,10 @@ impl StateManager { .await? .ok_or_else(|| EtcdError::StateOperation(format!("Node {} not found", node_id)))?; - log_warn!("etcd::state::manager", &format!("Marking node {} as offline", node_id)); + log_warn!( + "etcd::state::manager", + &format!("Marking node {} as offline", node_id) + ); node.status = NodeStatus::Offline; self.storage.save_node(&node).await } @@ -125,7 +137,10 @@ impl StateManager { node.role = role; self.storage.save_node(&node).await?; - log_info!("etcd::state::manager", &format!("Set node {} role to {:?}", node_id, node.role)); + log_info!( + "etcd::state::manager", + &format!("Set node {} role to {:?}", node_id, node.role) + ); Ok(()) } @@ -161,7 +176,10 @@ impl StateManager { created_at: chrono::Utc::now(), }; - log_info!("etcd::state::manager", &format!("Creating snapshot: {} for volume {}", name, volume_id)); + log_info!( + "etcd::state::manager", + &format!("Creating snapshot: {} for volume {}", name, volume_id) + ); self.storage.save_snapshot(&snapshot).await?; Ok(snapshot) } diff --git a/control-plane/volume-manager/src/handlers/volumes.rs b/control-plane/volume-manager/src/handlers/volumes.rs index bed3cefc..c6255977 100644 --- a/control-plane/volume-manager/src/handlers/volumes.rs +++ b/control-plane/volume-manager/src/handlers/volumes.rs @@ -34,10 +34,7 @@ pub async fn list_volumes(State(state): State) -> impl IntoResponse { } } -pub async fn get_volume( - State(state): State, - Path(id): Path, -) -> impl IntoResponse { +pub async fn get_volume(State(state): State, Path(id): Path) -> impl IntoResponse { match state.volume_service.get_volume(id).await { Ok(Some(vol)) => (StatusCode::OK, Json(serde_json::json!(vol))).into_response(), Ok(None) => ( diff --git a/control-plane/volume-manager/src/logger.rs b/control-plane/volume-manager/src/logger.rs index 8d9f9d62..727fe564 100644 --- a/control-plane/volume-manager/src/logger.rs +++ b/control-plane/volume-manager/src/logger.rs @@ -58,9 +58,7 @@ pub fn init_logger_with_service(service_name: &'static str) { let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")); let fmt_layer = fmt::layer().with_target(false).with_thread_ids(true); - let registry = tracing_subscriber::registry() - .with(filter) - .with(fmt_layer); + let registry = tracing_subscriber::registry().with(filter).with(fmt_layer); match build_otlp_provider(service_name) { Some(provider) => { @@ -79,11 +77,21 @@ pub fn log_message(level: LogLevel, module: &str, location: &str, description: & let lvl: Level = level.into(); match lvl { - Level::ERROR => event!(Level::ERROR, module = %module, location = %location, "{}", description), - Level::WARN => event!(Level::WARN, module = %module, location = %location, "{}", description), - Level::INFO => event!(Level::INFO, module = %module, location = %location, "{}", description), - Level::DEBUG => event!(Level::DEBUG, module = %module, location = %location, "{}", description), - Level::TRACE => event!(Level::TRACE, module = %module, location = %location, "{}", description), + Level::ERROR => { + event!(Level::ERROR, module = %module, location = %location, "{}", description) + } + Level::WARN => { + event!(Level::WARN, module = %module, location = %location, "{}", description) + } + Level::INFO => { + event!(Level::INFO, module = %module, location = %location, "{}", description) + } + Level::DEBUG => { + event!(Level::DEBUG, module = %module, location = %location, "{}", description) + } + Level::TRACE => { + event!(Level::TRACE, module = %module, location = %location, "{}", description) + } } } diff --git a/control-plane/volume-manager/src/metrics.rs b/control-plane/volume-manager/src/metrics.rs index 64d068b0..aa5a9792 100644 --- a/control-plane/volume-manager/src/metrics.rs +++ b/control-plane/volume-manager/src/metrics.rs @@ -1,5 +1,7 @@ use axum::response::IntoResponse; -use prometheus::{register_counter_vec, register_histogram_vec, CounterVec, Encoder, HistogramVec, TextEncoder}; +use prometheus::{ + register_counter_vec, register_histogram_vec, CounterVec, Encoder, HistogramVec, TextEncoder, +}; use std::sync::OnceLock; static HTTP_REQUESTS_TOTAL: OnceLock = OnceLock::new(); @@ -46,7 +48,10 @@ pub async fn metrics_handler() -> impl IntoResponse { .encode(&metric_families, &mut buffer) .expect("failed to encode metrics"); ( - [(axum::http::header::CONTENT_TYPE, "text/plain; version=0.0.4")], + [( + axum::http::header::CONTENT_TYPE, + "text/plain; version=0.0.4", + )], buffer, ) } diff --git a/control-plane/volume-manager/src/server.rs b/control-plane/volume-manager/src/server.rs index f2e06c07..54c61574 100644 --- a/control-plane/volume-manager/src/server.rs +++ b/control-plane/volume-manager/src/server.rs @@ -24,10 +24,22 @@ pub fn create_router(state: AppState) -> Router { .route("/volumes", axum::routing::post(volumes::create_volume)) .route("/volumes", get(volumes::list_volumes)) .route("/volumes/:id", get(volumes::get_volume)) - .route("/volumes/:id", axum::routing::delete(volumes::delete_volume)) - .route("/volumes/:id/attach", axum::routing::post(volumes::attach_volume)) - .route("/volumes/:id/detach", axum::routing::post(volumes::detach_volume)) - .route("/volumes/:id/snapshots", axum::routing::post(volumes::create_snapshot)) + .route( + "/volumes/:id", + axum::routing::delete(volumes::delete_volume), + ) + .route( + "/volumes/:id/attach", + axum::routing::post(volumes::attach_volume), + ) + .route( + "/volumes/:id/detach", + axum::routing::post(volumes::detach_volume), + ) + .route( + "/volumes/:id/snapshots", + axum::routing::post(volumes::create_snapshot), + ) .route("/volumes/:id/snapshots", get(volumes::list_snapshots)) .with_state(state) } diff --git a/control-plane/volume-manager/src/services/volume.rs b/control-plane/volume-manager/src/services/volume.rs index 11585b89..130dce45 100644 --- a/control-plane/volume-manager/src/services/volume.rs +++ b/control-plane/volume-manager/src/services/volume.rs @@ -1,4 +1,4 @@ -use crate::{log_warn}; +use crate::log_warn; use etcd_client::Client as EtcdClient; use sea_orm::DatabaseConnection; use std::sync::Arc; @@ -24,7 +24,11 @@ impl VolumeService { etcd: Arc>, ceph: Option>, ) -> Self { - Self { db, _etcd: etcd, ceph } + Self { + db, + _etcd: etcd, + ceph, + } } pub async fn create_volume(&self, req: CreateVolumeRequest) -> Result { @@ -67,7 +71,11 @@ impl VolumeService { .ok_or_else(|| format!("Volume {} not found", id))?; if let Some(ceph) = &self.ceph { - if let Err(e) = ceph.rbd_manager.delete_image(&model.pool, &model.image_name).await { + if let Err(e) = ceph + .rbd_manager + .delete_image(&model.pool, &model.image_name) + .await + { log_warn!("volume_service", &format!("Ceph RBD delete failed: {}", e)); } } @@ -86,7 +94,11 @@ impl VolumeService { .ok_or_else(|| format!("Volume {} not found", volume_id))?; let device = if let Some(ceph) = &self.ceph { - match ceph.rbd_manager.map_device(&model.pool, &model.image_name).await { + match ceph + .rbd_manager + .map_device(&model.pool, &model.image_name) + .await + { Ok(dev) => Some(dev), Err(e) => { log_warn!("volume_service", &format!("Ceph RBD map failed: {}", e)); @@ -141,7 +153,10 @@ impl VolumeService { .create_snapshot(&model.pool, &model.image_name, &req.name) .await { - log_warn!("volume_service", &format!("Ceph snapshot create failed: {}", e)); + log_warn!( + "volume_service", + &format!("Ceph snapshot create failed: {}", e) + ); } }