# Copyright 2024 RustFS Team # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # MinIO Mint against RustFS: multi-SDK client compatibility. # # ceph/s3-tests (see e2e-s3tests.yml and the ci.yml PR gate) exercises the S3 # API semantics through boto3 only. Mint complements it by running the # functional test suites of many REAL client SDKs and tools (awscli, mc, # aws-sdk-java/go/php/ruby, minio-go/java/js/py, s3cmd, ...), which catches # client-specific signing, encoding, and streaming edge cases a single client # cannot. # # This job is REPORT-ONLY for test failures: RustFS does not pass every mint # suite yet, so failures are inventoried in the job summary instead of turning # the run red. The job fails only when mint produces no results at all # (infrastructure error). Once a suite passes reliably, tightening this into a # baseline gate (like implemented_tests.txt for s3-tests) is the natural next # step. Tightening this into a per-suite baseline gate is tracked as ci-3 # (backlog#1149) and is intentionally NOT done here. # # Runner infrastructure (ci-2, backlog#1149): this job needs a working Docker # daemon to (a) build the RustFS source image with buildx and (b) run both the # RustFS server and the mint image. It previously ran on the self-hosted # `sm-standard-4` label, which is served by heterogeneous runners; its single # recorded run (28730530597, 2026-07-05) landed on a minimal pod with no # `/var/run/docker.sock`, so "Enable buildx" died at `docker buildx create` # ("failed to connect to the docker API at unix:///var/run/docker.sock") before # any test executed -- the same root cause ci-1 fixed for the weekly s3-tests # sweep. The fix is to pin to GitHub-hosted `ubuntu-latest`, which reliably # ships a running Docker daemon + buildx + python3, instead of trusting the # drifting self-hosted fleet. Tradeoff: the source Rust build is heavier on a # GitHub-hosted 4-vCPU runner than on a warm self-hosted host, but it stays # well inside the 120-minute budget and buys deterministic infra. The # docker-capable self-hosted `dind-sm-standard-2` label was the alternative but # has fewer cores and reintroduces fleet-state risk for no reliability gain. name: mint on: workflow_dispatch: inputs: suites: description: "Space-separated mint suites to run (empty = all, e.g. 'awscli mc minio-go')" required: false default: "" mode: description: "Mint mode: core (quick) or full" required: true type: choice default: "core" options: - core - full mint-image: description: "Mint image reference" required: false default: "minio/mint:edge" schedule: # Weekly, after the Sunday s3-tests full sweep (starts 02:00 UTC, up to # 3h) has finished, so the two never contend for the same runner pool. - cron: "0 6 * * 0" env: S3_ACCESS_KEY: rustfsadmin-ci S3_SECRET_KEY: rustfssecret-ci S3_REGION: us-east-1 RUST_LOG: info PLATFORM: linux/amd64 BUILDX_CACHE_SCOPE: rustfs-e2e-s3tests-source MINT_SUITES: ${{ github.event.inputs.suites || '' }} MINT_MODE: ${{ github.event.inputs.mode || 'core' }} # minio/mint:edge is a rolling tag, so an unpinned run is not reproducible and # can go red purely from an upstream mint change (ci-2, backlog#1149). Pin the # default to the linux/amd64 digest resolved on 2026-07-10. workflow_dispatch # can still override via the `mint-image` input (e.g. to test a newer edge). # Refresh: docker manifest inspect minio/mint:edge --verbose | grep digest # (or, without a docker daemon:) # TOKEN=$(curl -s "https://auth.docker.io/token?service=registry.docker.io&scope=repository:minio/mint:pull" | jq -r .token) # curl -sI -H "Authorization: Bearer $TOKEN" \ # -H "Accept: application/vnd.docker.distribution.manifest.v2+json" \ # https://registry-1.docker.io/v2/minio/mint/manifests/edge | grep -i docker-content-digest MINT_IMAGE: ${{ github.event.inputs.mint-image || 'minio/mint:edge@sha256:08a05e68893c68be2a83b6f79556853ed6aa3c6c9e64c823a00853e4e55d2200' }} concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true # Only alert-on-failure needs more than read access; it declares its own # job-level `issues: write`. permissions: contents: read defaults: run: shell: bash jobs: mint: # GitHub-hosted: reliably provides a running Docker daemon + buildx + # python3. See the header note (ci-2) for why the self-hosted sm-standard-4 # label was abandoned (no docker.sock on the pod its only run landed on). # TODO(ci-8): scheduled-failure alerting (auto-open issue on a red weekly # run) is added by the ci-8 composite action; do not implement it here. runs-on: ubuntu-latest timeout-minutes: 120 steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 - name: Enable buildx uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 - name: Build RustFS image (source, cached) run: | DOCKER_BUILDKIT=1 docker buildx build --load \ --platform "${PLATFORM}" \ --cache-from "type=gha,scope=${BUILDX_CACHE_SCOPE}" \ --cache-to "type=gha,mode=max,scope=${BUILDX_CACHE_SCOPE}" \ -t rustfs-ci \ -f Dockerfile.source . - name: Start RustFS run: | docker network inspect rustfs-net >/dev/null 2>&1 || docker network create rustfs-net docker rm -f rustfs-mint >/dev/null 2>&1 || true # The four disks share one physical device on the runner (a single # loopback filesystem), so the local physical-disk-independence guard # would refuse to start. Bypass it — this is the CI use case the guard # explicitly sanctions via RUSTFS_UNSAFE_BYPASS_DISK_CHECK. docker run -d --name rustfs-mint \ --network rustfs-net \ -p 9000:9000 \ -e RUSTFS_ADDRESS=0.0.0.0:9000 \ -e RUSTFS_ACCESS_KEY="${S3_ACCESS_KEY}" \ -e RUSTFS_SECRET_KEY="${S3_SECRET_KEY}" \ -e RUSTFS_VOLUMES="/data/rustfs{0...3}" \ -e RUSTFS_UNSAFE_BYPASS_DISK_CHECK=true \ -v /tmp/rustfs-mint:/data \ rustfs-ci - name: Wait for RustFS ready run: | for _ in {1..60}; do if curl -sf http://127.0.0.1:9000/health >/dev/null 2>&1; then echo "RustFS is ready" exit 0 fi if [ "$(docker inspect -f '{{.State.Running}}' rustfs-mint 2>/dev/null)" != "true" ]; then echo "RustFS container not running" >&2 docker logs rustfs-mint || true exit 1 fi sleep 2 done echo "Health check timed out" >&2 docker logs rustfs-mint || true exit 1 - name: Run mint run: | mkdir -p artifacts/mint-log # Mint exits non-zero when any test fails; that is inventory here, # not a gate (see header comment). Capture the exit code and let the # summary step decide. set +e # shellcheck disable=SC2086 # MINT_SUITES is intentionally word-split docker run --rm --network rustfs-net \ -e SERVER_ENDPOINT=rustfs-mint:9000 \ -e ACCESS_KEY=${S3_ACCESS_KEY} \ -e SECRET_KEY=${S3_SECRET_KEY} \ -e ENABLE_HTTPS=0 \ -e SERVER_REGION=${S3_REGION} \ -e MINT_MODE=${MINT_MODE} \ -v "${PWD}/artifacts/mint-log:/mint/log" \ "${MINT_IMAGE}" ${MINT_SUITES} echo "MINT_RC=$?" >> "$GITHUB_ENV" - name: Summarize results run: | python3 - <<'EOF' import json, os, pathlib, sys from collections import Counter log = pathlib.Path("artifacts/mint-log/log.json") if not log.is_file(): print("mint produced no log.json - infrastructure failure", file=sys.stderr) sys.exit(int(os.environ.get("MINT_RC", "1")) or 1) per_suite: dict[str, Counter] = {} failures: list[tuple[str, str, str]] = [] for line in log.read_text(encoding="utf-8").splitlines(): line = line.strip() if not line: continue try: entry = json.loads(line) except json.JSONDecodeError: continue suite = entry.get("name", "unknown") status = entry.get("status", "unknown").upper() per_suite.setdefault(suite, Counter())[status] += 1 if status == "FAIL": failures.append((suite, entry.get("function", ""), entry.get("error") or entry.get("message") or "")) lines = ["# Mint results", "", "| Suite | Pass | Fail | N/A |", "|-------|------|------|-----|"] for suite in sorted(per_suite): c = per_suite[suite] lines.append(f"| {suite} | {c.get('PASS', 0)} | {c.get('FAIL', 0)} | {c.get('NA', 0)} |") lines.append("") if failures: lines.append(f"## Failures ({len(failures)})") lines.append("") for suite, func, err in failures: first = err.strip().splitlines()[0] if err.strip() else "" lines.append(f"- `{suite}` / `{func}` {first}") lines.append("") lines.append("_Report-only: failures are inventory, not a gate (see workflow header)._") report = "\n".join(lines) print(report) summary = os.environ.get("GITHUB_STEP_SUMMARY") if summary: with open(summary, "a", encoding="utf-8") as fh: fh.write(report + "\n") EOF - name: Collect RustFS logs if: always() run: | mkdir -p artifacts/rustfs-mint docker logs rustfs-mint > artifacts/rustfs-mint/rustfs.log 2>&1 || true - name: Upload artifacts if: always() && env.ACT != 'true' uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 with: name: mint path: artifacts/** alert-on-failure: name: Alert on scheduled failure needs: [mint] # `always()` is required: without it this job is skipped when a needed # job fails. Alerts only for scheduled runs (backlog#1149 ci-8) — manual # dispatch failures are already watched by a human. if: always() && github.event_name == 'schedule' && contains(needs.*.result, 'failure') runs-on: ubuntu-latest timeout-minutes: 10 permissions: contents: read issues: write steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 - name: Open or update failure-tracking issue uses: ./.github/actions/schedule-failure-issue with: github-token: ${{ secrets.GITHUB_TOKEN }}