Repository navigation
Weekly release (manual) #19
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: | |
| inputs: | |
| hotfix_version: | |
| description: 'Version for an off-cycle release (e.g. 26.38.1). Publishes the current build under this version instead of the CalVer week.' | |
| required: false | |
| type: string | |
| 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 | |
| env: | |
| HOTFIX_VERSION: ${{ github.event.inputs.hotfix_version }} | |
| run: | | |
| if [[ -n "$HOTFIX_VERSION" ]]; then | |
| if [[ ! "$HOTFIX_VERSION" =~ ^[0-9]{2}\.[0-9]{2}(\.[0-9]+)?$ ]]; then | |
| echo "::error::Invalid hotfix version format: $HOTFIX_VERSION" | |
| exit 1 | |
| fi | |
| VERSION="$HOTFIX_VERSION" | |
| else | |
| VERSION=$(date -u +%g.%V) | |
| fi | |
| NEXT_VERSION=$(date -u -d "+7 days" +%g.%V) | |
| echo "version=$VERSION" >> "$GITHUB_OUTPUT" | |
| echo "next_version=$NEXT_VERSION" >> "$GITHUB_OUTPUT" | |
| if [[ -n "$HOTFIX_VERSION" ]]; then | |
| echo "Releasing hotfix $VERSION (NEXT_RELEASE_VERSION left unchanged)" | |
| else | |
| echo "Releasing $VERSION (next will be $NEXT_VERSION)" | |
| fi | |
| - 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-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 | |
| # A hotfix cut against a superseded release must not steal the "Latest" | |
| # badge from the newer release that is already published. | |
| IS_LATEST=true | |
| if [[ -n "$PREVIOUS_TAG" ]] \ | |
| && [[ "$(printf '%s\n%s\n' "$VERSION" "$PREVIOUS_TAG" | sort -V | tail -n1)" != "$VERSION" ]]; then | |
| IS_LATEST=false | |
| fi | |
| echo "Marking $VERSION as latest: $IS_LATEST (previous tag: ${PREVIOUS_TAG:-none})" | |
| ARGS=(--target "$GITHUB_SHA" --latest="$IS_LATEST" --title "$VERSION" --generate-notes) | |
| if [[ -n "$PREVIOUS_TAG" && "$PREVIOUS_TAG" != "$VERSION" ]]; then | |
| ARGS+=(--notes-start-tag "$PREVIOUS_TAG") | |
| fi | |
| gh release create "$VERSION" "${ARGS[@]}" | |
| 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 | |
| if: ${{ github.event.inputs.hotfix_version == '' }} | |
| 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" |