Skip to content

feat(redis): add P1 investigation tools — client list, list/queue dep… #774

feat(redis): add P1 investigation tools — client list, list/queue dep…

feat(redis): add P1 investigation tools — client list, list/queue dep… #774

Workflow file for this run

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