HAProxy daily maintenance #86
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: HAProxy daily maintenance | |
| on: | |
| schedule: | |
| - cron: '0 10 * * *' # Daily at 10:00 UTC | |
| workflow_dispatch: | |
| jobs: | |
| check: | |
| name: Check upstream version and scan current pinned for CVEs | |
| runs-on: ubuntu-22.04 | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Get current pinned version | |
| id: current | |
| run: | | |
| version=$(grep 'HAPROXY_VERSION=' ci/build/build-haproxy-dist.sh \ | |
| | head -1 \ | |
| | sed 's/.*:-\([0-9.]*\)}.*/\1/') | |
| echo "version=$version" >> "$GITHUB_OUTPUT" | |
| echo "Current pinned version: $version" | |
| - name: Get latest 2.8.x release from haproxy.org | |
| id: latest | |
| run: | | |
| # HAProxy doesn't tag every patch release on GitHub — only major | |
| # versions. The authoritative source for patch releases is the | |
| # download directory on haproxy.org. | |
| latest=$(curl -fsSL "https://www.haproxy.org/download/2.8/src/" \ | |
| | grep -oP 'haproxy-2\.8\.\d+' \ | |
| | sort -V \ | |
| | uniq \ | |
| | tail -1 \ | |
| | sed 's/^haproxy-//') | |
| echo "version=$latest" >> "$GITHUB_OUTPUT" | |
| echo "Latest upstream 2.8.x: $latest" | |
| - name: Compare versions | |
| id: compare | |
| run: | | |
| current="${{ steps.current.outputs.version }}" | |
| latest="${{ steps.latest.outputs.version }}" | |
| if [ "$current" != "$latest" ]; then | |
| echo "needs_bump=true" >> "$GITHUB_OUTPUT" | |
| echo "Bump needed: $current → $latest" | |
| else | |
| echo "needs_bump=false" >> "$GITHUB_OUTPUT" | |
| echo "Already up to date: $current" | |
| fi | |
| - name: Check if bump branch already exists | |
| id: branch | |
| if: steps.compare.outputs.needs_bump == 'true' | |
| run: | | |
| latest="${{ steps.latest.outputs.version }}" | |
| branch="bump-haproxy-${latest}" | |
| if git ls-remote --exit-code --heads origin "$branch" >/dev/null 2>&1; then | |
| echo "Branch $branch already exists on remote — skipping bump" | |
| echo "exists=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "exists=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Open bump PR | |
| if: steps.compare.outputs.needs_bump == 'true' && steps.branch.outputs.exists == 'false' | |
| run: | | |
| current="${{ steps.current.outputs.version }}" | |
| latest="${{ steps.latest.outputs.version }}" | |
| branch="bump-haproxy-${latest}" | |
| # Compute SHA-256 of the new upstream tarball so the bump PR | |
| # ships a verified checksum (release.yml fails the build otherwise). | |
| tarball_url="https://www.haproxy.org/download/2.8/src/haproxy-${latest}.tar.gz" | |
| new_sha=$(curl -fsSL "$tarball_url" | sha256sum | awk '{print $1}') | |
| echo "New tarball SHA-256: $new_sha" | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| git checkout -b "$branch" | |
| # Update HAPROXY_VERSION default and HAPROXY_SHA256 in build script. | |
| sed -i "s/HAPROXY_VERSION:-${current}/HAPROXY_VERSION:-${latest}/" \ | |
| ci/build/build-haproxy-dist.sh | |
| sed -i "s|^HAPROXY_SHA256=.*|HAPROXY_SHA256=\"${new_sha}\"|" \ | |
| ci/build/build-haproxy-dist.sh | |
| # Update version in pyproject.toml. | |
| sed -i "s/^version = \"${current}\"/version = \"${latest}\"/" pyproject.toml | |
| git add ci/build/build-haproxy-dist.sh pyproject.toml | |
| git commit -m "Bump HAProxy ${current} → ${latest}" | |
| git push origin "$branch" | |
| gh pr create \ | |
| --title "Bump HAProxy ${current} → ${latest}" \ | |
| --body "$(cat <<EOF | |
| ## Summary | |
| Automated version bump from the daily HAProxy upstream check. | |
| - HAProxy **${current}** → **${latest}** | |
| - Source: ${tarball_url} | |
| - Tarball SHA-256: \`${new_sha}\` | |
| ## Checklist | |
| - [ ] Review HAProxy changelog for breaking changes | |
| - [ ] Merging triggers tag-on-bump-merge.yml, which tags \`v${latest}\` and runs release.yml to rebuild and publish the wheel | |
| EOF | |
| )" \ | |
| --head "$branch" \ | |
| --base main \ | |
| --reviewer eicherseiji | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Notify Slack on version bump | |
| if: steps.compare.outputs.needs_bump == 'true' && steps.branch.outputs.exists == 'false' | |
| run: | | |
| curl -fsSL -X POST "${{ secrets.SLACK_WEBHOOK_URL }}" \ | |
| -H 'Content-type: application/json' \ | |
| -d "{\"text\":\"HAProxy upstream update: ${{ steps.current.outputs.version }} → ${{ steps.latest.outputs.version }}. PR opened in ray-project/ray-haproxy.\"}" | |
| # Daily CVE scan of the currently pinned HAProxy. Independent of whether | |
| # upstream has a new release. Catches CVEs disclosed against the version | |
| # already shipped to Ray Serve users. | |
| - name: Build current pinned HAProxy for scanning | |
| run: | | |
| docker run --rm \ | |
| -v "$PWD:/work" \ | |
| -e HAPROXY_VERSION=${{ steps.current.outputs.version }} \ | |
| -e OUTPUT_DIR=/work/dist \ | |
| quay.io/pypa/manylinux2014_x86_64:2026.01.02-1 \ | |
| /work/ci/build/build-haproxy-dist.sh | |
| sudo chown -R "$(id -u):$(id -g)" dist/ | |
| mkdir -p scan-target | |
| tar -xzf dist/haproxy-linux-x86_64.tar.gz -C scan-target/ | |
| - name: Scan current pinned for HIGH/CRITICAL CVEs | |
| id: scan | |
| run: | | |
| curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh \ | |
| | sh -s -- -b /usr/local/bin | |
| grype dir:scan-target/ -o json > scan.json | |
| high_count=$(jq '[.matches[] | |
| | select(.vulnerability.severity == "High" or .vulnerability.severity == "Critical")] | |
| | length' scan.json) | |
| echo "high_count=$high_count" >> "$GITHUB_OUTPUT" | |
| if [ "$high_count" -gt 0 ]; then | |
| summary=$(jq -r '[.matches[] | |
| | select(.vulnerability.severity == "High" or .vulnerability.severity == "Critical") | |
| | "• \(.vulnerability.id) (\(.vulnerability.severity)) in \(.artifact.name) \(.artifact.version)"] | |
| | unique | join("\n")' scan.json) | |
| { | |
| echo "summary<<SCAN_EOF" | |
| echo "$summary" | |
| echo "SCAN_EOF" | |
| } >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Notify Slack on CVE in current pinned | |
| if: steps.scan.outputs.high_count != '0' && steps.scan.outputs.high_count != '' | |
| run: | | |
| payload=$(jq -n \ | |
| --arg ver "${{ steps.current.outputs.version }}" \ | |
| --arg n "${{ steps.scan.outputs.high_count }}" \ | |
| --arg list "${{ steps.scan.outputs.summary }}" \ | |
| '{text: ":rotating_light: Current pinned HAProxy \($ver) has \($n) HIGH/CRITICAL CVE(s):\n\($list)\nTime to update Ray."}') | |
| curl -fsSL -X POST "${{ secrets.SLACK_WEBHOOK_URL }}" \ | |
| -H 'Content-type: application/json' \ | |
| -d "$payload" |