G-gen の杉村です。 Cloud Functions で動作する Python プログラムから Google Calendar API を呼び出す方法をご紹介します。
- 検証内容
- Google Calendar API 有効化
- サービスアカウント作成・設定
- ソースコードの解説
- Python 環境の準備
- Cloud Functions のデプロイ
- 動作確認
- ローカル環境でのテスト
検証内容
プログラムの内容
Cloud Functions で動作する Python プログラムから Google Calendar API を呼び出す際の認証について検証しました。
今回は単純化のため、以下のようなプログラムとしています。
- Google Calendar API をコールして日本の祝日一覧を取得
- 取得した祝日一覧を BigQuery テーブルに INSERT
実際のユースケースでは Google Calendar から従業員の予定を取得して BigQuery に投入し、分析するなどの用途が考えられます。
Google API への認証
今回検証したかった内容は Google Calendar を始めとする、 Google API への認証です。
Google Calendar や Gmail は Google Cloud 製品ではなく Google 製品です。そのため Cloud IAM を使った認証・認可がサポートされていません。
API キーによる認証、OAuth 2.0 による認証、サービスアカウントによる認証がサポートされています。
Cloud Functions など Google Cloud 上の実行環境で動作するプログラムではサービスアカウントを用いた認証が最もセキュア・低工数であると考えられるため、この方法を検証します。
検証の流れ
検証の全体の流れは以下のとおりです。
準備
- Google Cloud プロジェクトにおいて Google Calendar API を有効化
- サービスアカウントを作成
- サービスアカウントに
BigQuery データ編集者
ログ書き込み
ロールを付与 (※) - Python プログラムを Cloud Functions (2nd gen) にデプロイ (Functions にはサービスアカウントをアタッチ)
(※) BigQuery データ編集者
は BigQuery テーブルへのデータ書き込みため、 ログ書き込み
は Cloud Logging へのログ出力のため
処理の流れ
google-auth
ライブラリでサービスアカウントの認証情報を取得google-api-python-client
ライブラリでサービスインスタンス生成- Google Calendar API をコールして日本の祝日一覧を取得
google-cloud-bigquery
ライブラリで祝日一覧を BigQuery テーブルに INSERT
注目すべきは、サービスアカウントを作成すれば、IAM 権限を付与しなくても同じプロジェクトで有効化された Google Calendar API にアクセスできるという点です。
ここからは、各手順を説明していきます。
Google Calendar API 有効化
まずはじめに Google Cloud コンソール
> API とサービス
> 有効な API とサービス
の画面 (リンク) から Google Calendar API を有効化します。
同画面では多数の API のリストが表示されますが、テキストボックスでフィルタをかけることができます。 calendar
と入力するとサジェストされるはずです。
もしくは以下のコマンドでも API を有効化できます。
gcloud services enable calendar-json.googleapis.com
サービスアカウント作成・設定
サービスアカウント作成
Google Cloud コンソール
> IAM と管理
> サービス アカウント
の画面 (リンク) からサービスアカウントを作成します。自分のプロジェクトが正しく選択されていることを確認してください。
サービスアカウントはプロジェクトに所属するリソースです。 Google Calendar API を有効化したのと同じプロジェクトに、サービスアカウントを作成する必要があります。
今回は表示名を get-holidays
として作成します。
サービスアカウント ID は get-holidays@${PROJECT}.iam.gserviceaccount.com
となります (${PROJECT} はプロジェクト ID です) 。
サービスアカウントへ IAM 権限付与
次にこのサービスアカウントに IAM 権限を付与します。
Google Calendar API をコールするには IAM 権限は必要ありませんが、今回は BigQuery にデータを書き込んだり、Cloud Logging にログ出力するために IAM 権限が必要です。
今回はプロジェクトレベルでの権限付与とします。
Google Cloud コンソール
> IAM と管理
> IAM
の画面 (リンク) に遷移します。繰り返しになりますが自分のプロジェクトが正しく選択されていることを確認してください。
先程作成したサービスアカウントに BigQuery データ編集者
ログ書き込み
の IAM ロールを付与します。
コマンドライン
前述の「サービスアカウント作成」と「IAM 権限付与」の作業は以下のコマンドでも実施できます。
PROJECT="<プロジェクト ID に置き換えてください>" ACCOUNT_NAME="get-holidays" gcloud iam service-accounts create ${ACCOUNT_NAME} --display-name="${ACCOUNT_NAME}" gcloud projects add-iam-policy-binding ${PROJECT} --member="serviceAccount:${ACCOUNT_NAME}@${PROJECT}.iam.gserviceaccount.com" --role="roles/logging.logWriter" gcloud projects add-iam-policy-binding ${PROJECT} --member="serviceAccount:${ACCOUNT_NAME}@${PROJECT}.iam.gserviceaccount.com" --role="roles/bigquery.dataEditor"
ソースコードの解説
ソースコード
以下のソースコードを使います。
今回は Cloud Functions の HTTP 関数 を想定して用意しました。
#!/usr/bin/env python import datetime import logging from flask import abort import google.auth from googleapiclient.discovery import build import google.cloud.bigquery import google.cloud.logging # ロギング設定 logging.basicConfig( format = "[%(asctime)s][%(levelname)s] %(message)s" ) logger = logging.getLogger() # Cloud Logging への連携 logging_client = google.cloud.logging.Client() logging_client.setup_logging() logger.setLevel(logging.INFO) # BigQuery Data Transfer Service のクライアント生成 client = google.cloud.bigquery.Client() def get_holidays(dataset, table, year): """ 特定年の祝日一覧の取得とテーブルへの書き込み """ logger.info(f"Getting holidays for year: {year}") # 実行環境のデフォルトクレデンシャル = Cloud Functions にアタッチされているサービスアカウントを取得 credentials, project = google.auth.default() # サービスを生成 service = build('calendar', 'v3', credentials=credentials, cache_discovery=False) # Google Calendar API 呼び出し result = service.events().list( calendarId='japanese__ja@holiday.calendar.google.com', timeMin=str(year) + '-01-01T00:00:00.000000Z', timeMax=str(year) + '-12-31T23:59:59.999999Z', singleEvents=True, orderBy='startTime' ).execute() holiday_info = result.get('items', []) # INSERT するリスト作成 holidays = [] for holiday in holiday_info: name = holiday['summary'] date = holiday['start']['date'] holidays.append([name, date]) # スキーマ定義 schema = [ google.cloud.bigquery.SchemaField("name", "STRING", "REQUIRED", "祝日の名称"), google.cloud.bigquery.SchemaField("date", "DATE", "REQUIRED", "祝日の日付") ] # テーブルの定義 table_id = f"{project}.{dataset}.{table}" table = google.cloud.bigquery.Table(table_id, schema=schema) # テーブルにINSERT client.insert_rows(table=table, rows=holidays) return None def main(request): # リクエストからパラメータを取得 request_dict = request.get_json() logger.info(request_dict) # パラメータチェック if ('dataset' in request_dict): dataset = request_dict['dataset'] else: error_message = "Parameter dataset is missing." logger.error(error_message) return abort(400) if ('table' in request_dict): table = request_dict['table'] else: error_message = "Parameter table is missing." logger.error(error_message) return abort(400) # メイン処理 try: # 現在の西暦を取得 this_year = datetime.date.today().year # Google Calendar から祝日を取得して BigQuery に書き込み get_holidays(dataset, table, this_year) except Exception as e: logger.error(e) return abort(500) return "OK"
このソースコードの認証に関わる部分をご説明します。
パッケージのインポート
#!/usr/bin/env python import datetime import logging from flask import abort import google.auth from googleapiclient.discovery import build import google.cloud.bigquery import google.cloud.logging
必要なパッケージのインポートを行います。
Cloud Functions では必要なパッケージを requirements.txt に記載してデプロイパッケージに含めることで自動的に環境がビルドされます。 requirements.txt の作成を含めた Python の環境構築手順は後述します。
import google.auth
と from googleapiclient.discovery import build
が認証に関わるライブラリです。
認証情報取得
get_holidays 関数で Google Calendar API を呼び出しています。
# 実行環境のデフォルトクレデンシャル = Cloud Functions にアタッチされているサービスアカウントを取得
credentials, project = google.auth.default()
google-auth は Google API の認証のためのライブラリです。
default()
関数により実行環境のデフォルトクレデンシャル = 今回は Cloud Functions にアタッチされているサービスアカウントの認証情報を取得します。この書き方では credentials 変数にはライブラリ独自の Credentials 型で認証情報が代入され project 変数には str 型で実行環境のデフォルトプロジェクトのプロジェクト ID が代入されます (参考)。
なおかつては oauth2client
と言う名称のライブラリが存在しましたがこちらは deprecated (廃止予定・非推奨) となり現在は google-auth
が推奨です。
サービスオブジェクト作成
Google API Python Client の build()
によりサービスオブジェクトを生成します。Google の API を呼ぶためのインターフェイスを生成するイメージです。
# サービスを生成 service = build('calendar', 'v3', credentials=credentials, cache_discovery=False)
cache_discovery=False
は oauth2client
のバージョン 4 以前でサポートされていた機能を無効化するための記述です。これが無いと、以下のようなログメッセージが出力されます (実動作には影響ありません) 。
file_cache is only supported with oauth2client<4.0.0
API 呼び出し
# Google Calendar API 呼び出し result = service.events().list( calendarId='japanese__ja@holiday.calendar.google.com', timeMin=str(year) + '-01-01T00:00:00.000000Z', timeMax=str(year) + '-12-31T23:59:59.999999Z', singleEvents=True, orderBy='startTime' ).execute()
execute()
で実際に API を呼び出しています。 japanese__ja@holiday.calendar.google.com
は日本の祝日を保持しているカレンダーリソースで、 Google Calendar がデフォルトで持っています。
BigQuery への書き込み
BigQuery Python Client の insert_rows()
でテーブルにデータを INSERT します。
# INSERT するリスト作成 holidays = [] for holiday in holiday_info: name = holiday['summary'] date = holiday['start']['date'] holidays.append([name, date])
入力する値は [ ["カラムAの値1", "カラムBの値1"], ["カラムAの値2", "カラムBの値2"], ...]
のように二次元配列で渡すため、このように整形しています。
以下で実際に API を実行し、テーブルにデータを挿入します。
client.insert_rows(table=table, rows=holidays)
Python 環境の準備
ソースコードの解説はここまでです。
ここからは、ローカルで Python 環境を準備する方法を説明します。
必要に応じ以下のように venv 環境を作成し activate します。
python -m venv venv source venv/bin/activate
今回のプログラムで使うパッケージをインストールし requirements.txt を作成します。
pip install google-auth google-api-python-client google-cloud-logging google-cloud-bigquery
pip freeze > requirements.txt
なおソースコード中で flask のモジュールを import していますが Cloud Functions の python 実行環境には Flask パッケージが予め含まれているため、明示的に pip install したり requirements.txt に含める必要はありません。
Cloud Functions のデプロイ
以下のコマンドで Cloud Functions をデプロイします。
ソースコードと requirements.txt が存在するディレクトリでコマンド実行してください。またデプロイのパラメータは適宜設定ください。
PROJECT="<プロジェクト ID に置き換え>" ACCOUNT_NAME="get-holidays" FUNCTION="get-holidays" gcloud functions deploy ${FUNCTION} \ --quiet --gen2 \ --project=${PROJECT} \ --region=asia-northeast1 \ --runtime=python39 \ --service-account=${ACCOUNT_NAME}@${PROJECT}.iam.gserviceaccount.com \ --entry-point main \ --trigger-http
動作確認
デプロイが成功すると、標準出力にエンドポイント URL が表示されます。Google Cloud コンソールの Cloud Functions 画面から確認することもできますし、以下のコマンドで取得することもできます。
FUNCTION="get-holidays" URL=`gcloud functions describe ${FUNCTION} --region=asia-northeast1 --gen2 --format="value(serviceConfig.uri)"` echo ${URL}
以下の curl コマンドで function の動作確認をします。 INSERT 先のデータセットとテーブルは予め作成しておき、コマンド内の文字列を置き換えてください。
curl -X POST \ -H "Authorization: bearer $(gcloud auth print-identity-token)" \ -H "Content-Type: application/json" \ -d '{"dataset": "<データセット名に置き換え>", "table": "<テーブル名に置き換え>"}' \ ${URL}
なお当 function は呼び出し時に IAM 認証を必要とする設定になっていますので Authorization
ヘッダを付与しています。 $(gcloud auth print-identity-token)
によりローカル環境に設定されている Google アカウント権限でトークンを取得しています。
実行できたら、以下のように BigQuery テーブルにデータが INSERT されたことを確認します。

