Skip to content

feat(ci): AWS S3 SigV4リージョンのE2Eテストを追加 - #41

Open
tishin-endou wants to merge 20 commits into
RCOSDP:mainfrom
tishin-endou:feature/aws-s3-sigv4
Open

tishin-endou wants to merge 20 commits into
RCOSDP:mainfrom
tishin-endou:feature/aws-s3-sigv4

Conversation

@tishin-endou

@tishin-endou tishin-endou commented Jun 5, 2026 •

Copy link
Copy Markdown
Contributor

概要

実際のAWS S3に対してSigV4認証でE2Eテストを実行するCI設定を追加し、S3アドオンのSignature V2からV4への移行を検証します。

変更内容

CIワークフロー (.github/workflows/e2e-test.yml)

  • AWS S3 SigV4リージョンテスト用の user-aws-s3 テストグループをマトリクスに追加
  • AWS_S3_ENABLED 環境変数と条件分岐ロジックを追加
  • AWS認証情報のGitHub Secretsを参照:
    • AWS_S3_ACCESS_KEY、AWS_S3_SECRET_KEY
    • AWS_S3_LEGACY_REGION、AWS_S3_LEGACY_BUCKET_NAME
    • AWS_S3_V4_REGION、AWS_S3_V4_BUCKET_NAME

設定生成スクリプト (.github/scripts/generate_ci_config.sh)

  • 実AWS S3テスト設定用の --aws-s3 オプションを追加
  • レガシーリージョンとSigV4リージョンのバケットで storages_s3 設定を生成
  • 必須AWS環境変数のバリデーションを追加
  • --minio と --aws-s3 の排他チェックを追加

セットアップ要件

リポジトリに以下のGitHub Secretsの設定が必要です:

Secret 説明
AWS_S3_ACCESS_KEY テストユーザーのIAMアクセスキー
AWS_S3_SECRET_KEY テストユーザーのIAMシークレットキー
AWS_S3_LEGACY_REGION レガシー(V2)バケットのリージョン
AWS_S3_LEGACY_BUCKET_NAME レガシーリージョンのバケット名
AWS_S3_V4_REGION SigV4が必要なリージョン(例: ap-northeast-1)
AWS_S3_V4_BUCKET_NAME SigV4リージョンのバケット名

関連PR

Based on anqiuy/RDM-e2e-test-nb@feature/s3-sigv4

Co-Authored-By: An Qiuyu <qiuyu.an@hotmail.com>
@tishin-endou tishin-endou changed the title feat(ci): Add AWS S3 SigV4 region E2E tests feat(ci): AWS S3 SigV4リージョンのE2Eテストを追加 Jun 5, 2026
- テスト手順-管理者機能-S3-機関ストレージ.ipynb を新規作成
  (S3CompatSigV4版をベースに、Amazon S3用にエンドポイントURL関連を除去)
  URL/entityIDは実環境の固有ホスト名を残さず、他ノートブックと同様に
  example.comプレースホルダ or None(.config.yaml/プロンプト入力で補完)とした
- 取りまとめ-S3共通.ipynb: .config.yaml から未設定パラメータを補完する
  ローダーセルを追加(対話実行を容易にするため)
- .gitignore: .claude/ (ローカルのAI開発ツール設定) を除外
user-aws-s3 マトリクス項目は実AWS認証情報のGitHub Secretsを必要とするが、
パブリックリポジトリではSecretを登録できず、有効なままだと認証情報未設定で
CIが失敗する。そのため既定でコメントアウトし、ローカル/Secret設定済みforkで
テストする場合のみ有効化する旨をコメントで明記した。
generate_ci_config.sh 側の --aws-s3 処理は温存(有効化時にそのまま使える)。
コンフリクト解消(いずれも双方の追加を共存させる形):
- .github/workflows/e2e-test.yml: user-minio に upstream の wiki_enabled: false を
  取り込みつつ、コメントアウト済みの user-aws-s3 ブロックを維持
- .github/scripts/generate_ci_config.sh: usage 文字列に --aws-s3 と --wiki を併記
  (引数パース・処理ブロックは自動マージ済みで AWS_S3/WIKI 両対応)
@tishin-endou
tishin-endou marked this pull request as ready for review July 21, 2026 06:01
WaterButlerのAmazon S3プロバイダSigV4化に伴う回帰試験(S-0〜S-7)を
実施するテスト手順Notebookと、CompleteMultipartUploadのエラーコードを
採取してWaterButlerの判定表と突き合わせるスクリプトを追加する。

