# 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.

# DISABLED. This workflow is switched off in the repository's Actions settings
# (state: disabled_manually) and does not run on any trigger, including its cron
# and workflow_dispatch. That state lives in GitHub's UI and is invisible when
# reading this file, which has already misled at least one audit — hence this
# banner. Re-enabling is a UI action; anyone doing so should first check that the
# workflow still matches the current CI layout. See rustfs/backlog#1603.
#
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
        with:
          persist-credentials: false

      - 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
        with:
          persist-credentials: false
      - name: Open or update failure-tracking issue
        uses: ./.github/actions/schedule-failure-issue
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
