AWS S3バケット上のファイルを、一定期間経過後に自動削除する(Ruby)


定期的にプログラムで生成するような、サイズも少し大きめのファイルを外部のメンバーとやり取りをする場合、AWSのS3のバケットを一時ファイル置き場にすることがあります。

がっ！日々自動アップロードされるファイルによってS3上のファイル数・容量がモリモリ成長し、ボディーブローのようにお財布を削っていくのに対し、古いファイルを削除することをすっかり忘れちゃうことってありますよね。セキュリティ的にも古いファイルを置きっぱにするのはよろしくありません。

そんな事態を防ぐため、S3にファイルをアップして一定期間後に自動的に削除するメソッドを作りましたので、S3破産する前に使ってみてくださいませ。

## 準備：aws-sdkに、一定期間後にファイル自動削除するメソッドを追加

RubyのAWS操作用標準Gemであるaws-sdkの`Aws::S3::Client`クラスに`delete_expired_objects`という名前でメソッドをオーバーライドしますので、いつもどおりの`aws-sdk`の使用感のまま使用することができます。プロジェクト内の任意のファイルに、以下のクラスを設置してください。


```delete_expired_objects.rb
module Aws
  module S3
    class Client

      # 保管期限を過ぎたS3上のオブジェクトを削除する
      def delete_expired_objects(options)
        # 保管期限設定
        today = Time.now
        time_limit = today - options[:keep_time]

        # バケット内のファイル一覧取得
        # （ファイル1000件毎にループしながら取得）
        resp = self.list_objects({bucket: options[:bucket], prefix: options[:prefix]})
        loop do
          resp.contents.each do |object|
            # フォルダ(object名の末尾がスラッシュ)は対象外としてスキップ
            next if object.key[-1] == "/"

            # 保管期限を過ぎたファイルがあれば削除
            if object.last_modified < time_limit
              self.delete_object({bucket: options[:bucket], key: object.key})
            end
          end

          # 1000件以上オブジェクトがあれば、次の1000件を取得
          break if resp.last_page?
          resp = resp.next_page
        end
      end

    end
  end
end
```

## 自動削除実行をcronで定期実行する

以下のように実行用のスクリプトを書いて、cronでdailyくらいの頻度で実行してください。特殊な変数としては`TARGET_DIR`にS3のバケット上に作成した、削除対象としたいディレクトリ名を、`KEEP_TIME`にファイルを保持させて置きたい期間を秒単位で入力します。

```clean_bucket.rb
require_relative 'delete_expired_objects.rb
require 'aws-sdk'

# バケット接続設定
REGION            = "YOUR_REGION"
ACCESS_KEY        = "YOUR_ACCESS_KEY"
SECRET_ACCESS_KEY = "YOUR_SECRET_ACCESS_KEY"
BUCKET_NAME       = "YOUR_BUCKET_NAME"

# 削除対象ディレクトリ、保管期間設定
TARGET_DIR        = "test_dir/"
KEEP_TIME         = 60 * 5 # 5minutes

# S3インスタンス作成
Aws.config.update({
  region: REGION,
  credentials: Aws::Credentials.new(ACCESS_KEY, SECRET_ACCESS_KEY)
})
s3 = Aws::S3::Client.new

options = {
  bucket: BUCKET_NAME,
  prefix: TARGET_DIR,
  keep_time: KEEP_TIME
}

# 削除実行
s3.delete_expired_objects(options)
```

test.gifをアップロードから5分後に削除したいケースで試します。

test.gifのアップロードは15:58:19です。約3分後にスクリプトを実行しても、test.gifは削除されず、無事にバケット上に残っています。


```zsh
$ aws s3 ls s3://bucket_name/test_dir/
2016-01-21 15:57:07          0
2016-01-21 15:58:19        173 test.gif

$ date
2016年 1月21日 木曜日 16時01分09秒 JST

$ bundle exec ruby clean_bucket.rb

$ aws s3 ls s3://bucket_name/test_dir/
2016-01-21 15:57:07          0
2016-01-21 15:58:19        173 test.gif
```

アップロードから6分くらいたってスクリプトを実行すると…

```zsh
$ aws s3 ls s3://bucket_name/test_dir/
2016-01-21 15:57:07          0
2016-01-21 15:58:19        173 test.gif

$ date
2016年 1月21日 木曜日 16時04分03秒 JST

$ bundle exec ruby clean_bucket.rb

$ aws s3 ls s3://bucket_name/test_dir/
2016-01-21 15:57:07          0
```

test.gifがキレイに消えてます！

これでS3上は常がクリーンな状態に保たれます。＼(^o^)／

## バケットのライフサイクルルールとの違い

バケットのライフサイクルルール（ライフサイクルイベント）でもファイルの自動削除はできることをコメントで教えていただきました！ありがとうございました。m(_ _)m

お手軽に設定可能なので、普段使いにはライフサイクルルールで十分なケースが多そうです。ただ、ライフサイクルルールには、

- 1日単位でしか、自動削除のサイクルを選べない
- ルール設定のプレフィックスで「XXX/」と指定すると、バケット内のフォルダXXX/自体も削除されてしまう
- 細かい対象選択のカスタマイズができない

などの制約があるようなので、上記制約でお困りのかたは自作スクリプトでお掃除したほうが便利な場合もありそうですね。