Blog Entry  (Oct. 22, 2015, 6:13 a.m.)

Tilo Mitra's avatar

Python(boto)からサービスアカウントとキーファイル(p12)を使ってGoogle Cloud Storageにアクセスする

botoからGoogle Cloud Storageにアクセスする場合、s3との互換運用アクセスを有効にすることでアクセスキーとシークレットキーでアクセスすることが可能です。
http://qiita.com/itkr/items/d990e87a2540332ee0e5

ただしこの方法にはひとつ問題があります。互換運用アクセスで発行できるアクセスキーはGoogleユーザーアカウントにリンクされるため、複数人で行うプロジェクトでは不便です。そこでサービスアカウントとキーファイル(p12)でアクセスするという選択肢が出てきます。

依存ライブラリのインストール

ここではgcs-oauth2-boto-pluginというライブラリを使用します。

pip install gcs-oauth2-boto-plugin

設定ファイルの準備

サービスアカウントとキーファイルのパスを記述する設定ファイルを準備します。

ファイルの場所

デフォルトでは ~/.boto を見ますが、環境変数で以下のどちらかを用意すると任意のパスを指定できます。ちなみにこれはgsutilと同じです。

  • BOTO_CONFIG (単一ファイルを指定する)
  • BOTO_PATH ( : 区切りで複数ファイルを指定する)

pythonで環境変数を書く場合は下のように書きます。

import os
os.environ['BOTO_CONFIG'] = '/path.to/boto_config'

必要な記述

いろいろ設定を書くことが出来ますが、今回重要なのは次の設定です

[Credentials]
gs_service_key_file = /path.to/sample-KEYFILE.p12
gs_service_client_id = sample-service-account@developer.gserviceaccount.com

実装

基本コード

import boto

bucket_name = 'bucket_name'
uri = boto.storage_uri(bucket_name, 'gs')
print uri.get_bucket()

基本的にはgcs-oauth2-boto-pluginをインストールしていればこれだけです。

解説

なぜこれだけで良いのか

botoはAuthConnectionを貼る際にPluginを導入できる仕組みを用意しています。詳細に書くとbotoの実装の多くを転載することになってしまうので省きますが、botoのauth関連のコードを見ると get_plugin という関数の呼び出しを見つけることが出来ます。

boto/plugin.py
def get_plugin(cls, requested_capability=None):
    if not requested_capability:
        requested_capability = []
    result = []
    for handler in cls.__subclasses__():
        if handler.is_capable(requested_capability):
            result.append(handler)
    return result

__subclasses__ はサブクラスの一覧を表示しますが、この場合は AuthHandler クラスを継承しているクラスの一覧になります。gcs-oauth2-boto-pluginの中の OAuth2ServiceAccountAuth というクラスが AuthHandler を継承しているためプラグインとして認識されます。

ちなみに

自分で AuthHandler を継承したクラスを実装し、中でconfigファイルのパスを無理矢理上書きして動的に変更することもできます。が、たぶんもっとスマートにできると思います。

from boto.auth_handler import AuthHandler
from boto.pyami.config import Config

boto_path = ''

class SpamAuth(AuthHandler):
    def __init__(self, path, config, provider):
        config = Config(path=boto_path)
        # ... 略 ...

def spam(path='/path.to/boto_config'):
    global boto_path = path
    bucket_name = 'bucket_name'
    uri = boto.storage_uri(bucket_name, 'gs')
    print uri.get_bucket()

元の記事へ