Skip to content

sync-upstream-version #31

sync-upstream-version

sync-upstream-version #31

name: sync-upstream-version
on:
workflow_dispatch:
schedule:
- cron: "0 12 * * 1"
permissions:
contents: write
pull-requests: write
jobs:
sync:
# Runs by default. Set the repository variable DISABLE_UPSTREAM_VERSION_SYNC
# to 'true' to stop it.
if: vars.DISABLE_UPSTREAM_VERSION_SYNC != 'true'
runs-on: ubuntu-latest
env:
# Only whether the webhook is configured belongs at job scope: a step
# `if:` cannot read the `secrets` context, but the secret itself has no
# business in the environment of steps that install and run upstream
# code. The notification steps map the secret in themselves.
DISCORD_CONFIGURED: ${{ secrets.DISCORD_WEBHOOK != '' }}
steps:
- name: Checkout
uses: actions/checkout@v7
- name: TEMP failing step with continue-on-error
continue-on-error: true
run: exit 1
- name: TEMP does failure() fire
if: failure()
run: echo "FAILURE-CONTEXT-FIRED"
- name: Fetch latest upstream release
id: upstream
run: |
set -euo pipefail
api="https://api.github.com/repos/caronc/apprise/releases/latest"
tag="$(curl -sSL "$api" | jq -r '.tag_name')"
if [ -z "$tag" ] || [ "$tag" = "null" ]; then
echo "Failed to read latest upstream release tag." >&2
exit 1
fi
version="${tag#v}"
pinned="$(sed -n 's/^var UpstreamVersion = "\([^"]*\)"/\1/p' internal/version/version.go)"
echo "tag=$tag" >> "$GITHUB_OUTPUT"
echo "version=$version" >> "$GITHUB_OUTPUT"
echo "pinned=$pinned" >> "$GITHUB_OUTPUT"
if [ "$version" = "$pinned" ]; then
echo "changed=false" >> "$GITHUB_OUTPUT"
echo "Already pinned to $pinned." >> "$GITHUB_STEP_SUMMARY"
else
echo "changed=true" >> "$GITHUB_OUTPUT"
fi
- name: Update UpstreamVersion
if: steps.upstream.outputs.changed == 'true'
env:
UPSTREAM_VERSION: ${{ steps.upstream.outputs.version }}
run: |
python - <<'PY'
import os
import re
from pathlib import Path
path = Path("internal/version/version.go")
data = path.read_text()
version = os.environ["UPSTREAM_VERSION"]
pattern = r'(var\s+UpstreamVersion\s*=\s*")([^"]+)(")'
updated, count = re.subn(pattern, r'\g<1>' + version + r'\g<3>', data)
if count == 0:
raise SystemExit("UpstreamVersion not found in version.go")
if updated != data:
path.write_text(updated)
PY
# The bump alone only moves a constant. Measuring the gap it opens is the
# point: without this the PR looks trivial and the work stays invisible
# until someone reads a red parity run.
- name: Check out the new upstream release
if: steps.upstream.outputs.changed == 'true'
env:
UPSTREAM_TAG: ${{ steps.upstream.outputs.tag }}
run: |
set -euo pipefail
git -C .. clone --depth 1 --branch "$UPSTREAM_TAG" https://github.com/caronc/apprise apprise
- name: Set up Go
if: steps.upstream.outputs.changed == 'true'
uses: actions/setup-go@v7
with:
go-version-file: go.mod
- name: Set up Python
if: steps.upstream.outputs.changed == 'true'
uses: actions/setup-python@v7
with:
python-version: "3.14"
- name: Install parity dependencies
if: steps.upstream.outputs.changed == 'true'
run: bash scripts/ci/setup_parity_env.sh
# The report is written whether or not the suite passes, so a red run
# still names the providers and schemas that need porting.
- name: Measure the gap the new release opens
if: steps.upstream.outputs.changed == 'true'
continue-on-error: true
run: bash scripts/ci/run_parity_tests.sh
- name: Publish gap summary
if: steps.upstream.outputs.changed == 'true'
run: cat reports/parity_report.md >> "$GITHUB_STEP_SUMMARY"
- name: Compose pull request body
if: steps.upstream.outputs.changed == 'true'
env:
UPSTREAM_TAG: ${{ steps.upstream.outputs.tag }}
PINNED: ${{ steps.upstream.outputs.pinned }}
run: |
{
echo "Syncs \`internal/version.UpstreamVersion\` from \`$PINNED\` to upstream \`$UPSTREAM_TAG\`."
echo
echo "Moving the pin does not port anything: it points the parity suite at the new"
echo "release so the work becomes visible. The gap measured against this release is"
echo "below. Parity CI on this PR will be red until every row is closed."
echo
sed -n '/## Summary/,$p' reports/parity_report.md
} > reports/pr_body.md
- name: Create pull request
id: pr
if: steps.upstream.outputs.changed == 'true'
uses: peter-evans/create-pull-request@v8
with:
branch: chore/sync-upstream-version
delete-branch: true
commit-message: "chore: sync upstream apprise version to ${{ steps.upstream.outputs.tag }}"
title: "chore: sync upstream apprise version to ${{ steps.upstream.outputs.tag }}"
body-path: reports/pr_body.md
add-paths: internal/version/version.go
# A sync PR nobody looks at is the same as no sync at all, so the release
# and the gap it opens get announced rather than left in the PR list. The
# payload is built with jq because the report text is not ours to trust
# as JSON.
- name: Announce the new upstream release
if: steps.upstream.outputs.changed == 'true' && env.DISCORD_CONFIGURED == 'true'
# The pin has moved and the PR is open by this point, so a webhook that
# rejects us is not a failed sync. It also must not reach the failure
# notifier below, which posts to this same webhook and would fail the
# same way.
continue-on-error: true
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
UPSTREAM_TAG: ${{ steps.upstream.outputs.tag }}
PINNED: ${{ steps.upstream.outputs.pinned }}
PR_URL: ${{ steps.pr.outputs.pull-request-url }}
run: |
set -euo pipefail
summary=""
if [ -f reports/parity_report.md ]; then
# Truncated after capture, not through `head`: closing the pipe
# early sends sed a SIGPIPE, which pipefail turns into a failed
# step once the report outgrows the pipe buffer -- that is, exactly
# when the gap is big enough to be worth announcing.
summary="$(sed -n '/## Summary/,$p' reports/parity_report.md)"
summary="${summary:0:1200}"
fi
if [ -z "$summary" ]; then
summary="Parity report not generated; see the run log."
fi
content="$(printf 'upstream apprise %s is out (pinned: %s).\n%s\n\n%s' \
"$UPSTREAM_TAG" "$PINNED" "${PR_URL:-no sync PR was opened}" "$summary")"
jq -n --arg content "$content" '{content: $content}' \
| curl -sS --fail --connect-timeout 10 --max-time 30 \
-X POST -H "Content-Type: application/json" -d @- "$DISCORD_WEBHOOK"
# Without this the checker can break silently and the pin just quietly
# stops moving, which is the failure the checker exists to prevent.
- name: Notify Discord on sync failure
if: failure() && env.DISCORD_CONFIGURED == 'true'
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
run: |
run_url="https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}"
payload=$(printf '{"content":"upstream version sync failed for %s: %s"}' "$GITHUB_REPOSITORY" "$run_url")
curl -sS --fail --connect-timeout 10 --max-time 30 \
-X POST -H "Content-Type: application/json" -d "$payload" "$DISCORD_WEBHOOK"