Skip to content

Instantly share code, notes, and snippets.

@libkazz
Last active January 8, 2025 14:04
Show Gist options
  • Save libkazz/8d6ad4d315440118938f358ca839485d to your computer and use it in GitHub Desktop.
Save libkazz/8d6ad4d315440118938f358ca839485d to your computer and use it in GitHub Desktop.
S3 バージョニングをつかって誤って削除/更新したファイルを戻す
require 'json'
class Revert
attr_reader :uploaded_files, :deleted_files, :bucket_name
# S3 のバージョニングからファイルを復旧させる
# NOTE: 更新していないファイルを指定しても1つバージョンを戻してしまうことに注意
#
# - 誤って更新してしまったファイルを直前のバージョンに戻す
# - 誤って削除してしまったファイルを戻す
#
def self.run(uploaded_files: [], deleted_files: [], bucket_name:)
new(bucket_name: bucket_name)
.execute(uploaded_files: uploaded_files, deleted_files: deleted_files)
end
def initialize(bucket_name:)
@bucket_name = bucket_name
end
def execute(uploaded_files: [], deleted_files: [])
uploaded_files.each do |path|
revert_to_previous_version!(path)
end
deleted_files.each do |path|
restore_deleted!(path)
end
end
def revert_to_version!(path, version)
command = "aws s3api copy-object " \
+ "--bucket #{bucket_name} " \
+ "--copy-source #{bucket_name}/#{path}?versionId=#{version} " \
+ "--key #{path}"
system(command)
puts "Revert: #{path} (#{version})"
end
# 1つ前のバージョンに戻す
def revert_to_previous_version!(path)
version = previous_version(path)
return nil unless version
revert_to_version!(path, version)
end
# 削除フラグを削除してファイルを復旧する
def restore_deleted!(path)
version = delete_marker_version(path)
command = "aws s3api delete-object --bucket #{bucket_name} --key #{path} --version-id #{version}"
system(command)
puts "Restore: #{path} (#{version})"
end
def previous_version(path)
command = "aws s3api list-object-versions --bucket '#{bucket_name}' --prefix #{path} "
# + "--query 'Versions[].[Key, VersionId, IsLatest]'"
response = JSON.parse(`#{command}`)
previous = response["Versions"]
.select { !_1["IsLatest"] }
.sort_by { _1["LastModified"] }
.last
# previous がない場合は nil を返す
previous&.fetch("VersionId")
end
def delete_marker_version(path)
command = "aws s3api list-object-versions --bucket '#{bucket_name}' --prefix #{path}"
response = JSON.parse(`#{command}`)
delete_markers = response["DeleteMarkers"]
delete_markers.detect { _1["IsLatest"] }["VersionId"]
end
end
if __FILE__ == $PROGRAM_NAME
bucket_name = "localworks.jp"
uploaded_files = File.readlines("hoge2-upload.txt")
.reject { _1.start_with?('#') }
.map do |line|
line
.chomp
.sub(%r{\A.*s3://localworks.jp/}, "")
.sub(/ +$/, "")
end
deleted_files = File.readlines("hoge2-delete.txt")
.reject { _1.start_with?('#') }
.map do |line|
line
.chomp
.sub(%r{\A.*s3://localworks.jp/}, "")
.sub(/ +$/, "")
end
Revert
.new(bucket_name: bucket_name)
.execute(uploaded_files: uploaded_files, deleted_files: deleted_files)
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment