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
[GSUtil]
default_project_id = sampleproject-994
実装
基本コード
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
という関数の呼び出しを見つけることが出来ます。
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
を継承しているためプラグインとして認識されます。その中で設定ファイルからgs_service_key_file
など必要な情報を取得しています。
ちなみに
自分で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
boto_path = path
bucket_name = 'bucket_name'
uri = boto.storage_uri(bucket_name, 'gs')
print uri.get_bucket()
なお、この際にURIクラス(この場合BucketStorageUri
)は一度connectionを張るとprovider_pool
というdictの中にconnectionを保持するので、connectionを切り替える場合はdel BucketStorageUri.provider_pool['gs']
やBucketStorageUri.provider_pool = {}
のようにクリアしてあげないとダメみたいですね。