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 にアクセスしていることがわかります。

ぼくのかんがえたさいきょうの /etc/apt/sources.list

これ

# /etc/apt/sources.list
deb     mirror+file:/etc/apt/mirrors.txt jammy main restricted universe multiverse
deb-src mirror+file:/etc/apt/mirrors.txt jammy main restricted universe multiverse

deb     mirror+file:/etc/apt/mirrors.txt jammy-updates main restricted universe multiverse
deb-src mirror+file:/etc/apt/mirrors.txt jammy-updates main restricted universe multiverse

deb     http://security.ubuntu.com/ubuntu jammy-security main restricted universe multiverse
deb-src http://security.ubuntu.com/ubuntu jammy-security main restricted universe multiverse
# /etc/apt/mirrors.txt
# http://ap-northeast-1.ec2.archive.ubuntu.com/ubuntu/  priority:1
# http://asia-northeast1.gce.archive.ubuntu.com/ubuntu/ priority:1
https://ftp.udx.icscoe.jp/Linux/ubuntu/ priority:2
https://linux.yz.yamagata-u.ac.jp/ubuntu/   priority:3
http://archive.ubuntu.com/ubuntu/

です。

これで、

  • ftp.udx.icscoe.jp にアクセスして成功したらそれで OK
  • 失敗したら linux.yz.yamagata-u.ac.jp にアクセスして成功したらそれで OK
  • 更に失敗したら最後の砦の http://archive.ubuntu.com/ubuntu/ にアクセスする

な動作になります。

EC2 や GCE な向きは、リージョンを適宜変更して(まれによくコケる)REGION.ec2.archive.ubuntu.comREGION.gce.archive.ubuntu.com/ubuntu/ のコメントを外して最初に試行するとよいでしょう。

/etc/apt/mirrors.txt について詳しく説明すると、

  • priority の値が小さい順に試行して、失敗したら次のを試行する
  • priority の値がないのは最後に試行する
  • priority の値が同じもの同士、もしくは priority の値がないもの同士はその中からランダムで選ばれ、失敗したらその中からまたランダムで選んで試行を繰り返す
  • URL と priority の間はタブで区切る! スペースではダメ!!
    • 今回の超絶おハマりポイントです!!
    • cat -T /etc/apt/mirrors.txt^I となっているか確認しましょう
  • この機能が使えるのは apt 1.6 以上で、bionic のが 1.6.x なので bionic より古いと使えないかもです

詳細は apt-transport-mirror(1) を参照してください。

公式の apt repos のミラーの一覧はここにあるので、お好みのを選んでみてください。

ときに、爆速の ICSCoE が以前は掲載されていた記憶なんですが、公式ミラーから外れちゃったんすかね?

おハマりその 2

do-release-upgrade するときは事前に元の source.list に戻しましょう。

do-release-upgrade の過程で source.list の中のコードネームが書き換わるのですが、少なくとも bionic → focal のときはmirror+file: の行が bionic のままでエラー終了しちゃいました。

Ruby の net-ssh で OpenSSH 8.8 以上のサーバーにアクセスできない件

Ubuntu 22.04 LTS (Jammy) の OpenSSH 8.9 な sshd に対して、OpenSSH 7.2 以降の ssh ではアクセスできるけど Rubynet-ssh ではアクセスできない件。

理由は以下の通り。

  • OpenSSH 8.8 で ssh-rsa 署名が無効化された
  • rsa 鍵は ssh-rsa 署名の他に、rsa-sha2-256rsa-sha2-512 でも署名可能
  • 実装によっては、ネゴシエーション時に利用可能とわかれば、rsa-sha2-256rsa-sha2-512 署名を使う
    • OpenSSH は 7.2 以降で対応している
    • Rubynet-ssh は現時点の最新リリース版 6.1.0 では未対応
  • 従って、
    • OpenSSH 7.2 以降の sshrsa-sha2-512 署名を使って接続可能
    • Rubynet-sshssh-rsa 署名を使うので接続失敗

詳しい説明は、ヌーラボさんの「OpenSSHがSHA-1を使用したRSA署名を廃止、BacklogのGitで発生した問題と解決にいたるまでの道のり」とそこからリンクされているサイトを参照されたし。

Rubynet-ssh で接続するには、

これからSwitchをお迎えする人へ

年末に向け、新しく Switch をお迎えする人も多いかと思います。そんな人へ、スムーズに使い始められるようにお勧めの準備事項をまとめてみました。

(以前、某所に投稿したやつの 2021 年版です)

SDカード

ダウンロードソフトの保存先に必要なので買っときましょう。本体内蔵のは有機 EL モデルが 64GB、無印と Lite が 32GB なんであっという間にいっぱいになります。

microSDXC の 128GB 以上ならいいんでないかと。

