AWS CLIでバージョニングを有効にしたS3のオブジェクトを一括削除する

## はじめに

バージョニングが有効なS3のオブジェクトはVersionsとDeleteMarkersの両方を削除しないと、
S3上からデータを完全に削除することができません。

また、AWSコンソール上で作成したフォルダはコンソール上からは削除できなかったり、少々ハマりどころがあります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597765/d47426e5-581e-36fa-7dba-3fba8b2facb9.png)

そのため、一括削除用のシェルスクリプトを作成しました。
（追記：AWS CLIの出力結果がJSON形式で、Nodeで対応した方が楽そうだったので勉強がてらNodeでも対応してみました。）

## 作成したシェル

```sh:delete-s3-object.sh
echo 開始！

# バケット名
S3_BUCKET_NAME="バケット名"
# 削除対象をキーで絞り込み ※S3のフォルダパス
S3_OBJECT_KEY="test_dir/"
# Versionsの行数
versionsRows=1
# DeleteMarkersの行数
deleteMarkersRows=1
# key名のリスト
keys=()
# バージョンIDのリスト
versionIds=()

# Versionsの情報を取得
for i in $(
  aws s3api list-object-versions --bucket ${S3_BUCKET_NAME} \
    --query "Versions[?contains(Key,'${S3_OBJECT_KEY}')].{Key:Key, id:VersionId}" --output text
);
do
if [ $((${versionsRows} % 2)) = 1 ]; then
  # 奇数
  keys+=(${i})
else
  # 偶数
  versionIds+=(${i})
fi
let versionsRows++
done

# DeleteMarkersの情報を取得
for i in $(
  aws s3api list-object-versions --bucket ${S3_BUCKET_NAME} \
    --prefix ${S3_OBJECT_KEY} \
    --query "DeleteMarkers[?contains(Key,'${S3_OBJECT_KEY}')].{Key:Key, id:VersionId}" --output text
);
do
if [ $((${deleteMarkersRows} % 2)) = 1 ]; then
  # 奇数
  keys+=(${i})
else
  # 偶数
  versionIds+=(${i})
fi
let deleteMarkersRows++
done

# VersionsとDeleteMarkersの削除処理
count=0
for key in ${keys[@]}; do
  echo ${key}
  echo ${versionIds[count]}
  # 1件ずつkeyとversionIdを指定して削除する
  aws s3api delete-object --bucket ${S3_BUCKET_NAME} \
    --key ${key} --version-id ${versionIds[count]}
  let count++
done

echo 終了
```

`S3_BUCKET_NAME`と`S3_OBJECT_KEY`を任意の値に置き換えてご使用ください。
（※`S3_OBJECT_KEY`を空にするとバケット内のすべてのオブジェクトが消えるのでご注意ください。）

実行
```sh
sh delete-s3-object.sh
```

## Nodeで処理する

```js:delete-s3-object.js
const { execSync } = require('child_process')

const S3_BUCKET_NAME = "バケット名"
const S3_OBJECT_KEY = "S3のフォルダパス"

const stdout = execSync(
  `aws s3api list-object-versions \
  --bucket '${S3_BUCKET_NAME}' \
  --prefix '${S3_OBJECT_KEY}'`,
  { maxBuffer: 2048 * 1024 } // デフォルトの1024 * 1024 からバッファ上限を増やす
)

const results = stdout.toString()
if (!results) {
  console.log("ファイルが見つかりません")
  return
}

// 出力結果の文字列をJSONに変換
const jsonData = JSON.parse(results)

if (jsonData.Versions && jsonData.Versions.length) {
  console.log("Versionsの削除")
  jsonData.Versions.forEach(version => {
    const stdout = execSync(
      `aws s3api delete-object --bucket '${S3_BUCKET_NAME}' \
      --key '${version.Key}' --version-id ${version.VersionId}`
    )
    console.log(stdout.toString())
  })
}

if (jsonData.DeleteMarkers && jsonData.DeleteMarkers.length) {
  console.log("DeleteMarkersの削除")
  jsonData.DeleteMarkers.forEach(deleteMarker => {
    const stdout = execSync(
      `aws s3api delete-object --bucket '${S3_BUCKET_NAME}' \
      --key '${deleteMarker.Key}' --version-id ${deleteMarker.VersionId}`
    )
    console.log(stdout.toString())
  })
}
```

実行
```sh
node delete-s3-object.js
```

## 補足

調べたところ、他にも同じようなことをされている方がすでにいらっしゃいました。
私自身、シェルやLinuxコマンドについて精通している訳ではないので、他の方々の記事も参考にしていただければと思います。
以上、少しでも参考になれば幸いです。

[バージョニングを有効にしたS3で、オブジェクトを一気に削除したい](https://qiita.com/charon/items/d1dafba531714f6fddc4)
[【AWS】CLIからバージョニングされたS3バケットを削除する方法](https://predora005.hatenablog.com/entry/2021/04/19/190000)
[シェルコマンドを実行する方法(child_process)](https://www.wakuwakubank.com/posts/728-nodejs-child-process/)