機関ストレージはosfstorage経由で提供されるため、一覧ページング・削除・
サーバ側コピー/移動はs3プロバイダに到達しない。これらの試験項目は
S3アドオンを用いて実施する。
E2E_PLAN v0.2 の S-7(E-2 / K-4)。64MB のファイルをUIからアップロードし、
ETag の末尾にパート数の接尾辞が付くこと(= CompleteMultipartUpload を
通ったこと)とサイズ一致を確認する。run_chunked=False で無効化できる。
P2-1: WB の CONTIGUOUS_UPLOAD_SIZE_LIMIT は既定 128,000,000 で、64MB では
必ず単発 PUT になり CompleteMultipartUpload を通らない。chunked_file_size を
200,000,000(CHUNK_SIZE 64,000,000 × 3 + 8,000,000 = 4 パート)に変更し、
閾値の根拠を S-7 の説明セルに記載する。判定はマルチパート経路を assert し、
パート数の一致は env で閾値を変えられるため警告どまりとする。

P2-2: invalid-access-key-id / signature-does-not-match / no-such-bucket の
3 ケースを追加。いずれも UploadId とパートは正しい資格情報で作り、complete の
呼び出しだけを壊す。EntityTooLarge は 5TiB 超が必要なため採取不可として
理由付きで表に残す。
SigV4 回帰ノートブックのバケットを、クォータ NB と同じ「生成から削除までを
1 ライフサイクルにし、atexit で後始末を登録する」方式にした(boto3、mc は使わない)。

- s3_bucket / s3_bucket_versioned / s3_bucket_apne1 は「指定があればそれを使い
  (削除しない)、None なら生成する(後始末で delete_bucket まで行う)」に変更。
  既定は 3 つとも None。bucket_prefix / bucket_suffix / delete_generated_buckets を追加
- 新セル [6]: 3 バケットを生成(us-east-1 は CreateBucketConfiguration を付けない、
  apne1 は LocationConstraint を付ける)、ver はバージョニングを有効化して確認、
  GetBucketLocation を記録(us-east-1 が null になることが IF-3 の根拠)、
  atexit に cleanup_buckets を登録。S-6 用の export 行を印字する
- 旧 S-0 バケット確認セルは削除し、証跡 S-0-buckets.json を生成セルに統合
- 後始末: 生成バケットは版・削除マーカー・進行中 multipart を消してから
  delete_bucket(BucketNotEmpty は最大 3 回やり直し)、指定バケットは残骸だけ削除。
  記録先を S-3-cleanup.txt から S-9-cleanup.txt に変更し、生成バケットが消えた
  ことを確認するセルをチェックリストの直後に追加
- README と scripts/s3_complete_error_codes.py の E2E_S3_BUCKET の説明を更新

moto[s3] で生成 → バージョニング Enabled → 後始末(delete_bucket まで)と、
バケット指定時に削除されないことを確認した。staging2 / AWS には実行していない。
E2E の実行中に、失敗の理由が読めない・後始末が終わらない事象が出たので直す。

- run_pw: 例外を捕まえた直後に元の例外を stderr に出す。DOM ダンプと
  スクリーンショットの採取が先だと、ページが navigating のまま固まったときに
  採取側で時間を使い切り、何で落ちたのかが残らない
- run_pw: _save_screenshot の例外を握り潰す。ここで上げると元の例外が
  差し替わる。あわせて last_path を渡し忘れていたのを渡す
- _save_screenshot: full_page=False、timeout=10000。失敗時の証跡なので
  全画面である必要はなく、full_page=True はページ全体をスクロールしながら
  描画するため固まったページでは既定の 30 秒を使い切る
- _finish_pw_context: current_contexts の更新が [::-1](反転)で要素が減らず、
  末尾の再帰が同じコンテキストを処理し続けていた。[:-1] にする。
  実際には rmtree 済みの temp_dir を読みに行って FileNotFoundError で止まる
- _finish_pw_context: temp_dir は全コンテキストで共有しているので、残りを
  処理し終えてから rmtree する。video-N.webm の N は _video_index_offset で
  通し番号にする(コンテキストごとに 1 から振り直すと前の分を上書きする)
staging2 での実行(run-20260928-153426 / run-20260930-143654)で止まった箇所を直す。

