Firebase Remote Config の更新を自動化する Python スクリプトを書いてみた
IT技術
はじめに
こんにちは!福岡でモバイルアプリエンジニアをやっている こー です!
2021.11.05YOUは何しにライトコードへ?〜こーくん編〜プロジェクト内で安心感を与えられる存在になりたい!今回は、弊社のエンジニアである高さんにフィーチャー!技術力に定評のあ...
今回は、モバイルアプリエンジニアであれば馴染みの深い 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# -*- coding: utf-8 -*-
2#!/usr/bin/env python
3
4import json
5import os
6import sys
7from oauth2client.service_account import ServiceAccountCredentials
8import requests
9
10# 引数からバージョン番号を取得
11version = sys.argv[1]
12
13print(f'\n-------- update invalid version of remote config: to {version} -------\n')
14
15# ① credentials.json の生成
16credentials_file_name = "credentials.json"
17credentials = {
18 "type": "service_account",
19 "project_id": os.getenv('FIREBASE_PROJECT_NAME'),
20 "private_key_id": os.getenv('FIREBASE_PRIVATE_KEY_ID'),
21 "private_key": os.getenv('FIREBASE_PRIVATE_KEY').replace('\\n', '\n'),
22 "client_email": os.getenv('FIREBASE_CLIENT_EMAIL'),
23 "client_id": os.getenv('FIREBASE_CLIENT_ID'),
24 "auth_uri": "https://accounts.google.com/o/oauth2/auth",
25 "token_uri": "https://oauth2.googleapis.com/token",
26 "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
27 "client_x509_cert_url": os.getenv('FIREBASE_CLIENT_X509_CERT_URL')
28}
29with open(credentials_file_name, mode='wt', encoding='utf-8') as file:
30 json.dump(credentials, file, ensure_ascii=False, indent=2)
31
32
33# エンドポイントの設定
34project_name = os.getenv('FIREBASE_PROJECT_NAME')
35endpoint = f'https://firebaseremoteconfig.googleapis.com/v1/projects/{project_name}/remoteConfig'
36
37# アクセストークンの取得
38scope = ['https://www.googleapis.com/auth/firebase.remoteconfig']
39credentials = ServiceAccountCredentials.from_json_keyfile_name(
40 credentials_file_name,
41 scope
42)
43access_token = credentials.get_access_token().access_token
44if access_token is None:
45 raise Exception("could not get access token")
46
47# 現在のConfigデータ取得
48headers = {
49 'Authorization': f"Bearer {access_token}",
50}
51response_get = requests.get(
52 endpoint,
53 headers=headers,
54)
55config_json = response_get.json()
56
57# RemoteConfig更新にはetagが必須となるのでレスポンスヘッダから取得しておく
58etag = response_get.headers['etag']
59if etag is None:
60 raise Exception("could not get etag")
61
62# ② Remote Configの強制アップデート対象バージョン情報を更新する
63config_json['parameters']['invalid_version']['defaultValue']['value'] = version
64headers = {
65 'Authorization': f"Bearer {access_token}",
66 'Content-Type': 'application/json; UTF8',
67 'If-Match': etag,
68}
69response_put = requests.put(
70 endpoint,
71 headers=headers,
72 json=config_json,
73)
74if response_put.status_code == 200:
75 print("update succeeded: Remote Config")
76else:
77 raise Exception("update failed: Remote Config")
78
79# ③ 処理が終了したら、credentials.json を削除
80os.remove(credentials_file_name)
81
82print(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# (15行目〜)
2# ① credentials.json の生成
3credentials_file_name = "credentials.json"
4credentials = {
5 "type": "service_account",
6 "project_id": os.getenv('FIREBASE_PROJECT_NAME'),
7 "private_key_id": os.getenv('FIREBASE_PRIVATE_KEY_ID'),
8 "private_key": os.getenv('FIREBASE_PRIVATE_KEY').replace('\\n', '\n'),
9 "client_email": os.getenv('FIREBASE_CLIENT_EMAIL'),
10 "client_id": os.getenv('FIREBASE_CLIENT_ID'),
11 "auth_uri": "https://accounts.google.com/o/oauth2/auth",
12 "token_uri": "https://oauth2.googleapis.com/token",
13 "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
14 "client_x509_cert_url": os.getenv('FIREBASE_CLIENT_X509_CERT_URL')
15}
16with open(credentials_file_name, mode='wt', encoding='utf-8') as file:
17 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# (62行目〜)
2# ② Remote Configの強制アップデート対象バージョン情報を更新する
3config_json['parameters']['invalid_version']['defaultValue']['value'] = version
4headers = {
5 'Authorization': f"Bearer {access_token}",
6 'Content-Type': 'application/json; UTF8',
7 'If-Match': etag,
8}
9response_put = requests.put(
10 endpoint,
11 headers=headers,
12 json=config_json,
13)
14if response_put.status_code == 200:
15 print("update succeeded: Remote Config")
16else:
17 raise Exception("update failed: Remote Config")
取得した現在の Remote Config の設定情報の json 内容を書き換えて PUT リクエストを送信します。
Remote Config の設定情報の json 構成は以下のようになっています。
1{
2 "parameters": {
3 "invalid_version": {
4 "defaultValue": {
5 "value": "x.x.x"
6 },
7 "description": "強制アップデート対象バージョン"
8 }
9 }
10}
今回は環境による値の条件分岐を行なっていないので、 defaultValue のみ更新する比較的シンプルな構成になっています。
分岐条件の conditions や、分岐した値の conditionalValue なども jsonから設定できるので、詳しくは 公式ドキュメント を参照してください。
PUT リクエストを送信する際は、GET リクエストの際にレスポンスヘッダに付与されている etag を付与する必要があるので気をつけましょう!
③ 処理が終了したら、credentials.json を削除
1# (79行目〜)
2# ③ 処理が終了したら、credentials.jsonを削除
3os.remove(credentials_file_name)
このままスクリプトを終了すると、作成した credentials.json が作業マシン上に残存してしまうので、漏洩を防ぐためにも更新が終了したら credentials.json を削除する処理を忘れずに書くようにします。
これで、 python update_invalid_version_remote_config.py 2.0.0 のようにコマンド実行することで Remote Config を更新できるスクリプトができました!
この引数に渡すバージョンの取得や、コマンド実行を CI のワークフローに組み込むことで自動化が実現できるようになります!
さいごに
色々意気揚々と作り Bitrise に組み込もうとしたこのスクリプトですが、
結局プロジェクトでの運用が大きく変わったせいで、このスクリプトが日の目を見ることはありませんでした。
供養のため、ここに記事を残した次第です(号泣)
「使われることじゃなく、作ったことに意味がある!」
めげることなく思いついたら色々作っていこうと思います!!
参考になったり、使えそうだなと思った方はどんどん使ってあげてください!
きっとこのスクリプトも喜びます(笑)
最後までご覧いただきありがとうございました!
ライトコードでは、エンジニアを積極採用中!
ライトコードでは、エンジニアを積極採用しています!社長と一杯しながらお話しする機会もご用意しております。そのほかカジュアル面談等もございますので、くわしくは採用情報をご確認ください。
採用情報へ
「クレヨンしんちゃんは人生のマニュアル」が口癖なモバイルアプリとバックエンドやってる人