
Firebase Remote Config の更新を自動化する Python スクリプトを書いてみた
2022.10.24
はじめに
こんにちは!福岡でモバイルアプリエンジニアをやっている こー です!
今回は、モバイルアプリエンジニアであれば馴染みの深い Firebase Remote Config に関する記事でございます。
スクリプトのお話になるので、モバイルアプリ開発の直接的な技術とは少し距離がありますが、ご覧いただけると幸いです!
Firebase Remote Config って何?
Firebase Remote Config (以下 Remote Config)とは、Firebase が提供しているモバイルアプリ向けクラウドサービスの1つで、コンソール上で Key-Value 型の値を設定することで、それをリアルタイムにアプリに渡すことができるサービスです。
アプリの外観や動作の変更を素直に行う場合、通常はアプリのコードを修正しリリースやアップデートを行いますが、Remote Config を使うと、コンソール上の値を更新するだけでアプリに渡せるので、リリースやアップデートなしにそれらを実現することができます。
「そんなのあるんだ!?」って興味を持った方は、ぜひ 公式ドキュメント を読んでまず使ってみてください!
Remote Config の導入や基礎的な内容は、今回の主題から外れますので割愛します。
ご容赦くださいませ m(_ _)m
何がやりたかったの?
僕が担当していたプロジェクトでは、アプリの強制アップデート機能を Remote Config で実現していました。
- Remote Config に強制アップデート対象バージョンを登録
- アプリ起動時にバージョンが強制アップデートバージョン以下だった場合、インストールページへ誘導
という仕組みですね。
ですので、アプリをリリースしたら強制アップデート対象バージョンを更新する必要があるのですが、
- Remote Config のコンソールページを開く
- 強制アップデート対象バージョンを設定する
- 公開ボタンをポチる
という、アナログな方法を取っていました。
ですが、この方法には以下の問題点が浮かびます。
- 更新を失念するかもしれない
- バージョン情報の打ち間違いが起こるかもしれない
- 面倒
- とても面倒
- めっちゃ面倒
このプロジェクトでは CI(継続的インテグレーション)に Bitrise を利用していたので、
「スクリプト書いてリリースのワークフローで実行できたら自動化できるやんけ!」
と思い、 Remote Config を更新を行うスクリプトを書くことにしました。
Python を選んだのは、個人的に一番楽に書けるからです(笑)
どんなコード書いた?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | # -*- coding: utf-8 -*- #!/usr/bin/env python import json import os import sys from oauth2client.service_account import ServiceAccountCredentials import requests # 引数からバージョン番号を取得 version = sys.argv[1] print(f'\n-------- update invalid version of remote config: to {version} -------\n') # ① credentials.json の生成 credentials_file_name = "credentials.json" credentials = { "type": "service_account", "project_id": os.getenv('FIREBASE_PROJECT_NAME'), "private_key_id": os.getenv('FIREBASE_PRIVATE_KEY_ID'), "private_key": os.getenv('FIREBASE_PRIVATE_KEY').replace('\\n', '\n'), "client_email": os.getenv('FIREBASE_CLIENT_EMAIL'), "client_id": os.getenv('FIREBASE_CLIENT_ID'), "auth_uri": "https://accounts.google.com/o/oauth2/auth", "token_uri": "https://oauth2.googleapis.com/token", "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", "client_x509_cert_url": os.getenv('FIREBASE_CLIENT_X509_CERT_URL') } with open(credentials_file_name, mode='wt', encoding='utf-8') as file: json.dump(credentials, file, ensure_ascii=False, indent=2) # エンドポイントの設定 project_name = os.getenv('FIREBASE_PROJECT_NAME') endpoint = f'https://firebaseremoteconfig.googleapis.com/v1/projects/{project_name}/remoteConfig' # アクセストークンの取得 scope = ['https://www.googleapis.com/auth/firebase.remoteconfig'] credentials = ServiceAccountCredentials.from_json_keyfile_name( credentials_file_name, scope ) access_token = credentials.get_access_token().access_token if access_token is None: raise Exception("could not get access token") # 現在のConfigデータ取得 headers = { 'Authorization': f"Bearer {access_token}", } response_get = requests.get( endpoint, headers=headers, ) config_json = response_get.json() # RemoteConfig更新にはetagが必須となるのでレスポンスヘッダから取得しておく etag = response_get.headers['etag'] if etag is None: raise Exception("could not get etag") # ② Remote Configの強制アップデート対象バージョン情報を更新する config_json['parameters']['invalid_version']['defaultValue']['value'] = version headers = { 'Authorization': f"Bearer {access_token}", 'Content-Type': 'application/json; UTF8', 'If-Match': etag, } response_put = requests.put( endpoint, headers=headers, json=config_json, ) if response_put.status_code == 200: print("update succeeded: Remote Config") else: raise Exception("update failed: Remote Config") # ③ 処理が終了したら、credentials.json を削除 os.remove(credentials_file_name) print(f'\n-------- finished updating invalid version of remote config: to {version} -------\n') |
(Python は 3.7.2 を利用)
まず、このスクリプトで行っていることを端的にまとめると、
- プロジェクトの ID やプライベートアクセスキーなどの情報をまとめた credentials.json を作成
- credentials.json を利用して、 Firebase APIへのアクセストークンを取得。
- Remote Config へのエンドポイントを指定。
- GET リクエストで現在の Remote Config の設定情報を取得し、そこから更新の際に必要な etag を保持する。
- 更新データの json と etag を付与して、PUT リクエストを送信して更新する。
となります。
コード上で番号が振ってあるところをポイントとして、ピックアップして説明していきます。
① credentials.json の生成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | # (15行目〜) # ① credentials.json の生成 credentials_file_name = "credentials.json" credentials = { "type": "service_account", "project_id": os.getenv('FIREBASE_PROJECT_NAME'), "private_key_id": os.getenv('FIREBASE_PRIVATE_KEY_ID'), "private_key": os.getenv('FIREBASE_PRIVATE_KEY').replace('\\n', '\n'), "client_email": os.getenv('FIREBASE_CLIENT_EMAIL'), "client_id": os.getenv('FIREBASE_CLIENT_ID'), "auth_uri": "https://accounts.google.com/o/oauth2/auth", "token_uri": "https://oauth2.googleapis.com/token", "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", "client_x509_cert_url": os.getenv('FIREBASE_CLIENT_X509_CERT_URL') } with open(credentials_file_name, mode='wt', encoding='utf-8') as file: json.dump(credentials, file, ensure_ascii=False, indent=2) |
この project_id や private_key_id などの項目は、Firebase コンソールの [設定] メニュー -> [サービスアカウント] の [Firebase Admin SDK] 内にある [新しい秘密鍵の生成] でダウンロードできるキーファイルに記述されていますので、生成しダウンロードします。
ダウンロードの際に出てくるポップアップにもありますが、このキーファイルに記載されている情報は決して公開してはいけません。
このキーファイルに記載されている情報は決して公開してはいけません。
推敲のし忘れではありません、大事なことなので2回言いました。
この情報を使えば Firebase API を通じて、プロジェクト上のデータを読み書きできます。
それは言い換えると、 このキーファイルの情報さえあれば誰でもプロジェクト内のデータを触れてしまう、ということです。
プロジェクトの秘匿情報を外部に公開するのと同義なので、重大なインシデントに繋がります。
これを回避するために、以下の内容を必ず意識しましょう。
- コードに秘匿情報をベタ書きしないこと
- 秘匿情報は CI 上の Secrets などに配置し、環境変数を通じて取得するように書くこと
自信がないのであれば、知見のある方にレビューを通してもらったり、一緒に作業するようにしましょう!
② Remote Config の強制アップデート対象バージョンの更新
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | # (62行目〜) # ② Remote Configの強制アップデート対象バージョン情報を更新する config_json['parameters']['invalid_version']['defaultValue']['value'] = version headers = { 'Authorization': f"Bearer {access_token}", 'Content-Type': 'application/json; UTF8', 'If-Match': etag, } response_put = requests.put( endpoint, headers=headers, json=config_json, ) if response_put.status_code == 200: print("update succeeded: Remote Config") else: raise Exception("update failed: Remote Config") |
取得した現在の Remote Config の設定情報の json 内容を書き換えて PUT リクエストを送信します。
Remote Config の設定情報の json 構成は以下のようになっています。
1 2 3 4 5 6 7 8 9 10 | { "parameters": { "invalid_version": { "defaultValue": { "value": "x.x.x" }, "description": "強制アップデート対象バージョン" } } } |
今回は環境による値の条件分岐を行なっていないので、 defaultValue のみ更新する比較的シンプルな構成になっています。
分岐条件の conditions や、分岐した値の conditionalValue なども jsonから設定できるので、詳しくは 公式ドキュメント を参照してください。
PUT リクエストを送信する際は、GET リクエストの際にレスポンスヘッダに付与されている etag を付与する必要があるので気をつけましょう!
③ 処理が終了したら、credentials.json を削除
1 2 3 | # (79行目〜) # ③ 処理が終了したら、credentials.jsonを削除 os.remove(credentials_file_name) |
このままスクリプトを終了すると、作成した credentials.json が作業マシン上に残存してしまうので、漏洩を防ぐためにも更新が終了したら credentials.json を削除する処理を忘れずに書くようにします。
これで、 python update_invalid_version_remote_config.py 2.0.0 のようにコマンド実行することで Remote Config を更新できるスクリプトができました!
この引数に渡すバージョンの取得や、コマンド実行を CI のワークフローに組み込むことで自動化が実現できるようになります!
さいごに
色々意気揚々と作り Bitrise に組み込もうとしたこのスクリプトですが、
結局プロジェクトでの運用が大きく変わったせいで、このスクリプトが日の目を見ることはありませんでした。
供養のため、ここに記事を残した次第です(号泣)
「使われることじゃなく、作ったことに意味がある!」
めげることなく思いついたら色々作っていこうと思います!!
参考になったり、使えそうだなと思った方はどんどん使ってあげてください!
きっとこのスクリプトも喜びます(笑)
最後までご覧いただきありがとうございました!
書いた人はこんな人

- 「クレヨンしんちゃんは人生のマニュアル」が口癖なモバイルアプリとバックエンドやってる人