diff --git a/.config/mise/tasks/github-actions/archive-storage-contract b/.config/mise/tasks/github-actions/archive-storage-contract new file mode 100755 index 000000000..0a72f9b9f --- /dev/null +++ b/.config/mise/tasks/github-actions/archive-storage-contract @@ -0,0 +1,74 @@ +#!/bin/sh +#MISE description="Run an archive storage CI lifecycle phase" + +set -eu + +root=$(CDPATH='' cd -- "$(dirname -- "$0")/../../../.." && pwd) +xunit_project="$root/src/EventStore.Core.XUnit.Tests/EventStore.Core.XUnit.Tests.csproj" +core_project="$root/src/EventStore.Core.Tests/EventStore.Core.Tests.csproj" +phase=${1:?archive storage phase is required} +platform=${TARGET_ARCH:-x64} + +run_recovery_test() { + dotnet test \ + --configuration Release \ + --no-build \ + -p:Platform="$platform" \ + -p:RunS3Tests=true \ + --filter "FullyQualifiedName~S3RestartRecoveryTests" \ + --logger:GitHubActions \ + "$xunit_project" +} + +cd "$root" + +case "$phase" in +contract) + dotnet test \ + --configuration Release \ + -p:Platform="$platform" \ + -p:ContinuousIntegrationBuild=true \ + -p:RunS3Tests=true \ + --filter "FullyQualifiedName~S3ReaderTests|FullyQualifiedName~S3WriterTests|FullyQualifiedName~S3MetricsTests|FullyQualifiedName~S3FixtureLifecycleTests" \ + --logger:GitHubActions \ + "$xunit_project" + ;; +recovery) + if [ "${EVENTSTORE_S3_RECOVERY_PHASE:-}" = unavailable ]; then + timeout 20 dotnet test \ + --configuration Release \ + --no-build \ + -p:Platform="$platform" \ + -p:RunS3Tests=true \ + --filter "FullyQualifiedName~S3RestartRecoveryTests" \ + --logger:GitHubActions \ + "$xunit_project" + else + run_recovery_test + fi + ;; +stop-storage) + : "${RUSTFS_CONTAINER_ID:?RUSTFS_CONTAINER_ID is required}" + timeout 20 docker stop --timeout 10 "$RUSTFS_CONTAINER_ID" + test "$(docker inspect --format '{{.State.Status}}' "$RUSTFS_CONTAINER_ID")" = exited + ;; +restart-and-verify) + : "${RUSTFS_CONTAINER_ID:?RUSTFS_CONTAINER_ID is required}" + docker start "$RUSTFS_CONTAINER_ID" + timeout 30 sh -c 'until curl --output /dev/null --silent --fail http://localhost:9000/health; do sleep 1; done' + run_recovery_test + ;; +cluster-restore) + dotnet test \ + --configuration Release \ + -p:Platform="$platform" \ + -p:ContinuousIntegrationBuild=true \ + --filter "TestCategory=ArchiveIntegration" \ + --logger:GitHubActions \ + "$core_project" + ;; +*) + echo "Unsupported archive storage phase: $phase" >&2 + exit 2 + ;; +esac diff --git a/.config/mise/tasks/github-actions/container b/.config/mise/tasks/github-actions/container new file mode 100755 index 000000000..130652418 --- /dev/null +++ b/.config/mise/tasks/github-actions/container @@ -0,0 +1,45 @@ +#!/bin/sh +#MISE description="Run a reusable container workflow phase" + +set -eu + +root=$(CDPATH='' cd -- "$(dirname -- "$0")/../../../.." && pwd) +phase=${1:?container phase is required} + +cd "$root" + +case "$phase" in +verify) + docker run --rm eventstore --insecure --what-if + ;; +export-test) + docker save eventstore-test -o eventstore-test.tar + ;; +export-runtime) + docker save eventstore -o eventstore.tar + ;; +load-test) + docker load -i eventstore-test.tar + ;; +load-runtime) + docker load --input eventstore.tar + ;; +test) + : "${TEST_GROUP:?TEST_GROUP is required}" + docker run \ + --env TEST_GROUP="$TEST_GROUP" \ + --volume "$root/test-results:/build/test-results" \ + --rm \ + eventstore-test + ;; +normalize-results) + if [ -d test-results ]; then + sudo chown -R "$(id -u):$(id -g)" test-results + sudo chmod -R u+rwX,go+rX test-results + fi + ;; +*) + echo "Unsupported container phase: $phase" >&2 + exit 2 + ;; +esac diff --git a/.config/mise/tasks/github-actions/docker-compose-smoke b/.config/mise/tasks/github-actions/docker-compose-smoke new file mode 100755 index 000000000..3be4a5a58 --- /dev/null +++ b/.config/mise/tasks/github-actions/docker-compose-smoke @@ -0,0 +1,45 @@ +#!/bin/sh +#MISE description="Build and validate the local TLS Docker Compose cluster" + +set -eu + +root=$(CDPATH='' cd -- "$(dirname -- "$0")/../../../.." && pwd) +fingerprints_before=$(mktemp) +fingerprints_after=$(mktemp) +compose_started=0 +cleanup() { + rm -f "$fingerprints_before" "$fingerprints_after" + if [ "$compose_started" -eq 1 ]; then + docker compose down + fi +} +trap cleanup EXIT HUP INT TERM + +cd "$root" +docker compose build +docker compose run --rm cert-gen +docker compose run --rm --entrypoint sh cert-gen -c ' + test -z "$(find /certs/ca -mindepth 1 -maxdepth 1 ! -name ca.crt -print -quit)" + test "$(stat -c %a /certs/ca/ca.crt)" = 644 + for certificate in /certs/node*/node.crt; do + test "$(stat -c %a "$certificate")" = 644 + done + for private_key in /certs/node*/node.key; do + test "$(stat -c %a "$private_key")" = 600 + done +' +docker compose run --rm --entrypoint sh cert-gen -c \ + 'sha256sum /certs/ca/ca.crt /certs/node*/node.crt /certs/node*/node.key' >"$fingerprints_before" +docker compose run --rm cert-gen +docker compose run --rm --entrypoint sh cert-gen -c \ + 'sha256sum /certs/ca/ca.crt /certs/node*/node.crt /certs/node*/node.key' >"$fingerprints_after" +diff --unified "$fingerprints_before" "$fingerprints_after" +compose_started=1 +docker compose up --detach +printf 'Waiting for cluster' +for port in 2111 2112 2113; do + timeout 60 sh -c \ + "until curl --output /dev/null --silent --fail --cacert certs/ca/ca.crt https://localhost:$port/-/readiness; do printf '.'; sleep 2; done" +done +docker compose down +compose_started=0 diff --git a/.config/mise/tasks/github-actions/dotnet-build b/.config/mise/tasks/github-actions/dotnet-build new file mode 100755 index 000000000..451f7e181 --- /dev/null +++ b/.config/mise/tasks/github-actions/dotnet-build @@ -0,0 +1,69 @@ +#!/usr/bin/env bash +#MISE description="Run a reusable dotnet build workflow phase" + +set -euo pipefail + +root=$(cd -- "$(dirname -- "$0")/../../../.." && pwd) +phase=${1:?dotnet build phase is required} + +case "$phase" in +collect-results) + if [[ -d "$root/test-results" ]]; then + find "$root/test-results" -name "*.html" -exec cat {} + >"$root/test-results.html" + else + : >"$root/test-results.html" + fi + exit 0 + ;; +compile | verify | test) ;; +*) + echo "Unsupported dotnet build phase: $phase" >&2 + exit 2 + ;; +esac + +: "${TARGET_ARCH:?TARGET_ARCH is required}" +: "${BUILD_CONFIGURATION:?BUILD_CONFIGURATION is required}" + +protobuf_tools_args=() +if [[ "$TARGET_ARCH" == arm64 ]]; then + protobuf_tools_args=(-p:Protobuf_ProtocFullPath="$(command -v protoc)") +fi + +cd "$root" + +case "$phase" in +compile) + dotnet build \ + --configuration "$BUILD_CONFIGURATION" \ + -p:Platform="$TARGET_ARCH" \ + "${protobuf_tools_args[@]}" \ + src/EventStore.sln + ;; +verify) + dotnet run \ + --project src/EventStore.ClusterNode \ + --configuration "$BUILD_CONFIGURATION" \ + -p:Platform="$TARGET_ARCH" \ + "${protobuf_tools_args[@]}" \ + -- --insecure --what-if + ;; +test) + while IFS= read -r -d '' test_project; do + timeout --signal=TERM --kill-after=30s 30m \ + dotnet test \ + --configuration "$BUILD_CONFIGURATION" \ + -p:Platform="$TARGET_ARCH" \ + "${protobuf_tools_args[@]}" \ + -p:ContinuousIntegrationBuild=true \ + --blame \ + --settings ./ci/ci.runsettings \ + --logger:GitHubActions \ + --logger:html \ + --logger:trx \ + --logger:"console;verbosity=normal" \ + --results-directory="$(pwd)/test-results/$test_project" \ + "$test_project" + done < <(find ./src -maxdepth 1 -type d -name "*.Tests" -print0) + ;; +esac diff --git a/.config/mise/tasks/github-actions/nuget-authenticate b/.config/mise/tasks/github-actions/nuget-authenticate new file mode 100755 index 000000000..b988c47fb --- /dev/null +++ b/.config/mise/tasks/github-actions/nuget-authenticate @@ -0,0 +1,13 @@ +#!/bin/sh +#MISE description="Authenticate dotnet against GitHub Packages" + +set -eu + +: "${NUGET_GITHUB_ACTOR:?NUGET_GITHUB_ACTOR is required}" +: "${NUGET_GITHUB_TOKEN:?NUGET_GITHUB_TOKEN is required}" + +dotnet nuget add source "https://nuget.pkg.github.com/TrogonStack/index.json" \ + --name github \ + --username "$NUGET_GITHUB_ACTOR" \ + --password "$NUGET_GITHUB_TOKEN" \ + --store-password-in-clear-text diff --git a/.config/mise/tasks/github-actions/publish-container b/.config/mise/tasks/github-actions/publish-container new file mode 100755 index 000000000..8b427d54b --- /dev/null +++ b/.config/mise/tasks/github-actions/publish-container @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +#MISE description="Publish the container tags implied by the GitHub ref" + +set -euo pipefail + +: "${GITHUB_REF:?GITHUB_REF is required}" +: "${GITHUB_REF_NAME:?GITHUB_REF_NAME is required}" +: "${IMAGE:?IMAGE is required}" + +tags=() + +if [[ "$GITHUB_REF" == refs/heads/master ]]; then + tags+=(ci) +elif [[ "$GITHUB_REF" =~ ^refs/tags/v([0-9]+)\.([0-9]+)\.([0-9]+)(-[0-9A-Za-z.-]+)?$ ]]; then + major=${BASH_REMATCH[1]} + minor=${BASH_REMATCH[2]} + version=${GITHUB_REF_NAME#v} + tags+=("$version") + + if [[ "$version" == *-* ]]; then + tags+=(preview) + else + tags+=("$major.$minor" "$major" stable) + fi +else + echo "Unsupported image publication ref: $GITHUB_REF" >&2 + exit 1 +fi + +for tag in "${tags[@]}"; do + docker tag eventstore "$IMAGE:$tag" + docker push "$IMAGE:$tag" +done diff --git a/.config/mise/tasks/github-actions/resolve-comparison-refs b/.config/mise/tasks/github-actions/resolve-comparison-refs new file mode 100755 index 000000000..84b447fac --- /dev/null +++ b/.config/mise/tasks/github-actions/resolve-comparison-refs @@ -0,0 +1,21 @@ +#!/bin/sh +#MISE description="Write the C# quality comparison refs to GITHUB_OUTPUT" + +set -eu + +: "${GITHUB_OUTPUT:?GITHUB_OUTPUT is required}" +: "${EVENT_NAME:?EVENT_NAME is required}" + +if [ "$EVENT_NAME" = pull_request ]; then + : "${BASE_SHA:?BASE_SHA is required for pull requests}" + : "${HEAD_SHA:?HEAD_SHA is required for pull requests}" + base=$BASE_SHA + head=$HEAD_SHA +else + : "${BEFORE_SHA:?BEFORE_SHA is required for push events}" + : "${CURRENT_SHA:?CURRENT_SHA is required for push events}" + base=$BEFORE_SHA + head=$CURRENT_SHA +fi + +printf 'base=%s\nhead=%s\n' "$base" "$head" >>"$GITHUB_OUTPUT" diff --git a/.config/mise/tasks/github-actions/vulnerability-scan b/.config/mise/tasks/github-actions/vulnerability-scan new file mode 100755 index 000000000..7750e2ce2 --- /dev/null +++ b/.config/mise/tasks/github-actions/vulnerability-scan @@ -0,0 +1,19 @@ +#!/bin/sh +#MISE description="Restore packages and fail when vulnerable dependencies are reported" + +set -eu + +root=$(CDPATH='' cd -- "$(dirname -- "$0")/../../../.." && pwd) +results=$(mktemp) +trap 'rm -f "$results"' EXIT HUP INT TERM + +cd "$root/src" +dotnet restore +if ! dotnet list package --vulnerable --include-transitive >"$results"; then + cat "$results" + exit 1 +fi +cat "$results" +if grep -q "has the following vulnerable packages" "$results"; then + exit 1 +fi diff --git a/.github/workflows/build-container-reusable.yml b/.github/workflows/build-container-reusable.yml index cd47df646..4d1e2b6f7 100644 --- a/.github/workflows/build-container-reusable.yml +++ b/.github/workflows/build-container-reusable.yml @@ -37,7 +37,13 @@ jobs: uses: actions/checkout@v7 with: fetch-depth: 0 - if: ${{ inputs.mode != 'test-only' }} + - name: Install task runner + id: mise + uses: jdx/mise-action@v4.2.3 + with: + version: 2026.8.2 + install: false + cache: false - name: Install net10.0 uses: actions/setup-dotnet@v6 with: @@ -75,8 +81,7 @@ jobs: nuget_auth_token=${{ secrets.GITHUB_TOKEN }} - name: Verify Build if: ${{ inputs.mode != 'test-only' }} - run: | - docker run --rm eventstore --insecure --what-if + run: mise run --skip-tools github-actions:container -- verify - name: Build Test Container uses: docker/build-push-action@v7 if: ${{ inputs.mode != 'test-only' }} @@ -94,12 +99,10 @@ jobs: nuget_auth_token=${{ secrets.GITHUB_TOKEN }} - name: Export Test Container if: ${{ inputs.mode == 'build-only' }} - run: | - docker save eventstore-test -o eventstore-test.tar + run: mise run --skip-tools github-actions:container -- export-test - name: Export Container if: ${{ inputs.mode == 'build-only' && github.event_name == 'push' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v')) }} - run: | - docker save eventstore -o eventstore.tar + run: mise run --skip-tools github-actions:container -- export-runtime - name: Publish Test Container uses: actions/upload-artifact@v7 if: ${{ inputs.mode == 'build-only' }} @@ -123,23 +126,15 @@ jobs: name: ${{ inputs.test-image-artifact-name }} - name: Load Test Container if: ${{ inputs.mode == 'test-only' }} - run: | - docker load -i eventstore-test.tar + run: mise run --skip-tools github-actions:container -- load-test - name: Run Tests if: ${{ inputs.mode != 'build-only' }} - run: | - docker run \ - --env TEST_GROUP="${{ inputs.test-group-name }}" \ - --volume "$(pwd)/test-results:/build/test-results" \ - --rm \ - eventstore-test + env: + TEST_GROUP: ${{ inputs.test-group-name }} + run: mise run --skip-tools github-actions:container -- test - name: Normalize Test Result Permissions - if: failure() - run: | - if [ -d test-results ]; then - sudo chown -R "$(id -u):$(id -g)" test-results - sudo chmod -R u+rwX,go+rX test-results - fi + if: ${{ failure() && steps.mise.outcome == 'success' }} + run: mise run --skip-tools github-actions:container -- normalize-results - name: Publish Test Results uses: actions/upload-artifact@v7 if: failure() diff --git a/.github/workflows/build-reusable.yml b/.github/workflows/build-reusable.yml index f518acc4d..ce3258962 100644 --- a/.github/workflows/build-reusable.yml +++ b/.github/workflows/build-reusable.yml @@ -31,56 +31,37 @@ jobs: uses: actions/setup-dotnet@v6 with: dotnet-version: 10.0.x + - name: Install task runner + id: mise + uses: jdx/mise-action@v4.2.3 + with: + version: 2026.8.2 + install_args: protoc + cache: false - name: Authenticate NuGet GitHub Packages - shell: bash - run: | - dotnet nuget add source "https://nuget.pkg.github.com/TrogonStack/index.json" \ - --name github \ - --username "${{ github.actor }}" \ - --password "${{ secrets.GITHUB_TOKEN }}" \ - --store-password-in-clear-text - - name: Install native protobuf compiler - if: ${{ inputs.arch == 'arm64' }} - shell: bash - run: | - sudo apt-get update - sudo apt-get install -y --no-install-recommends protobuf-compiler + env: + NUGET_GITHUB_ACTOR: ${{ github.actor }} + NUGET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: mise run --skip-tools github-actions:nuget-authenticate - name: Compile - shell: bash env: + BUILD_CONFIGURATION: ${{ matrix.configuration }} NUGET_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - protobuf_tools_args=() - if [ "${{ inputs.arch }}" = "arm64" ]; then - protobuf_tools_args=(-p:Protobuf_ProtocFullPath=/usr/bin/protoc) - fi - - dotnet build --configuration ${{ matrix.configuration }} -p:Platform=${{ inputs.arch }} "${protobuf_tools_args[@]}" src/EventStore.sln + TARGET_ARCH: ${{ inputs.arch }} + run: mise run --skip-tools github-actions:dotnet-build -- compile - name: Verify Build - shell: bash - run: | - protobuf_tools_args=() - if [ "${{ inputs.arch }}" = "arm64" ]; then - protobuf_tools_args=(-p:Protobuf_ProtocFullPath=/usr/bin/protoc) - fi - - dotnet run --project src/EventStore.ClusterNode --configuration ${{ matrix.configuration }} -p:Platform=${{ inputs.arch }} "${protobuf_tools_args[@]}" -- --insecure --what-if + env: + BUILD_CONFIGURATION: ${{ matrix.configuration }} + TARGET_ARCH: ${{ inputs.arch }} + run: mise run --skip-tools github-actions:dotnet-build -- verify - name: Run Tests - shell: bash - run: | - protobuf_tools_args=() - if [ "${{ inputs.arch }}" = "arm64" ]; then - protobuf_tools_args=(-p:Protobuf_ProtocFullPath=/usr/bin/protoc) - fi - - find ./src -maxdepth 1 -type d -name "*.Tests" -print0 \ - | xargs -r -I{} -0 -n1 bash -c \ - 'timeout --signal=TERM --kill-after=30s 30m dotnet test --configuration ${{ matrix.configuration }} -p:Platform=${{ inputs.arch }} "${@:2}" -p:ContinuousIntegrationBuild=true --blame --settings ./ci/ci.runsettings --logger:GitHubActions --logger:html --logger:trx --logger:"console;verbosity=normal" --results-directory="$(pwd)/test-results/$1" "$1"' - '{}' "${protobuf_tools_args[@]}" + env: + BUILD_CONFIGURATION: ${{ matrix.configuration }} + TARGET_ARCH: ${{ inputs.arch }} + run: mise run --skip-tools github-actions:dotnet-build -- test - name: Collect Test Results - shell: bash - if: always() - run: | - echo $(find ./test-results -name "*.html" | xargs cat) > test-results.html + if: ${{ always() && steps.mise.outcome == 'success' }} + run: mise run --skip-tools github-actions:dotnet-build -- collect-results - name: Publish Test Results (HTML) uses: actions/upload-artifact@v7 if: always() diff --git a/.github/workflows/common.yml b/.github/workflows/common.yml index d088542fd..ee9ea019c 100644 --- a/.github/workflows/common.yml +++ b/.github/workflows/common.yml @@ -38,9 +38,11 @@ jobs: - name: Install repository tools uses: jdx/mise-action@v4.2.3 with: + version: 2026.8.2 + install_args: github:open-telemetry/weaver cache: false - name: Verify semantic conventions - run: mise run semconv:check + run: mise run --skip-tools semconv:check csharp-quality: runs-on: ubuntu-latest @@ -54,24 +56,26 @@ jobs: uses: actions/setup-dotnet@v6 with: dotnet-version: 10.0.x + - name: Install task runner + uses: jdx/mise-action@v4.2.3 + with: + version: 2026.8.2 + install: false + cache: false - name: Set up .NET NuGet authentication - run: | - dotnet nuget add source "https://nuget.pkg.github.com/TrogonStack/index.json" \ - --name "github" \ - --username "${{ github.actor }}" \ - --password ${{ secrets.GITHUB_TOKEN }} \ - --store-password-in-clear-text + env: + NUGET_GITHUB_ACTOR: ${{ github.actor }} + NUGET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: mise run --skip-tools github-actions:nuget-authenticate - name: Resolve comparison refs id: refs - shell: bash - run: | - if [[ "${{ github.event_name }}" == "pull_request" ]]; then - echo "base=${{ github.event.pull_request.base.sha }}" >> "$GITHUB_OUTPUT" - echo "head=${{ github.event.pull_request.head.sha }}" >> "$GITHUB_OUTPUT" - else - echo "base=${{ github.event.before }}" >> "$GITHUB_OUTPUT" - echo "head=${{ github.sha }}" >> "$GITHUB_OUTPUT" - fi + env: + EVENT_NAME: ${{ github.event_name }} + BASE_SHA: ${{ github.event.pull_request.base.sha }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + BEFORE_SHA: ${{ github.event.before }} + CURRENT_SHA: ${{ github.sha }} + run: mise run --skip-tools github-actions:resolve-comparison-refs - name: Restore run: | dotnet restore src/EventStore.sln @@ -96,19 +100,19 @@ jobs: uses: actions/setup-dotnet@v6 with: dotnet-version: 10.0.x + - name: Install task runner + uses: jdx/mise-action@v4.2.3 + with: + version: 2026.8.2 + install: false + cache: false - name: Set up .NET NuGet authentication - run: | - dotnet nuget add source "https://nuget.pkg.github.com/TrogonStack/index.json" \ - --name "github" \ - --username ${{ github.actor }} \ - --password ${{ secrets.GITHUB_TOKEN }} \ - --store-password-in-clear-text + env: + NUGET_GITHUB_ACTOR: ${{ github.actor }} + NUGET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: mise run --skip-tools github-actions:nuget-authenticate - name: Scan for Vulnerabilities - run: | - cd src - dotnet restore - dotnet list package --vulnerable --include-transitive | tee vulnerabilities.txt - ! cat vulnerabilities.txt | grep -q "has the following vulnerable packages" + run: mise run --skip-tools github-actions:vulnerability-scan protolock: runs-on: ubuntu-latest @@ -152,87 +156,45 @@ jobs: uses: actions/setup-dotnet@v6 with: dotnet-version: 10.0.x + - name: Install task runner + uses: jdx/mise-action@v4.2.3 + with: + version: 2026.8.2 + install: false + cache: false - name: Set up .NET NuGet authentication env: NUGET_GITHUB_ACTOR: ${{ github.actor }} NUGET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - dotnet nuget add source "https://nuget.pkg.github.com/TrogonStack/index.json" \ - --name "github" \ - --username "$NUGET_GITHUB_ACTOR" \ - --password "$NUGET_GITHUB_TOKEN" \ - --store-password-in-clear-text + run: mise run --skip-tools github-actions:nuget-authenticate - name: Run archive storage contract tests - run: | - dotnet test \ - --configuration Release \ - -p:Platform=x64 \ - -p:ContinuousIntegrationBuild=true \ - -p:RunS3Tests=true \ - --filter "FullyQualifiedName~S3ReaderTests|FullyQualifiedName~S3WriterTests|FullyQualifiedName~S3MetricsTests|FullyQualifiedName~S3FixtureLifecycleTests" \ - --logger:GitHubActions \ - src/EventStore.Core.XUnit.Tests/EventStore.Core.XUnit.Tests.csproj + run: mise run --skip-tools github-actions:archive-storage-contract -- contract - name: Seed archive restart recovery data id: seed_archive_recovery env: EVENTSTORE_S3_RECOVERY_PHASE: seed - run: | - dotnet test \ - --configuration Release \ - --no-build \ - -p:Platform=x64 \ - -p:RunS3Tests=true \ - --filter "FullyQualifiedName~S3RestartRecoveryTests" \ - --logger:GitHubActions \ - src/EventStore.Core.XUnit.Tests/EventStore.Core.XUnit.Tests.csproj + run: mise run --skip-tools github-actions:archive-storage-contract -- recovery - name: Stop RustFS without removing its data env: RUSTFS_CONTAINER_ID: ${{ job.services.rustfs.id }} - run: | - timeout 20 docker stop --timeout 10 "$RUSTFS_CONTAINER_ID" - test "$(docker inspect --format '{{.State.Status}}' "$RUSTFS_CONTAINER_ID")" = "exited" + run: mise run --skip-tools github-actions:archive-storage-contract -- stop-storage - name: Assert archive storage is unavailable env: EVENTSTORE_S3_RECOVERY_PHASE: unavailable - run: | - timeout 20 dotnet test \ - --configuration Release \ - --no-build \ - -p:Platform=x64 \ - -p:RunS3Tests=true \ - --filter "FullyQualifiedName~S3RestartRecoveryTests" \ - --logger:GitHubActions \ - src/EventStore.Core.XUnit.Tests/EventStore.Core.XUnit.Tests.csproj + run: mise run --skip-tools github-actions:archive-storage-contract -- recovery - name: Restart RustFS and verify archive recovery if: ${{ always() && steps.seed_archive_recovery.outcome == 'success' }} env: EVENTSTORE_S3_RECOVERY_PHASE: verify-cleanup RUSTFS_CONTAINER_ID: ${{ job.services.rustfs.id }} - run: | - docker start "$RUSTFS_CONTAINER_ID" - timeout 30 bash -c -- 'until curl --output /dev/null --silent --fail http://localhost:9000/health; do sleep 1; done' - dotnet test \ - --configuration Release \ - --no-build \ - -p:Platform=x64 \ - -p:RunS3Tests=true \ - --filter "FullyQualifiedName~S3RestartRecoveryTests" \ - --logger:GitHubActions \ - src/EventStore.Core.XUnit.Tests/EventStore.Core.XUnit.Tests.csproj + run: mise run --skip-tools github-actions:archive-storage-contract -- restart-and-verify - name: Run archive cluster restore gate - run: | - dotnet test \ - --configuration Release \ - -p:Platform=x64 \ - -p:ContinuousIntegrationBuild=true \ - --filter "TestCategory=ArchiveIntegration" \ - --logger:GitHubActions \ - src/EventStore.Core.Tests/EventStore.Core.Tests.csproj + run: mise run --skip-tools github-actions:archive-storage-contract -- cluster-restore docker-compose: runs-on: ubuntu-latest @@ -240,6 +202,12 @@ jobs: steps: - name: Checkout uses: actions/checkout@v7 + - name: Install task runner + uses: jdx/mise-action@v4.2.3 + with: + version: 2026.8.2 + install: false + cache: false - name: Github Registry Docker Login uses: docker/login-action@v4 with: @@ -250,26 +218,4 @@ jobs: env: DOCKER_BUILDKIT: 1 NUGET_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - docker compose build - docker compose run --rm cert-gen - docker compose run --rm --entrypoint sh cert-gen -c ' - test -z "$(find /certs/ca -mindepth 1 -maxdepth 1 ! -name ca.crt -print -quit)" - test "$(stat -c %a /certs/ca/ca.crt)" = 644 - for certificate in /certs/node*/node.crt; do - test "$(stat -c %a "$certificate")" = 644 - done - for private_key in /certs/node*/node.key; do - test "$(stat -c %a "$private_key")" = 600 - done - ' - docker compose run --rm --entrypoint sh cert-gen -c 'sha256sum /certs/ca/ca.crt /certs/node*/node.crt /certs/node*/node.key' > /tmp/certificate-fingerprints.before - docker compose run --rm cert-gen - docker compose run --rm --entrypoint sh cert-gen -c 'sha256sum /certs/ca/ca.crt /certs/node*/node.crt /certs/node*/node.key' > /tmp/certificate-fingerprints.after - diff --unified /tmp/certificate-fingerprints.before /tmp/certificate-fingerprints.after - docker compose up --detach - printf 'Waiting for cluster' - for port in 2111 2112 2113; do - timeout 60 bash -c -- "until curl --output /dev/null --silent --fail --cacert certs/ca/ca.crt https://localhost:$port/-/readiness; do printf '.'; sleep 2; done" - done - docker compose down + run: mise run --skip-tools github-actions:docker-compose-smoke diff --git a/.github/workflows/tag-docker-images.yml b/.github/workflows/tag-docker-images.yml index a0368b791..47a50bb0f 100644 --- a/.github/workflows/tag-docker-images.yml +++ b/.github/workflows/tag-docker-images.yml @@ -12,12 +12,22 @@ jobs: name: Publish runs-on: ubuntu-latest steps: + - name: Checkout + uses: actions/checkout@v7 + with: + persist-credentials: false + - name: Install task runner + uses: jdx/mise-action@v4.2.3 + with: + version: 2026.8.2 + install: false + cache: false - name: Download Container uses: actions/download-artifact@v8 with: name: ${{ inputs.image-artifact-name }} - name: Load Container - run: docker load --input eventstore.tar + run: mise run --skip-tools github-actions:container -- load-runtime - name: Login to GitHub Container Registry uses: docker/login-action@v4 with: @@ -27,28 +37,4 @@ jobs: - name: Publish Container env: IMAGE: ghcr.io/trogonstack/trogoneventstore - run: | - tags=() - - if [[ "$GITHUB_REF" == "refs/heads/master" ]]; then - tags+=(ci) - elif [[ "$GITHUB_REF" =~ ^refs/tags/v([0-9]+)\.([0-9]+)\.([0-9]+)(-[0-9A-Za-z.-]+)?$ ]]; then - major="${BASH_REMATCH[1]}" - minor="${BASH_REMATCH[2]}" - version="${GITHUB_REF_NAME#v}" - tags+=("$version") - - if [[ "$version" == *-* ]]; then - tags+=(preview) - else - tags+=("$major.$minor" "$major" stable) - fi - else - echo "Unsupported image publication ref: $GITHUB_REF" >&2 - exit 1 - fi - - for tag in "${tags[@]}"; do - docker tag eventstore "$IMAGE:$tag" - docker push "$IMAGE:$tag" - done + run: mise run --skip-tools github-actions:publish-container diff --git a/mise.toml b/mise.toml index b01405618..4cda8ddc9 100644 --- a/mise.toml +++ b/mise.toml @@ -1,5 +1,6 @@ [tools] "github:open-telemetry/weaver" = "0.24.2" +protoc = "35.1" [tasks."ui:dev"] description = "Run a local file-backed node for Razor UI testing"