From 5c350348d63af1b1cfd807412cc11fc616044da2 Mon Sep 17 00:00:00 2001 From: Francisco Brito Date: Fri, 15 Mar 2024 15:48:27 -0300 Subject: [PATCH] feature/#7907-script-to-change-local-storage-to-aws --- lib/active_storage/migrator.rb | 35 +++++++++++++++++++++++ lib/tasks/storage_migrations.rake | 13 +++++++++ spec/lib/active_storage/migrator_spec.rb | 34 ++++++++++++++++++++++ spec/lib/tasks/storage_migrations_spec.rb | 28 ++++++++++++++++++ spec/rails_helper.rb | 4 +++ 5 files changed, 114 insertions(+) create mode 100644 lib/active_storage/migrator.rb create mode 100644 lib/tasks/storage_migrations.rake create mode 100644 spec/lib/active_storage/migrator_spec.rb create mode 100644 spec/lib/tasks/storage_migrations_spec.rb diff --git a/lib/active_storage/migrator.rb b/lib/active_storage/migrator.rb new file mode 100644 index 000000000..55787b962 --- /dev/null +++ b/lib/active_storage/migrator.rb @@ -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? + + print '.' + + blob.open do |io| + checksum = blob.checksum + to_service.upload(blob.key, io, checksum: checksum) + end + end + end + end +end diff --git a/lib/tasks/storage_migrations.rake b/lib/tasks/storage_migrations.rake new file mode 100644 index 000000000..93d085545 --- /dev/null +++ b/lib/tasks/storage_migrations.rake @@ -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 diff --git a/spec/lib/active_storage/migrator_spec.rb b/spec/lib/active_storage/migrator_spec.rb new file mode 100644 index 000000000..78cd687be --- /dev/null +++ b/spec/lib/active_storage/migrator_spec.rb @@ -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 diff --git a/spec/lib/tasks/storage_migrations_spec.rb b/spec/lib/tasks/storage_migrations_spec.rb new file mode 100644 index 000000000..6d7d517ed --- /dev/null +++ b/spec/lib/tasks/storage_migrations_spec.rb @@ -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 diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 616d62d28..0dc019378 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -7,6 +7,10 @@ require 'rspec/rails' 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'