Weekly release (manual) #16
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: Weekly release (manual) | |
| on: | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: weekly-release | |
| cancel-in-progress: false | |
| jobs: | |
| prepare: | |
| runs-on: self-hosted | |
| outputs: | |
| version: ${{ steps.compute.outputs.version }} | |
| next_version: ${{ steps.compute.outputs.next_version }} | |
| previous_tag: ${{ steps.previous.outputs.previous_tag }} | |
| jira_version_id: ${{ steps.jira.outputs.version_id }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: Compute CalVer version | |
| id: compute | |
| run: | | |
| VERSION=$(date -u +%g.%V) | |
| NEXT_VERSION=$(date -u -d "+7 days" +%g.%V) | |
| echo "version=$VERSION" >> "$GITHUB_OUTPUT" | |
| echo "next_version=$NEXT_VERSION" >> "$GITHUB_OUTPUT" | |
| echo "Releasing $VERSION (next will be $NEXT_VERSION)" | |
| - name: Look up previous CalVer tag | |
| id: previous | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| shell: flox activate -d flox/base -- bash -eo pipefail {0} | |
| run: | | |
| PREVIOUS_TAG=$(gh release list --json tagName \ | |
| --jq '[.[] | select(.tagName | test("^[0-9]+\\.[0-9]+$"))] | .[0].tagName // ""') | |
| echo "previous_tag=$PREVIOUS_TAG" >> "$GITHUB_OUTPUT" | |
| echo "Previous CalVer tag: ${PREVIOUS_TAG:-none}" | |
| - name: Look up Jira version | |
| id: jira | |
| env: | |
| JIRA_EMAIL: ${{ secrets.JIRA_EMAIL }} | |
| JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} | |
| VERSION: ${{ steps.compute.outputs.version }} | |
| run: | | |
| VERSION_DATA=$(curl -fs --user "$JIRA_EMAIL:$JIRA_API_TOKEN" \ | |
| "https://appdevforall.atlassian.net/rest/api/3/project/ADFA/versions") | |
| VERSION_ID=$(echo "$VERSION_DATA" | jq -r --arg v "$VERSION" \ | |
| '.[] | select(.name == $v) | .id') | |
| if [[ -z "$VERSION_ID" || "$VERSION_ID" == "null" ]]; then | |
| echo "::error::Jira version $VERSION not found in project ADFA" | |
| exit 1 | |
| fi | |
| echo "version_id=$VERSION_ID" >> "$GITHUB_OUTPUT" | |
| echo "Jira version $VERSION → id $VERSION_ID" | |
| pull-apk: | |
| needs: prepare | |
| runs-on: self-hosted | |
| permissions: | |
| contents: read | |
| id-token: write | |
| env: | |
| STAGING_DIR: ./_release-staging | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| variant: [V7, V8] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| persist-credentials: false | |
| - name: Reset staging directory | |
| run: | | |
| rm -rf "$STAGING_DIR" | |
| mkdir -p "$STAGING_DIR" | |
| - name: Authenticate to Google Cloud | |
| id: gcp-auth | |
| uses: google-github-actions/auth@v2 | |
| with: | |
| workload_identity_provider: ${{ secrets.WIF_PROVIDER }} | |
| service_account: ${{ secrets.IDENTITY_EMAIL }} | |
| token_format: 'access_token' | |
| - name: Download matching APK from Firebase App Distribution | |
| id: locate | |
| env: | |
| GCP_ACCESS_TOKEN: ${{ steps.gcp-auth.outputs.access_token }} | |
| FIREBASE_APP_ID: ${{ secrets.FIREBASE_APP_ID }} | |
| VARIANT: ${{ matrix.variant }} | |
| DISPLAY_VERSION_PREFIX: 'C-r-' | |
| run: | | |
| set -euo pipefail | |
| case "$VARIANT" in | |
| V7) BUILD_TYPE_TAG='RELEASE 32-bit' ;; | |
| V8) BUILD_TYPE_TAG='RELEASE 64-bit' ;; | |
| *) | |
| echo "::error::Unknown matrix variant: $VARIANT" | |
| exit 1 | |
| ;; | |
| esac | |
| PROJECT_NUMBER=$(echo "$FIREBASE_APP_ID" | cut -d: -f2) | |
| if [[ -z "$PROJECT_NUMBER" ]]; then | |
| echo "::error::Could not parse project number from FIREBASE_APP_ID" | |
| exit 1 | |
| fi | |
| LIST_URL="https://firebaseappdistribution.googleapis.com/v1/projects/${PROJECT_NUMBER}/apps/${FIREBASE_APP_ID}/releases?pageSize=50" | |
| LIST_JSON=$(curl -fsS -H "Authorization: Bearer $GCP_ACCESS_TOKEN" "$LIST_URL") | |
| MATCH_JSON=$(echo "$LIST_JSON" | jq \ | |
| --arg tag "$BUILD_TYPE_TAG" \ | |
| --arg prefix "$DISPLAY_VERSION_PREFIX" ' | |
| .releases // [] | |
| | map(select( | |
| ((.displayVersion // "") | startswith($prefix)) | |
| and ((.releaseNotes.text // "") | contains($tag)) | |
| )) | |
| | .[0] // empty | |
| ') | |
| if [[ -z "$MATCH_JSON" || "$MATCH_JSON" == "null" ]]; then | |
| echo "::error::No Firebase release found with displayVersion starting '$DISPLAY_VERSION_PREFIX' and release notes containing '$BUILD_TYPE_TAG'" | |
| echo "::group::Firebase releases.list response (last 50)" | |
| echo "$LIST_JSON" | jq '.releases // [] | map({displayVersion, createTime, releaseNotes: (.releaseNotes.text // "")})' | |
| echo "::endgroup::" | |
| exit 1 | |
| fi | |
| DOWNLOAD_URI=$(echo "$MATCH_JSON" | jq -r '.binaryDownloadUri // empty') | |
| DISPLAY_VERSION=$(echo "$MATCH_JSON" | jq -r '.displayVersion // "unknown"') | |
| CREATE_TIME=$(echo "$MATCH_JSON" | jq -r '.createTime // "unknown"') | |
| if [[ -z "$DOWNLOAD_URI" ]]; then | |
| echo "::error::Matched release has no binaryDownloadUri" | |
| echo "$MATCH_JSON" | jq . | |
| exit 1 | |
| fi | |
| SAFE_VERSION=$(echo "$DISPLAY_VERSION" | tr -c 'A-Za-z0-9._-' '_') | |
| APK_PATH="${STAGING_DIR}/firebase-${VARIANT}-${SAFE_VERSION}.apk" | |
| echo "Match: displayVersion=$DISPLAY_VERSION createTime=$CREATE_TIME tag=$BUILD_TYPE_TAG" | |
| curl -fsSL -o "$APK_PATH" "$DOWNLOAD_URI" | |
| if [[ ! -s "$APK_PATH" ]]; then | |
| echo "::error::Downloaded APK is empty" | |
| exit 1 | |
| fi | |
| ls -la "$APK_PATH" | |
| echo "apk_path=$APK_PATH" >> "$GITHUB_OUTPUT" | |
| - name: Upload APK as workflow artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: apk-${{ matrix.variant }} | |
| path: ${{ steps.locate.outputs.apk_path }} | |
| if-no-files-found: error | |
| retention-days: 1 | |
| - name: Clean up staging | |
| if: always() | |
| run: rm -rf "$STAGING_DIR" | |
| release-codeonthego: | |
| needs: [prepare, pull-apk] | |
| runs-on: self-hosted | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: Tag and create GitHub Release (idempotent) | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| VERSION: ${{ needs.prepare.outputs.version }} | |
| PREVIOUS_TAG: ${{ needs.prepare.outputs.previous_tag }} | |
| shell: flox activate -d flox/base -- bash -eo pipefail {0} | |
| run: | | |
| if gh release view "$VERSION" >/dev/null 2>&1; then | |
| echo "Release $VERSION already exists; skipping creation." | |
| exit 0 | |
| fi | |
| if [[ -n "$PREVIOUS_TAG" && "$PREVIOUS_TAG" != "$VERSION" ]]; then | |
| gh release create "$VERSION" \ | |
| --target "$GITHUB_SHA" \ | |
| --latest \ | |
| --title "$VERSION" \ | |
| --generate-notes \ | |
| --notes-start-tag "$PREVIOUS_TAG" | |
| else | |
| gh release create "$VERSION" \ | |
| --target "$GITHUB_SHA" \ | |
| --latest \ | |
| --title "$VERSION" \ | |
| --generate-notes | |
| fi | |
| upload-r2: | |
| needs: [prepare, pull-apk] | |
| runs-on: self-hosted | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| variant: [V7, V8] | |
| env: | |
| AWS_ACCESS_KEY_ID: ${{ vars.CLOUDFLARE_KEY_ID }} | |
| AWS_SECRET_ACCESS_KEY: ${{ secrets.CLOUDFLARE_SECRET_ACCESS_KEY }} | |
| AWS_DEFAULT_REGION: auto | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| persist-credentials: false | |
| - name: Download APK artifact | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: apk-${{ matrix.variant }} | |
| path: ./_apk | |
| - name: Rename APK with release version + arch | |
| id: rename | |
| env: | |
| VERSION: ${{ needs.prepare.outputs.version }} | |
| VARIANT: ${{ matrix.variant }} | |
| run: | | |
| case "$VARIANT" in | |
| V8) ARCH=armv8a ;; | |
| V7) ARCH=armv7a ;; | |
| esac | |
| SRC=$(find ./_apk -type f -name '*.apk' | head -n1) | |
| DEST="CodeOnTheGo-release${VERSION}-${ARCH}.apk" | |
| mv "$SRC" "./$DEST" | |
| echo "dest=$DEST" >> "$GITHUB_OUTPUT" | |
| echo "Prepared: $DEST" | |
| - name: Upload to Cloudflare R2 (apk-repo bucket) | |
| shell: flox activate -d flox/base -- bash -eo pipefail {0} | |
| env: | |
| R2_ACCOUNT_ID: ${{ vars.CLOUDFLARE_ACCOUNT_ID }} | |
| DEST: ${{ steps.rename.outputs.dest }} | |
| run: | | |
| aws s3 cp "./$DEST" "s3://apk-repo/$DEST" \ | |
| --endpoint-url "https://${R2_ACCOUNT_ID}.r2.cloudflarestorage.com" \ | |
| --content-type "application/vnd.android.package-archive" | |
| finalize: | |
| needs: [prepare, release-codeonthego, upload-r2] | |
| runs-on: self-hosted | |
| permissions: | |
| contents: read | |
| actions: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| persist-credentials: false | |
| - name: Close Jira version (idempotent) | |
| env: | |
| JIRA_EMAIL: ${{ secrets.JIRA_EMAIL }} | |
| JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} | |
| VERSION_ID: ${{ needs.prepare.outputs.jira_version_id }} | |
| run: | | |
| curl -fs -X PUT \ | |
| "https://appdevforall.atlassian.net/rest/api/3/version/$VERSION_ID" \ | |
| --user "$JIRA_EMAIL:$JIRA_API_TOKEN" \ | |
| --header 'Content-Type: application/json' \ | |
| --data "{\"released\": true, \"releaseDate\": \"$(date -u +%Y-%m-%d)\"}" | |
| echo "" | |
| echo "Jira version $VERSION_ID marked released" | |
| - name: Mint GitHub App token | |
| id: app-token | |
| uses: actions/create-github-app-token@v1 | |
| with: | |
| app-id: ${{ vars.RELEASE_BOT_APP_ID }} | |
| private-key: ${{ secrets.RELEASE_BOT_PRIVATE_KEY }} | |
| - name: Set NEXT_RELEASE_VERSION repo variable | |
| env: | |
| GH_TOKEN: ${{ steps.app-token.outputs.token }} | |
| NEXT_VERSION: ${{ needs.prepare.outputs.next_version }} | |
| shell: flox activate -d flox/base -- bash -eo pipefail {0} | |
| run: | | |
| gh variable set NEXT_RELEASE_VERSION --body "$NEXT_VERSION" --repo "$GITHUB_REPOSITORY" | |
| echo "NEXT_RELEASE_VERSION set to $NEXT_VERSION" |