Ruby を使って AWS S3 のある場所のファイルを別のバケットにまとめてコピーする方法を紹介します。
環境
Rails の中のタスクのひとつとして作成しましたが Rails なくても実装可能です。
- Rails 6.0.2.1
- Ruby 2.6.5
- Gem aws-sdk-s3 1.60.1
やりたかったこと
S3 の bucket/folder
にあるファイルたちを全て another_bucket/folder
内 に構造を保った状態でコピーしたかった。
プログラム
コピーに使用したプログラムを紹介します。
Rails の中のタスクのひとつとして作成しました。 bundle add aws-sdk-s3
を実行して AWS の SDK を追加しておきます。
# S3 SDK reference: https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html
require 'uri'
namespace :s3 do
namespace :something do
task copy: :environment do
s3_client = build_s3_client
copy_bucket_to_bucket(s3_client, 'bucket', 'another_bucket', 'folder/')
end
end
def build_s3_client
Aws::S3::Client.new(
access_key_id: Rails.configuration.x.s3_user.access_key,
secret_access_key: Rails.configuration.x.s3_user.access_secret,
region: 'ap-northeast-1'
)
end
# @param [Aws::S3::Client] s3_client
# @param [String] source_bucket
# @param [String] destination_bucket
# @param, [String] prefix
def copy_bucket_to_bucket(s3_client, source_bucket, destination_bucket, prefix = '')
s3_client.list_objects(bucket: source_bucket, prefix: prefix).to_h[:contents].each do |object|
puts "copy #{object[:key]}"
option = {
#acl: 'public-read',
bucket: destination_bucket,
copy_source: URI::encode("/#{source_bucket}/" << object[:key]),
key: object[:key],
}
s3_client.copy_object(option)
end
end
end
copy_bucket_to_bucket
が実際に S3 内 でのコピーを行うメソッドです。
注意
Bucket のデフォルトの設定では Access Denied というエラーが出るので acl: 'public-read',
をコメントアウトしています。
下のように Bucket の設定で 2つのチェックボックスをオフにすると acl: 'public-read'
が使えるようになります。
別アカウントのS3にコピーする場合
require 'uri'
require 'aws-sdk-s3'
def build_source_s3_client
Aws::S3::Client.new(
access_key_id: 'SOURCE_ACCESS_KEY',
secret_access_key: 'SOURCE_ACCESS_SECRET',
region: 'ap-northeast-1'
)
end
def build_destination_s3_client
Aws::S3::Client.new(
access_key_id: 'DESTINATION_ACCESS_KEY',
secret_access_key: 'DESTINATION_ACCESS_SECRET',
region: 'ap-northeast-1'
)
end
# @param [Aws::S3::Client] source_s3_client
# @param [String] source_bucket
# @param [Aws::S3::Client] destination_s3_client
# @param [String] destination_bucket
# @param [String] prefix
def copy_bucket_to_bucket(
source_s3_client, source_bucket, destination_s3_client, destination_bucket, prefix = '')
source_s3_client.list_objects(bucket: source_bucket, prefix: prefix).to_h[:contents].each do |object|
option = {
bucket: destination_bucket,
copy_source: URI::encode("/#{source_bucket}/" << object[:key]),
key: object[:key],
}
# if already exists, skip
contents = destination_s3_client.list_objects(bucket: destination_bucket, prefix: object[:key]).to_h[:contents]
if contents != nil && contents.size > 0
puts "skip #{object[:key]}"
sleep 0.1
next
end
puts "copy #{object[:key]}"
source_s3_client.get_object(bucket: source_bucket, key: object[:key]) do |chunk|
destination_s3_client.put_object(
bucket: destination_bucket, key: object[:key], body: chunk)
end
end
end
source_s3_client = build_source_s3_client
destination_s3_client = build_destination_s3_client
copy_bucket_to_bucket(
source_s3_client, 'source-bucket',
destination_s3_client, 'destination-bucket', '')
使用した環境
- ruby 2.5.3p105
- Gem aws-sdk-s3 (1.145.0)