Creating a GitHub Action for GitOps Management of Firebase Remote Config Feature Flags

riddle
MIXI DEVELOPERS
Published in
5 min readApr 9, 2024

--

Hello, I’m riddle from the SRE Group at MIXI Development Division.

Table of Contents:

  1. What is Feature Flag Development?
  2. Challenges in Managing Feature Flags
  3. How to Use
  4. Introducing Our CI & CD
    4.1 Directory Layout
    4.2 check_remote_config.yaml
    4.3 publish_remote_config.yaml
    4.4 action.yaml
  5. Conclusion

What is Feature Flag Development?

Feature flag development is a mechanism for toggling application features on or off after release.

In feature flag development, for example, when releasing new features, you can first make them available to a select group of users or quickly turn off a feature if issues arise post-release. Firebase Remote Config is one commonly used tool for feature flag development.

Challenges in Managing Feature Flags

Firebase Remote Config settings can be added or changed via the Web UI, API, and SDK.

The Web UI allows intuitive settings configuration but requires manual operation, which is time-consuming and prone to errors when configuring multiple environments. As of March 23, 2024, I couldn’t find any convenient tools that wrap the SDK or API for easier use.

To address this, I created a tool that manages Firebase Remote Config settings through Git and automates the configuration with GitHub Actions.

https://github.com/lirlia/firebase-remote-config-actions

This offers two main benefits:

  • Updates to Remote Config can be done declaratively
  • Configuration syntax checks and differences can be reviewed

Now, let’s see how it’s used.

How to Use

First, store a template.json file in your repository. Then, by invoking the following GitHub Actions, you can update Remote Config.

Note: Prior to rewriting Firebase Remote Config, Google Cloud permissions had been obtained via Workload Identity. (Please ensure this ServiceAccount has Admin permissions for Remote Config.)

name: Manage Firebase Remote Config
on: [push]
jobs:
firebase-remote-config:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: google-github-actions/auth@v2
with:
project_id: YOUR_PROJECT_ID
workload_identity_provider: YOUR_PROVIDER
# this flag must be set to true to create the credentials file
# firebase actions require GOOGLE_APPLICATION_CREDENTIALS to be set
create_credentials_file: true

- name: Validate Firebase Remote Config
uses: lirlia/firebase-remote-config-actions@main
with:
command: 'publish'
# Please specify the absolute path.
template-file-path: '${{ github.workspace }}/template.json'
service-account-email: 'xxx@yyyy.iam.gserviceaccount.com'

This tool has three commands:

  • validate: Checks the syntax of the template file
  • diff: Compares the local template file with the remote configuration
  • publish: Deploys the template file to the remote configuration

For example, using diff allows you to see differences from the remote settings.

{
parameterGroups: {
Feature Flags: {
parameters: {
+ featureFlagA: {
+ defaultValue: {
+ value: "false"
+ }
+ conditionalValues: {
+ ios-1.0.0: {
+ value: "true"
+ }
+ android-1.0.0: {
+ value: "true"
+ }
+ }
+ valueType: "BOOLEAN"
+ }
featureFlagB: {
- conditionalValues: {
- ios-0.1.0: {
- value: "true"
- }
- android-0.1.0: {
- value: "true"
- }
- }
defaultValue: {
- value: "false"
+ value: "true"
}
}
}
}
}
conditions: [
{
- name: "ios-0.1.0"
+ name: "ios-1.0.0"
- expression: "app.id == 'xx' && app.version.>=(['0.1.0'])"
+ expression: "app.id == 'xx' && app.version.>=(['1.0.0'])"
}
{
- name: "android-0.1.0"
+ name: "android-1.0.0"
- expression: "app.id == 'xx' && app.version.>=(['0.1.0'])"
+ expression: "app.id == 'xx' && app.version.>=(['1.0.0'])"
}
]
}d

Introducing Our CI & CD

In my team, we manage remote_config_environment_name.json files in our Git repository. Changes to these files trigger CI/CD to check and update Firebase Remote Config.

Directory Layout

- .github
- actions
- remote-config
- action.yaml
- workflows
- check_remote_config.yaml
- publish_remote_config.yaml
- remote_config_production.json
- remote_config_staging.json

check_remote_config.yaml

Validates and diffs remote_config_environment_name.json when a PR is created.

name: check remote config
on:
pull_request:
branches:
- main
- staging
paths:
- remote_config_staging.json
- remote_config_production.json
- .github/workflows/check-remote-config.yaml

permissions:
contents: read
id-token: write
pull-requests: write