- 管理者ログイン: admin_idp_entity_id が無い環境では GakuNin Embedded DS
  (「所属している機関を選択」)が出る。これまでは entityID 必須で落としていたが、
  embedded-wayf.js の window.inc_search_list から admin_idp_name で entityID を
  引いて選べるようにした。一致しないと alert が出て送信されないだけなので、
  押す前に JS 側の一覧で確かめる
- ダッシュボード: wait_dashboard_loaded を追加。[新規プロジェクト作成]は
  一覧より先に出るので、それだけを待つと「読み込み中」と「プロジェクトが無い」を
  区別できず、ensure_project_exists が同名プロジェクトを二重に作る。
  スピナーのままや Ember の「APIは利用できません」はリロードして待ち直す
- 機関ストレージのノード: 表示名は管理画面の #storage_name で決まり、S-1 の登録は
  値が入っていれば上書きしない(セル [18])ため、staging2 では既存の
  'E2E Quota MinIO (S3CompatSigV4)' が残っていてパラメータと食い違っていた。
  osfstorage のアイコンでノードを特定して実名を読む
  open_institutional_storage を追加し、食い違いは黙って合わせず必ず出力する
- S3 アドオンの接続: [有効にする]の click ハンドラは project-addons-page.js が
  バンドル読み込み後に bind するので、visible になった瞬間に押すと捨てられる。
  jQuery._data でハンドラが付くまで待つ。[プロフィールからアカウントをインポート]も
  knockout の importAuth バインド待ちなので、出るまで押し直す。
  既に有効・既に接続済みの場合も掛け直せるようにし、どれにも当たらなければ落とす
- 応答レコーダ: セル再実行時の二重登録を防ぐ。GET 以外は request_body を残す
  (action の "move"/"copy" は要求本文にしかなく、応答の links.move は
  成否に関係なく必ずあるので判定に使えない)。一覧ツールバーのダウンロード URL に
  乗る OSF の accessToken(`?token=`)を証跡からマスクする
- HAR: 取り直しで 2 回目のセッションを回すと finish_pw_context が同じ場所へ
  上書きするので、既存の har.zip を har-N.zip に退避してから閉じる
- バケット名を input() で聞くのをやめた。既定は 3 つとも生成なので、聞かれた側は
  Enter で流すしかなく、誤って既存バケット名を入れると後始末で削除されない側に
  倒れる。指定したいときは .config.yaml に書く(セル [1] が補完する)。
  どれが生成でどれが指定かをセルの出力に並べる
- CreateBucket の前に sts:GetCallerIdentity で資格情報だけを確かめる。
  この API は IAM 権限を要らないので「鍵が違う」と「権限が足りない」を切り分け
  られる。S3 互換ストレージ(MinIO)の鍵を .config.yaml に残したまま実行して
  CreateBucket の InvalidAccessKeyId で初めて気づく、という回り道を無くす。
  アカウント ID / ARN は証跡にも出力にも残さない
- CreateBucket の InvalidAccessKeyId / SignatureDoesNotMatch /
  InvalidClientTokenId にも同じ案内を出す
- atexit への cleanup_buckets 登録をセル再実行で二重にしない
- S-2 の判定: GetBucketLocation は us-east-1 を None で返すので、生の値を
  GetBucketLocation_raw に残し、表示は `or 'us-east-1'` に揃える
  (セル [6] の bucket_state、および WB の _check_region と同じ解釈)
- 後始末チェックリストの後ろに cleanup_buckets() を手で叩くためのセルを追加
ファイル詳細画面の[ダウンロード]では G-10(presigned URL への 302)を
原理的に踏めないことが分かったので、押す場所と判定を作り直す。

詳細画面のボタンは `<a href="download">`(filepage/index.js L522)で、飛び先は
OSF の `/<guid>/download`。そこが `direct=None` を入れた WB URL に redirect する
(addons/base/views.py L984)。WB 側は `accept_url='direct' not in query_arguments`
(metadata.py L84)なので accept_url=False になり、presigned を返さず WB 自身が
ストリームする(= 200)。`direct` を付けないのはファイル一覧のツールバーの
[ダウンロード]だけ(fangorn.js L1641 buildTreeBeardDownload、足すのは token のみ)。

- S-1 のダウンロードは一覧に戻ってツールバーの[ダウンロード]を押す。
  ツールバーのボタンは選択行(tb.multiselected())に対して出るので先に行を選ぶ。
  フォルダの「Zip 形式でダウンロード」も fa-download なので span の文字で絞る
- 主信号を HAR から応答イベント(s1_g10_redirects)に変更。セル [9] のレコーダは
  ヘッダを残さず Location が取れず、HAR はダウンロードのリダイレクト経路を
  落とすことがあるため。HAR は補助として残す
