Closes #361 Three bugs in the hourly deploy workflow's change-detection logic caused the Play Store to silently fall behind whenever a deploy failed or all-android jobs were skipped. **Bug 1 (primary): commit_sha → head_sha** Forgejo's API returns head_sha; commit_sha was always None. This meant LAST_DEPLOYED_SHA was always empty, so the diff fell back to HEAD~1..HEAD — only the single most recent commit was inspected. If android changes landed in an earlier commit, they were silently missed. **Bug 2: Skipped runs counted as 'deployed'** A workflow run where deploy-playstore was skipped (android=false) has status=success, so it was treated as a successful deploy. Now the code queries each run's job results and only trusts a run where the 'Build & Deploy to Play Store' job's own conclusion=success. **Bug 3: Narrow fallback when SHA unknown** When LAST_DEPLOYED_SHA could not be determined the workflow diffed HEAD~1..HEAD — potentially missing many commits. Now it defaults to android=true / linux=true (deploy everything) as the safe fallback. Additional changes: - ::error:: / ::warning:: / ::notice:: annotations so skip/failure reasons surface in the Actions UI. - scripts/verify_playstore_deploy.py: new post-deploy check that queries the internal track and fails if the latest version code is more than 1 hour old. (Version codes are Unix timestamps set by ci/main.go's PublishAndroid.) Catches silent deploy failures the upload API did not reject. - scripts/test_verify_playstore_deploy.py: 5 unit tests for the verify script (all pass). Co-authored-by: Thomas SharedInbox <sharedinbox@thomas-guettler.de> Reviewed-on: https://codeberg.org/guettli/sharedinbox/pulls/364
95 lines
3.0 KiB
Python
95 lines
3.0 KiB
Python
#!/usr/bin/env python3
|
|
"""Verify that the Android app was recently published to the Play Store internal track.
|
|
|
|
The publish-android pipeline sets versionCode = int(time.Now().Unix()), so a
|
|
freshly deployed release always has a version code close to the current Unix
|
|
timestamp. This script queries the internal track and fails if the latest
|
|
version code is older than _MAX_DEPLOY_AGE_SECONDS, which would mean the
|
|
deployment silently did not land.
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import sys
|
|
import time
|
|
|
|
from google.auth.transport.requests import AuthorizedSession
|
|
from google.oauth2 import service_account
|
|
|
|
PACKAGE_NAME = "de.sharedinbox.mua"
|
|
TRACK = "internal"
|
|
_BASE = "https://androidpublisher.googleapis.com/androidpublisher/v3/applications"
|
|
# Allow up to one hour for the build + upload to complete.
|
|
_MAX_DEPLOY_AGE_SECONDS = 3600
|
|
|
|
|
|
def main():
|
|
config_json = os.environ.get("PLAY_STORE_CONFIG_JSON")
|
|
if not config_json:
|
|
print("Error: PLAY_STORE_CONFIG_JSON environment variable not set", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
creds = service_account.Credentials.from_service_account_info(
|
|
json.loads(config_json),
|
|
scopes=["https://www.googleapis.com/auth/androidpublisher"],
|
|
)
|
|
session = AuthorizedSession(creds)
|
|
|
|
# Open a read-only edit to query the current track state.
|
|
edit_resp = session.post(f"{_BASE}/{PACKAGE_NAME}/edits", json={}, timeout=30)
|
|
edit_resp.raise_for_status()
|
|
edit_id = edit_resp.json()["id"]
|
|
|
|
try:
|
|
track_resp = session.get(
|
|
f"{_BASE}/{PACKAGE_NAME}/edits/{edit_id}/tracks/{TRACK}",
|
|
timeout=30,
|
|
)
|
|
track_resp.raise_for_status()
|
|
track_data = track_resp.json()
|
|
finally:
|
|
# Discard the edit — we made no changes.
|
|
try:
|
|
session.delete(f"{_BASE}/{PACKAGE_NAME}/edits/{edit_id}", timeout=30)
|
|
except Exception:
|
|
pass
|
|
|
|
releases = track_data.get("releases", [])
|
|
if not releases:
|
|
print(
|
|
f"ERROR: No releases found on {TRACK} track — deploy may have failed silently",
|
|
file=sys.stderr,
|
|
)
|
|
sys.exit(1)
|
|
|
|
all_version_codes = [
|
|
int(vc)
|
|
for release in releases
|
|
for vc in release.get("versionCodes", [])
|
|
]
|
|
if not all_version_codes:
|
|
print("ERROR: Latest release has no version codes", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
latest_vc = max(all_version_codes)
|
|
now = int(time.time())
|
|
# versionCode is set to Unix timestamp by PublishAndroid in ci/main.go.
|
|
age_seconds = now - latest_vc
|
|
|
|
print(f"Latest version code on {TRACK} track: {latest_vc}")
|
|
print(f"Current time: {now} — version code age: {age_seconds}s")
|
|
|
|
if age_seconds > _MAX_DEPLOY_AGE_SECONDS:
|
|
print(
|
|
f"::error::Latest version code {latest_vc} is {age_seconds}s old "
|
|
f"(limit: {_MAX_DEPLOY_AGE_SECONDS}s). The deploy may have failed silently.",
|
|
file=sys.stderr,
|
|
)
|
|
sys.exit(1)
|
|
|
|
print(f"OK: version {latest_vc} verified on {TRACK} track ({age_seconds}s old)")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|