jobs:
check_remote_config:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- id: staging
if: github.base_ref == 'staging'
uses: ./.github/actions/remote-config
with:
project_id: XXXXXXX
workload_identity_provider: projects/XXXXXXX/locations/global/workloadIdentityPools/pool/providers/github
service_account_email: remote-config-updater@XXXXXXX.iam.gserviceaccount.com
remote_config_path: ${{ github.workspace }}/remote_config_staging.json
command: check

- id: production
if: github.base_ref == 'main'
uses: ./.github/actions/remote-config
with:
project_id: XXXXXXX
workload_identity_provider: projects/XXXXXXX/locations/global/workloadIdentityPools/pool/providers/github
service_account_email: remote-config-updater@XXXXXXX.iam.gserviceaccount.com
remote_config_path: ${{ github.workspace }}/remote_config_production.json
command: check

publish_remote_config.yaml

Deploys changes in remote_config_environment_name.json to Firebase Remote Config when pushed to the main or staging branch.

name: publish remote config
on:
push:
branches:
- main
- staging
paths:
- remote_config_staging.json
- remote_config_production.json

permissions:
contents: read
id-token: write

jobs:
check_remote_config:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- id: staging
if: github.ref_name == 'staging'
uses: ./.github/actions/remote-config
with:
project_id: XXXXXXX
workload_identity_provider: projects/XXXXXXX/locations/global/workloadIdentityPools/pool/providers/github
service_account_email: remote-config-updater@XXXXXXX.iam.gserviceaccount.com
remote_config_path: ${{ github.workspace }}/remote_config_staging.json
command: publish

- id: production
if: github.ref_name == 'main'
uses: ./.github/actions/remote-config
with:
project_id: XXXXXXX
workload_identity_provider: projects/XXXXXXX/locations/global/workloadIdentityPools/pool/providers/github
service_account_email: remote-config-updater@XXXXXXX.iam.gserviceaccount.com
remote_config_path: ${{ github.workspace }}/remote_config_production.json
command: publish

action.yaml

Defines the GitHub Actions used in check_remote_config.yaml and publish_remote_config.yaml. Processes differ based on the command specified.

name: "remote config"
description: execute remote config for firebase
inputs:
project_id:
description: GCP Project ID
required: true
workload_identity_provider:
description: workload identity provider
required: true
service_account_email:
description: service account email
required: true
remote_config_path:
description: remote config path
required: true
command:
description: command(check/publish)
required: true
default: "check"

outputs:
diff:
description: result(only for check command)
value: ${{ steps.diff.outputs.diff }}
is_valid:
description: result
value: ${{ steps.validate.outputs.is_valid }}
invalid-reason:
description: result(only for is_valid is false)
value: ${{ steps.validate.outputs.invalid-reason }}

runs:
using: "composite"
steps:
- uses: google-github-actions/auth@v2
with:
project_id: ${{ inputs.project_id }}
workload_identity_provider: ${{ inputs.workload_identity_provider }}
create_credentials_file: true

- id: validate
uses: lirlia/firebase-remote-config-actions@v0.0.1
with:
command: validate
template-file-path: ${{ inputs.remote_config_path }}
service-account-email: ${{ inputs.service_account_email }}

- name: check validate is ok
shell: bash
run: |
if [ "${{ steps.validate.outputs.is_valid }}" == "false" ]; then
echo "Remote config validation failed"
echo "${{ steps.validate.outputs.invalid-reason }}"
exit 1
fi

- id: diff
uses: lirlia/firebase-remote-config-actions@v0.0.1
if: ${{ inputs.command == 'check' }}
with:
command: diff
template-file-path: ${{ inputs.remote_config_path }}
service-account-email: ${{ inputs.service_account_email }}

- uses: lirlia/firebase-remote-config-actions@v0.0.1
if: ${{ inputs.command == 'publish' }}
with:
command: publish
template-file-path: ${{ inputs.remote_config_path }}
service-account-email: ${{ inputs.service_account_email }}

- name: Comment PR
uses: thollander/actions-comment-pull-request@v2
if: ${{ inputs.command == 'check' && steps.diff.outputs.diff != '' }}
with:
message: |
## Remote Config Diff
```diff
${{ steps.diff.outputs.diff }}
```
comment_tag: execution

Conclusion

Managing Firebase Remote Config json files with Git allows for syntax checks and diffs in advance, helping to prevent mistakes during release.

Additionally, automating releases with GitHub Actions reduces operational costs while enabling declarative configuration settings, making it extremely convenient.

Give it a try!

https://github.com/lirlia/firebase-remote-config-actions

Special thanks to our English team for their awesome support.

--

--