diff --git a/.github/actions/runner-fallback/action.yml b/.github/actions/runner-fallback/action.yml new file mode 100644 index 0000000..9b320f2 --- /dev/null +++ b/.github/actions/runner-fallback/action.yml @@ -0,0 +1,61 @@ +# actions/runner-fallback/action.yml +name: "Runner Fallback" +description: | + Chooses a self-hosted runner when one with the required labels is online, + otherwise returns a fallback GitHub-hosted label. +inputs: + primary-runner: + description: 'Comma-separated label list for the preferred self-hosted runner (e.g. "self-hosted,linux")' + required: true + fallback-runner: + description: 'Comma-separated label list or single label for the fallback (e.g. "ubuntu-latest")' + required: true + github-token: + description: "PAT or GITHUB_TOKEN with `repo` scope" + required: true + +outputs: + use-runner: + description: "JSON array of labels you can feed straight into runs-on" + value: ${{ steps.pick.outputs.use-runner }} + +runs: + using: "composite" + steps: + - name: Check self-hosted fleet + id: pick + shell: bash + env: + TOKEN: ${{ inputs.github-token }} + PRIMARY: ${{ inputs.primary-runner }} + FALLBACK: ${{ inputs.fallback-runner }} + run: | + # -------- helper ----------- + to_json_array () { + local list="$1"; IFS=',' read -ra L <<<"$list" + printf '['; printf '"%s",' "${L[@]}"; printf ']' + } + # -------- query API --------- + repo="${{ github.repository }}" + runners=$(curl -s -H "Authorization: Bearer $TOKEN" \ + -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/$repo/actions/runners?per_page=100") + # Split wanted labels + IFS=',' read -ra WANT <<<"$PRIMARY" + online_found=0 + while read -r row; do + labels=$(jq -r '.labels[].name' <<<"$row") + ok=1 + for w in "${WANT[@]}"; do + grep -Fxq "$w" <<<"$labels" || { ok=0; break; } + done + [ "$ok" -eq 1 ] && { online_found=1; break; } + done < <(jq -c '.runners[] | select(.status=="online")' <<<"$runners") + + if [ "$online_found" -eq 1 ]; then + echo "✅ Self-hosted runner online." + echo "use-runner=$(to_json_array "$PRIMARY")" >>"$GITHUB_OUTPUT" + else + echo "❌ No matching self-hosted runner online - using fallback." + echo "use-runner=$(to_json_array "$FALLBACK")" >>"$GITHUB_OUTPUT" + fi diff --git a/.github/runners/runner-arm/Dockerfile b/.github/runners/runner-arm/Dockerfile new file mode 100644 index 0000000..741114e --- /dev/null +++ b/.github/runners/runner-arm/Dockerfile @@ -0,0 +1,30 @@ +FROM ubuntu:latest + +ARG RUNNER_VERSION="2.323.0" + +# Prevents installdependencies.sh from prompting the user and blocking the image creation +ARG DEBIAN_FRONTEND=noninteractive + +RUN apt update -y && apt upgrade -y && useradd -m docker +RUN apt install -y --no-install-recommends \ + curl jq build-essential libssl-dev libffi-dev python3 python3-venv python3-dev python3-pip \ + # dot net core dependencies + libicu74 libssl3 libkrb5-3 zlib1g libcurl4 + + +RUN cd /home/docker && mkdir actions-runner && cd actions-runner \ + && curl -O -L https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-arm64-${RUNNER_VERSION}.tar.gz \ + && tar xzf ./actions-runner-linux-arm64-${RUNNER_VERSION}.tar.gz + +RUN chown -R docker ~docker && /home/docker/actions-runner/bin/installdependencies.sh + +COPY entrypoint.sh entrypoint.sh + +# make the script executable +RUN chmod +x entrypoint.sh + +# since the config and run script for actions are not allowed to be run by root, +# set the user to "docker" so all subsequent commands are run as the docker user +USER docker + +ENTRYPOINT ["./entrypoint.sh"] \ No newline at end of file diff --git a/.github/runner/docker-compose.yml b/.github/runners/runner-arm/docker-compose.yml similarity index 100% rename from .github/runner/docker-compose.yml rename to .github/runners/runner-arm/docker-compose.yml diff --git a/.github/runner/entrypoint.sh b/.github/runners/runner-arm/entrypoint.sh similarity index 100% rename from .github/runner/entrypoint.sh rename to .github/runners/runner-arm/entrypoint.sh diff --git a/.github/runner/example.env b/.github/runners/runner-arm/example.env similarity index 100% rename from .github/runner/example.env rename to .github/runners/runner-arm/example.env diff --git a/.github/runner/start.sh b/.github/runners/runner-arm/start.sh similarity index 100% rename from .github/runner/start.sh rename to .github/runners/runner-arm/start.sh diff --git a/.github/runner/Dockerfile b/.github/runners/runner-x64/Dockerfile similarity index 100% rename from .github/runner/Dockerfile rename to .github/runners/runner-x64/Dockerfile diff --git a/.github/runners/runner-x64/docker-compose.yml b/.github/runners/runner-x64/docker-compose.yml new file mode 100644 index 0000000..3ecee8e --- /dev/null +++ b/.github/runners/runner-x64/docker-compose.yml @@ -0,0 +1,18 @@ +# docker-compose.yml + +services: + github-runner: + build: + context: . + args: + RUNNER_VERSION: 2.323.0 + # container_name commented to allow for multiple runners + # container_name: github-runner + env_file: + - .env + volumes: + - runner-work:/home/runner/actions-runner/_work + restart: unless-stopped + +volumes: + runner-work: diff --git a/.github/runners/runner-x64/entrypoint.sh b/.github/runners/runner-x64/entrypoint.sh new file mode 100644 index 0000000..97e8609 --- /dev/null +++ b/.github/runners/runner-x64/entrypoint.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +REPOSITORY=$REPO +ACCESS_TOKEN=$GH_TOKEN +LABELS=$RUNNER_LABELS + +# echo "REPO ${REPOSITORY}" +# echo "ACCESS_TOKEN ${ACCESS_TOKEN}" + +REG_TOKEN=$(curl -X POST -H "Authorization: token ${ACCESS_TOKEN}" -H "Accept: application/vnd.github+json" https://api.github.com/repos/${REPOSITORY}/actions/runners/registration-token | jq .token --raw-output) + +cd /home/docker/actions-runner + +./config.sh --url https://github.com/${REPOSITORY} --token ${REG_TOKEN} --labels ${LABELS} + +cleanup() { + echo "Removing runner..." + ./config.sh remove --unattended --token ${REG_TOKEN} +} + +trap 'cleanup; exit 130' INT +trap 'cleanup; exit 143' TERM + +./run.sh & wait $! \ No newline at end of file diff --git a/.github/runners/runner-x64/example.env b/.github/runners/runner-x64/example.env new file mode 100644 index 0000000..6ab2dc8 --- /dev/null +++ b/.github/runners/runner-x64/example.env @@ -0,0 +1,9 @@ + +# Repository name +REPO="Magnus167/rustframe" + +# GitHub runner token +GH_TOKEN="some_token_here" + +# Labels for the runner +RUNNER_LABELS=self-hosted-linux,linux \ No newline at end of file diff --git a/.github/runners/runner-x64/start.sh b/.github/runners/runner-x64/start.sh new file mode 100644 index 0000000..891f4d4 --- /dev/null +++ b/.github/runners/runner-x64/start.sh @@ -0,0 +1,4 @@ + + +docker compose up -d --build +# docker compose up -d --build --scale github-runner=2 diff --git a/.github/workflows/docs-and-testcov.yml b/.github/workflows/docs-and-testcov.yml index 2307ec2..94eab46 100644 --- a/.github/workflows/docs-and-testcov.yml +++ b/.github/workflows/docs-and-testcov.yml @@ -17,8 +17,23 @@ permissions: pages: write jobs: - docs-and-testcov: + pick-runner: runs-on: ubuntu-latest + outputs: + runner: ${{ steps.choose.outputs.use-runner }} + steps: + - uses: actions/checkout@v4 + + - id: choose + uses: ./.github/actions/runner-fallback + with: + primary-runner: "self-hosted,ubuntu-latest" + fallback-runner: "ubuntu-latest" + github-token: ${{ secrets.GITHUB_TOKEN }} + + docs-and-testcov: + needs: pick-runner + runs-on: ${{ fromJson(needs.pick-runner.outputs.runner) }} steps: - uses: actions/checkout@v4 @@ -28,10 +43,10 @@ jobs: with: toolchain: stable override: true - + - name: Build documentation run: cargo doc --no-deps --release - + - name: Prepare documentation for Pages run: | @@ -45,28 +60,26 @@ jobs: run: | mkdir -p testcov cargo tarpaulin --engine llvm --out Html --out Json - + - name: Check for tarpaulin-report.html run: | if [ ! -f tarpaulin-report.html ]; then echo "tarpaulin-report.html not found!" exit 1 fi - + - name: Export tarpaulin coverage badge JSON + # extract raw coverage and round to 2 decimal places run: | - # extract raw coverage - coverage=$(jq '.coverage' tarpaulin-report.json) - # round to 2 decimal places - formatted=$(printf "%.2f" "$coverage") - # build the badge JSON using the pre-formatted string - jq --arg message "$formatted" \ - '{schemaVersion:1, - label:"tarpaulin-report", - message:$message, - color:"blue"}' \ - tarpaulin-report.json \ - > tarpaulin-badge.json + coverage=$(jq '.coverage' tarpaulin-report.json) + formatted=$(printf "%.2f" "$coverage") + jq --arg message "$formatted" \ + '{schemaVersion:1, + label:"tarpaulin-report", + message:$message, + color:"blue"}' \ + tarpaulin-report.json \ + > tarpaulin-badge.json - name: Save last commit date JSON run: | @@ -78,7 +91,7 @@ jobs: color:"blue"}' \ <(echo '{}') \ > last-commit-date.json - + - name: Copy files to output directory run: | mkdir output diff --git a/.github/workflows/run-unit-tests.yml b/.github/workflows/run-unit-tests.yml index 2811fca..c6e2f91 100644 --- a/.github/workflows/run-unit-tests.yml +++ b/.github/workflows/run-unit-tests.yml @@ -11,10 +11,24 @@ concurrency: cancel-in-progress: true jobs: + pick-runner: + runs-on: ubuntu-latest + outputs: + runner: ${{ steps.choose.outputs.use-runner }} + steps: + - uses: actions/checkout@v4 + - id: choose + uses: ./.github/actions/runner-fallback + with: + primary-runner: "self-hosted,ubuntu-latest" + fallback-runner: "ubuntu-latest" + github-token: ${{ secrets.GITHUB_TOKEN }} + run-unit-tests: + needs: pick-runner if: github.event.pull_request.draft == false name: run-unit-tests - runs-on: ubuntu-latest + runs-on: ${{ fromJson(needs.pick-runner.outputs.runner) }} env: CARGO_TERM_COLOR: always @@ -24,12 +38,16 @@ jobs: run: rustup update stable - name: Install cargo-llvm-cov uses: taiki-e/install-action@cargo-llvm-cov - - name: Generate code coverage - run: cargo llvm-cov --all-features --workspace --lcov --output-path lcov.info - - name: Run doc-tests + + - name: Run doctests run: cargo test --doc --all-features --workspace --release + + - name: Run unit tests with code coverage + run: cargo llvm-cov --all-features --release --workspace --lcov --output-path lcov.info + - name: Test docs generation run: cargo doc --no-deps --release + - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 with: