Build and deploy hybird apps for testing #4
This file contains 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: Build and deploy hybird apps for testing | |
on: | |
workflow_dispatch: | |
inputs: | |
PULL_REQUEST_NUMBER: | |
description: Pull Request number for correct placement of apps | |
required: true | |
OLD_DOT_COMMIT: | |
description: The branch, tag or SHA to checkout on Old Dot side | |
required: false | |
pull_request_target: | |
types: [opened, synchronize, labeled] | |
branches: ['*ci-test/**'] | |
env: | |
PULL_REQUEST_NUMBER: ${{ github.event.number || github.event.inputs.PULL_REQUEST_NUMBER }} | |
OLD_DOT_COMMIT: ${{ github.event.inputs.OLD_DOT_COMMIT }} | |
jobs: | |
validateActor: | |
runs-on: ubuntu-latest | |
outputs: | |
READY_TO_BUILD: ${{ fromJSON(steps.isExpensifyEmployee.outputs.IS_EXPENSIFY_EMPLOYEE) && fromJSON(steps.hasReadyToBuildLabel.outputs.HAS_READY_TO_BUILD_LABEL) }} | |
steps: | |
- name: Is Expensify employee | |
id: isExpensifyEmployee | |
run: | | |
if gh api /orgs/Expensify/teams/expensify-expensify/memberships/${{ github.actor }} --silent; then | |
echo "IS_EXPENSIFY_EMPLOYEE=true" >> "$GITHUB_OUTPUT" | |
else | |
echo "IS_EXPENSIFY_EMPLOYEE=false" >> "$GITHUB_OUTPUT" | |
fi | |
env: | |
GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_TOKEN }} | |
- id: hasReadyToBuildLabel | |
name: Set HAS_READY_TO_BUILD_LABEL flag | |
run: | | |
echo "HAS_READY_TO_BUILD_LABEL=$(gh pr view "${{ env.PULL_REQUEST_NUMBER }}" --repo Expensify/App --json labels --jq '.labels[].name' | grep -q 'Ready To Build' && echo 'true')" >> "$GITHUB_OUTPUT" | |
if [[ "$HAS_READY_TO_BUILD_LABEL" != 'true' ]]; then | |
echo "The 'Ready to Build' label is not attached to the PR #${{ env.PULL_REQUEST_NUMBER }}" | |
fi | |
env: | |
GITHUB_TOKEN: ${{ github.token }} | |
getBranchRef: | |
runs-on: ubuntu-latest | |
needs: validateActor | |
if: ${{ fromJSON(needs.validateActor.outputs.READY_TO_BUILD) }} | |
outputs: | |
REF: ${{ steps.getHeadRef.outputs.REF }} | |
steps: | |
- name: Checkout | |
if: ${{ github.event_name == 'workflow_dispatch' }} | |
uses: actions/checkout@v4 | |
- name: Check if pull request number is correct | |
if: ${{ github.event_name == 'workflow_dispatch' }} | |
id: getHeadRef | |
run: | | |
set -e | |
echo "REF=$(gh pr view ${{ github.event.inputs.PULL_REQUEST_NUMBER }} --json headRefOid --jq '.headRefOid')" >> "$GITHUB_OUTPUT" | |
env: | |
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
postGitHubCommentBuildStarted: | |
runs-on: ubuntu-latest | |
needs: [validateActor, getBranchRef] | |
if: ${{ fromJSON(needs.validateActor.outputs.READY_TO_BUILD) }} | |
steps: | |
- name: Add build start comment | |
uses: actions/github-script@v7 | |
with: | |
github-token: ${{ github.token }} | |
script: | | |
const workflowURL = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`; | |
github.rest.issues.createComment({ | |
owner: context.repo.owner, | |
repo: context.repo.repo, | |
issue_number: process.env.PULL_REQUEST_NUMBER, | |
body: `🚧 @${{ github.actor }} has triggered a test hybrid app build. You can view the [workflow run here](${workflowURL}).` | |
}); | |
androidHybrid: | |
name: Build Android HybridApp | |
needs: [validateActor, getBranchRef] | |
if: ${{ fromJSON(needs.validateActor.outputs.READY_TO_BUILD) }} | |
runs-on: ubuntu-latest-xl | |
outputs: | |
S3_APK_PATH: ${{ steps.exportAndroidS3Path.outputs.S3_APK_PATH }} | |
steps: | |
- name: Checkout | |
uses: actions/checkout@v4 | |
with: | |
submodules: true | |
ref: ${{ github.event.pull_request.head.sha || needs.getBranchRef.outputs.REF }} | |
token: ${{ secrets.OS_BOTIFY_TOKEN }} | |
# fetch-depth: 0 is required in order to fetch the correct submodule branch | |
fetch-depth: 0 | |
- name: Update submodule to match main | |
env: | |
OLD_DOT_COMMIT: ${{ env.OLD_DOT_COMMIT }} | |
run: | | |
git submodule update --init --remote | |
if [[ -z "$OLD_DOT_COMMIT" ]]; then | |
git fetch | |
git checkout ${{ env.OLD_DOT_COMMIT }} | |
fi | |
- name: Configure MapBox SDK | |
run: ./scripts/setup-mapbox-sdk.sh ${{ secrets.MAPBOX_SDK_DOWNLOAD_TOKEN }} | |
- name: Setup Node | |
id: setup-node | |
uses: ./.github/actions/composite/setupNode | |
- name: Run grunt build | |
run: | | |
cd Mobile-Expensify | |
npm run grunt:build:shared | |
- name: Setup dotenv | |
run: | | |
cp .env.staging .env.adhoc | |
sed -i 's/ENVIRONMENT=staging/ENVIRONMENT=adhoc/' .env.adhoc | |
echo "PULL_REQUEST_NUMBER=${{ inputs.pull_request_number }}" >> .env.adhoc | |
- name: Setup Java | |
uses: actions/setup-java@v4 | |
with: | |
distribution: 'oracle' | |
java-version: '17' | |
- name: Setup Ruby | |
uses: ruby/setup-ruby@v1.190.0 | |
with: | |
bundler-cache: true | |
- name: Install 1Password CLI | |
uses: 1password/install-cli-action@v1 | |
- name: Load files from 1Password | |
env: | |
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} | |
run: | | |
op document get --output ./upload-key.keystore upload-key.keystore | |
op document get --output ./android-fastlane-json-key.json android-fastlane-json-key.json | |
# Copy the keystore to the Android directory for Fullstory | |
cp ./upload-key.keystore Mobile-Expensify/Android | |
- name: Load Android upload keystore credentials from 1Password | |
id: load-credentials | |
uses: 1password/load-secrets-action@v2 | |
with: | |
export-env: false | |
env: | |
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} | |
ANDROID_UPLOAD_KEYSTORE_PASSWORD: op://Mobile-Deploy-CI/Repository-Secrets/ANDROID_UPLOAD_KEYSTORE_PASSWORD | |
ANDROID_UPLOAD_KEYSTORE_ALIAS: op://Mobile-Deploy-CI/Repository-Secrets/ANDROID_UPLOAD_KEYSTORE_ALIAS | |
ANDROID_UPLOAD_KEY_PASSWORD: op://Mobile-Deploy-CI/Repository-Secrets/ANDROID_UPLOAD_KEY_PASSWORD | |
- name: Build Android app | |
id: build | |
env: | |
ANDROID_UPLOAD_KEYSTORE_PASSWORD: ${{ steps.load-credentials.outputs.ANDROID_UPLOAD_KEYSTORE_PASSWORD }} | |
ANDROID_UPLOAD_KEYSTORE_ALIAS: ${{ steps.load-credentials.outputs.ANDROID_UPLOAD_KEYSTORE_ALIAS }} | |
ANDROID_UPLOAD_KEY_PASSWORD: ${{ steps.load-credentials.outputs.ANDROID_UPLOAD_KEY_PASSWORD }} | |
run: bundle exec fastlane android build_adhoc_hybrid | |
- name: Configure AWS Credentials | |
uses: aws-actions/configure-aws-credentials@v4 | |
with: | |
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} | |
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} | |
aws-region: us-east-1 | |
- name: Upload Android AdHoc build to S3 | |
run: bundle exec fastlane android upload_s3 | |
env: | |
S3_ACCESS_KEY: ${{ secrets.AWS_ACCESS_KEY_ID }} | |
S3_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} | |
S3_BUCKET: ad-hoc-expensify-cash | |
S3_REGION: us-east-1 | |
- name: Export S3 path | |
id: exportAndroidS3Path | |
run: | | |
# $s3APKPath is set from within the Fastfile, android upload_s3 lane | |
echo "S3_APK_PATH=$s3APKPath" >> "$GITHUB_OUTPUT" | |
iosHybrid: | |
name: Build and deploy iOS for testing | |
needs: [validateActor, getBranchRef] | |
if: ${{ fromJSON(needs.validateActor.outputs.READY_TO_BUILD) }} | |
env: | |
DEVELOPER_DIR: /Applications/Xcode_15.2.0.app/Contents/Developer | |
runs-on: macos-13-xlarge | |
steps: | |
- name: Checkout | |
uses: actions/checkout@v4 | |
with: | |
submodules: true | |
ref: ${{ github.event.pull_request.head.sha || needs.getBranchRef.outputs.REF }} | |
token: ${{ secrets.OS_BOTIFY_TOKEN }} | |
# fetch-depth: 0 is required in order to fetch the correct submodule branch | |
fetch-depth: 0 | |
- name: Update submodule to match main | |
env: | |
OLD_DOT_COMMIT: ${{ env.OLD_DOT_COMMIT }} | |
run: | | |
git submodule update --init --remote | |
if [[ -z "$OLD_DOT_COMMIT" ]]; then | |
git fetch | |
git checkout ${{ env.OLD_DOT_COMMIT }} | |
fi | |
- name: Configure MapBox SDK | |
run: ./scripts/setup-mapbox-sdk.sh ${{ secrets.MAPBOX_SDK_DOWNLOAD_TOKEN }} | |
- name: Setup Node | |
id: setup-node | |
uses: ./.github/actions/composite/setupNode | |
- name: Create .env.adhoc file based on staging and add PULL_REQUEST_NUMBER env to it | |
run: | | |
cp .env.staging .env.adhoc | |
sed -i '' 's/ENVIRONMENT=staging/ENVIRONMENT=adhoc/' .env.adhoc | |
echo "PULL_REQUEST_NUMBER=$PULL_REQUEST_NUMBER" >> .env.adhoc | |
- name: Setup Ruby | |
uses: ruby/setup-ruby@v1.190.0 | |
with: | |
bundler-cache: true | |
- name: Install New Expensify Gems | |
run: bundle install | |
- name: Cache Pod dependencies | |
uses: actions/cache@v4 | |
id: pods-cache | |
with: | |
path: Mobile-Expensify/iOS/Pods | |
key: ${{ runner.os }}-pods-cache-${{ hashFiles('Mobile-Expensify/iOS/Podfile.lock', 'firebase.json') }} | |
- name: Compare Podfile.lock and Manifest.lock | |
id: compare-podfile-and-manifest | |
run: echo "IS_PODFILE_SAME_AS_MANIFEST=${{ hashFiles('Mobile-Expensify/iOS/Podfile.lock') == hashFiles('Mobile-Expensify/iOS/Manifest.lock') }}" >> "$GITHUB_OUTPUT" | |
- name: Install cocoapods | |
uses: nick-fields/retry@3f757583fb1b1f940bc8ef4bf4734c8dc02a5847 | |
if: steps.pods-cache.outputs.cache-hit != 'true' || steps.compare-podfile-and-manifest.outputs.IS_PODFILE_SAME_AS_MANIFEST != 'true' || steps.setup-node.outputs.cache-hit != 'true' | |
with: | |
timeout_minutes: 10 | |
max_attempts: 5 | |
command: npm run pod-install | |
- name: Install 1Password CLI | |
uses: 1password/install-cli-action@v1 | |
- name: Load files from 1Password | |
env: | |
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} | |
run: | | |
op document get --output ./OldApp_AdHoc.mobileprovision OldApp_AdHoc | |
op document get --output ./OldApp_AdHoc_Share_Extension.mobileprovision OldApp_AdHoc_Share_Extension | |
op document get --output ./OldApp_AdHoc_Notification_Service.mobileprovision OldApp_AdHoc_Notification_Service | |
- name: Decrypt certificate | |
run: cd ios && gpg --quiet --batch --yes --decrypt --passphrase="$LARGE_SECRET_PASSPHRASE" --output Certificates.p12 Certificates.p12.gpg | |
env: | |
LARGE_SECRET_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} | |
- name: Build AdHoc app | |
run: bundle exec fastlane ios build_adhoc_hybrid | |
- name: Configure AWS Credentials | |
uses: aws-actions/configure-aws-credentials@v4 | |
with: | |
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} | |
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} | |
aws-region: us-east-1 | |
- name: Upload AdHoc build to S3 | |
run: bundle exec fastlane ios upload_s3 | |
env: | |
S3_ACCESS_KEY: ${{ secrets.AWS_ACCESS_KEY_ID }} | |
S3_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} | |
S3_BUCKET: ad-hoc-expensify-cash | |
S3_REGION: us-east-1 | |
- name: Upload Artifact | |
uses: actions/upload-artifact@v4 | |
with: | |
name: ios | |
path: ./ios_paths.json | |
postGithubComment: | |
runs-on: ubuntu-latest | |
name: Post a GitHub comment with app download links for testing | |
needs: [validateActor, getBranchRef, androidHybrid, iosHybrid] | |
if: ${{ always() }} | |
steps: | |
- name: Checkout | |
uses: actions/checkout@v4 | |
if: ${{ fromJSON(needs.validateActor.outputs.READY_TO_BUILD) }} | |
with: | |
ref: ${{ github.event.pull_request.head.sha || needs.getBranchRef.outputs.REF }} | |
- name: Download Artifact | |
uses: actions/download-artifact@v4 | |
if: ${{ fromJSON(needs.validateActor.outputs.READY_TO_BUILD) }} | |
- name: Read JSONs with iOS paths | |
id: get_ios_path | |
if: ${{ needs.iosHybrid.result == 'success' }} | |
run: | | |
content_ios="$(cat ./ios/ios_paths.json)" | |
content_ios="${content_ios//'%'/'%25'}" | |
content_ios="${content_ios//$'\n'/'%0A'}" | |
content_ios="${content_ios//$'\r'/'%0D'}" | |
ios_path=$(echo "$content_ios" | jq -r '.html_path') | |
echo "ios_path=$ios_path" >> "$GITHUB_OUTPUT" | |
- name: Publish links to apps for download | |
if: ${{ fromJSON(needs.validateActor.outputs.READY_TO_BUILD) }} | |
uses: ./.github/actions/javascript/postTestBuildComment | |
with: | |
PR_NUMBER: ${{ env.PULL_REQUEST_NUMBER }} | |
GITHUB_TOKEN: ${{ github.token }} | |
ANDROID: ${{ needs.androidHybrid.result }} | |
IOS: ${{ needs.iosHybrid.result }} | |
ANDROID_LINK: ${{ needs.androidHybrid.outputs.S3_APK_PATH }} | |
IOS_LINK: ${{ steps.get_ios_path.outputs.ios_path }} |