はじめに
こんにちは!山内です。 この前、Googleドライブ(Google Workspace)上のファイルをAmazon S3にアップロードしたいという要件がありました。 最初はGAS(Google Apps Script)で実現しようと思ったのですが、対象となるファイルのサイズがGASの制限値である50MBを超えていたので、結局LambdaでGoogle Drive APIを叩いて実現することにしました。 その時、ちょっとコツが必要だったのでその内容を記事にします!
※画面は2022/03/24 時点のものです。
※画面は2022/03/24 時点のものです。
環境準備
今回はGoogle Drive APIを使うので、まずはGoogle認証周りの環境を準備します。 また、Pythonおよびpipはインストールされている前提です。
Google認証情報の生成
※GCPプロジェクトの作成は他にたくさん記事がありますので割愛します。
- GCPのダッシュボードを開く
- サイドメニューの「APIとサービス」>「認証情報」をクリック
- 「+認証情報を作成」>「OAuthクライアントID」をクリック
- 次の項目を入力し、「作成」ボタンをクリック
- アプリケーションの種類: ウェブアプリケーション
- 名前 : 任意の名前
- 「JSONをダウンロード」をクリックしてから、「OK」をクリック
- Google Drive API公式のQuickStart(Python版)のStep1にある下記コマンドを実行
1pip install --upgrade google-api-python-client google-auth-httplib2 google-auth-oauthlib
- 同ページのStep2にあるPythonファイルを任意のディレクトリに作成(quickstart.py) この時、12行目を下記の通り修正
1SCOPES = ['https://www.googleapis.com/auth/drive.readonly']
- quickstart.pyと同じディレクトリに手順5でダウンロードしたJSONファイルを「credentials.json」という名前で格納
- 下記コマンドでquickstart.pyを実行
1python quickstart.py
- デフォルトのブラウザでGoogle認証画面が表示されるので、アクセスしたいGoogleドライブのファイルに対して、アクセス権限を持つGoogleアカウントでログイン
※警告が表示された場合は、「詳細を表示」>「[GCPのプロジェクト名](安全ではないページ)に移動」をクリック - アクセス権を確認し、「続行」をクリック
- 認証フローが完了したからウィンドウを閉じていいよ、と表示されるのでブラウザウィンドウを閉じる
- quickstart.pyと同じディレクトリにtoken.jsonが作成されていることを確認
※Pythonの実行ログにて、ログインしたGoogleアカウントのドライブ上のファイルのうち、10件が表示されることを確認できる
AWSリソースの生成
Amazon S3のバケットおよびAWS Lambdaの関数を作成します。 S3にアップロードするので、IAMポリシーの設定は忘れないように! quickstart.pyを利用する際にpip installしたモジュールをLambdaレイヤー化しておくと良いと思います。
※各AWSリソースの作成は他にたくさん記事がありますので割愛します。
※各AWSリソースの作成は他にたくさん記事がありますので割愛します。
S3にアップロードするコードの実装
準備が終わったのでいよいよ、Lambda関数のコードを実装します。 ここまで来ればあっという間です!
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 83 84 85 86 87 88 |
import json import io import boto3 from google.oauth2 import credentials from google.auth.transport.requests import Request from google.oauth2.credentials import Credentials from google_auth_oauthlib.flow import InstalledAppFlow from googleapiclient.discovery import build from googleapiclient.errors import HttpError from googleapiclient.http import MediaIoBaseDownload # Google認証関連 AUTHORIZED_USER_INFO = {"token": "トークン", "refresh_token": "リフレッシュトークン", "token_uri": "https://oauth2.googleapis.com/token", "client_id": "クライアントID", "client_secret": "クライアントシークレット", "scopes": ["https://www.googleapis.com/auth/drive.readonly"], "expiry": "有効期限"} AUTHORIZED_USER_SCOPES = ["https://www.googleapis.com/auth/drive.readonly"] SCOPED_CREDENTIALS = credentials.Credentials.from_authorized_user_info(AUTHORIZED_USER_INFO, AUTHORIZED_USER_SCOPES) # S3関連 UPLOAD_BUCKET_NAME = "S3バケット名" S3 = boto3.resource("s3") UPLOAD_BUCKET = S3.Bucket(UPLOAD_BUCKET_NAME) def build_google_api_client(): """Google APIクライアントをビルドします。 Google APIクライアントをビルドします。 トークンの有効期限が切れていた場合は自動でリフレッシュします。 Args: なし Returns: Object: Google APIクライアント """ # 認証情報のリフレッシュ if SCOPED_CREDENTIALS and SCOPED_CREDENTIALS.expired and SCOPED_CREDENTIALS.refresh_token: SCOPED_CREDENTIALS.refresh(Request()) print("Refreshed credentials.") # Google APIクライアントのビルド client = build("drive", "v3", credentials=SCOPED_CREDENTIALS) return client def lambda_handler(event, context): # Google APIクライアントのビルド client = build_google_api_client() # Google Drive APIの呼び出し results = client.files().list( pageSize=1, fields="nextPageToken, files(id, name)" ).execute() items = results.get("files", []) if not items: print("No files found.") else: print("Files:") for item in items: print(u"{0} ({1})".format(item["name"], item["id"])) # 1つ目のファイル情報の取得 request = client.files().get_media(fileId=items[0]["id"]) # ファイル情報の読み込み fh = io.BytesIO() downloader = MediaIoBaseDownload(fh, request) done = False while done is False: status, done = downloader.next_chunk() print ("Download %d%%." % int(status.progress() * 100)) # ファイルのアップロード UPLOAD_BUCKET.put_object( Key=item["name"], Body=fh.getvalue() ) print("Upload completed.") return { "statusCode": 200, "body": json.dumps("Completed!") } |
動作確認
楽しい動作確認の時間です! サンプル用のテキストファイルをGoogleドライブにアップロードしておきました。
Lambdaのテスト実行機能で実行してみます。 実行前のS3は空です。
実行後、ちゃんとファイルがアップロードされてますね!
ダウンロードして中身が壊れていないことも確認できました。
元々の課題だった、50MB以上の大きなファイルもこの通りアップロードできることが確認できます。
Lambdaのテスト実行機能で実行してみます。 実行前のS3は空です。
実行後、ちゃんとファイルがアップロードされてますね!
ダウンロードして中身が壊れていないことも確認できました。
元々の課題だった、50MB以上の大きなファイルもこの通りアップロードできることが確認できます。
注意点
やってていくつか注意が必要だと思ったことがあるのでまとめます。
- 実現したい内容に適したスコープを設定する必要がある (参照:GoogleAPIのOAuth2.0スコープ)
- Googleドライブ上へのファイルへのアクセス権限はGCPプロジェクトのオーナーが持っている必要がある
- ファイルのサイズが大きい場合はLambdaのメモリを増やす必要がある
特に、1つ目についてはややこしかったので補足しておきます。 例えば、今回利用したスコープでは、「Google ドライブのすべてのファイルを表示およびダウンロード」のみ可能ですが、もし「Google ドライブ内のファイルのメタデータの表示と管理」したい場合は別のスコープを設定する必要があります。その場合は、quickstart.pyとLambdaのコード上にあるSCOPEが「https://www.googleapis.com/auth/drive.metadata」に置き換わります。
おわりに
Google認証周りって初見だと結構複雑ですよね。 こういったちょっとしたコードからスタートすると取っつきやすいですし、実際に触ることで理解が深められると思うので、皆さまも是非トライしてみてください!
執筆者プロフィール
- 社内の開発プロジェクトの技術支援や、Javaにおける社内標準フレームワークの開発を担当しています。Spring BootとTDDに手を出しつつ、LINE Botとかもいじったりしています。最近はマイクロサービスを勉強しつつ、クラウドアプリケーションを開発できるエンジニアの育成にも力を入れてます!