粗悪品を掴まされないように、信頼できる販路で信頼できるメーカーのを購入しましょう。(個人的には SanDiskTranscend

ちなみにソフトの容量はこんな感じ:

  • ブレワイ: 14.4GB
  • Xenoblade DE: 13.6GB
  • スプラ2: 6.1GB
  • あつ森: 6.6GB

ニンテンドーアカウント

Switch内の「ユーザー」に「ニンテンドーアカウント」を連携させるとポイント溜まったりニンテンドーeショップでソフト買えたりできます。

ので、「ニンテンドーアカウント」は予め作っておいた方がスムーズに始められるかと。

Switch が無くても PC or スマホでここからアカウント作れます。

ちな、Nintendo Switch Online には「ニンテンドーアカウント」は必須です。

子どもアカウント

0 歳から 17 歳までのアカウントは「子どもアカウント」として登録できて、「みまもり設定」で各種制限を加えることができます。

一度「子どもアカウント」にすると 13 歳以上になるまで「一般アカウント」にすることができませんが、ボイチャ使うゲームを子どもがやらない限りはまぁ「子どもアカウント」でいいんじゃないかと思います。

Nintendo Switch Online

Nintendo Switch Online は入るのをお勧めします。

オンラインプレイだけじゃなくて、セーブデータお預かり(セーブデータのバックアップ)もできるので。

300時間のハイラルの思い出が吹っ飛んだら立ち直れませんよね?

ただ、セーブデータお預かりに対応してないゲームもあるので気をつけてください…

あとは、昔のファミコンスーファミのゲームも遊べるようになります!

個人だと 2,400 円/年。ファミリープラン(最大 8 アカウント)だと 4,500 円/年です。

NINTENDO 64 好きなら、10 月下旬に追加予定の「追加パック」も要チェック。

Proコン

必要になったらでおk

ゲームはダウンロード版?パッケージ版?

ゲームカードを入れ替えるのが地味にめんどいのでダウンロード版をお勧めします。

が、パッケージ版のメリットもあるので状況次第で。

  • 飽きたら売り飛ばしたい
  • Switchを複数台もってて、両方でプレイしたい(連携してるニンテンドーアカウントが別の場合)
  • 中古で安く買いたい
  • ご賞味したい(ゲームカードは舐めると味がします。中古の場合は抵抗ありますが…)

液晶保護フィルム

持ち運び時にキズがついたらイヤなので自分は貼ってます。

キャリングケース

持ち運ぶときにはあった方がいいかもす。

カバンに放り込んで液晶に傷がついたりスティックがぐんにゃり逝ったりすると悲しいので、かさばらないソフトケース持ってます。

あと、ケースに入れても入れなくても、持ち運ぶときは電源オフにした方がいいす! カバンの中で、スティックに何かが触れるとスリープから覚めてしばらくしてまたスリープして〜を繰り返して、さてやるか!!ってときにバッテリーが尽きてると悲しいので。(経験者)

goのtviewを使う時は、

FAQ

にも書いてあるけど、 export LC_CTYPE="en_US.UTF-8" しましょう。

じゃないと、枠線がガビガビになったり行頭の1文字が書けたりしちゃいます。

もしくは、こんな感じで環境変数をセットして再実行するのでもいいかと。

func init() {
    // https://github.com/rivo/tview/wiki/FAQ#why-do-my-borders-look-weird
    if os.Getenv("LC_CTYPE") != "en_US.UTF-8" {
        os.Setenv("LC_CTYPE", "en_US.UTF-8")
        env := os.Environ()
        if err := syscall.Exec(os.Args[0], os.Args, env); err != nil {
            panic(err)
        }
    }
}

内部NLBによるパケットの書き換えのまとめ

そういえば、内部 NLB 構成ってどうやって同一セグメントのクライアントにパケットが返ってきてるんだろ? リアルサーバーからみたときに、パケットの送信元がクライアントのになる DSR だとすると返せるのはわかるんだけど、VIP (NLBのIPアドレス) 宛のパケットを受ける設定してなくても動いてるし、NAT (DNAT) 型だとすると(クライアントが同一セグメントにいるので)戻りのパケットが NAT した NLB を経由しないんでクライアントに破棄されるだろうし??? とふと疑問を持ったので調べてみた結果です。

調べたところ、リアルサーバーに届くパケットはこんな感じでした:

  • ターゲットをインスタンスIDで指定した場合
    • src MAC = NLBのMAC
    • src IP = クライアントのIP
    • dst MAC = リアルサーバーのMAC
    • dst IP = リアルサーバーのIP
  • ターゲットをIPアドレスで指定した場合
    • src MAC = NLBのMAC
    • src IP = NLBのIP
    • dst MAC = リアルサーバーのMAC
    • dst IP = リアルサーバーのIP

まとめると:

  • 内部 NLB は NAT型 の L4LB
  • dstはリアルサーバーのMACアドレスIPアドレスに書き換えられる(DNAT)
  • srcのMACアドレスはNLBのMACアドレスに書き換えられる
    • リアルサーバーからの戻りのパケットが必ずNLBを経由するようにこうなってると思われ
    • DNATのみだと、srcはクライアントのMAC、IPになるので、リアルサーバーはNLBを経由せずに直接クライアントに戻りのパケットを送信しちゃって、クライアントは送ったのと違うところからパケットが返って来ることになるので破棄しちゃう
  • ターゲットタイプで違うのは、srcのIPアドレスのみ
  • ターゲットをインスタンスIDで指定したときの注意点

な感じでした。なるほどー

良いお年を!

MySQLで生パスワードからauthentication_stringを得る方法

SELECT PASSWORD('mypass');

mysql.user の authentication_string に格納されるのを得られるんですが、MySQL 8から PASSWORD() 関数がなくなったのでどうすればいいかというと、

SELECT CONCAT('*', UPPER(SHA1(UNHEX(SHA1('mypass')))));

こうすればいいです。

と、 @yoku0825 さんに教えてもらいました!