之前我的文件上传到存储文件夹中。但现在我想在 s3 存储桶上上传图像。如何迁移 s3 存储桶上现有的本地数据?
我在这里找到了脚本https://www.stefanwiert.de/blog/2018/11/05/active-storage-migrate- Between-providers-from-local-to-amazon/ 但出现错误
NoMethodError(为 Active Storage 调用私有方法“open”
那么我应该如何将本地数据迁移到 s3 存储桶呢?
有没有更简单的方法?
根据 Dorian 的回答,我构建了一个更简单的版本。对于此版本,您不需要选择类或知道/关心如何调用类内的方法。
它应该也适用于
has_many_attached
(因为我们从附件本身开始)
就像 Dorian 的版本一样,您需要在此之前添加 s3 的配置并部署它
ActiveStorage::Attachment.find_each do |at|
next unless at.blob.service_name == "local"
begin
blob = at.blob
blob.open do |f|
at.record.send(at.name).attach(io: f, content_type: blob.content_type, filename: blob.filename)
end
blob.destroy
rescue ActiveStorage::FileNotFoundError
# Add some message or warning here if you fancy
end
end
嗨,我也遇到了这个错误,我已将脚本更改为 rake 任务,如下所示:
# frozen_string_literal: true
namespace :active_storage do
desc 'Migrate ActiveStorage files from local to Amazon S3'
task migrate: :environment do
module ActiveStorage
class Downloader
def initialize(blob, tempdir: nil)
@blob = blob
@tempdir = tempdir
end
def download_blob_to_tempfile
open_tempfile do |file|
download_blob_to file
verify_integrity_of file
yield file
end
end
private
attr_reader :blob, :tempdir
def open_tempfile
file = Tempfile.open(["ActiveStorage-#{blob.id}-", blob.filename.extension_with_delimiter], tempdir)
begin
yield file
ensure
file.close!
end
end
def download_blob_to(file)
file.binmode
blob.download { |chunk| file.write(chunk) }
file.flush
file.rewind
end
def verify_integrity_of(file)
raise ActiveStorage::IntegrityError unless Digest::MD5.file(file).base64digest == blob.checksum
end
end
end
module AsDownloadPatch
def open(tempdir: nil, &block)
ActiveStorage::Downloader.new(self, tempdir: tempdir).download_blob_to_tempfile(&block)
end
end
Rails.application.config.to_prepare do
ActiveStorage::Blob.send(:include, AsDownloadPatch)
end
def migrate(from, to)
configs = Rails.configuration.active_storage.service_configurations
from_service = ActiveStorage::Service.configure from, configs
to_service = ActiveStorage::Service.configure to, configs
ActiveStorage::Blob.service = from_service
puts "#{ActiveStorage::Blob.count} Blobs to go..."
ActiveStorage::Blob.find_each do |blob|
print '.'
file = Tempfile.new("file#{Time.now}")
file.binmode
file << blob.download
file.rewind
checksum = blob.checksum
to_service.upload(blob.key, file, checksum: checksum)
rescue Errno::ENOENT
puts 'Rescued by Errno::ENOENT statement.'
next
end
end
migrate(:local, :s3)
end
end
一种更简单的方法:
amazon
部分添加到您的 config/storage.yml
喜欢
将您的存储服务更改为
:amazon
中的
config/environments/production.rb
将此 rake 任务添加为
lib/tasks/storage.rake
namespace :storage do
task reupload: :environment do
[User, Event].each do |clazz|
collection = clazz.with_attached_image
puts "#{clazz} has #{collection.count} images"
collection.find_each do |user|
next unless user.image.blob
user
.image
.blob
.open do |f|
user.image.attach(io: f, filename: user.image.blob.filename)
end
print "."
end
puts
end
end
end
在本地运行并测试以使用
rake storage:reupload
进行尝试(并在 config.active_storage.service = :amazon
中更改为 config/environments/development.rb
)
检查本地一切是否正常(您的图像应链接到您的 AWS URL)
上传到您的服务器(例如
cap deploy
)
在项目目录下运行
RAILS_ENV=production bundle exec rake storage:reupload
稍等一下,具体取决于您重新上传的图像数量
利润!享受!派对时间!
优点
缺点
has_many_attached
但不应该有太多改变image
(没有什么很难修复的)namespace :active_storage do
desc 'Migrate ActiveStorage files from local to minio'
task migrate: :environment do
module ActiveStorage
class Downloader
def initialize(blob, tempdir: nil)
@blob = blob
@tempdir = tempdir
end
def download_blob_to_tempfile
open_tempfile do |file|
download_blob_to file
verify_integrity_of file
yield file
end
end
private
attr_reader :blob, :tempdir
def open_tempfile
file = Tempfile.open(["ActiveStorage-#{blob.id}-", blob.filename.extension_with_delimiter], tempdir)
begin
yield file
ensure
file.close!
end
end
def download_blob_to(file)
file.binmode
blob.download { |chunk| file.write(chunk) }
file.flush
file.rewind
end
def verify_integrity_of(file)
raise ActiveStorage::IntegrityError unless Digest::MD5.file(file).base64digest == blob.checksum
end
end
end
module AsDownloadPatch
def open(tempdir: nil, &block)
ActiveStorage::Downloader.new(self, tempdir: tempdir).download_blob_to_tempfile(&block)
end
end
Rails.application.config.to_prepare do
ActiveStorage::Blob.send(:include, AsDownloadPatch)
end
def migrate(from, to)
config_file = Rails.root.join("config/storage.yml")
configs = ActiveSupport::ConfigurationFile.parse(config_file)
from_service = ActiveStorage::Service.configure from, configs
to_service = ActiveStorage::Service.configure to, configs
ActiveStorage::Blob.service = from_service
puts "#{ActiveStorage::Blob.count} Blobs to go..."
ActiveStorage::Blob.find_each do |blob|
print '.'
file = Tempfile.new("file#{Time.now}")
file.binmode
file << blob.download
file.rewind
checksum = blob.checksum
to_service.upload(blob.key, file, checksum: checksum)
rescue Errno::ENOENT
puts 'Rescued by Errno::ENOENT statement.'
next
end
end
migrate(:local, :minio)
end
end
将本地文件迁移到 s3-service 的更简单方法是:
—首先设置为镜像,
— 将所有镜像与 rake 同步,
— 删除镜像设置,
—检查后删除本地文件
我想这个解决方案会有帮助: 如何同步新的 ActiveStorage 镜像?
根据 RobbeVP 的回答,我使它可以完成以下任务:
namespace :active_storage do
task reupload_to_s3: :environment do
raise "Please switch the active storage service to 'amazon' first" if ENV['ACTIVE_STORAGE_SERVICE'] != 'amazon'
ActiveStorage::Attachment.find_each do |at|
next unless at.blob.service_name == "local"
begin
blob = at.blob
blob.open do |f|
at.record.send(at.name).attach(io: f, content_type: blob.content_type, filename: blob.filename)
end
rescue ActiveStorage::FileNotFoundError
puts "FileNotFoundError: ActiveStorage::Attachment##{at.id}"
end
end
end
task delete_local_attachments: :environment do
ActiveStorage::Attachment.find_each do |at|
next unless at.blob.service_name == "local"
at.purge
end
end
end
这是更新且更具描述性的任务
# rails active_storage:migrate
namespace :active_storage do
desc 'Migrate active storage from one service to another'
task migrate: :environment do
module AsDownloadPatch
def open(tempdir: nil, &block)
ActiveStorage::Downloader.new(self, tempdir: tempdir).download_blob_to_tempfile(&block)
end
end
Rails.application.config.to_prepare do
ActiveStorage::Blob.send(:include, AsDownloadPatch)
end
def log_info(message)
puts "[INFO] #{Time.now.strftime('%Y-%m-%d %H:%M:%S')} - #{message}"
end
def log_error(message)
puts "[ERROR] #{Time.now.strftime('%Y-%m-%d %H:%M:%S')} - #{message}"
end
def migrate(from, to)
log_info "Started migrating Assets from #{from} to #{to}"
orphaned_attachments = 0
missing_file_count = 0
success_file_count = 0
config_file = Rails.root.join("config/storage.yml")
configs = ActiveSupport::ConfigurationFile.parse(config_file)
from_service = ActiveStorage::Service.configure(from, configs)
to_service = ActiveStorage::Service.configure(to, configs)
ActiveStorage::Blob.service = to_service
target_attchments = ActiveStorage::Attachment.joins(:blob).where(blob: {service_name: from.to_s})
log_info "Total #{target_attchments.count} attachments to be processed"
target_attchments.find_each do |attachment|
if orphan_attachment?(attachment)
orphaned_attachments += 1
log_info "Orphan attachment found: id = #{attachment.id}, resource = #{attachment.record_type}(#{attachment.record_id})"
next
end
begin
blob = attachment.blob
if blob.present?
blob.open do |file|
attachment.record.send(attachment.name).attach(io: file, content_type: blob.content_type, filename: blob.filename)
end
success_file_count += 1
else
log_info "Missing blob from attachment = #{attachment.id}"
end
rescue ActiveStorage::FileNotFoundError => e
missing_file_count += 1
log_error "File Not found for attachment = #{attachment.id}, blob = #{blob.id}"
rescue => err
log_error "Failed to upload attachment for record #{attachment.record.class_name}: #{attachment.record.id}"
end
end
log_info "Migration completed! #{success_file_count} Successfully migrated !\n\n"
log_info "#{orphaned_attachments} orphaned attachments found and skipped. \n\n"
log_info "#{missing_file_count} Files Missing to upload!"
end
def orphan_attachment?(attachment)
attachment.record_type.safe_constantize.nil? || attachment.record.nil?
end
migrate(:local, :amazon)
end
end