diff --git a/.github/scripts/push_releases.py b/.github/scripts/push_releases.py new file mode 100644 index 0000000..2127e66 --- /dev/null +++ b/.github/scripts/push_releases.py @@ -0,0 +1,102 @@ +"""Push GitHub release metadata to the WordPress site.""" + +from __future__ import annotations + +import json +import os +import time +import urllib.error +import urllib.request +from typing import Any + +REPO = os.environ["REPO"] +WP_URL = os.environ["WP_URL"].rstrip("/") +WP_TOKEN = os.environ["WP_TOKEN"] +BUST_CACHE_FIRST = os.environ.get("BUST_CACHE_FIRST", "false").lower() == "true" +GH_TOKEN = os.environ.get("GITHUB_TOKEN") +HEADERS = { + "Content-Type": "application/json", + "X-OGR-Token": WP_TOKEN, +} +GH_API_HEADERS: dict[str, str] = { + "Accept": "application/vnd.github+json", +} +if GH_TOKEN: + GH_API_HEADERS["Authorization"] = f"Bearer {GH_TOKEN}" + + +def gh_json(endpoint: str, allow_missing: bool = False) -> Any: + url = f"https://api.github.com/{endpoint}" + request = urllib.request.Request(url, headers=GH_API_HEADERS) + try: + with urllib.request.urlopen(request, timeout=30) as response: + return json.load(response) + except urllib.error.HTTPError as exc: + if allow_missing and exc.code == 404: + return None + raise + + +def fetch_payload() -> dict[str, Any]: + latest = gh_json(f"repos/{REPO}/releases/latest", allow_missing=True) + all_releases = gh_json(f"repos/{REPO}/releases?per_page=20", allow_missing=True) + if not isinstance(all_releases, list): + all_releases = [] + return { + "repo": REPO, + "releases": all_releases[:20], + "latest": latest if isinstance(latest, dict) and latest.get("tag_name") else None, + } + + +def push_releases(payload: dict[str, Any]) -> dict[str, Any]: + """Transmit release payload to the WordPress endpoint.""" + data = json.dumps(payload).encode() + req = urllib.request.Request( + f"{WP_URL}/wp-json/ogr/v1/push-releases", + data=data, + headers=HEADERS, + method="POST", + ) + with urllib.request.urlopen(req, timeout=30) as response: + return json.load(response) + + +def bust_cache() -> dict[str, Any]: + """Hit the optional bust-cache endpoint so the next push lands on fresh data.""" + req = urllib.request.Request( + f"{WP_URL}/wp-json/ogr/v1/bust-cache", + data=b"{}", + headers=HEADERS, + method="POST", + ) + with urllib.request.urlopen(req, timeout=20) as response: + return json.load(response) + + +def main() -> None: + payload = fetch_payload() + + def attempt() -> dict[str, Any]: + return push_releases(payload) + + for attempt_num in range(2): + try: + result = attempt() + print(f"Pushed {result['count']} releases for {result['repo']}") + return + except Exception as exc: + print("First push attempt failed." if attempt_num == 0 else "Second push attempt failed.") + if attempt_num == 0: + if BUST_CACHE_FIRST: + print("Cache nudge enabled. Hitting bust-cache before retry...") + bust_result = bust_cache() + print("Cache nudge response:", bust_result) + print("Retrying once in 10s...") + time.sleep(10) + continue + raise + + +if __name__ == "__main__": + main() diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3fd83a7..2b06aa9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -127,8 +127,10 @@ jobs: push-to-wordpress: needs: release - if: ${{ github.event_name != 'workflow_dispatch' || inputs.dry_run != true }} - uses: Orinks/orinks-github-releases/.github/workflows/push-releases.yml@master + if: ${{ needs.release.result == 'success' }} + uses: ./.github/workflows/push-releases.yml + with: + bust_cache_first: false secrets: WP_PUSH_TOKEN: ${{ secrets.WP_PUSH_TOKEN }} WP_SITE_URL: ${{ secrets.WP_SITE_URL }} diff --git a/.github/workflows/push-releases.yml b/.github/workflows/push-releases.yml new file mode 100644 index 0000000..d8ab865 --- /dev/null +++ b/.github/workflows/push-releases.yml @@ -0,0 +1,39 @@ +name: Push releases to WordPress + +on: + workflow_call: + inputs: + bust_cache_first: + description: "Bust WordPress cache before retrying push" + required: false + default: false + type: boolean + secrets: + WP_PUSH_TOKEN: + required: true + WP_SITE_URL: + required: true + workflow_dispatch: + inputs: + bust_cache_first: + description: "Bust WordPress cache before retrying push" + required: false + default: false + type: boolean + +jobs: + push: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Checkout repo + uses: actions/checkout@v4 + - name: Validate required secrets + env: + WP_TOKEN: ${{ secrets.WP_PUSH_TOKEN }} + WP_URL: ${{ secrets.WP_SITE_URL }} + REPO: ${{ github.repository }} + BUST_CACHE_FIRST: ${{ inputs.bust_cache_first || false }} + run: | + set -euo pipefail + python3 .github/scripts/push_releases.py diff --git a/.github/workflows/update-wordpress.yml b/.github/workflows/update-wordpress.yml index 71b625c..f32d288 100644 --- a/.github/workflows/update-wordpress.yml +++ b/.github/workflows/update-wordpress.yml @@ -3,10 +3,19 @@ name: Update WordPress on release on: release: types: [published] + workflow_dispatch: + inputs: + bust_cache_first: + description: "Bust WordPress cache before retrying push" + required: false + default: false + type: boolean jobs: push-to-wordpress: - uses: Orinks/orinks-github-releases/.github/workflows/push-releases.yml@master + uses: ./.github/workflows/push-releases.yml + with: + bust_cache_first: ${{ inputs.bust_cache_first || false }} secrets: WP_PUSH_TOKEN: ${{ secrets.WP_PUSH_TOKEN }} WP_SITE_URL: ${{ secrets.WP_SITE_URL }}