- 判定の候補から `direct` 付きと `revisions=`(版一覧)を外す。混ぜると
  「302 が出ない = 決定-19 が効いていない」という誤った結論になる
- 失敗時のメッセージを「G-10 の経路を踏んでいない」と「踏んだのに 302 に
  ならない」で言い分ける。取り違えると PR の欠陥を捏造することになる。
  判定材料として has_direct を全エントリに残す
- S-2 のダウンロードも同じツールバーのボタンに揃える
run-20260928-153426 の S-3-wb-responses.json が `[]` だった原因。

- フォルダ名のクリックは Treebeard の multiselect で、fangorn の
  onmultiselect(_fangornMultiselect)は選択するだけ。子の遅延読み込みは
  toggleFolder 経由でしか走らないため、名前を押しただけでは `?meta=` が
  1 本も飛ばない。行頭の tb-toggle-icon を押すようにした(既に開いている
  = fa-minus のときは押さない)
- 展開済みのフォルダをもう一度開いても lazyload は走らない(item.load)ので、
  セルを再実行しても同じ条件になるようファイルページを読み直してから始める
- 1 本目の一覧要求が返るまで待ち、飛ばないときは行の HTML を添えて落とす。
  0 本のままスクロールに入ると「増えない」としか分からない
- 行の確認に入る前に #tb-tbody の scrollTop を 0 に戻す。treebeard は表示範囲の
  行しか DOM に置かない(仮想スクロール)ので、上のセルのスクロールで底に
  いるまま始めると 1 件目 "0001" が DOM に無く scroll_into_view_if_needed が
  待ち続ける
これまでの判定は 2 か所とも成立しない見方をしていた。

- 操作の種別: 応答本文の `links.move` を見ていたが、これは成否・種別に関係なく
  必ず入っているので copy でも has_move=True になる。action は POST の *要求*
  本文にしかないので、セル [9] で拾った request_body から読む。
  素のドラッグが copy になっていないか(getCopyMode の altKey 残り)も見る
- 成功の知らせ: $osf.growl を待っていたが、成功は treebeard の行 notify で出る
  (fangorn.js L693)。growl が出るのは失敗したときだけ(L744)なので、
  growl を見ていると成功は永遠に拾えない。行 notify を待ち(表示は 1 秒で
  消えるので sleep を挟まない)、growl は「出ていないこと」を確かめる側に回す

あわせて実機で落ちた箇所を直す。

- ドラッグの前に行を click する。ドラッグの対象は tb.multiselected() なので、
  選んでおかないと掴むものが無く POST が 1 本も出ない。POST が 0 本なら落とす
- コピーが成功すると fangorn はコピー先を開いて同名の行を出すため、ファイル名
  だけの locator が 2 行に当たって strict mode 違反になる(run-20260930-171843)。
  行数を控えてからコピー先を畳む。畳まないと次の移動でどちらを掴むか決まらない
- 「コピーなので元が残っている」は画面では深さでしか区別できないので、
  S3 の head_object で確かめる(移動で消える前に見る)
- S-5 の開始時に前回の残骸(ルート / コピー先 / 移動先)を消す。残っていると
  「移動したのに元が残っている」の判定が前回の結果に引きずられる
run-20261001-121409 で S-1 が「#s3_modal が hidden のまま」で落ちた原因。

- [保存]の submit ハンドラは rdm-institutional-storage-page.js L78 が
  バンドル実行時に bind する。goto_institutional_storage が待つのは
  ラジオボタンの表示だけで、これは HTML の解析時点で満たされるため、
  バンドルが遅いとハンドラが付く前に押してしまう。その場合 e.preventDefault()
  (L103)も効かず素の GET 送信になり、ページが再読込みされるだけで
  確認ダイアログも認証モーダルも出ない。保存済みプロバイダが既に s3 だと
  再読込み後の画面は押す前と見分けがつかない。jQuery._data で submit
  ハンドラが付くまで待つ(webpack の externals で require('jquery') は
  window.jQuery と同一)
- 確認ダイアログが出ないケースを except で握りつぶすのをやめ、その場で落とす。
  これまでは 20 秒待った末に「モーダルが開かない」としか分からなかった
- btn-danger を押した後に「Verification failed」の growl の有無を見る。
  照合に失敗すると callback が呼ばれずダイアログだけ閉じる(osfHelpers.js
  L790)ので、「送信が成立していない」と区別できるようにする。growl は
  delay=0 で自動では消えない(growlBox.js L20)
