Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Script to migrate images between providers #9117

Open
wants to merge 6 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
35 changes: 35 additions & 0 deletions lib/active_storage/migrator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
require 'yaml'
require 'erb'

module ActiveStorage
class Migrator
def self.migrate(from_service_name, to_service_name)
yaml_with_env = ERB.new(File.read('config/storage.yml')).result
configs = YAML.load(yaml_with_env)

from_service = ActiveStorage::Service.configure(from_service_name, { from_service_name.to_sym => configs[from_service_name.to_s] })
to_service = ActiveStorage::Service.configure(to_service_name, { to_service_name.to_sym => configs[to_service_name.to_s] })

# Check if services are configured correctly
if from_service.nil? || to_service.nil?
puts "Error: The services '#{from_service_name}' or '#{to_service_name}' are not configured correctly."
return
end

# Configure the blob service for the source service
ActiveStorage::Blob.service = from_service

puts "#{ActiveStorage::Blob.count} Blobs to migrate from #{from_service_name} to #{to_service_name}"
ActiveStorage::Blob.find_each do |blob|
next unless blob.image?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line prevents uploading of audio files received via WhatsApp. It will possibly impact PDF's and other files in general.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @hiagodotme, of course, I assumed they wanted to export only images because the issue specifically mentioned images, but we can add other types of files without a problem. 👍🏼


print '.'

blob.open do |io|
checksum = blob.checksum
to_service.upload(blob.key, io, checksum: checksum)
end
end
end
end
end
13 changes: 13 additions & 0 deletions lib/tasks/storage_migrations.rake
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace :storage do
desc 'Migrate blobs from one storage service to another'
task migrate: :environment do
from_service = ENV['FROM']
to_service = ENV['TO']

if from_service.nil? || to_service.nil?
raise 'Missing FROM or TO argument. Usage: FROM=service_name TO=service_name rake storage:migrate'
end

ActiveStorage::Migrator.migrate(from_service.to_sym, to_service.to_sym)
end
end
34 changes: 34 additions & 0 deletions spec/lib/active_storage/migrator_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
require 'rails_helper'

RSpec.describe ActiveStorage::Migrator do
describe '.migrate' do
let(:from_service_stub) { double('from_service') }
let(:to_service_stub) { double('to_service') }
let(:blob_spy) { spy('ActiveStorage::Blob') }

before do
allow(ActiveStorage::Service).to receive(:configure).with(:from_service, any_args).and_return(from_service_stub)
allow(ActiveStorage::Service).to receive(:configure).with(:to_service, any_args).and_return(to_service_stub)
allow(ActiveStorage::Blob).to receive(:find_each).and_yield(blob_spy)
end

context 'when services are configured correctly' do
it 'migrates blobs from one service to another' do
allow(blob_spy).to receive(:image?).and_return(true)

expect(ActiveStorage::Service).to receive(:configure).with(:from_service, any_args)
expect(ActiveStorage::Service).to receive(:configure).with(:to_service, any_args)

described_class.migrate(:from_service, :to_service)
end
end

context 'when services are not configured correctly' do
it 'prints an error message' do
allow(from_service_stub).to receive(:nil?).and_return(true)

expect { described_class.migrate(:from_service, :to_service) }.to output("Error: The services 'from_service' or 'to_service' are not configured correctly.\n").to_stdout
end
end
end
end
28 changes: 28 additions & 0 deletions spec/lib/tasks/storage_migrations_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
require 'rails_helper'
require 'rake'

RSpec.describe 'storage_migrations' do
describe 'rake task' do

context 'when FROM argument is missing' do
before do
ENV['FROM'] = nil
end

it 'raises an error' do
expect { Rake::Task['storage:migrate'].invoke }.to raise_error(RuntimeError, 'Missing FROM or TO argument. Usage: FROM=service_name TO=service_name rake storage:migrate')
end
end

context 'when TO argument is missing' do
before do
ENV['FROM'] = 'service_name'
ENV['TO'] = nil
end

it 'raises an error' do
expect { Rake::Task['storage:migrate'].invoke }.to raise_error(RuntimeError, 'Missing FROM or TO argument. Usage: FROM=service_name TO=service_name rake storage:migrate')
end
end
end
end
4 changes: 4 additions & 0 deletions spec/rails_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
require 'pundit/rspec'
require 'sidekiq/testing'

# Load Rake tasks
require 'rake'
Rails.application.load_tasks

# test-prof helpers for tests optimization
require 'test_prof/recipes/rspec/before_all'
require 'test_prof/recipes/rspec/let_it_be'
Expand Down