feat(redis): add P1 investigation tools — client list, list/queue dep… #774
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release | |
| on: | |
| push: | |
| branches: [main] | |
| paths-ignore: | |
| - "docs/**" | |
| - "infra/**" | |
| - "tests/**" | |
| - "**/*.md" | |
| - "**/*.mdx" | |
| - ".claude/**" | |
| - ".github/**" | |
| schedule: | |
| - cron: "30 0 * * *" | |
| workflow_dispatch: | |
| inputs: | |
| channel: | |
| description: "Release channel to publish." | |
| required: false | |
| default: release | |
| type: choice | |
| options: | |
| - release | |
| - main | |
| tag: | |
| description: "Optional tag to release (e.g. v2026.4.5 or v0.1). Ignored for the main channel." | |
| required: false | |
| type: string | |
| permissions: | |
| contents: write | |
| actions: read | |
| models: read | |
| concurrency: | |
| group: release-${{ github.event_name == 'push' && 'main' || (github.event_name == 'workflow_dispatch' && inputs.channel == 'main' && 'main') || 'stable' }} | |
| cancel-in-progress: true | |
| jobs: | |
| prepare: | |
| if: github.repository == 'Tracer-Cloud/opensre' | |
| runs-on: ubuntu-latest | |
| outputs: | |
| channel: ${{ steps.meta.outputs.channel }} | |
| tag_name: ${{ steps.meta.outputs.tag_name }} | |
| steps: | |
| - uses: actions/checkout@v5 | |
| with: | |
| fetch-depth: 0 | |
| - name: Resolve release metadata | |
| id: meta | |
| env: | |
| EVENT_NAME: ${{ github.event_name }} | |
| DISPATCH_CHANNEL: ${{ inputs.channel }} | |
| DISPATCH_TAG: ${{ inputs.tag }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| channel="release" | |
| if [ "$EVENT_NAME" = "push" ]; then | |
| channel="main" | |
| fi | |
| if [ "$EVENT_NAME" = "workflow_dispatch" ] && [ "$DISPATCH_CHANNEL" = "main" ]; then | |
| channel="main" | |
| fi | |
| if [ "$channel" = "main" ]; then | |
| if [ "$EVENT_NAME" = "workflow_dispatch" ] && [ -n "$DISPATCH_TAG" ]; then | |
| echo "The main channel does not accept a custom tag." >&2 | |
| exit 1 | |
| fi | |
| echo "channel=main" >> "$GITHUB_OUTPUT" | |
| echo "tag_name=nightly" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| if [ "$EVENT_NAME" = "workflow_dispatch" ] && [ -n "$DISPATCH_TAG" ]; then | |
| tag_name="$DISPATCH_TAG" | |
| else | |
| year="$(date -u +%Y)" | |
| month="$(date -u +%-m)" | |
| day="$(date -u +%-d)" | |
| tag_name="v${year}.${month}.${day}" | |
| fi | |
| echo "channel=release" >> "$GITHUB_OUTPUT" | |
| echo "tag_name=${tag_name}" >> "$GITHUB_OUTPUT" | |
| verify: | |
| runs-on: ubuntu-latest | |
| needs: prepare | |
| if: github.event_name != 'push' | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@v8.1.0 | |
| with: | |
| enable-cache: true | |
| cache-dependency-glob: | | |
| pyproject.toml | |
| uv.lock | |
| - name: Set up Python | |
| uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.13" | |
| - name: Install dependencies | |
| run: uv sync --frozen --extra dev | |
| - name: Lint | |
| run: make lint | |
| - name: Type check | |
| run: make typecheck | |
| - name: CLI smoke tests | |
| run: make test-cli-smoke | |
| build-python-dist: | |
| if: needs.prepare.outputs.channel == 'release' && needs.verify.result == 'success' | |
| runs-on: ubuntu-latest | |
| needs: [verify, prepare] | |
| env: | |
| TAG_NAME: ${{ needs.prepare.outputs.tag_name }} | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@v8.1.0 | |
| with: | |
| enable-cache: true | |
| cache-dependency-glob: | | |
| pyproject.toml | |
| uv.lock | |
| - name: Set up Python | |
| uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.13" | |
| - name: Sync release version | |
| shell: bash | |
| run: python packaging/sync_release_version.py --tag "$TAG_NAME" | |
| - name: Build Python distributions | |
| run: | | |
| uv sync --frozen --extra release-dist | |
| uv run python -m build | |
| uv run twine check dist/* | |
| - name: Verify Python distribution version | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| VERSION="${TAG_NAME#v}" | |
| test -f "dist/opensre-${VERSION}.tar.gz" | |
| test -f "dist/opensre-${VERSION}-py3-none-any.whl" | |
| - name: Upload Python distributions | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: release-python-dist | |
| path: dist/* | |
| if-no-files-found: error | |
| build-binaries: | |
| if: needs.prepare.outputs.channel == 'main' || needs.verify.result == 'success' | |
| needs: [verify, prepare] | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - runner: ubuntu-latest | |
| target: linux-x64 | |
| binary_name: opensre | |
| archive_ext: tar.gz | |
| - runner: ubuntu-24.04-arm | |
| target: linux-arm64 | |
| binary_name: opensre | |
| archive_ext: tar.gz | |
| - runner: macos-15-intel | |
| target: darwin-x64 | |
| binary_name: opensre | |
| archive_ext: tar.gz | |
| - runner: macos-latest | |
| target: darwin-arm64 | |
| binary_name: opensre | |
| archive_ext: tar.gz | |
| - runner: windows-latest | |
| target: windows-x64 | |
| binary_name: opensre.exe | |
| archive_ext: zip | |
| # windows-arm64 is currently excluded from the default release matrix: | |
| # cryptography does not publish win_arm64 wheels, so dependency install | |
| # falls back to a source build that requires an OpenSSL toolchain on the | |
| # GitHub-hosted runner. | |
| runs-on: ${{ matrix.runner }} | |
| env: | |
| RELEASE_CHANNEL: ${{ needs.prepare.outputs.channel }} | |
| TAG_NAME: ${{ needs.prepare.outputs.tag_name }} | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@v8.1.0 | |
| with: | |
| enable-cache: true | |
| cache-dependency-glob: | | |
| pyproject.toml | |
| uv.lock | |
| - name: Set up Python | |
| uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.13" | |
| - name: Sync release version | |
| if: env.RELEASE_CHANNEL == 'release' | |
| shell: bash | |
| run: python packaging/sync_release_version.py --tag "$TAG_NAME" | |
| - name: Install binary build dependencies | |
| shell: bash | |
| run: uv sync --frozen --extra release-binary | |
| - name: Build binary | |
| run: uv run pyinstaller packaging/opensre.spec --clean --noconfirm | |
| - name: Smoke test binary (Unix) | |
| if: runner.os != 'Windows' | |
| shell: bash | |
| run: | | |
| set -uo pipefail | |
| # Capture the exit status explicitly so a crashing binary still prints | |
| # its stdout/stderr (under `set -e` the failed substitution aborted the | |
| # step before the output was ever shown, hiding the real error). | |
| set +e | |
| VERSION_OUTPUT="$(./dist/${{ matrix.binary_name }} --version 2>&1)" | |
| VERSION_STATUS=$? | |
| set -e | |
| printf '%s\n' "$VERSION_OUTPUT" | |
| if [ "$VERSION_STATUS" -ne 0 ]; then | |
| printf '::error::%s --version exited with status %s\n' "${{ matrix.binary_name }}" "$VERSION_STATUS" >&2 | |
| exit "$VERSION_STATUS" | |
| fi | |
| if [ "$RELEASE_CHANNEL" = "release" ]; then | |
| VERSION="${TAG_NAME#v}" | |
| case "$VERSION_OUTPUT" in | |
| *"$VERSION"*) ;; | |
| *) | |
| printf 'Binary version mismatch: expected %s but saw %s\n' "$VERSION" "$VERSION_OUTPUT" >&2 | |
| exit 1 | |
| ;; | |
| esac | |
| fi | |
| ./dist/${{ matrix.binary_name }} -h >/dev/null | |
| - name: Smoke test binary (Windows) | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: | | |
| $versionOutput = & ".\dist\${{ matrix.binary_name }}" --version 2>&1 | Out-String | |
| $versionText = $versionOutput.Trim() | |
| Write-Host $versionText | |
| if ($env:RELEASE_CHANNEL -eq "release") { | |
| $expectedVersion = $env:TAG_NAME.TrimStart("v") | |
| if ($versionText -notmatch [regex]::Escape($expectedVersion)) { | |
| throw "Binary version mismatch. Expected '$expectedVersion' but saw '$versionText'." | |
| } | |
| } | |
| & ".\dist\${{ matrix.binary_name }}" -h | Out-Null | |
| - name: Package binary archive (Unix) | |
| if: runner.os != 'Windows' | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| if [ "$RELEASE_CHANNEL" = "release" ]; then | |
| ASSET_BASENAME="opensre_${TAG_NAME#v}_${{ matrix.target }}" | |
| else | |
| ASSET_BASENAME="opensre_main_${{ matrix.target }}" | |
| fi | |
| tar -C dist -czf "${ASSET_BASENAME}.tar.gz" "${{ matrix.binary_name }}" | |
| shasum -a 256 "${ASSET_BASENAME}.tar.gz" > "${ASSET_BASENAME}.tar.gz.sha256" | |
| - name: Package binary archive (Windows) | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: | | |
| if ($env:RELEASE_CHANNEL -eq "release") { | |
| $assetBaseName = "opensre_$($env:TAG_NAME.TrimStart('v'))_${{ matrix.target }}" | |
| } else { | |
| $assetBaseName = "opensre_main_${{ matrix.target }}" | |
| } | |
| Compress-Archive -Path "dist\${{ matrix.binary_name }}" -DestinationPath "${assetBaseName}.zip" | |
| $hash = (Get-FileHash -Algorithm SHA256 "${assetBaseName}.zip").Hash.ToLowerInvariant() | |
| Set-Content -Path "${assetBaseName}.zip.sha256" -Value "$hash ${assetBaseName}.zip" | |
| - name: Upload binary archive | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: ${{ env.RELEASE_CHANNEL == 'main' && 'main-' || '' }}release-${{ matrix.target }} | |
| path: | | |
| opensre_*_${{ matrix.target }}.${{ matrix.archive_ext }} | |
| opensre_*_${{ matrix.target }}.${{ matrix.archive_ext }}.sha256 | |
| if-no-files-found: error | |
| publish-release: | |
| if: needs.prepare.outputs.channel == 'release' | |
| runs-on: ubuntu-latest | |
| needs: | |
| - prepare | |
| - build-python-dist | |
| - build-binaries | |
| steps: | |
| - uses: actions/checkout@v5 | |
| with: | |
| ref: ${{ github.event.repository.default_branch }} | |
| fetch-depth: 0 | |
| - name: Download release artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: release-* | |
| path: release-assets | |
| merge-multiple: true | |
| - name: Resolve release context | |
| id: release_ctx | |
| env: | |
| TAG_NAME: ${{ needs.prepare.outputs.tag_name }} | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| default_branch="${{ github.event.repository.default_branch }}" | |
| git fetch origin "$default_branch" --tags --force | |
| target_sha="$(git rev-parse "origin/$default_branch")" | |
| previous_tag="$( | |
| git tag --list 'v[0-9][0-9][0-9][0-9].[0-9]*.[0-9]*' --sort=-v:refname \ | |
| | grep -v -x "$TAG_NAME" \ | |
| | head -n 1 \ | |
| || true | |
| )" | |
| range_spec="$target_sha" | |
| if [ -n "$previous_tag" ]; then | |
| range_spec="${previous_tag}..${target_sha}" | |
| fi | |
| { | |
| printf 'target_sha=%s\n' "$target_sha" | |
| printf 'previous_tag=%s\n' "$previous_tag" | |
| printf 'range_spec=%s\n' "$range_spec" | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Create release notes | |
| env: | |
| TAG_NAME: ${{ needs.prepare.outputs.tag_name }} | |
| RANGE_SPEC: ${{ steps.release_ctx.outputs.range_spec }} | |
| PREVIOUS_TAG: ${{ steps.release_ctx.outputs.previous_tag }} | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| { | |
| echo "## Changelog" | |
| echo | |
| if [ -n "$PREVIOUS_TAG" ]; then | |
| echo "_Changes since ${PREVIOUS_TAG}_" | |
| else | |
| echo "_Changes up to ${TAG_NAME}_" | |
| fi | |
| echo | |
| git log "$RANGE_SPEC" --no-merges -n 100 --pretty='- %s (%h) — %an' | |
| echo | |
| } > GENERATED_CHANGELOG.md | |
| # Summarize commits into prose via GitHub Models (gpt-4o-mini). | |
| # Falls back to raw changelog if the API is unavailable. | |
| raw_commits="$(git log "$RANGE_SPEC" --no-merges --pretty='%s' | head -40 || true)" | |
| if [ -n "$raw_commits" ]; then | |
| api_body="$(jq -n --arg commits "$raw_commits" '{ | |
| model: "gpt-4o-mini", | |
| messages: [ | |
| { | |
| role: "system", | |
| content: "You are writing a Discord release announcement for an open-source SRE CLI tool called opensre. Summarize the git commits into 2-4 short, punchy prose sentences — no bullet points, no headers, no markdown. Write in present tense, active voice. Focus on what users will notice: new features, fixes, performance, integrations. Be specific but concise." | |
| }, | |
| {role: "user", content: ("Commits:\n" + $commits)} | |
| ], | |
| max_tokens: 300, | |
| temperature: 0.4 | |
| }')" | |
| curl -sS --max-time 20 \ | |
| -H "Authorization: Bearer $GITHUB_TOKEN" \ | |
| -H "Content-Type: application/json" \ | |
| "https://models.inference.ai.azure.com/chat/completions" \ | |
| -d "$api_body" 2>/dev/null \ | |
| | jq -r '.choices[0].message.content // empty' 2>/dev/null \ | |
| | tr -d '\r' > DISCORD_NARRATIVE.md || true | |
| if [ -s DISCORD_NARRATIVE.md ]; then | |
| echo "LLM narrative generated ($(wc -c < DISCORD_NARRATIVE.md) bytes)." | |
| else | |
| echo "LLM summary unavailable; Discord will use raw changelog." | |
| rm -f DISCORD_NARRATIVE.md | |
| fi | |
| fi | |
| CB='```' | |
| { | |
| printf '## Install\n\n' | |
| printf '### cURL (macOS / Linux)\n\n%sbash\ncurl -fsSL https://install.opensre.com | bash\n%s\n\n' "$CB" "$CB" | |
| printf '### cURL (macOS / Linux, latest main build)\n\n%sbash\ncurl -fsSL https://install.opensre.com | bash -s -- --main\n%s\n\n' "$CB" "$CB" | |
| printf '### Homebrew (macOS / Linux)\n\n%sbash\nbrew tap tracer-cloud/tap\nbrew install tracer-cloud/tap/opensre\n%s\n\n' "$CB" "$CB" | |
| printf '### PowerShell (Windows)\n\n%spowershell\nirm https://install.opensre.com | iex\n%s\n\n' "$CB" "$CB" | |
| printf '### Python\n\n%sbash\npipx install opensre\n%s\n\n' "$CB" "$CB" | |
| } > RELEASE_NOTES.md | |
| cat GENERATED_CHANGELOG.md >> RELEASE_NOTES.md | |
| - name: Create GitHub release | |
| id: github_release | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| TAG_NAME: ${{ needs.prepare.outputs.tag_name }} | |
| TARGET_SHA: ${{ steps.release_ctx.outputs.target_sha }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| if gh release view "$TAG_NAME" --repo "${{ github.repository }}" >/dev/null 2>&1; then | |
| echo "created=false" >> "$GITHUB_OUTPUT" | |
| echo "Release $TAG_NAME already exists; nothing to do." | |
| exit 0 | |
| fi | |
| VERSION="${TAG_NAME#v}" | |
| latest_flag="--latest=false" | |
| release_title="Latest" | |
| case "$TAG_NAME" in | |
| v[0-9][0-9][0-9][0-9].*) ;; | |
| *) | |
| latest_flag="--latest" | |
| release_title="OpenSRE ${VERSION}" | |
| ;; | |
| esac | |
| gh release create "$TAG_NAME" \ | |
| release-assets/* \ | |
| --repo "${{ github.repository }}" \ | |
| --target "$TARGET_SHA" \ | |
| --title "$release_title" \ | |
| --notes-file RELEASE_NOTES.md \ | |
| "$latest_flag" | |
| echo "created=true" >> "$GITHUB_OUTPUT" | |
| - name: Announce on Discord | |
| if: steps.github_release.outputs.created == 'true' && !github.event.repository.fork && !github.event.repository.private | |
| continue-on-error: true | |
| env: | |
| DISCORD_WEBHOOK_URL_UPDATE: ${{ secrets.DISCORD_WEBHOOK_URL_UPDATE }} | |
| DISCORD_RELEASES_ROLE_ID: ${{ secrets.DISCORD_RELEASES_ROLE_ID }} | |
| DISCORD_RELEASE_LOGO_EMOJI: ${{ secrets.DISCORD_RELEASE_LOGO_EMOJI }} | |
| DISCORD_RELEASE_LOGO_URL: ${{ secrets.DISCORD_RELEASE_LOGO_URL }} | |
| RELEASE_TAG: ${{ needs.prepare.outputs.tag_name }} | |
| RELEASE_URL: ${{ github.server_url }}/${{ github.repository }}/releases/tag/${{ needs.prepare.outputs.tag_name }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| if [ -z "${DISCORD_WEBHOOK_URL_UPDATE:-}" ]; then | |
| echo "DISCORD_WEBHOOK_URL_UPDATE is not set; skipping Discord announcement." | |
| exit 0 | |
| fi | |
| # Prefer LLM-generated narrative; fall back to raw changelog. | |
| if [ -f DISCORD_NARRATIVE.md ] && [ -s DISCORD_NARRATIVE.md ]; then | |
| changelog_file="DISCORD_NARRATIVE.md" | |
| elif [ -f GENERATED_CHANGELOG.md ]; then | |
| changelog_file="GENERATED_CHANGELOG.md" | |
| else | |
| echo "No changelog available." > /tmp/discord_fallback.md | |
| changelog_file="/tmp/discord_fallback.md" | |
| fi | |
| # Use an external Python script to build the JSON payload. | |
| # This avoids jq issues with multiline LLM output containing control characters. | |
| payload="$(CHANGELOG_FILE="$changelog_file" python3 .github/scripts/build-discord-payload.py)" | |
| curl --fail -sS --max-time 30 -X POST -H "Content-Type: application/json" \ | |
| -d "$payload" "$DISCORD_WEBHOOK_URL_UPDATE" | |
| - name: Sync Homebrew tap formula | |
| if: steps.github_release.outputs.created == 'true' | |
| continue-on-error: true | |
| env: | |
| HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }} | |
| VERSION: ${{ needs.prepare.outputs.tag_name }} | |
| ASSET_DIR: release-assets | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| if [ -z "${HOMEBREW_TAP_GITHUB_TOKEN:-}" ]; then | |
| echo "HOMEBREW_TAP_GITHUB_TOKEN is not set; skipping Homebrew tap sync." | |
| exit 0 | |
| fi | |
| export VERSION="${VERSION#v}" | |
| bash .github/scripts/sync-homebrew-tap-formula.sh | |
| publish-main-release: | |
| if: needs.prepare.outputs.channel == 'main' | |
| runs-on: ubuntu-latest | |
| needs: | |
| - prepare | |
| - build-binaries | |
| steps: | |
| - uses: actions/checkout@v5 | |
| with: | |
| fetch-depth: 0 | |
| - name: Download main release artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: main-release-* | |
| path: main-release-assets | |
| merge-multiple: true | |
| - name: Create release notes | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| short_sha="$(printf '%s' "$GITHUB_SHA" | cut -c1-7)" | |
| built_at="$(date -u +"%Y-%m-%d %H:%M UTC")" | |
| CB='```' | |
| { | |
| printf '## Main build\n\nRolling binary build from `main`.\n\n' | |
| printf -- '- Commit: `%s`\n- Built: %s\n\n' "$short_sha" "$built_at" | |
| printf '### Install\n\nStable:\n\n%sbash\ncurl -fsSL https://install.opensre.com | bash\n%s\n\n' "$CB" "$CB" | |
| printf 'Main:\n\n%sbash\ncurl -fsSL https://install.opensre.com | bash -s -- --main\n%s\n\n' "$CB" "$CB" | |
| printf 'Windows stable:\n\n%spowershell\nirm https://install.opensre.com | iex\n%s\n' "$CB" "$CB" | |
| } > MAIN_RELEASE_NOTES.md | |
| - name: Move nightly tag to the latest commit | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| git tag -f nightly "$GITHUB_SHA" | |
| git push origin refs/tags/nightly --force | |
| - name: Publish rolling main release | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| if gh release view nightly --repo "${{ github.repository }}" >/dev/null 2>&1; then | |
| gh release upload nightly main-release-assets/* --repo "${{ github.repository }}" --clobber | |
| gh release edit nightly \ | |
| --repo "${{ github.repository }}" \ | |
| --title "Main" \ | |
| --notes-file MAIN_RELEASE_NOTES.md | |
| exit 0 | |
| fi | |
| gh release create nightly \ | |
| main-release-assets/* \ | |
| --repo "${{ github.repository }}" \ | |
| --target "$GITHUB_SHA" \ | |
| --title "Main" \ | |
| --notes-file MAIN_RELEASE_NOTES.md \ | |
| --prerelease |