AWS CLIでS3バケット上の指定したパスのオブジェクトを移動し、ついでにタグを付けるシェルスクリプトを作成してみた
データアナリティクス事業本部 機械学習チームの鈴木です。
AWS CLIでS3バケット上の指定したパスのオブジェクトを移動し、ついでにタグを付けるという一連の操作をプログラムから実行したいことがありました。
この処理が可能なのか確認するため、自分でシェルスクリプトを作成してみたのでご紹介します。
前提
やりたいこと
以下のように複数のオブジェクトがS3バケット上にあったとします。
これらを指定した別のディレクトリに移動させます。
このとき、指定したタグを付与します。今回は1つのタグを付与することとします。
初回の移動でできた階層(この例だとdone/
)については、再度スクリプトを実行した際に、この階層まで対象にならないように除外する設定もできるようにしたいです。
AWS CLIのバージョン
ローカルからbashスクリプトよりAWS CLIを使いS3オブジェクトの操作を行いました。
AWS CLIのバージョンは以下の通りでした。
aws --version # aws-cli/2.13.28 Python/3.11.6 Darwin/22.6.0 exe/x86_64 prompt/off
作成したシェルスクリプト
以下のシェルスクリプトを作成しました。
#!/bin/bash # # Move S3 Objects and put them tags. err() { echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: $*" >&2 } SOURCE=$1 DESTINATION=$2 EXCLUDE_FOLDER=$3 OBJECT_TAG_KEY=$4 OBJECT_TAG_VALUE=$5 PROFILE=$6 echo "S3 objects source: $SOURCE" echo "S3 objects destination: $DESTINATION" echo "S3 objects will be put tagging: key is $OBJECT_TAG_KEY, value is $OBJECT_TAG_VALUE" # ファイル移動の情報だけ取得できるよう、--no-progressオプションをつける # --recursiveを指定しているので、移動先が含まれている場合は除外する。 MOVED_OBJECT_URIS="$(aws s3 mv $SOURCE $DESTINATION --exclude "${EXCLUDE_FOLDER}*" \ --recursive --no-progress --output text \ --profile $PROFILE | cut -d ' ' -f 4)" if [ $? -ne 0 ]; then err "Error: failed to move files from $SOURCE to $DESTINATION" exit 1 fi echo "S3 objects have been successfully moved to: $DESTINATION" # 改行を区切りとして配列に変える MOVED_OBJECT_URIS_ARR=$(IFS='\n'; (echo "$MOVED_OBJECT_URIS")) for uri in $MOVED_OBJECT_URIS_ARR; do bucket_name="$(echo $uri | cut -d '/' -f 3)" object_key="$(echo $uri | cut -d '/' -f 4-)" echo "Put tagging to: Bucket_name is $bucket_name, Object key is $object_key" aws s3api put-object-tagging --bucket $bucket_name --key $object_key --profile $PROFILE \ --tagging "{\"TagSet\": [{ \"Key\": \"$OBJECT_TAG_KEY\", \"Value\": \"$OBJECT_TAG_VALUE\" }]}" if [ $? -ne 0 ]; then err "Error: failed to put tag to $uri" exit 1 fi done
例えば、以下のように実行しました。
./move_put_tagging.sh s3://バケット名/sample/ s3://バケット名/sample/done/ done/ test_tag true my_profile
引数は以下の通りです。
- 第1引数: 移動元のS3オブジェクトプレフィクス
- 第2引数: 移動先のS3オブジェクトプレフィクス
- 第3引数: 第1引数で指定したプレフィクスのパスから除外したい値
- 第4引数: 付与したいタグのキー
- 第5引数: 付与したいタグのバリュー
- 第6引数: API実行に使用するプロファイル名 ※プロファイルで認証情報を管理している前提
今回は第3引数で--exclude
オプションを使った除外を入れています。除外したいものがない場合は存在しないオブジェクトの値を入れる想定であることにご注意ください。これは単に除外が必要なケースを想定した場合に、これ以上複雑にならない良い実装が思いつきませんでした。
作成したシェルスクリプトのポイント
移動した後のオブジェクトの取得
aws s3api put-object-tagging
コマンドで移動後のオブジェクトにタグを付けたいのですが、対象となる移動後のオブジェクトのオブジェクトキーを取得する必要がありました。
これはaws s3 mv
コマンドで--no-progress
オプションをつけて結果を出力し、cut
コマンドで該当部分を取得しました。
aws s3 mv
コマンドでは、例えば以下のような文字列が標準出力されました。半角スペース区切りで4列目を抽出できれば、タグ付けに必要な情報は取得できそうです。
move: s3://バケット名/sample/sample.csv to s3://バケット名/sample/done/sample.csv move: s3://バケット名/sample/sample2.csv to s3://バケット名/sample/done/sample2.csv
コマンド単体で使っているとあまり気にしていなかったのですが、--no-progress
オプションがない場合、これ以外にもアクションの実行状況に関する文字列が出力されます。
Completed 183 Bytes/366 Bytes (1.1 KiB/s) with 2 file(s) remaining Completed 366 Bytes/366 Bytes (1.9 KiB/s) with 2 file(s) remaining move: s3://バケット名/sample/sample.csv to s3://バケット名/sample/done/sample.csv Completed 366 Bytes/366 Bytes (1.9 KiB/s) with 1 file(s) remaining move: s3://バケット名/sample/sample2.csv to s3://バケット名/sample/done/sample2.csv
これだと不要な文字列を抜き出してしまうため、--no-progress
オプションを使用することで解決しました。
移動とタグ付けのコマンドの使い分けについて
今回は高レベル(s3)コマンドでオブジェクトを移動し、移動した後のオブジェクトに対してAPI レベル(s3 api)コマンドでタグ付けしました。
AWS CLIではaws s3 mv
コマンドでオブジェクトを移動できますが、このときに合わせてタグを付与するオプションはありませんでした。この仕様については、AWS CLIのGitHubレポジトリでも長い間議論されていることからも分かりました。
ちなみに、Python SDKではcopy_object
メソッドでオブジェクトをコピーする際にTagging
引数で指定できそうでした。
そもそもAWS CLIの出力を取得して加工して……というくらい複雑なことをするのであれば、Pythonでスクリプトを実装する方が良いかもしれません。
なお、CopyObjectアクション自体にはリクエストパラメータとしてリクエストに含めたタグに置換するx-amz-tagging
があるので、単にAWS CLIとしてそういう仕様となっているようでした。
最後に
AWS CLIでS3バケット上の指定したパスのオブジェクトを移動し、ついでにタグを付けるというシェルスクリプトの実装例でした。
今回は元々aws s3 mv
コマンドを使ったスクリプトを運用していてそれに機能追加したいという前提だったのですが、思ったよりもAWS CLIの仕様やコマンドの使い分けなど前提として知っておくべきことがあり苦戦しました。
ある程度複雑な処理でもあるため、これくらいの複雑さになれば、シェルスクリプトではなくPythonスクリプトに替えてしまうことも選択肢に入れても良いかもしれませんね。