ローカル環境でのテスト
ローカル環境での Functions のテスト
Cloud Functions のデプロイには 2 分程度の時間がかかります。コード修正後に動作確認したいとき、いちいちデプロイしていたのでは時間がかかりすぎてしまいます。
functions-framework
というライブラリを使うことで、ローカル PC 環境で Cloud Functions を動作させ、テストすることができます。
ここからは、その手順をご紹介します。
サービスアカウントのキーのダウンロード
まずローカルの仮想的な Functions から実際に Google Cloud API をコールする際の認証のため、サービスアカウントのキーをダウンロードします。
Google Cloud コンソール
> IAM と管理
> サービス アカウント
の画面 (リンク) から今回作成した get-holidays
サービスアカウントを選択し、詳細画面へ遷移します。
キー
というタブから「鍵を追加」を押下して「新しい鍵を作成」を選択します。
JSON 形式で鍵を「作成」し、ダウンロードします。
今回はローカルのソースコードと同じディレクトリに test-cred.json
として保存します。
このファイルが漏洩すると、サービスアカウントが持つ権限で好きに Google Cloud 環境を操作できてしまうことになるので、十分お気をつけください。
functions-framework インストール
functions-framework
を使うことでローカル環境で HTTP 関数をテストすることができます。
pip でパッケージをインストールします。手順は以下を参考にしてください。
仮想 Cloud Functions 実行
ソースコードと同じディレクトリで以下を実行してください。
GOOGLE_APPLICATION_CREDENTIALS="./test-cred.json" functions-framework --target main --debug
これでダウンロードしたサービスアカウントキーを実行環境のデフォルト認証情報として設定したうえで仮想的な Cloud Functions を起動できます。仮想的な Functions は 8080/tcp ポートで待ち受けします。
なお本来、ローカルの仮想 function から Google Cloud API を呼ぶだけであれば Google アカウントの権限で一度 gcloud auth application-default login
(参考) を実行すればキーのダウンロードや環境変数での指定は不要です。
しかし今回は Google Calendar API の呼び出しがあり、これが上記コマンドによる認証情報の設定 (Google アカウントによるアプリケーションデフォルトクレデンシャル設定) に対応していないため、本来はできれば避けるべきですがキーのダウンロード・指定を行いました。
リクエスト
以下の curl リクエストで実際に動作させることができます。データセット名とテーブル名は実際のものに置き換えてください。
curl localhost:8080 -X POST -H "Content-Type: application/json" -d '{"dataset": "testdataset", "table": "holidays"}'
杉村 勇馬 (記事一覧)
執行役員 CTO / クラウドソリューション部 部長
元警察官という経歴を持つ現 IT エンジニア。クラウド管理・運用やネットワークに知見。AWS 12資格、Google Cloud認定資格11資格。X (旧 Twitter) では Google Cloud や AWS のアップデート情報をつぶやいています。
Follow @y_sugi_it