- bootbox-body の <strong> を .first で取り、複数あっても strict mode
  違反にしない
run-20261001-130318 の実行で分かった、証跡と最終確認の表示の誤りを直す。

- 最終確認セル [73]: 後始末で generated_buckets が空になるため、3 つ作って
  3 つ消していても cleanup_buckets() が「生成したバケットは無い(すべて
  パラメータ指定)」と出していた。生成時の名前を generated_bucket_names に
  控え、list_buckets の実際と突き合わせて「生成 N / 削除 N / 残存 N」を
  出すようにする。BucketAlreadyExists の再試行で作りかけを消したときは
  控えからも落とす
- INDEX.md の対象 SHA: osf_deployed_sha が未記入のままだと
  「RCOSDP/RDM-osf.io#746 (None)」という証跡が残るので、未設定のときは
  「未設定(S-0-jenkins-build.txt を参照)」と書く。セル [11] の警告は従来どおり
- INDEX.md の取りこぼし: セル [67] は S-1-download-redirect.json(HAR の判定)
  と S-9-cleanup.txt(後始末)より先に走るため、この 2 つが表に載っていなかった。
  生成処理を write_index() に切り出し、後始末セル [71] の末尾で作り直す。
  INDEX 自身と finish_pw_context の成果物(har*/video-*/last-*/console.log)は
  表に出さない(末尾の注で説明しているもの)
社内のデプロイ基盤名・環境名・内部文書名を取り除き、このリポジトリだけで
読める手順にする。あわせて S-6 スクリプトを単体で回せるようにする。

- S-0 の説明: 特定のデプロイツール/ジョブのビルドログを前提にしていたのを
  「試験環境にデプロイされている RDM-waterbutler / RDM-osf.io のコミット SHA
  が分かる記録(デプロイ時のログ等)」に改める。保存名も
  S-0-jenkins-build.txt -> S-0-build-log.txt
- 環境名を「試験環境」に置き換える。URL は .config.yaml から来るので本文には
  出ない
- 証跡の既定の置き場をリポジトリ直下の ./evidence/run-<YYYYmmdd-HHMMSS> に。
  別の場所は evidence_dir パラメータで与える。evidence/ を .gitignore に追加
- wb_deployed_sha をハードコードから None 既定のパラメータにし、
  osf_deployed_sha と同じく未記入なら警告する。INDEX.md も両方を
  「未設定(S-0-build-log.txt を参照)」と書く
- scripts/s3_complete_error_codes.py: 資格情報を boto3 の既定の解決順
  (環境変数 / AWS_PROFILE / インスタンスロール)で解決するようにし、
  --create-bucket で採取用バケットを作って**例外時も**空にして削除する

また、*-institutional-storage-after.txt が before と同じ中身になっていたのを
直す。モーダルを閉じただけではページが保存前の描画のままなので、サーバから
読み直してから選択状態と表示名を採り、Amazon S3 になっていることを確かめる。
ノートブックの「後始末チェックリスト」のうち画面操作が要る 2 つ
(本ノートブックが作ったプロジェクトの削除、ユーザー設定 →
「アドオンアカウント構成」からの Amazon S3 連携解除)を単体で回せるようにする。
手でやると消し忘れ・消し違いが起きるうえ、何を消したかの証跡が残らない。

- 接続先と資格情報は .config.yaml から読む(セル [1] と同じ)。**値は印字しない**
- --dry-run で対象の有無と画面だけを採る。想定と違うものが入っていないか
  先に確かめてから本実行する
- 削除前後のプロジェクト一覧と、連携解除の前後を S-9-*.png として残す。
  削除後に対象が残っていたら assert で落とす(「消した」と書いて残さない)
- **機関ストレージには触らない。** 元の設定に戻したあとに触ると壊すため
- session 証跡(video-N.webm / har.zip / console.log / last-*)は固定名なので、
  証跡ディレクトリ直下ではなく cleanup/ に分けて書く。直下にすると本採取が
  残した同名ファイルを上書きする
- scripts/ 直下から起動すると sys.path[0] が scripts/ になり
  scripts/playwright.py が本物の playwright パッケージを隠すので、
  自分のディレクトリを sys.path から外す
- login は about:blank のまま呼ぶと IdP 選択に届かない。セル [24] と同じ順で
  rdm_url を開き、同意ボタンを捌いてから呼ぶ
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant