Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions .github/ci/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# CI fixtures

Files here exist so that the `flipcash-tests` job in [`../workflows/ci.yml`](../workflows/ci.yml)
can build, unit-test and lint the app **without any credentials**.

## Why

GitHub does not expose repository Actions secrets to `pull_request` runs opened by
`dependabot[bot]` — Dependabot reads from its own secret store — or by forks. On those runs every
`secrets.*` expression resolves to an empty string. Two things then break before Gradle produces
anything useful:

1. `google-services.json` never gets written, so the `com.google.gms.google-services` plugin fails
to configure `:apps:flipcash:app`.
2. The secrets Gradle plugin copies each `local.properties` entry into `BuildConfig` verbatim, so
an empty value emits `public static final String BUGSNAG_API_KEY = ;` and
`compileDebugJavaWithJavac` fails.

The result was that every dependency PR was red for a reason unrelated to the bump, which destroys
the signal: a genuinely broken bump looked exactly like a healthy one.

The job therefore falls back to placeholders whenever a secret is empty. That is safe because the
lane runs `generateEmojiList flipcashTestDebug :apps:flipcash:app:lintDebug` — it compiles,
unit-tests and lints, and never contacts Firebase, Bugsnag, Mixpanel or Coinbase.

## `google-services.placeholder.json`

A **fake** Firebase config, committed on purpose. Structurally valid, deliberately meaningless:
project number `000000000000`, project id `flipcash-ci-placeholder`, zero-filled API key.

`client[].client_info.android_client_info.package_name` must stay in sync with the app's
`applicationId` (`com.flipcash.app.android`) or the plugin errors with "No matching client found
for package name".

## `local.properties` placeholders

Not a file — the fallbacks are inline in the workflow's `Write local.properties` step, zero-filled
for `BUGSNAG_API_KEY`, `MIXPANEL_API_KEY`, `COINBASE_ONRAMP_API_KEY` and
`GOOGLE_CLOUD_PROJECT_NUMBER`. Each key is substituted independently, so a run with only some
secrets available still uses the real ones it has, and the job logs a notice naming every key it
faked.

## Do not put real keys here

Anything that needs real credentials — release builds, upload lanes, the Maestro E2E suite — reads
them from secrets in its own workflow. If a job in `ci.yml` ever needs the *real* values on
Dependabot PRs, add them under **Settings → Secrets and variables → Dependabot** (repo admin
required) under the same names `ci.yml` already references.
29 changes: 29 additions & 0 deletions .github/ci/google-services.placeholder.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"project_info": {
"project_number": "000000000000",
"project_id": "flipcash-ci-placeholder",
"storage_bucket": "flipcash-ci-placeholder.appspot.com"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:000000000000:android:0000000000000000000000",
"android_client_info": {
"package_name": "com.flipcash.app.android"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyA0000000000000000000000000000000000"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": []
}
}
}
],
"configuration_version": "1"
}
87 changes: 68 additions & 19 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ jobs:
with:
fetch-depth: 1

# Cheap supply-chain guard: fails the run if gradle/wrapper/gradle-wrapper.jar
# is not a byte-for-byte match for a jar published by Gradle.
- name: Validate Gradle wrapper
uses: gradle/actions/wrapper-validation@v4

- name: Setup Java env
uses: actions/setup-java@v3
with:
Expand All @@ -42,26 +47,70 @@ jobs:
ruby-version: 2.7.2
bundler-cache: true

- name: Decode Google Services JSON file
uses: timheuer/base64-to-file@v1
id: google_services_json_file
with:
fileName: google-services.json
fileDir: ./apps/flipcash/app/src
encodedString: ${{ secrets.FLIPCASH2_GOOGLE_SERVICES }}


- name: Setup BugSnag API Key
run: echo BUGSNAG_API_KEY=\"${{ secrets.FLIPCASH_BUGSNAG_API_KEY }}\" > ./local.properties

- name: Setup Google Cloud Project Number
run: echo GOOGLE_CLOUD_PROJECT_NUMBER=${{ secrets.GOOGLE_CLOUD_PROJECT_NUMBER }} >> ./local.properties

