Conao3 Notes

awsrを本家AWS CLIの新しいバージョンに毎日追従させる

前の記事で紹介した awsr は、AWS CLI v2 とバイト単位で同じ出力を返す Rust 製の CLI です。 同じ出力を返し続けるには、本家の新しいバージョンが出るたびに awsr も同じバージョンへ上げる必要があります。 本家は 2026-09-30 から 2026-11-05 までの 36 日間に 20 回リリースしていて、平日はほぼ毎日新しいバージョンが出ます。 そこで、本家の新しいバージョンへの追従を GitHub Actions で毎日回すようにしました。 新しいバージョンが出ると、workflow がバージョンを上げる変更を作って本家と比べ、結果を載せた PR を作ります。

次の画像は、2.39.5 が出たときに workflow が作った PR です。 移植する前の比較の結果が、表に並んでいます。

img

バージョンを上げると変わるもの

awsr は本家の決まった 1 つのバージョンに合わせて作っていて、そのバージョンを models/VERSION に書いています。 flake.nix は、同じバージョンの本家のソースと公式の Linux 向けのインストーラを nix で取り込みます。 本家のバージョンを上げると、次の 3 つが変わります。

  • botocore のモデル: awsr がバイナリに埋め込む、サービスと操作の定義です。
  • テストの期待値: コマンドの表、引数の表のハッシュ、help の文書、waiter の説明は、本家のモデルとソースから作っています。
  • 本家のコードの変更: 独自の処理を持つコマンドや、認証の処理の変更です。本家の changelog では api-change 以外の項目 (enhancement や bugfix) にあたります。

モデルと期待値は本家から機械で作れますが、本家のコードの変更は Rust へ移植する必要があります。

バージョンを上げるスクリプト

機械で作れる部分は scripts/update-reference.sh で作り直します。

$ scripts/update-reference.sh 2.39.5

このスクリプトは次の順に作業します。

  1. nix-prefetch-url で本家のソースとインストーラのハッシュを取り、 flake.nix のバージョンとハッシュを書き換える。
  2. 新しいバージョンの devShell に入り、 scripts/vendor-models.sh でモデルを取り込み直す。モデルを持つ 3 つのデータ crate のバージョンも、ここで本家のバージョンにそろう。
  3. コマンドの表、引数の表のハッシュ、help の文書、waiter の説明を、本家のソースから作り直す。
  4. nix fmt で整形する。

期待値を作るスクリプトは、2.39.3 のソースで動かすと commit 済みの期待値と同じ中身になることを確かめてから使っています。

毎日回す workflow

Reference の workflow は、毎日 12:00 UTC に本家の最新のタグを調べます。 models/VERSION より新しいバージョンがあれば、上のスクリプトでバージョンを上げてから、次の 4 つで本家と比べます。

  • make check (fmt、clippy、テスト)
  • 差分テスト: モデルが変わったサービスの全操作を awsr と本家の両方で動かし、出力とリクエストを比べる。
  • help の比較: 同じサービスの help ページを、バイト単位で比べる。
  • シナリオと認証の比較: 手で書いたシナリオ、 s3 コマンドのシナリオ、 configure や sso login などの認証まわりのコマンドを比べる。

比べ終わると、 reference/<バージョン> のブランチを push し、結果の表を載せた PR を作ります。 PR の本文には本家の changelog のうち api-change 以外の項目も並べます。 比較が 1 つでも失敗するか移植の要る項目があれば、PR を draft にします。

img

変わったサービスだけを比べる

差分テストを全件回すと、23 万件を超える組み合わせを本家と比べることになります。 本家の実行結果のキャッシュがないと、手元の 14 コアの PC で約 100 分かかりました。 公開リポジトリで使う GitHub Actions の ubuntu-latest は 4 コアなので、全件を毎日回すのは現実的ではありません。

そこで、毎日の比較はモデルが変わったサービスに絞ります。 2.39.3 から 2.39.5 では 34 サービスのモデルが変わっていて、その全操作の組み合わせは 48,504 件でした。 このバージョンに上げたときの run は全体で約 71 分かかりました。 そのうち差分テストが release ビルドを含めて約 44 分、help の 4,189 ページの比較が約 18 分です。

2.39.5 で移植したもの

2.39.3 から 2.39.5 に上げた PR は conao3/rust-awscli#6 です。 2.39.4 と 2.39.5 の changelog には、 api-change 以外の項目が 9 つありました。 そのうち awsr に移植したのは、次の 4 つです。

  • Bearer 認証のトークンを、HTTPS 以外の endpoint へ送らない。
  • s3 cp 、 s3 mv 、 s3 sync で、手元から S3 への転送に --tags を付けられる。
  • 転送が PreconditionFailed で失敗したとき、 --no-overwrite を付けた転送だけを、すでにあるファイルの skip として扱う。
  • deploy install が書く設定ファイルの権限を 600 にする。

残りの 5 つは、CRT の転送クライアントと Windows での aws update の項目です。 awsr は classic の転送クライアントだけを実装していて、 update のコマンドもまだ実装していないので、この 5 つは移植の対象になりません。

Bearer 認証の変更は、2.39.5 で増えた account-access-runtime のサービスで見つかりました。 差分テストはこのサービスの 24 件で一致せず、本家は Bearer authorization requires an HTTPS endpoint. で止まり、 awsr はリクエストを送っていました。 この変更を移植すると、こんどは 2.39.3 の本家と比べたときに Bearer 認証を使う別のサービスが一致しなくなります。 そのため、本家のバージョンに依存する移植は、バージョンを上げる PR の中で行います。 PR のブランチに master を merge し、その上に移植の commit を積んで、手元ですべての比較が通ってから merge しました。

--tags は既存のシナリオでは比べられないので、 s3 コマンドのシナリオを 6 つ足しました。 足したシナリオがタグを確かに比べていることは、 awsr のタグを付ける処理を一時的に外し、upload の 4 つが失敗することで確かめています。

runner の上でだけ見つかった差分

2.39.5 の PR を作った run では、 deploy install と deploy uninstall のシナリオが 3 つ失敗しました。 同じシナリオは手元の PC では一致します。

deploy install は、EC2 のインスタンスの上では動かないように作られていて、 169.254.169.254 のメタデータのサービスに問い合わせて EC2 の上かを調べます。 本家は Python の urlopen で HTTP の GET を送り、エラーの応答を受けたときは EC2 ではないと判断します。 awsr は TCP でつながるかだけを見ていたので、このアドレスに何かのサービスが応答する環境では EC2 の上だと判断していました。 GitHub Actions の runner は Azure か AWS の上で動き、Azure のメタデータのサービスも同じアドレスを使います。 Azure のメタデータのサービスは、 Metadata: true のヘッダーがない要求を拒みます。

awsr も HTTP の GET を送り、応答のステータスが 400 未満のときだけ EC2 の上と判断するように直しました。 手元では、 unshare -rnm で作った user namespace の中に 169.254.169.254 で 400 か 200 を返すサーバーを立て、 /etc/lsb-release を Ubuntu のものに差し替えて、本家と awsr の出力が同じになることを確かめています。