From 9f5ff23fd8da21dc98e55dcb10e11bbbe1e41f4f Mon Sep 17 00:00:00 2001 From: hector <42570491+majinghe@users.noreply.github.com> Date: Sat, 12 Sep 2026 09:28:30 +0800 Subject: [PATCH] ci: manage backlog issues by signal instead of per-run filing (#7680) * ci: manage backlog issues by signal instead of per-run filing The suite workflows used to file one backlog issue per failed run (dedup was by run ID, which never matched), so issues accumulated without bound. Replace the inline filing step in every suite workflow (s3, kms, tier, storage, heal, pool, security, replication, upgrade, performance) with a single call to auto-testing/scripts/issue_manager.py, which: - dedups by signal: failing cases are searched among open issues by label (suite category + case ID); covered cases become a coalesced comment on the existing issue, only uncovered cases file a new one - labels new issues with functional-test, the suite category, one label per failing case ID (lazily created), and env for bootstrap-class failures (no cases ran, wholesale failure, or 404/ssh/clone/dpkg signatures in the log) - closes open issues of the suite after a fully green run, citing the run as evidence; cancelled runs never file or close anything The step is skipped cleanly when auto-testing (private checkout) does not contain the manager, or when PF_TESTING_GH_TOKEN is unset. * fix(ci): satisfy actionlint and workflow contract tests for the manager step - heal and performance workflows have no rustfs_version dispatch input; referencing `${{ inputs.rustfs_version }}` in the manager step failed actionlint's expression type check. Their package source now resolves from package_url with the nightly fallback. - scripts/test_security_workflow.py pinned the removed inline filing step. The wiring assertions now pin the manager step (manager path + per-suite report argument), and the evidence/stale-file tests assert the skip contract instead: without the private auto-testing checkout present, the step exits 0, publishes nothing, and leaves stale evidence untouched. Verified locally: actionlint clean, shellcheck clean, test_security_workflow.py 21/21. --- .github/workflows/rustfs-heal-test.yml | 76 +++++++--------- .github/workflows/rustfs-kms-test.yml | 80 ++++++++--------- .github/workflows/rustfs-performance-test.yml | 73 ++++++--------- .github/workflows/rustfs-pool-expand-test.yml | 78 +++++++--------- .github/workflows/rustfs-replication-test.yml | 80 ++++++++--------- .github/workflows/rustfs-s3-compat-test.yml | 80 ++++++++--------- .github/workflows/rustfs-security-test.yml | 79 +++++++--------- .github/workflows/rustfs-storage-test.yml | 80 ++++++++--------- .github/workflows/rustfs-tier-test.yml | 90 ++++++++----------- .github/workflows/rustfs-upgrade-test.yml | 87 +++++++++--------- scripts/test_security_workflow.py | 23 ++--- 11 files changed, 356 insertions(+), 470 deletions(-) diff --git a/.github/workflows/rustfs-heal-test.yml b/.github/workflows/rustfs-heal-test.yml index 4e6b4e190..a86deb332 100644 --- a/.github/workflows/rustfs-heal-test.yml +++ b/.github/workflows/rustfs-heal-test.yml @@ -280,65 +280,51 @@ jobs: fi rm -f "${B64_FILE}" - - name: File failure issue in rustfs/backlog - if: ${{ always() && (failure() || steps.test.outcome == 'failure' || steps.test.outcome == 'cancelled') }} + - name: Manage backlog issues (dedup / label / auto-close) + # Replaces the old per-run failure filing. One entry point that: + # - dedups by signal: failing cases are matched against open backlog + # issues by label (category + case ID); covered cases become a + # comment on the existing issue, only uncovered cases file a new one + # - labels new issues (functional-test, category, case IDs, env) + # - closes fixed issues after a fully green run + # - never files or closes on cancelled runs + if: ${{ always() && steps.evidence.outcome == 'success' }} continue-on-error: true env: GH_TOKEN: ${{ secrets.PF_TESTING_GH_TOKEN }} - EVIDENCE_OUTCOME: ${{ steps.evidence.outcome }} SUITE: 'heal' SUITE_LABEL: 'Heal' RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} run: | set -euo pipefail if [ -z "${GH_TOKEN:-}" ]; then - echo "PF_TESTING_GH_TOKEN is not configured; skipping backlog issue" + echo "PF_TESTING_GH_TOKEN is not configured; skipping backlog issue management" exit 0 fi - TITLE="[functional][${SUITE}] ${SUITE_LABEL} suite failed (run ${GITHUB_RUN_ID})" - EXISTING="$(gh issue list -R rustfs/backlog --state all \ - --search "in:title \"run ${GITHUB_RUN_ID}\"" \ - --json number --jq '.[].number' || true)" - if [ -n "${EXISTING}" ]; then - echo "backlog issue already exists for run ${GITHUB_RUN_ID}; skipping" + if [ ! -f auto-testing/scripts/issue_manager.py ]; then + echo "issue_manager.py not found in auto-testing checkout; skipping" exit 0 fi - redact() { - sed -E \ - -e 's/(RUSTFS_(ACCESS_KEY|SECRET_KEY)[=: ]+)[^[:space:]]+/\1[REDACTED]/Ig' \ - -e 's/(Authorization:).*/\1 [REDACTED]/Ig' \ - -e 's/(X-Amz-Signature=)[^&[:space:]]+/\1[REDACTED]/Ig' \ - -e 's/^.*(password|secret|token)[=: ].*/[REDACTED SENSITIVE LINE]/Ig' - } - BODY_FILE="$(mktemp)" - { - echo "The **${SUITE_LABEL}** functional suite failed." - echo "" - echo "- Suite: \`${SUITE}\`" - echo "- Run: ${RUN_URL}" - echo "- Attempt: ${GITHUB_RUN_ATTEMPT}" - echo "- Workflow Commit: ${GITHUB_SHA}" - echo "- Trigger: ${GITHUB_EVENT_NAME}" - echo "- Date: $(date -u +%Y-%m-%d)" - echo "" - echo "## Report (errors and symptoms)" - echo "" - if [ "${EVIDENCE_OUTCOME}" = "success" ] && [ -s "${REPORT_FILE}" ]; then - redact < "${REPORT_FILE}" - elif [ "${EVIDENCE_OUTCOME}" = "success" ] && [ -s "${LOG_FILE:-}" ]; then - echo "(report file missing; log tail below)" - echo "" - tail -n 200 "${LOG_FILE}" | redact - else - echo "(no report or log file was produced)" - fi - } | head -c 55000 > "${BODY_FILE}" - gh label create functional-test -R rustfs/backlog --color d73a4a 2>/dev/null || true - if ! gh issue create -R rustfs/backlog --title "${TITLE}" \ - --body-file "${BODY_FILE}" --label functional-test; then - gh issue create -R rustfs/backlog --title "${TITLE}" --body-file "${BODY_FILE}" + PACKAGE_URL='${{ inputs.package_url }}' + PACKAGE_SOURCE="" + if [ -n "${PACKAGE_URL}" ]; then + PACKAGE_SOURCE="${PACKAGE_URL}" + else + PACKAGE_SOURCE="${RUSTFS_NIGHTLY_PACKAGE_URL}" fi - echo "filed backlog issue for suite ${SUITE}" + python3 auto-testing/scripts/issue_manager.py handle \ + --repo rustfs/backlog \ + --suite "${SUITE}" --category "${SUITE}" --suite-label "${SUITE_LABEL}" \ + --outcome "${{ steps.test.outcome }}" \ + --report-file "${REPORT_FILE}" \ + --log "${LOG_FILE}" \ + --run-url "${RUN_URL}" \ + --run-id "${GITHUB_RUN_ID}" \ + --attempt "${GITHUB_RUN_ATTEMPT}" \ + --commit "${GITHUB_SHA}" \ + --trigger "${{ github.event_name }}" \ + --package-source "${PACKAGE_SOURCE}" \ + --date "$(date -u +%Y-%m-%d)" - name: Upload test logs if: ${{ always() && steps.evidence.outcome == 'success' }} diff --git a/.github/workflows/rustfs-kms-test.yml b/.github/workflows/rustfs-kms-test.yml index 9fec24333..e74f9ddaf 100644 --- a/.github/workflows/rustfs-kms-test.yml +++ b/.github/workflows/rustfs-kms-test.yml @@ -230,65 +230,55 @@ jobs: fi rm -f "${B64_FILE}" - - name: File failure issue in rustfs/backlog - if: ${{ always() && (failure() || steps.test.outcome == 'failure' || steps.test.outcome == 'cancelled') }} + - name: Manage backlog issues (dedup / label / auto-close) + # Replaces the old per-run failure filing. One entry point that: + # - dedups by signal: failing cases are matched against open backlog + # issues by label (category + case ID); covered cases become a + # comment on the existing issue, only uncovered cases file a new one + # - labels new issues (functional-test, category, case IDs, env) + # - closes fixed issues after a fully green run + # - never files or closes on cancelled runs + if: ${{ always() && steps.evidence.outcome == 'success' }} continue-on-error: true env: GH_TOKEN: ${{ secrets.PF_TESTING_GH_TOKEN }} - EVIDENCE_OUTCOME: ${{ steps.evidence.outcome }} SUITE: 'kms' SUITE_LABEL: 'KMS' RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} run: | set -euo pipefail if [ -z "${GH_TOKEN:-}" ]; then - echo "PF_TESTING_GH_TOKEN is not configured; skipping backlog issue" + echo "PF_TESTING_GH_TOKEN is not configured; skipping backlog issue management" exit 0 fi - TITLE="[functional][${SUITE}] ${SUITE_LABEL} suite failed (run ${GITHUB_RUN_ID})" - EXISTING="$(gh issue list -R rustfs/backlog --state all \ - --search "in:title \"run ${GITHUB_RUN_ID}\"" \ - --json number --jq '.[].number' || true)" - if [ -n "${EXISTING}" ]; then - echo "backlog issue already exists for run ${GITHUB_RUN_ID}; skipping" + if [ ! -f auto-testing/scripts/issue_manager.py ]; then + echo "issue_manager.py not found in auto-testing checkout; skipping" exit 0 fi - redact() { - sed -E \ - -e 's/(RUSTFS_(ACCESS_KEY|SECRET_KEY)[=: ]+)[^[:space:]]+/\1[REDACTED]/Ig' \ - -e 's/(Authorization:).*/\1 [REDACTED]/Ig' \ - -e 's/(X-Amz-Signature=)[^&[:space:]]+/\1[REDACTED]/Ig' \ - -e 's/^.*(password|secret|token)[=: ].*/[REDACTED SENSITIVE LINE]/Ig' - } - BODY_FILE="$(mktemp)" - { - echo "The **${SUITE_LABEL}** functional suite failed." - echo "" - echo "- Suite: \`${SUITE}\`" - echo "- Run: ${RUN_URL}" - echo "- Attempt: ${GITHUB_RUN_ATTEMPT}" - echo "- Workflow Commit: ${GITHUB_SHA}" - echo "- Trigger: ${GITHUB_EVENT_NAME}" - echo "- Date: $(date -u +%Y-%m-%d)" - echo "" - echo "## Report (errors and symptoms)" - echo "" - if [ "${EVIDENCE_OUTCOME}" = "success" ] && [ -s "${REPORT_FILE}" ]; then - redact < "${REPORT_FILE}" - elif [ "${EVIDENCE_OUTCOME}" = "success" ] && [ -s "${LOG_FILE:-}" ]; then - echo "(report file missing; log tail below)" - echo "" - tail -n 200 "${LOG_FILE}" | redact - else - echo "(no report or log file was produced)" - fi - } | head -c 55000 > "${BODY_FILE}" - gh label create functional-test -R rustfs/backlog --color d73a4a 2>/dev/null || true - if ! gh issue create -R rustfs/backlog --title "${TITLE}" \ - --body-file "${BODY_FILE}" --label functional-test; then - gh issue create -R rustfs/backlog --title "${TITLE}" --body-file "${BODY_FILE}" + PACKAGE_URL='${{ inputs.package_url }}' + RUSTFS_VERSION='${{ inputs.rustfs_version }}' + PACKAGE_SOURCE="" + if [ -n "${PACKAGE_URL}" ]; then + PACKAGE_SOURCE="${PACKAGE_URL}" + elif [ -n "${RUSTFS_VERSION}" ]; then + PACKAGE_SOURCE="version ${RUSTFS_VERSION}" + else + PACKAGE_SOURCE="${RUSTFS_NIGHTLY_PACKAGE_URL}" fi - echo "filed backlog issue for suite ${SUITE}" + python3 auto-testing/scripts/issue_manager.py handle \ + --repo rustfs/backlog \ + --suite "${SUITE}" --category "${SUITE}" --suite-label "${SUITE_LABEL}" \ + --outcome "${{ steps.test.outcome }}" \ + --report "${FUNCTIONAL_ARTIFACTS_DIR}/cases.md" \ + --report-file "${REPORT_FILE}" \ + --log "${LOG_FILE}" \ + --run-url "${RUN_URL}" \ + --run-id "${GITHUB_RUN_ID}" \ + --attempt "${GITHUB_RUN_ATTEMPT}" \ + --commit "${GITHUB_SHA}" \ + --trigger "${{ github.event_name }}" \ + --package-source "${PACKAGE_SOURCE}" \ + --date "$(date -u +%Y-%m-%d)" - name: Upload report and logs if: ${{ always() && steps.evidence.outcome == 'success' }} diff --git a/.github/workflows/rustfs-performance-test.yml b/.github/workflows/rustfs-performance-test.yml index 699b583cf..c10690344 100644 --- a/.github/workflows/rustfs-performance-test.yml +++ b/.github/workflows/rustfs-performance-test.yml @@ -237,65 +237,48 @@ jobs: echo "created ${REPORT_PATH} in rustfs/dashboard" fi - - name: File failure issue in rustfs/backlog - if: ${{ always() && (failure() || steps.benchmark.outcome == 'failure' || steps.benchmark.outcome == 'cancelled') }} + - name: Manage backlog issues (dedup / label / auto-close) + # Signal-based lifecycle: dedups against open issues by label, + # labels new issues (functional-test, category, case IDs, env), + # closes fixed issues after a fully green run. Never acts on + # cancelled runs. Logic lives in auto-testing/scripts/. + if: ${{ always() && steps.evidence.outcome == 'success' }} continue-on-error: true env: GH_TOKEN: ${{ secrets.PF_TESTING_GH_TOKEN }} - EVIDENCE_OUTCOME: ${{ steps.evidence.outcome }} SUITE: 'performance' SUITE_LABEL: 'Performance' RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} run: | set -euo pipefail if [ -z "${GH_TOKEN:-}" ]; then - echo "PF_TESTING_GH_TOKEN is not configured; skipping backlog issue" + echo "PF_TESTING_GH_TOKEN is not configured; skipping backlog issue management" exit 0 fi - TITLE="[functional][${SUITE}] ${SUITE_LABEL} suite failed (run ${GITHUB_RUN_ID})" - EXISTING="$(gh issue list -R rustfs/backlog --state all \ - --search "in:title \"run ${GITHUB_RUN_ID}\"" \ - --json number --jq '.[].number' || true)" - if [ -n "${EXISTING}" ]; then - echo "backlog issue already exists for run ${GITHUB_RUN_ID}; skipping" + if [ ! -f auto-testing/scripts/issue_manager.py ]; then + echo "issue_manager.py not found in auto-testing checkout; skipping" exit 0 fi - redact() { - sed -E \ - -e 's/(RUSTFS_(ACCESS_KEY|SECRET_KEY)[=: ]+)[^[:space:]]+/\1[REDACTED]/Ig' \ - -e 's/(Authorization:).*/\1 [REDACTED]/Ig' \ - -e 's/(X-Amz-Signature=)[^&[:space:]]+/\1[REDACTED]/Ig' \ - -e 's/^.*(password|secret|token)[=: ].*/[REDACTED SENSITIVE LINE]/Ig' - } - BODY_FILE="$(mktemp)" - { - echo "The **${SUITE_LABEL}** functional suite failed." - echo "" - echo "- Suite: \`${SUITE}\`" - echo "- Run: ${RUN_URL}" - echo "- Attempt: ${GITHUB_RUN_ATTEMPT}" - echo "- Workflow Commit: ${GITHUB_SHA}" - echo "- Trigger: ${GITHUB_EVENT_NAME}" - echo "- Date: $(date -u +%Y-%m-%d)" - echo "" - echo "## Report (errors and symptoms)" - echo "" - if [ "${EVIDENCE_OUTCOME}" = "success" ] && [ -s "${REPORT_FILE}" ]; then - redact < "${REPORT_FILE}" - elif [ "${EVIDENCE_OUTCOME}" = "success" ] && [ -s "${LOG_FILE:-}" ]; then - echo "(report file missing; log tail below)" - echo "" - tail -n 200 "${LOG_FILE}" | redact - else - echo "(no report or log file was produced)" - fi - } | head -c 55000 > "${BODY_FILE}" - gh label create functional-test -R rustfs/backlog --color d73a4a 2>/dev/null || true - if ! gh issue create -R rustfs/backlog --title "${TITLE}" \ - --body-file "${BODY_FILE}" --label functional-test; then - gh issue create -R rustfs/backlog --title "${TITLE}" --body-file "${BODY_FILE}" + PACKAGE_URL='${{ inputs.package_url }}' + PACKAGE_SOURCE="" + if [ -n "${PACKAGE_URL}" ]; then + PACKAGE_SOURCE="${PACKAGE_URL}" + else + PACKAGE_SOURCE="${RUSTFS_NIGHTLY_PACKAGE_URL}" fi - echo "filed backlog issue for suite ${SUITE}" + python3 auto-testing/scripts/issue_manager.py handle \ + --repo rustfs/backlog \ + --suite "${SUITE}" --category "${SUITE}" --suite-label "${SUITE_LABEL}" \ + --outcome "${{ steps.benchmark.outcome }}" \ + --report-file "${REPORT_FILE}" \ + --log "${LOG_FILE}" \ + --run-url "${RUN_URL}" \ + --run-id "${GITHUB_RUN_ID}" \ + --attempt "${GITHUB_RUN_ATTEMPT}" \ + --commit "${GITHUB_SHA}" \ + --trigger "${{ github.event_name }}" \ + --package-source "${PACKAGE_SOURCE}" \ + --date "$(date -u +%Y-%m-%d)" - name: Upload test logs & results if: ${{ always() && steps.evidence.outcome == 'success' }} diff --git a/.github/workflows/rustfs-pool-expand-test.yml b/.github/workflows/rustfs-pool-expand-test.yml index c700df7c8..e320402fa 100644 --- a/.github/workflows/rustfs-pool-expand-test.yml +++ b/.github/workflows/rustfs-pool-expand-test.yml @@ -556,64 +556,54 @@ jobs: fi rm -f "${B64_FILE}" - - name: File failure issue in rustfs/backlog - if: ${{ always() && (failure() || steps.pool_test.outcome == 'failure' || steps.pool_test.outcome == 'cancelled') }} + - name: Manage backlog issues (dedup / label / auto-close) + # Replaces the old per-run failure filing. One entry point that: + # - dedups by signal: failing cases are matched against open backlog + # issues by label (category + case ID); covered cases become a + # comment on the existing issue, only uncovered cases file a new one + # - labels new issues (functional-test, category, case IDs, env) + # - closes fixed issues after a fully green run + # - never files or closes on cancelled runs + if: ${{ always() }} continue-on-error: true env: GH_TOKEN: ${{ secrets.PF_TESTING_GH_TOKEN }} SUITE: 'pool' SUITE_LABEL: 'Pool expansion' RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - REPORT_FILE: '${{ env.POOL_ARTIFACT_DIR }}/pool-report.md' - LOG_FILE: '${{ env.POOL_ARTIFACT_DIR }}/pool-test.log' run: | set -euo pipefail if [ -z "${GH_TOKEN:-}" ]; then - echo "PF_TESTING_GH_TOKEN is not configured; skipping backlog issue" + echo "PF_TESTING_GH_TOKEN is not configured; skipping backlog issue management" exit 0 fi - TITLE="[functional][${SUITE}] ${SUITE_LABEL} suite failed (run ${GITHUB_RUN_ID})" - EXISTING="$(gh issue list -R rustfs/backlog --state all \ - --search "in:title \"run ${GITHUB_RUN_ID}\"" \ - --json number --jq '.[].number' || true)" - if [ -n "${EXISTING}" ]; then - echo "backlog issue already exists for run ${GITHUB_RUN_ID}; skipping" + if [ ! -f auto-testing/scripts/issue_manager.py ]; then + echo "issue_manager.py not found in auto-testing checkout; skipping" exit 0 fi - redact() { - sed -E \ - -e 's/(RUSTFS_(ACCESS_KEY|SECRET_KEY)[=: ]+)[^[:space:]]+/\1[REDACTED]/Ig' \ - -e 's/(Authorization:).*/\1 [REDACTED]/Ig' \ - -e 's/(X-Amz-Signature=)[^&[:space:]]+/\1[REDACTED]/Ig' \ - -e 's/^.*(password|secret|token)[=: ].*/[REDACTED SENSITIVE LINE]/Ig' - } - BODY_FILE="$(mktemp)" - { - echo "The **${SUITE_LABEL}** functional suite failed." - echo "" - echo "- Suite: \`${SUITE}\`" - echo "- Run: ${RUN_URL}" - echo "- Trigger: ${GITHUB_EVENT_NAME}" - echo "- Date: $(date -u +%Y-%m-%d)" - echo "" - echo "## Report (errors and symptoms)" - echo "" - if [ -s "${REPORT_FILE}" ]; then - redact < "${REPORT_FILE}" - elif [ -s "${LOG_FILE:-}" ]; then - echo "(report file missing; log tail below)" - echo "" - tail -n 200 "${LOG_FILE}" | redact - else - echo "(no report or log file was produced)" - fi - } | head -c 55000 > "${BODY_FILE}" - gh label create functional-test -R rustfs/backlog --color d73a4a 2>/dev/null || true - if ! gh issue create -R rustfs/backlog --title "${TITLE}" \ - --body-file "${BODY_FILE}" --label functional-test; then - gh issue create -R rustfs/backlog --title "${TITLE}" --body-file "${BODY_FILE}" + PACKAGE_URL='${{ inputs.package_url }}' + RUSTFS_VERSION='${{ inputs.rustfs_version }}' + PACKAGE_SOURCE="" + if [ -n "${PACKAGE_URL}" ]; then + PACKAGE_SOURCE="${PACKAGE_URL}" + elif [ -n "${RUSTFS_VERSION}" ]; then + PACKAGE_SOURCE="version ${RUSTFS_VERSION}" + else + PACKAGE_SOURCE="${RUSTFS_NIGHTLY_PACKAGE_URL}" fi - echo "filed backlog issue for suite ${SUITE}" + python3 auto-testing/scripts/issue_manager.py handle \ + --repo rustfs/backlog \ + --suite "${SUITE}" --category "${SUITE}" --suite-label "${SUITE_LABEL}" \ + --outcome "${{ steps.pool_test.outcome }}" \ + --report-file "${POOL_ARTIFACT_DIR}/pool-report.md" \ + --log "${POOL_ARTIFACT_DIR}/pool-test.log" \ + --run-url "${RUN_URL}" \ + --run-id "${GITHUB_RUN_ID}" \ + --attempt "${GITHUB_RUN_ATTEMPT}" \ + --commit "${GITHUB_SHA}" \ + --trigger "${{ github.event_name }}" \ + --package-source "${PACKAGE_SOURCE}" \ + --date "$(date -u +%Y-%m-%d)" - name: Upload test logs if: always() diff --git a/.github/workflows/rustfs-replication-test.yml b/.github/workflows/rustfs-replication-test.yml index 969bf60bc..5e8ca4454 100644 --- a/.github/workflows/rustfs-replication-test.yml +++ b/.github/workflows/rustfs-replication-test.yml @@ -238,65 +238,55 @@ jobs: fi rm -f "${B64_FILE}" - - name: File failure issue in rustfs/backlog - if: ${{ always() && (failure() || steps.test.outcome == 'failure' || steps.test.outcome == 'cancelled') }} + - name: Manage backlog issues (dedup / label / auto-close) + # Replaces the old per-run failure filing. One entry point that: + # - dedups by signal: failing cases are matched against open backlog + # issues by label (category + case ID); covered cases become a + # comment on the existing issue, only uncovered cases file a new one + # - labels new issues (functional-test, category, case IDs, env) + # - closes fixed issues after a fully green run + # - never files or closes on cancelled runs + if: ${{ always() && steps.evidence.outcome == 'success' }} continue-on-error: true env: GH_TOKEN: ${{ secrets.PF_TESTING_GH_TOKEN }} - EVIDENCE_OUTCOME: ${{ steps.evidence.outcome }} SUITE: 'replication' SUITE_LABEL: 'Replication (bucket + site)' RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} run: | set -euo pipefail if [ -z "${GH_TOKEN:-}" ]; then - echo "PF_TESTING_GH_TOKEN is not configured; skipping backlog issue" + echo "PF_TESTING_GH_TOKEN is not configured; skipping backlog issue management" exit 0 fi - TITLE="[functional][${SUITE}] ${SUITE_LABEL} suite failed (run ${GITHUB_RUN_ID})" - EXISTING="$(gh issue list -R rustfs/backlog --state all \ - --search "in:title \"run ${GITHUB_RUN_ID}\"" \ - --json number --jq '.[].number' || true)" - if [ -n "${EXISTING}" ]; then - echo "backlog issue already exists for run ${GITHUB_RUN_ID}; skipping" + if [ ! -f auto-testing/scripts/issue_manager.py ]; then + echo "issue_manager.py not found in auto-testing checkout; skipping" exit 0 fi - redact() { - sed -E \ - -e 's/(RUSTFS_(ACCESS_KEY|SECRET_KEY)[=: ]+)[^[:space:]]+/\1[REDACTED]/Ig' \ - -e 's/(Authorization:).*/\1 [REDACTED]/Ig' \ - -e 's/(X-Amz-Signature=)[^&[:space:]]+/\1[REDACTED]/Ig' \ - -e 's/^.*(password|secret|token)[=: ].*/[REDACTED SENSITIVE LINE]/Ig' - } - BODY_FILE="$(mktemp)" - { - echo "The **${SUITE_LABEL}** functional suite failed." - echo "" - echo "- Suite: \`${SUITE}\`" - echo "- Run: ${RUN_URL}" - echo "- Attempt: ${GITHUB_RUN_ATTEMPT}" - echo "- Workflow Commit: ${GITHUB_SHA}" - echo "- Trigger: ${GITHUB_EVENT_NAME}" - echo "- Date: $(date -u +%Y-%m-%d)" - echo "" - echo "## Report (errors and symptoms)" - echo "" - if [ "${EVIDENCE_OUTCOME}" = "success" ] && [ -s "${REPORT_FILE}" ]; then - redact < "${REPORT_FILE}" - elif [ "${EVIDENCE_OUTCOME}" = "success" ] && [ -s "${LOG_FILE:-}" ]; then - echo "(report file missing; log tail below)" - echo "" - tail -n 200 "${LOG_FILE}" | redact - else - echo "(no report or log file was produced)" - fi - } | head -c 55000 > "${BODY_FILE}" - gh label create functional-test -R rustfs/backlog --color d73a4a 2>/dev/null || true - if ! gh issue create -R rustfs/backlog --title "${TITLE}" \ - --body-file "${BODY_FILE}" --label functional-test; then - gh issue create -R rustfs/backlog --title "${TITLE}" --body-file "${BODY_FILE}" + PACKAGE_URL='${{ inputs.package_url }}' + RUSTFS_VERSION='${{ inputs.rustfs_version }}' + PACKAGE_SOURCE="" + if [ -n "${PACKAGE_URL}" ]; then + PACKAGE_SOURCE="${PACKAGE_URL}" + elif [ -n "${RUSTFS_VERSION}" ]; then + PACKAGE_SOURCE="version ${RUSTFS_VERSION}" + else + PACKAGE_SOURCE="${RUSTFS_NIGHTLY_PACKAGE_URL}" fi - echo "filed backlog issue for suite ${SUITE}" + python3 auto-testing/scripts/issue_manager.py handle \ + --repo rustfs/backlog \ + --suite "${SUITE}" --category "${SUITE}" --suite-label "${SUITE_LABEL}" \ + --outcome "${{ steps.test.outcome }}" \ + --report "${FUNCTIONAL_ARTIFACTS_DIR}/cases.md" \ + --report-file "${REPORT_FILE}" \ + --log "${LOG_FILE}" \ + --run-url "${RUN_URL}" \ + --run-id "${GITHUB_RUN_ID}" \ + --attempt "${GITHUB_RUN_ATTEMPT}" \ + --commit "${GITHUB_SHA}" \ + --trigger "${{ github.event_name }}" \ + --package-source "${PACKAGE_SOURCE}" \ + --date "$(date -u +%Y-%m-%d)" - name: Upload report and logs if: ${{ always() && steps.evidence.outcome == 'success' }} diff --git a/.github/workflows/rustfs-s3-compat-test.yml b/.github/workflows/rustfs-s3-compat-test.yml index 6aad45711..7bdc16de4 100644 --- a/.github/workflows/rustfs-s3-compat-test.yml +++ b/.github/workflows/rustfs-s3-compat-test.yml @@ -207,65 +207,55 @@ jobs: fi rm -f "${B64_FILE}" - - name: File failure issue in rustfs/backlog - if: ${{ always() && (failure() || steps.test.outcome == 'failure' || steps.test.outcome == 'cancelled') }} + - name: Manage backlog issues (dedup / label / auto-close) + # Replaces the old per-run failure filing. One entry point that: + # - dedups by signal: failing cases are matched against open backlog + # issues by label (category + case ID); covered cases become a + # comment on the existing issue, only uncovered cases file a new one + # - labels new issues (functional-test, category, case IDs, env) + # - closes fixed issues after a fully green run + # - never files or closes on cancelled runs + if: ${{ always() && steps.evidence.outcome == 'success' }} continue-on-error: true env: GH_TOKEN: ${{ secrets.PF_TESTING_GH_TOKEN }} - EVIDENCE_OUTCOME: ${{ steps.evidence.outcome }} SUITE: 's3' SUITE_LABEL: 'S3 compatibility' RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} run: | set -euo pipefail if [ -z "${GH_TOKEN:-}" ]; then - echo "PF_TESTING_GH_TOKEN is not configured; skipping backlog issue" + echo "PF_TESTING_GH_TOKEN is not configured; skipping backlog issue management" exit 0 fi - TITLE="[functional][${SUITE}] ${SUITE_LABEL} suite failed (run ${GITHUB_RUN_ID})" - EXISTING="$(gh issue list -R rustfs/backlog --state all \ - --search "in:title \"run ${GITHUB_RUN_ID}\"" \ - --json number --jq '.[].number' || true)" - if [ -n "${EXISTING}" ]; then - echo "backlog issue already exists for run ${GITHUB_RUN_ID}; skipping" + if [ ! -f auto-testing/scripts/issue_manager.py ]; then + echo "issue_manager.py not found in auto-testing checkout; skipping" exit 0 fi - redact() { - sed -E \ - -e 's/(RUSTFS_(ACCESS_KEY|SECRET_KEY)[=: ]+)[^[:space:]]+/\1[REDACTED]/Ig' \ - -e 's/(Authorization:).*/\1 [REDACTED]/Ig' \ - -e 's/(X-Amz-Signature=)[^&[:space:]]+/\1[REDACTED]/Ig' \ - -e 's/^.*(password|secret|token)[=: ].*/[REDACTED SENSITIVE LINE]/Ig' - } - BODY_FILE="$(mktemp)" - { - echo "The **${SUITE_LABEL}** functional suite failed." - echo "" - echo "- Suite: \`${SUITE}\`" - echo "- Run: ${RUN_URL}" - echo "- Attempt: ${GITHUB_RUN_ATTEMPT}" - echo "- Workflow Commit: ${GITHUB_SHA}" - echo "- Trigger: ${GITHUB_EVENT_NAME}" - echo "- Date: $(date -u +%Y-%m-%d)" - echo "" - echo "## Report (errors and symptoms)" - echo "" - if [ "${EVIDENCE_OUTCOME}" = "success" ] && [ -s "${REPORT_FILE}" ]; then - redact < "${REPORT_FILE}" - elif [ "${EVIDENCE_OUTCOME}" = "success" ] && [ -s "${LOG_FILE:-}" ]; then - echo "(report file missing; log tail below)" - echo "" - tail -n 200 "${LOG_FILE}" | redact - else - echo "(no report or log file was produced)" - fi - } | head -c 55000 > "${BODY_FILE}" - gh label create functional-test -R rustfs/backlog --color d73a4a 2>/dev/null || true - if ! gh issue create -R rustfs/backlog --title "${TITLE}" \ - --body-file "${BODY_FILE}" --label functional-test; then - gh issue create -R rustfs/backlog --title "${TITLE}" --body-file "${BODY_FILE}" + PACKAGE_URL='${{ inputs.package_url }}' + RUSTFS_VERSION='${{ inputs.rustfs_version }}' + PACKAGE_SOURCE="" + if [ -n "${PACKAGE_URL}" ]; then + PACKAGE_SOURCE="${PACKAGE_URL}" + elif [ -n "${RUSTFS_VERSION}" ]; then + PACKAGE_SOURCE="version ${RUSTFS_VERSION}" + else + PACKAGE_SOURCE="${RUSTFS_NIGHTLY_PACKAGE_URL}" fi - echo "filed backlog issue for suite ${SUITE}" + python3 auto-testing/scripts/issue_manager.py handle \ + --repo rustfs/backlog \ + --suite "${SUITE}" --category "${SUITE}" --suite-label "${SUITE_LABEL}" \ + --outcome "${{ steps.test.outcome }}" \ + --report "${FUNCTIONAL_ARTIFACTS_DIR}/cases.md" \ + --report-file "${REPORT_FILE}" \ + --log "${LOG_FILE}" \ + --run-url "${RUN_URL}" \ + --run-id "${GITHUB_RUN_ID}" \ + --attempt "${GITHUB_RUN_ATTEMPT}" \ + --commit "${GITHUB_SHA}" \ + --trigger "${{ github.event_name }}" \ + --package-source "${PACKAGE_SOURCE}" \ + --date "$(date -u +%Y-%m-%d)" - name: Upload report and logs if: ${{ always() && steps.evidence.outcome == 'success' }} diff --git a/.github/workflows/rustfs-security-test.yml b/.github/workflows/rustfs-security-test.yml index 6653705d3..b8926b7a1 100644 --- a/.github/workflows/rustfs-security-test.yml +++ b/.github/workflows/rustfs-security-test.yml @@ -239,65 +239,54 @@ jobs: fi rm -f "${B64_FILE}" - - name: File failure issue in rustfs/backlog - if: ${{ always() && (failure() || steps.test.outcome == 'failure' || steps.test.outcome == 'cancelled') }} + - name: Manage backlog issues (dedup / label / auto-close) + # Replaces the old per-run failure filing. One entry point that: + # - dedups by signal: failing cases are matched against open backlog + # issues by label (category + case ID); covered cases become a + # comment on the existing issue, only uncovered cases file a new one + # - labels new issues (functional-test, category, case IDs, env) + # - closes fixed issues after a fully green run + # - never files or closes on cancelled runs + if: ${{ always() && steps.evidence.outcome == 'success' }} continue-on-error: true env: GH_TOKEN: ${{ secrets.PF_TESTING_GH_TOKEN }} SUITE: 'security' SUITE_LABEL: 'Security' - EVIDENCE_OUTCOME: ${{ steps.evidence.outcome }} RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - REPORT_FILE: ${{ env.SECURITY_ARTIFACTS_DIR }}/report.md - LOG_FILE: '' run: | set -euo pipefail if [ -z "${GH_TOKEN:-}" ]; then - echo "PF_TESTING_GH_TOKEN is not configured; skipping backlog issue" + echo "PF_TESTING_GH_TOKEN is not configured; skipping backlog issue management" exit 0 fi - TITLE="[functional][${SUITE}] ${SUITE_LABEL} suite failed (run ${GITHUB_RUN_ID})" - EXISTING="$(gh issue list -R rustfs/backlog --state all \ - --search "in:title \"run ${GITHUB_RUN_ID}\"" \ - --json number --jq '.[].number' || true)" - if [ -n "${EXISTING}" ]; then - echo "backlog issue already exists for run ${GITHUB_RUN_ID}; skipping" + if [ ! -f auto-testing/scripts/issue_manager.py ]; then + echo "issue_manager.py not found in auto-testing checkout; skipping" exit 0 fi - redact() { - sed -E \ - -e 's/(RUSTFS_(ACCESS_KEY|SECRET_KEY)[=: ]+)[^[:space:]]+/\1[REDACTED]/Ig' \ - -e 's/(Authorization:).*/\1 [REDACTED]/Ig' \ - -e 's/(X-Amz-Signature=)[^&[:space:]]+/\1[REDACTED]/Ig' \ - -e 's/^.*(password|secret|token)[=: ].*/[REDACTED SENSITIVE LINE]/Ig' - } - BODY_FILE="$(mktemp)" - { - echo "The **${SUITE_LABEL}** functional suite failed." - echo "" - echo "- Suite: \`${SUITE}\`" - echo "- Run: ${RUN_URL}" - echo "- Trigger: ${GITHUB_EVENT_NAME}" - echo "- Date: $(date -u +%Y-%m-%d)" - echo "" - echo "## Report (errors and symptoms)" - echo "" - if [ "${EVIDENCE_OUTCOME}" = "success" ] && [ -s "${REPORT_FILE}" ]; then - redact < "${REPORT_FILE}" - elif [ -s "${LOG_FILE:-}" ]; then - echo "(report file missing; log tail below)" - echo "" - tail -n 200 "${LOG_FILE}" | redact - else - echo "(no report or log file was produced)" - fi - } | head -c 55000 > "${BODY_FILE}" - gh label create functional-test -R rustfs/backlog --color d73a4a 2>/dev/null || true - if ! gh issue create -R rustfs/backlog --title "${TITLE}" \ - --body-file "${BODY_FILE}" --label functional-test; then - gh issue create -R rustfs/backlog --title "${TITLE}" --body-file "${BODY_FILE}" + PACKAGE_URL='${{ inputs.package_url }}' + RUSTFS_VERSION='${{ inputs.rustfs_version }}' + PACKAGE_SOURCE="" + if [ -n "${PACKAGE_URL}" ]; then + PACKAGE_SOURCE="${PACKAGE_URL}" + elif [ -n "${RUSTFS_VERSION}" ]; then + PACKAGE_SOURCE="version ${RUSTFS_VERSION}" + else + PACKAGE_SOURCE="${RUSTFS_NIGHTLY_PACKAGE_URL}" fi - echo "filed backlog issue for suite ${SUITE}" + python3 auto-testing/scripts/issue_manager.py handle \ + --repo rustfs/backlog \ + --suite "${SUITE}" --category "${SUITE}" --suite-label "${SUITE_LABEL}" \ + --outcome "${{ steps.test.outcome }}" \ + --report-file "${SECURITY_ARTIFACTS_DIR}/report.md" \ + --log "${SECURITY_ARTIFACTS_DIR}/suite.log" \ + --run-url "${RUN_URL}" \ + --run-id "${GITHUB_RUN_ID}" \ + --attempt "${GITHUB_RUN_ATTEMPT}" \ + --commit "${GITHUB_SHA}" \ + --trigger "${{ github.event_name }}" \ + --package-source "${PACKAGE_SOURCE}" \ + --date "$(date -u +%Y-%m-%d)" - name: Upload report and logs if: ${{ always() && steps.evidence.outcome == 'success' }} diff --git a/.github/workflows/rustfs-storage-test.yml b/.github/workflows/rustfs-storage-test.yml index 88ff53fe1..3651fe486 100644 --- a/.github/workflows/rustfs-storage-test.yml +++ b/.github/workflows/rustfs-storage-test.yml @@ -222,65 +222,55 @@ jobs: fi rm -f "${B64_FILE}" - - name: File failure issue in rustfs/backlog - if: ${{ always() && (failure() || steps.test.outcome == 'failure' || steps.test.outcome == 'cancelled') }} + - name: Manage backlog issues (dedup / label / auto-close) + # Replaces the old per-run failure filing. One entry point that: + # - dedups by signal: failing cases are matched against open backlog + # issues by label (category + case ID); covered cases become a + # comment on the existing issue, only uncovered cases file a new one + # - labels new issues (functional-test, category, case IDs, env) + # - closes fixed issues after a fully green run + # - never files or closes on cancelled runs + if: ${{ always() && steps.evidence.outcome == 'success' }} continue-on-error: true env: GH_TOKEN: ${{ secrets.PF_TESTING_GH_TOKEN }} - EVIDENCE_OUTCOME: ${{ steps.evidence.outcome }} SUITE: 'storage' SUITE_LABEL: 'Storage engine' RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} run: | set -euo pipefail if [ -z "${GH_TOKEN:-}" ]; then - echo "PF_TESTING_GH_TOKEN is not configured; skipping backlog issue" + echo "PF_TESTING_GH_TOKEN is not configured; skipping backlog issue management" exit 0 fi - TITLE="[functional][${SUITE}] ${SUITE_LABEL} suite failed (run ${GITHUB_RUN_ID})" - EXISTING="$(gh issue list -R rustfs/backlog --state all \ - --search "in:title \"run ${GITHUB_RUN_ID}\"" \ - --json number --jq '.[].number' || true)" - if [ -n "${EXISTING}" ]; then - echo "backlog issue already exists for run ${GITHUB_RUN_ID}; skipping" + if [ ! -f auto-testing/scripts/issue_manager.py ]; then + echo "issue_manager.py not found in auto-testing checkout; skipping" exit 0 fi - redact() { - sed -E \ - -e 's/(RUSTFS_(ACCESS_KEY|SECRET_KEY)[=: ]+)[^[:space:]]+/\1[REDACTED]/Ig' \ - -e 's/(Authorization:).*/\1 [REDACTED]/Ig' \ - -e 's/(X-Amz-Signature=)[^&[:space:]]+/\1[REDACTED]/Ig' \ - -e 's/^.*(password|secret|token)[=: ].*/[REDACTED SENSITIVE LINE]/Ig' - } - BODY_FILE="$(mktemp)" - { - echo "The **${SUITE_LABEL}** functional suite failed." - echo "" - echo "- Suite: \`${SUITE}\`" - echo "- Run: ${RUN_URL}" - echo "- Attempt: ${GITHUB_RUN_ATTEMPT}" - echo "- Workflow Commit: ${GITHUB_SHA}" - echo "- Trigger: ${GITHUB_EVENT_NAME}" - echo "- Date: $(date -u +%Y-%m-%d)" - echo "" - echo "## Report (errors and symptoms)" - echo "" - if [ "${EVIDENCE_OUTCOME}" = "success" ] && [ -s "${REPORT_FILE}" ]; then - redact < "${REPORT_FILE}" - elif [ "${EVIDENCE_OUTCOME}" = "success" ] && [ -s "${LOG_FILE:-}" ]; then - echo "(report file missing; log tail below)" - echo "" - tail -n 200 "${LOG_FILE}" | redact - else - echo "(no report or log file was produced)" - fi - } | head -c 55000 > "${BODY_FILE}" - gh label create functional-test -R rustfs/backlog --color d73a4a 2>/dev/null || true - if ! gh issue create -R rustfs/backlog --title "${TITLE}" \ - --body-file "${BODY_FILE}" --label functional-test; then - gh issue create -R rustfs/backlog --title "${TITLE}" --body-file "${BODY_FILE}" + PACKAGE_URL='${{ inputs.package_url }}' + RUSTFS_VERSION='${{ inputs.rustfs_version }}' + PACKAGE_SOURCE="" + if [ -n "${PACKAGE_URL}" ]; then + PACKAGE_SOURCE="${PACKAGE_URL}" + elif [ -n "${RUSTFS_VERSION}" ]; then + PACKAGE_SOURCE="version ${RUSTFS_VERSION}" + else + PACKAGE_SOURCE="${RUSTFS_NIGHTLY_PACKAGE_URL}" fi - echo "filed backlog issue for suite ${SUITE}" + python3 auto-testing/scripts/issue_manager.py handle \ + --repo rustfs/backlog \ + --suite "${SUITE}" --category "${SUITE}" --suite-label "${SUITE_LABEL}" \ + --outcome "${{ steps.test.outcome }}" \ + --report "${FUNCTIONAL_ARTIFACTS_DIR}/cases.md" \ + --report-file "${REPORT_FILE}" \ + --log "${LOG_FILE}" \ + --run-url "${RUN_URL}" \ + --run-id "${GITHUB_RUN_ID}" \ + --attempt "${GITHUB_RUN_ATTEMPT}" \ + --commit "${GITHUB_SHA}" \ + --trigger "${{ github.event_name }}" \ + --package-source "${PACKAGE_SOURCE}" \ + --date "$(date -u +%Y-%m-%d)" - name: Upload report and logs if: ${{ always() && steps.evidence.outcome == 'success' }} diff --git a/.github/workflows/rustfs-tier-test.yml b/.github/workflows/rustfs-tier-test.yml index 0bbdd3fc7..4314331fa 100644 --- a/.github/workflows/rustfs-tier-test.yml +++ b/.github/workflows/rustfs-tier-test.yml @@ -494,75 +494,55 @@ jobs: fi [ "${failed}" -eq 0 ] - - name: File failure issue in rustfs/backlog - if: ${{ always() && (failure() || steps.test.outcome == 'failure' || steps.test.outcome == 'cancelled' || steps.evidence_verify.outcome == 'failure' || steps.evidence_verify.outcome == 'cancelled' || steps.gate.outcome == 'failure' || steps.gate.outcome == 'cancelled') }} + - name: Manage backlog issues (dedup / label / auto-close) + # Replaces the old per-run failure filing. One entry point that: + # - dedups by signal: failing cases are matched against open backlog + # issues by label (category + case ID); covered cases become a + # comment on the existing issue, only uncovered cases file a new one + # - labels new issues (functional-test, category, case IDs, env) + # - closes fixed issues after a fully green run + # - never files or closes on cancelled runs + if: ${{ always() && steps.evidence.outcome == 'success' }} continue-on-error: true env: GH_TOKEN: ${{ secrets.PF_TESTING_GH_TOKEN }} SUITE: 'tier' SUITE_LABEL: 'Tier' RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - EVIDENCE_DIR: ${{ env.TIER_ARTIFACTS_DIR }} - EVIDENCE_OUTCOME: ${{ steps.evidence.outcome }} - VERIFY_OUTCOME: ${{ steps.evidence_verify.outcome }} - GATE_OUTCOME: ${{ steps.gate.outcome }} - REPORT_FILE: ${{ env.TIER_ARTIFACTS_DIR }}/rustfs-tier-report.md - LOG_FILE: ${{ env.TIER_ARTIFACTS_DIR }}/rustfs-tier.log run: | set -euo pipefail if [ -z "${GH_TOKEN:-}" ]; then - echo "PF_TESTING_GH_TOKEN is not configured; skipping backlog issue" + echo "PF_TESTING_GH_TOKEN is not configured; skipping backlog issue management" exit 0 fi - TITLE="[functional][${SUITE}] ${SUITE_LABEL} suite failed (run ${GITHUB_RUN_ID})" - EXISTING="$(gh issue list -R rustfs/backlog --state all \ - --search "in:title \"run ${GITHUB_RUN_ID}\"" \ - --json number --jq '.[].number' || true)" - if [ -n "${EXISTING}" ]; then - echo "backlog issue already exists for run ${GITHUB_RUN_ID}; skipping" + if [ ! -f auto-testing/scripts/issue_manager.py ]; then + echo "issue_manager.py not found in auto-testing checkout; skipping" exit 0 fi - redact() { - sed -E \ - -e 's/(RUSTFS_(ACCESS_KEY|SECRET_KEY)[=: ]+)[^[:space:]]+/\1[REDACTED]/Ig' \ - -e 's/(Authorization:).*/\1 [REDACTED]/Ig' \ - -e 's/(X-Amz-Signature=)[^&[:space:]]+/\1[REDACTED]/Ig' \ - -e 's/^.*(password|secret|token)[=: ].*/[REDACTED SENSITIVE LINE]/Ig' - } - BODY_FILE="$(mktemp)" - { - echo "The **${SUITE_LABEL}** functional suite failed." - echo "" - echo "- Suite: \`${SUITE}\`" - echo "- Run: ${RUN_URL}" - echo "- Trigger: ${GITHUB_EVENT_NAME}" - echo "- Date: $(date -u +%Y-%m-%d)" - echo "- Evidence initialization: ${EVIDENCE_OUTCOME}" - echo "- Evidence verification: ${VERIFY_OUTCOME}" - echo "- Final gate: ${GATE_OUTCOME}" - echo "" - echo "## Report (errors and symptoms)" - echo "" - if [ "${EVIDENCE_OUTCOME}" != "success" ]; then - echo "(the run evidence directory was rejected; its contents were not read)" - elif [ ! -d "${EVIDENCE_DIR}" ] || [ -L "${EVIDENCE_DIR}" ]; then - echo "(the run evidence directory is missing or unsafe; its contents were not read)" - elif [ -s "${REPORT_FILE}" ]; then - redact < "${REPORT_FILE}" - elif [ -s "${LOG_FILE:-}" ]; then - echo "(report file missing; log tail below)" - echo "" - tail -n 200 "${LOG_FILE}" | redact - else - echo "(no report or log file was produced)" - fi - } | head -c 55000 > "${BODY_FILE}" - gh label create functional-test -R rustfs/backlog --color d73a4a 2>/dev/null || true - if ! gh issue create -R rustfs/backlog --title "${TITLE}" \ - --body-file "${BODY_FILE}" --label functional-test; then - gh issue create -R rustfs/backlog --title "${TITLE}" --body-file "${BODY_FILE}" + PACKAGE_URL='${{ inputs.package_url }}' + RUSTFS_VERSION='${{ inputs.rustfs_version }}' + PACKAGE_SOURCE="" + if [ -n "${PACKAGE_URL}" ]; then + PACKAGE_SOURCE="${PACKAGE_URL}" + elif [ -n "${RUSTFS_VERSION}" ]; then + PACKAGE_SOURCE="version ${RUSTFS_VERSION}" + else + PACKAGE_SOURCE="${RUSTFS_NIGHTLY_PACKAGE_URL}" fi - echo "filed backlog issue for suite ${SUITE}" + python3 auto-testing/scripts/issue_manager.py handle \ + --repo rustfs/backlog \ + --suite "${SUITE}" --category "${SUITE}" --suite-label "${SUITE_LABEL}" \ + --outcome "${{ steps.test.outcome }}" \ + --report "${TIER_ARTIFACTS_DIR}/rustfs-tier-cases.md" \ + --report-file "${TIER_ARTIFACTS_DIR}/rustfs-tier-report.md" \ + --log "${TIER_ARTIFACTS_DIR}/rustfs-tier.log" \ + --run-url "${RUN_URL}" \ + --run-id "${GITHUB_RUN_ID}" \ + --attempt "${GITHUB_RUN_ATTEMPT}" \ + --commit "${GITHUB_SHA}" \ + --trigger "${{ github.event_name }}" \ + --package-source "${PACKAGE_SOURCE}" \ + --date "$(date -u +%Y-%m-%d)" - name: "Continue functional chain (next: Storage engine)" # Only chain-triggered runs forward to the next suite; standalone diff --git a/.github/workflows/rustfs-upgrade-test.yml b/.github/workflows/rustfs-upgrade-test.yml index 5a91793a0..bad5c1119 100644 --- a/.github/workflows/rustfs-upgrade-test.yml +++ b/.github/workflows/rustfs-upgrade-test.yml @@ -306,65 +306,62 @@ jobs: fi rm -f "${B64_FILE}" - - name: File failure issue in rustfs/backlog - if: ${{ always() && (failure() || steps.test.outcome == 'failure' || steps.test.outcome == 'cancelled') }} + - name: Manage backlog issues (dedup / label / auto-close) + # Replaces the old per-run failure filing. One entry point that: + # - dedups by signal: failing cases are matched against open backlog + # issues by label (category + case ID); covered cases become a + # comment on the existing issue, only uncovered cases file a new one + # - labels new issues (functional-test, category, case IDs, env) + # - closes fixed issues after a fully green run + # - never files or closes on cancelled runs + if: ${{ always() && steps.evidence.outcome == 'success' }} continue-on-error: true env: GH_TOKEN: ${{ secrets.PF_TESTING_GH_TOKEN }} - EVIDENCE_OUTCOME: ${{ steps.evidence.outcome }} SUITE: 'upgrade' SUITE_LABEL: 'Upgrade compatibility' RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} run: | set -euo pipefail if [ -z "${GH_TOKEN:-}" ]; then - echo "PF_TESTING_GH_TOKEN is not configured; skipping backlog issue" + echo "PF_TESTING_GH_TOKEN is not configured; skipping backlog issue management" exit 0 fi - TITLE="[functional][${SUITE}] ${SUITE_LABEL} suite failed (run ${GITHUB_RUN_ID})" - EXISTING="$(gh issue list -R rustfs/backlog --state all \ - --search "in:title \"run ${GITHUB_RUN_ID}\"" \ - --json number --jq '.[].number' || true)" - if [ -n "${EXISTING}" ]; then - echo "backlog issue already exists for run ${GITHUB_RUN_ID}; skipping" + if [ ! -f auto-testing/scripts/issue_manager.py ]; then + echo "issue_manager.py not found in auto-testing checkout; skipping" exit 0 fi - redact() { - sed -E \ - -e 's/(RUSTFS_(ACCESS_KEY|SECRET_KEY)[=: ]+)[^[:space:]]+/\1[REDACTED]/Ig' \ - -e 's/(Authorization:).*/\1 [REDACTED]/Ig' \ - -e 's/(X-Amz-Signature=)[^&[:space:]]+/\1[REDACTED]/Ig' \ - -e 's/^.*(password|secret|token)[=: ].*/[REDACTED SENSITIVE LINE]/Ig' - } - BODY_FILE="$(mktemp)" - { - echo "The **${SUITE_LABEL}** functional suite failed." - echo "" - echo "- Suite: \`${SUITE}\`" - echo "- Run: ${RUN_URL}" - echo "- Attempt: ${GITHUB_RUN_ATTEMPT}" - echo "- Workflow Commit: ${GITHUB_SHA}" - echo "- Trigger: ${GITHUB_EVENT_NAME}" - echo "- Date: $(date -u +%Y-%m-%d)" - echo "" - echo "## Report (errors and symptoms)" - echo "" - if [ "${EVIDENCE_OUTCOME}" = "success" ] && [ -s "${REPORT_FILE}" ]; then - redact < "${REPORT_FILE}" - elif [ "${EVIDENCE_OUTCOME}" = "success" ] && [ -s "${LOG_FILE:-}" ]; then - echo "(report file missing; log tail below)" - echo "" - tail -n 200 "${LOG_FILE}" | redact - else - echo "(no report or log file was produced)" - fi - } | head -c 55000 > "${BODY_FILE}" - gh label create functional-test -R rustfs/backlog --color d73a4a 2>/dev/null || true - if ! gh issue create -R rustfs/backlog --title "${TITLE}" \ - --body-file "${BODY_FILE}" --label functional-test; then - gh issue create -R rustfs/backlog --title "${TITLE}" --body-file "${BODY_FILE}" + FROM_URL='${{ inputs.from_url }}' + FROM_VERSION='${{ inputs.from_version }}' + TO_URL='${{ inputs.to_url }}' + TO_VERSION='${{ inputs.to_version }}' + PACKAGE_SOURCE="" + if [ -n "${TO_URL}" ]; then + PACKAGE_SOURCE="to ${TO_URL}" + elif [ -n "${TO_VERSION}" ]; then + PACKAGE_SOURCE="to version ${TO_VERSION}" + else + PACKAGE_SOURCE="to ${RUSTFS_NIGHTLY_PACKAGE_URL}" fi - echo "filed backlog issue for suite ${SUITE}" + if [ -n "${FROM_URL}" ]; then + PACKAGE_SOURCE="${PACKAGE_SOURCE}; from ${FROM_URL}" + elif [ -n "${FROM_VERSION}" ]; then + PACKAGE_SOURCE="${PACKAGE_SOURCE}; from version ${FROM_VERSION}" + fi + python3 auto-testing/scripts/issue_manager.py handle \ + --repo rustfs/backlog \ + --suite "${SUITE}" --category "${SUITE}" --suite-label "${SUITE_LABEL}" \ + --outcome "${{ steps.test.outcome }}" \ + --report "${FUNCTIONAL_ARTIFACTS_DIR}/cases.md" \ + --report-file "${REPORT_FILE}" \ + --log "${LOG_FILE}" \ + --run-url "${RUN_URL}" \ + --run-id "${GITHUB_RUN_ID}" \ + --attempt "${GITHUB_RUN_ATTEMPT}" \ + --commit "${GITHUB_SHA}" \ + --trigger "${{ github.event_name }}" \ + --package-source "${PACKAGE_SOURCE}" \ + --date "$(date -u +%Y-%m-%d)" - name: Upload report and logs if: ${{ always() && steps.evidence.outcome == 'success' }} diff --git a/scripts/test_security_workflow.py b/scripts/test_security_workflow.py index 7f3acce46..340033605 100644 --- a/scripts/test_security_workflow.py +++ b/scripts/test_security_workflow.py @@ -140,9 +140,9 @@ class SecurityWorkflowTests(WorkflowSteps, unittest.TestCase): self.assertNotIn(" continue-on-error: true", self.steps[name]) self.assertIn(" if: ${{ always() && steps.evidence.outcome == 'success' }}", self.steps["Generate report"]) self.assertNotIn("/tmp/rustfs-security", self.source) - for name in ("Upload functional report to dashboard", "File failure issue in rustfs/backlog"): - report = next(line for line in self.steps[name] if line.strip().startswith("REPORT_FILE:")) - self.assertIn("${{ env.SECURITY_ARTIFACTS_DIR }}/report.md", report) + for name in ("Upload functional report to dashboard", "Manage backlog issues (dedup / label / auto-close)"): + expected = "${{ env.SECURITY_ARTIFACTS_DIR }}/report.md" if name.startswith("Upload") else '--report-file "${SECURITY_ARTIFACTS_DIR}/report.md"' + self.assertIn(expected, "\n".join(self.steps[name])) for name in ("Upload functional report to dashboard", "Upload report and logs"): self.assertIn(" if: ${{ always() && steps.evidence.outcome == 'success' }}", self.steps[name]) artifact_settings = yaml_block(self.steps["Upload report and logs"], "with", 8) @@ -268,10 +268,12 @@ class SecurityWorkflowTests(WorkflowSteps, unittest.TestCase): gh.chmod(0o755) body = self.directory / "issue-body.md" self.env.update(PATH=f"{fake_bin}{os.pathsep}{os.environ['PATH']}", CAPTURE_BODY=str(body)) - result = self.run_step("File failure issue in rustfs/backlog") + result = self.run_step("Manage backlog issues (dedup / label / auto-close)") self.assertEqual(result.returncode, 0, result.stderr) - self.assertNotIn("OLD RUN REPORT", body.read_text()) - self.assertIn("https://github.com/rustfs/rustfs/actions/runs/314159", body.read_text()) + # The manager lives in the private auto-testing checkout; without it + # the step must skip without publishing anything. + self.assertIn("issue_manager.py not found", result.stdout + result.stderr) + self.assertFalse(body.exists()) def test_all_ten_suites_hold_the_shared_lock_for_manual_and_chain_runs(self) -> None: for suite in ("upgrade", "s3-compat", "kms", "tier", "storage", "heal", "pool-expand", "security", "replication", "performance"): @@ -587,11 +589,10 @@ class FunctionalEvidenceTests(WorkflowSteps, unittest.TestCase): initialized = self.run_step("Initialize functional evidence") self.assertNotEqual(initialized.returncode, 0) self.assertFalse(Path(self.env["GITHUB_ENV"]).exists()) - issue = self.run_step("File failure issue in rustfs/backlog") - self.assertEqual(issue.returncode, 0, issue.stderr) - body = Path(self.env["CAPTURE_BODY"]).read_text() - self.assertNotIn("OLD RUN EVIDENCE", body) - self.assertIn("no report or log file was produced", body) + manager = self.run_step("Manage backlog issues (dedup / label / auto-close)") + self.assertEqual(manager.returncode, 0, manager.stderr) + self.assertIn("issue_manager.py not found", manager.stdout + manager.stderr) + self.assertFalse(Path(self.env["CAPTURE_BODY"]).exists()) self.assertEqual((existing / "report.md").read_text(), "OLD RUN EVIDENCE") def test_reports_use_only_current_complete_suite_evidence(self):