Files
clean-architecture-backend-…/.github/workflows/redis-sdk-topology.yml
T
DongHyeonkaandClaude Opus 5 e34519113b fix(ci): 실행되지 않거나 실패할 수 없던 CI 게이트 정상화
가장 큰 것: 문서화된 아키텍처 게이트가 규칙을 하나도 실행하지 않았다.
워크플로 7곳과 CLAUDE.md 가 --tests '*CleanArchitectureTest' 를 지정했으나
그 이름의 클래스는 존재하지 않는다. 4곳은 매칭 0건으로 하드 실패하고,
3곳은 다른 필터와 병기돼 아키텍처 규칙 0개를 돌고 초록으로 통과했다.
필터를 패키지 글롭 dev.caskeleton.bootstrap.architecture.* 로 교체했다.
*ArchitectureTest 글롭은 20개 중 12개만 잡고 ArchRuleDiscoveryContractTest 등
8개를 놓치므로 쓰지 않았다.

그 외:
- ci-gate-matrix 의 release_blocking 이 강제되지 않아 trivy-fs 가 빨개도
  release-gate 가 초록이던 것을 실제 의존으로 연결
- build-logic TestKit 이 어떤 CI 에서도 돌지 않던 것을 ci-quality-gates 에 연결
- jpa-next-* 3개, object-storage, fileserver-pr 의 실패할 수 없거나
  트리거되지 않던 잡 정리
- 릴리스 태그 네임스페이스 분열로 v* 태그가 web·websocket 게이트를
  건너뛰던 것 수정
- 워크플로 SHA 잠금 28개 재생성 (verify-gradle-wrapper.sh)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 16:52:44 +09:00

202 lines
9.4 KiB
YAML

