AWS と GCP のクライアントライブラリ(Python)のドキュメントのありか

毎回迷子になるのでまとめときます。

AWS

概要

の中に、高レベルの Resource低レベルの Client があります。

すべてのサービスで Resource が実装されているわけではないので、統一性を求めて個人的には Client を使っています。でも、Resource は Collection の all()ec2.instances.all())や filter() を呼ぶとイテレータが返ってきて、それを for で回せばよいのでいちいち paginator の処理を書かなくてよいのは便利ですね。

Resource

  • リファレンス
  • サンプルコード
    • Resource や Action のページにコード片が掲載されているので、それを参考にするとよいです。

Client

tips

たくさんのアカウントでなにかしたい

たくさんのアカウントでなにかしたいときに、それぞれのアカウントのクレデンシャルを用意するのはたいへんなので、Organizations を導入しているのが前提ですが、assume role を使うとよいです。

こんな感じのを用意して、

def assumed_session(
    *,
    management_session: boto3.session.Session,
    assumed_account_id: str,
    role_name='OrganizationAccountAccessRole',
) -> boto3.session.Session:

    res = management_session.client('sts').assume_role(
        RoleArn=f"arn:aws:iam::{assumed_account_id}:role/{role_name}",
        RoleSessionName=f"{assumed_account_id}@{role_name}"[:64],
    )

    return boto3.session.Session(
        aws_access_key_id=res['Credentials']['AccessKeyId'],
        aws_secret_access_key=res['Credentials']['SecretAccessKey'],
        aws_session_token=res['Credentials']['SessionToken'],
    )

管理アカウントのセッションとメンバーアカウントの ID を渡すと、メンバーアカウントのセッションが返るので、それを使って Resource や Client を作って処理します。

さらに、任意の関数を受けて concurrent.futures.ThreadPoolExecutor を使って並列実行する便利関数を用意しておくといろいろ捗ります。

GCP

概要

の 2 種類があります。

https://cloud.google.com/apis/docs/client-libraries-explained に依れば、

一部の Google Cloud APIs では、言語によっては Cloud クライアント ライブラリを利用できません。これらの API のいずれかを使用する際に希望する言語の Cloud クライアント ライブラリが存在しない場合は、以前のスタイルのクライアント ライブラリ(Google API クライアント ライブラリ)を引き続き使用できます。

とのことなので、使いたいサービスが高レベルの google-cloud-python で実装されていればそれを、そうでなければ低レベルの google-api-python-client を使うとよさそうです。

が、昔(今は改善されてるかも知れません)、google-cloud-python が意図した通りに動かなくてハマったことがあるので、自分は google-api-python-client を使うようにしています。

google-cloud-python

google-api-python-client

API

tips

update は丸ごと置き換えの場合が多いので注意しましょう

update メソッドはぜーんぶまるごと総入れ替えの場合が多いです。ドキュメントに注意書きがあるはずですが、見落として部分更新のつもりで一部のパラメータだけ添えて update すると、指定しなかったパラメータの値が消えちゃうことがあるので注意しましょう。

多くの API では部分更新の patch メソッドが提供されているのでそれを使うか、まず現在の値を得た後、一部の値を変更してすべて update メソッドに添えるかしましょう。

gcloud の credentials を拝借する

API アクセスに必要な credentials は色々な方法で用意できますが、予め gcloud auth login して生成された credentials を拝借することもできます。

#!/usr/bin/env python3

import json
import os
import sqlite3
import sys

import google.auth.transport.requests
import google.oauth2.credentials


def credentials_from_gcloud() -> google.oauth2.credentials.Credentials:
    """gcloud の credentials を拝借する"""

    con = sqlite3.connect(os.path.expanduser('~/.config/gcloud/credentials.db'))
    con.row_factory = sqlite3.Row
    rows = con.execute('''
    SELECT *
      FROM credentials
     WHERE account_id LIKE "%@gmail.com" -- 適当に変えて使ってください。
    ''').fetchall()

    assert len(rows) == 1

    cred_data = json.loads(rows[0]['value'])
    cred = google.oauth2.credentials.Credentials(
        'gcloud',
        client_id=cred_data['client_id'],
        client_secret=cred_data['client_secret'],
        token_uri=cred_data['token_uri'],
        refresh_token=cred_data['refresh_token'],
    )

    try:
        cred.refresh(google.auth.transport.requests.Request())

    except google.auth.exceptions.RefreshError as e:
        print('期限切れてるみたいだから gcloud auth login してねん。', file=sys.stderr)
        raise e

    return cred


credentials = credentials_from_gcloud()

プロジェクトの一覧を得る方法

Resource Manager APIprojects.list でプロジェクトの一覧を得ることができます。

が、Google Workspace(旧 GSuite)を利用していて組織リソースが存在する場合、何も考えずに projects.list するとおびただしい数のプロジェクトが返ってくるかもしれません。

これは、Apps Script プロジェクトが作成されると同時に GCP のプロジェクトも作成され、それが 組織/system-gsuite/apps-script/ のフォルダの中に配置されるためです。

なので、リソースの管理apps-script フォルダの ID(数字です)を調べて、そのフォルダ ID を親とするプロジェクトは除外するようにすればよいです。

import googleapiclient.discovery

projects: list[dict] = []

# 除外する apps-script フォルダの ID
apps_script_folder_id = '999999999999'

crm_client = googleapiclient.discovery.build(
    'cloudresourcemanager', 'v3',
    credentials=credentials,
)
projects_r = crm_client.projects()

request = projects_r.list(
    filter=f"NOT parent.id={apps_script_folder_id}"
)

while request is not None:
    response = request.execute()

    projects.extend(response['projects'])

    request = projects_r.list_next(request, response)

for project in projects:
    print(project['projectId'])

また、請求先アカウントにリンクされているプロジェクトの一覧(=非課金の野良プロジェクトは除外したい)なら、Cloud Billing APIbillingAccounts.projects.list を使うのもよいと思います。

gcloud がアクセスしている API を知りたい

--verbosity=debug を指定すると API のエンドポイントが表示されます。

例えば、gcloud alpha services quota list がアクセスしている API を知りたい時は、このように実行すると、

$ gcloud --verbosity=debug alpha services quota list --service='bigquery.googleapis.com' --consumer='projects/oreno-project'`

DEBUG: Running [gcloud.alpha.services.quota.list] with arguments: [--consumer: "projects/oreno-project", --service: "bigquery.googleapis.com", --verbosity: "debug"]
DEBUG: Starting new HTTPS connection (1): serviceusage.googleapis.com:443
DEBUG: https://serviceusage.googleapis.com:443 "GET /v1beta1/projects/oreno-project/services/bigquery.googleapis.com/consumerQuotaMetrics?alt=json HTTP/1.1" 200 None
...

と出力され、Service Usage APIv1beta1services.consumerQuotaMetrics にアクセスしていることがわかります。