sync-upstream-version #31
Workflow file for this run
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: 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" |