- name: Setup Mixpanel API Key
run: echo MIXPANEL_API_KEY=\"${{ secrets.FLIPCASH_MIXPANEL_API_KEY }}\" >> ./local.properties
# GitHub does not expose regular Actions secrets to `pull_request` runs opened by
# Dependabot (it reads from a separate Dependabot secret store) or by forks, so every
# `secrets.*` below resolves to an empty string on those runs. This lane only builds and
# runs unit tests + lint — it never talks to Firebase, Bugsnag, Mixpanel or Coinbase — so
# it falls back to a committed placeholder config instead of failing. That keeps dependency
# PRs honestly red or green on the bump itself rather than uniformly red on missing config.
#
# If a future job in this workflow ever needs the *real* values on Dependabot PRs, add them
# under Settings -> Secrets and variables -> Dependabot (repo admin required); the names are
# the same ones referenced here.
- name: Provision google-services.json
env:
FLIPCASH2_GOOGLE_SERVICES: ${{ secrets.FLIPCASH2_GOOGLE_SERVICES }}
run: |
set -euo pipefail
dest=apps/flipcash/app/src/google-services.json
mkdir -p "$(dirname "$dest")"
if [ -n "${FLIPCASH2_GOOGLE_SERVICES:-}" ]; then
printf '%s' "$FLIPCASH2_GOOGLE_SERVICES" | base64 --decode > "$dest"
echo "Wrote google-services.json from the FLIPCASH2_GOOGLE_SERVICES secret."
else
cp .github/ci/google-services.placeholder.json "$dest"
echo "::notice title=Using placeholder Firebase config::Repository secrets are not available on this run (Dependabot or fork PR). Copied .github/ci/google-services.placeholder.json instead; unit tests and lint do not need real Firebase credentials."
fi

- name: Setup Coinbase OnRamp API Key
run: echo COINBASE_ONRAMP_API_KEY=${{ secrets.COINBASE_ONRAMP_API_KEY }} >> ./local.properties
# The secrets Gradle plugin copies every entry in local.properties into BuildConfig
# verbatim, so an *empty* value emits `public static final String X = ;` and the app fails
# to compile — writing empty strings is not a safe fallback. Each key therefore gets a
# zero-filled placeholder when its secret is unavailable (Dependabot / fork PRs); nothing
# in this lane calls out to Bugsnag, Mixpanel or Coinbase.
#
# Values are passed through the environment rather than interpolated into the script so a
# secret containing a quote or backtick cannot break (or escape) the shell. The quoting of
# each line is kept exactly as it was, since BUGSNAG_API_KEY is also read verbatim into a
# manifest placeholder.
- name: Write local.properties
env:
BUGSNAG_API_KEY: ${{ secrets.FLIPCASH_BUGSNAG_API_KEY }}
GOOGLE_CLOUD_PROJECT_NUMBER: ${{ secrets.GOOGLE_CLOUD_PROJECT_NUMBER }}
MIXPANEL_API_KEY: ${{ secrets.FLIPCASH_MIXPANEL_API_KEY }}
COINBASE_ONRAMP_API_KEY: ${{ secrets.COINBASE_ONRAMP_API_KEY }}
run: |
set -euo pipefail
# Substitute an obviously-fake value for any key whose secret came through empty.
placeheld=""
for spec in \
"BUGSNAG_API_KEY=00000000000000000000000000000000" \
"GOOGLE_CLOUD_PROJECT_NUMBER=000000000000" \
"MIXPANEL_API_KEY=00000000000000000000000000000000" \
"COINBASE_ONRAMP_API_KEY=00000000-0000-0000-0000-000000000000"
do
name=${spec%%=*}
if [ -z "${!name:-}" ]; then
printf -v "$name" '%s' "${spec#*=}"
placeheld="$placeheld $name"
fi
done
{
echo "BUGSNAG_API_KEY=\"$BUGSNAG_API_KEY\""
echo "GOOGLE_CLOUD_PROJECT_NUMBER=$GOOGLE_CLOUD_PROJECT_NUMBER"
echo "MIXPANEL_API_KEY=\"$MIXPANEL_API_KEY\""
echo "COINBASE_ONRAMP_API_KEY=$COINBASE_ONRAMP_API_KEY"
} > ./local.properties
[ -z "$placeheld" ] || echo "::notice title=Using placeholder API keys::Secrets are not available on this run (Dependabot or fork PR); zero-filled placeholders used for:$placeheld"

- name: Run Flipcash tests
run: bundle exec fastlane android flipcash_tests
Expand Down
Loading