# Redis SDK topology evidence.
#
# The lanes in infra/redis-sdk answer what the deterministic in-memory gateway cannot — Sentinel
# promotion behaviour, Cluster redirects, ACL coverage. docs/redis/support-matrix.md records which
# lane produced which evidence, and RedisSupportMatrixTest refuses an evidence claim that does not
# name the test class behind it.
#
# Three cadences, because the cost and the question differ:
#
# pull_request standalone only, current supported version. The cheapest lane that can still
# catch "this change cannot talk to a real Redis at all". A PR gate that starts
# three topologies is a PR gate people learn to ignore.
# schedule the full supported-version x topology matrix, nightly. This is where Sentinel
# promotion and Cluster redirect evidence comes from.
# workflow_dispatch one lane on demand, for reproducing a specific failure.
#
# A release candidate uses the nightly matrix run for its tag: `release-candidate` selects the full
# matrix on demand so an RC does not have to wait for the next scheduled run.
#
# Each lane has its own endpoint. A sentinel is not a data node and a cluster node is not the whole
# cluster, so the address, port, and (for Sentinel) the monitored primary's name are per-lane rather
# than one hardcoded 6379 that happens to be right for standalone only.
#
# The Gradle task is fail-closed on its own account: an unknown mode, a missing endpoint, a lane
# with no tagged test class, and a run that executed zero tests are all errors. This workflow does
# not need to re-check those, but it does have to keep the evidence, which is why every run uploads
# the JUnit XML together with the commit SHA, the server version and the resolved image digest. An
# evidence artifact that cannot say which image produced it is not evidence.
name: redis-sdk-topology
on:
pull_request:
paths:
- "src/adapter/outbound/cache-redis/**"
- "infra/redis-sdk/**"
- ".github/workflows/redis-sdk-topology.yml"
schedule:
# 02:30 UTC daily. Nightly, not hourly: the matrix starts real servers.
- cron: "30 2 * * *"
workflow_dispatch:
inputs:
topology:
description: standalone, sentinel, cluster, tls, or release-candidate for the full matrix
required: true
default: standalone
type: choice
options: [standalone, sentinel, cluster, tls, release-candidate]
redis_version:
description: server version tag
required: true
default: "7.4"
type: string
permissions:
contents: read
jobs:
# The matrix is computed rather than duplicated per trigger, so adding a supported version is one
# edit and no trigger can silently keep testing an old set.
lanes:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.select.outputs.matrix }}
steps:
- id: select
run: |
set -euo pipefail
case "${{ github.event_name }}" in
pull_request)
matrix='{"include":[{"topology":"standalone","redis_version":"7.4"}]}'
;;
schedule)
matrix='{"include":[
{"topology":"standalone","redis_version":"7.2"},
{"topology":"standalone","redis_version":"7.4"},
{"topology":"standalone","redis_version":"8.2"},
{"topology":"sentinel","redis_version":"7.2"},
{"topology":"sentinel","redis_version":"7.4"},
{"topology":"sentinel","redis_version":"8.2"},
{"topology":"cluster","redis_version":"7.2"},
{"topology":"cluster","redis_version":"7.4"},
{"topology":"cluster","redis_version":"8.2"},
{"topology":"tls","redis_version":"7.4"},
{"topology":"tls","redis_version":"8.2"}]}'
;;
*)
if [ "${{ inputs.topology }}" = "release-candidate" ]; then
matrix='{"include":[
{"topology":"standalone","redis_version":"7.2"},
{"topology":"standalone","redis_version":"7.4"},
{"topology":"standalone","redis_version":"8.2"},
{"topology":"sentinel","redis_version":"7.2"},
{"topology":"sentinel","redis_version":"7.4"},
{"topology":"sentinel","redis_version":"8.2"},
{"topology":"cluster","redis_version":"7.2"},
{"topology":"cluster","redis_version":"7.4"},
{"topology":"cluster","redis_version":"8.2"},
{"topology":"tls","redis_version":"7.4"},
{"topology":"tls","redis_version":"8.2"}]}'
else
matrix='{"include":[{"topology":"${{ inputs.topology }}","redis_version":"${{ inputs.redis_version }}"}]}'
fi
;;
esac
printf 'matrix=%s\n' "$(printf '%s' "$matrix" | tr -d '\n ')" >> "$GITHUB_OUTPUT"
topology-evidence:
needs: lanes
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.lanes.outputs.matrix) }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
with:
distribution: temurin
java-version: "21.0.11+10"
cache: gradle
cache-dependency-path: |
src/**/*.gradle
src/**/gradle-wrapper.properties
src/**/gradle.lockfile
- name: Start the topology
env:
REDIS_VERSION: ${{ matrix.redis_version }}
run: docker compose -f "infra/redis-sdk/${{ matrix.topology }}/compose.yml" up -d --wait
- name: Record the image digest
id: image
run: |
set -euo pipefail
# The tag says 7.4; the digest says which 7.4. Evidence that names only the tag cannot be
# reproduced once the tag moves.
#
# This used to end in `|| echo 'unresolved'`, which absorbed the failure that `set -e` was
# there to catch: the manifest below recorded `image_digest=unresolved`, the upload
# satisfied `if-no-files-found: error`, and the lane went green holding exactly the
# artifact this workflow's header calls "not evidence". Compose pulls the image in the
# step before this one, so RepoDigests is populated; if it is not, the run has nothing to
# certify and says so.
digest="$(docker image inspect --format '{{index .RepoDigests 0}}' \
"redis:${{ matrix.redis_version }}")"
if [[ -z "$digest" ]]; then
echo "::error::no repository digest for redis:${{ matrix.redis_version }}; this run cannot say which image produced its evidence"
exit 1
fi
printf 'digest=%s\n' "$digest" >> "$GITHUB_OUTPUT"
- name: Run the topology contracts
working-directory: src
run: |
set -euo pipefail
case '${{ matrix.topology }}' in
standalone) port=6379; extra='' ;;
sentinel) port=27010; extra='-Predis.topology.master=skeleton' ;;
cluster) port=7100; extra='' ;;
# The TLS lane's CA is generated at start-up, so the trust material is extracted from
# the lane rather than checked in. A checked-in key is a secret in the repository
# however loudly the file is named "test".
tls)
port=6390
docker compose -f ../infra/redis-sdk/tls/compose.yml cp redis:/tls/ca.crt "$RUNNER_TEMP/redis-lane-ca.pem"
extra="-Predis.topology.trust-material=$RUNNER_TEMP/redis-lane-ca.pem"
;;
*) echo "unknown topology"; exit 1 ;;
esac
./gradlew :adapter:outbound:cache-redis:redisTopologyTest --console=plain \
-Predis.topology.host=localhost \
-Predis.topology.port="$port" \
-Predis.topology.mode='${{ matrix.topology }}' \
$extra
- name: Write the evidence manifest
if: always()
run: |
set -euo pipefail
out=src/adapter/outbound/cache-redis/build/test-results/redisTopologyTest
mkdir -p "$out"
cat > "$out/evidence-manifest.txt" <<MANIFEST
commit=${{ github.sha }}
workflow_run=${{ github.run_id }}
trigger=${{ github.event_name }}
topology=${{ matrix.topology }}
redis_version=${{ matrix.redis_version }}
image_digest=${{ steps.image.outputs.digest }}
MANIFEST
- name: Preserve the evidence
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # actions/upload-artifact@v4.6.2
with:
name: redis-topology-${{ matrix.topology }}-${{ matrix.redis_version }}
path: |
src/adapter/outbound/cache-redis/build/test-results/redisTopologyTest/**
src/adapter/outbound/cache-redis/build/reports/tests/redisTopologyTest/**
if-no-files-found: error
retention-days: 90
- name: Stop the topology
if: always()
run: docker compose -f "infra/redis-sdk/${{ matrix.topology }}/compose.yml" down -v