PythonからGCPへ接続するときの認証設定

# TL;DR
PythonからGCPへ接続する際の認証情報の設定方法が、サービスアカウントのJSONファイルのパスを指定する方法しか見つからなかった。
Gitには認証JSONを乗せたくないし、本番運用時にサーバーにアップロードするのも面倒なので楽に設定できる方法がないか調査した結果をまとめました。

# 1. GCPからJSONファイルをダウンロード
詳しくは割愛。中身はこんな感じ。

```credential.json
{
  "type": "service_account",
  "project_id": "***************",
  "private_key_id": "***************",
  "private_key": "-----BEGIN PRIVATE KEY-----************-----END PRIVATE KEY-----\n",
  "client_email": "***************@***************.iam.gserviceaccount.com",
  "client_id": "***************",
  "auth_uri": "https://accounts.google.com/o/oauth2/auth",
  "token_uri": "https://oauth2.googleapis.com/token",
  "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
  "client_x509_cert_url": "***************"
}
```

# 2. ライブラリをインストール
```
pip install google-cloud-storage python-dotenv
```

## google-cloud-storage
今回は、GCSへ接続するためのライブラリで認証情報を設定していきます。

## python-dotenv
環境変数を`.env`から読み込むために使用します。

# 3. `.env`に環境変数を設定
先ほどダウンロードしたJSONファイルの中身をコピペします。

```
GCP_PROJECT_ID=********
GCS_PRIVATE_KEY_ID=********
GCS_PRIVATE_KEY=********
GCS_CLIENT_MAIL=********
GCS_CLIENT_ID=********
GCS_CLIENT_X509_CERT_URL=********
```

# 4. 環境変数を読み込み
```settings.py
from dotenv import load_dotenv, find_dotenv

load_dotenv(find_dotenv())
```

# 5. Clientクラスを作成
`GCS_PRIVATE_KEY`には改行コード`\n`が含まれているのですが、環境変数経由で読み込むとエスケープされてしまうので、`replace`で元に戻してあげます。

```client.py
import os
from google.oauth2.service_account import Credentials
from google.cloud import storage


class GoogleCloudStorageClient:
    
    cred = Credentials.from_service_account_info({
        "type": "service_account",
        "project_id": os.environ["GCP_PROJECT_ID"],
        "private_key_id": os.environ["GCS_PRIVATE_KEY_ID"],
        "private_key": os.environ["GCS_PRIVATE_KEY"].replace("\\n", "\n"),
        "client_email": os.environ["GCS_CLIENT_MAIL"],
        "client_id": os.environ["GCS_CLIENT_ID"],
        "auth_uri": "https://accounts.google.com/o/oauth2/auth",
        "token_uri": "https://oauth2.googleapis.com/token",
        "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
        "client_x509_cert_url": os.environ["GCS_CLIENT_X509_CERT_URL"]
    })
    client = storage.Client(credentials=cred)

    @classmethod
    def _create_bucket_instance(cls,
                                bucket_name: str,
                                destination_blob_name: str):
        bucket = cls.client.bucket(bucket_name)
        return bucket.blob(destination_blob_name)

    @classmethod
    def upload_file(cls,
                    bucket_name: str,
                    destination_blob_name: str,
                    local_file_path: str) -> None:
        blob = cls._create_bucket_instance(bucket_name, destination_blob_name)
        blob.upload_from_filename(local_file_path)
```

## `storage.Client()`
`credentials`に何も設定していない場合は環境変数に認証JSONファイルパスを設定しないといけません。
今回はJSONファイルが無くても認証が通るように、ライブラリで用意されている認証情報オブジェクトを生成して渡しています。

# 6. 呼び出し
```main.py
bucket_name = "XXX"  # 保存先のバケット名
destination_blob_name = "YYY/ZZZ.csv"  # 保存先となるバケット内でのファイルパス
local_file_path = "LOCAL/FILE.csv"  # 保存したいファイルパス

GoogleCloudStorageClient.upload_file(
    bucket_name,
    destination_blob_name,
    local_file_path
)
```