demarches-normaliennes/app/services/carrierwave_active_storage_migration_service.rb

174 lines
5.5 KiB
Ruby
Raw Normal View History

class CarrierwaveActiveStorageMigrationService
def ensure_openstack_copy_possible!(uploader)
ensure_active_storage_openstack!
ensure_carrierwave_openstack!(uploader)
ensure_active_storage_and_carrierwave_credetials_match(uploader)
end
def ensure_active_storage_openstack!
# If we manage to get the client, it means that ActiveStorage is on OpenStack
openstack_client!
end
def openstack_client!
@openstack_client ||= active_storage_openstack_client!
end
def active_storage_openstack_client!
service = ActiveStorage::Blob.service
if defined?(ActiveStorage::Service::DsProxyService) &&
service.is_a?(ActiveStorage::Service::DsProxyService)
service = service.wrapped
end
if !defined?(ActiveStorage::Service::OpenStackService) ||
!service.is_a?(ActiveStorage::Service::OpenStackService)
raise StandardError, 'ActiveStorage must be backed by OpenStack'
end
service.client
end
def ensure_carrierwave_openstack!(uploader)
storage = fog_client!(uploader)
if !defined?(Fog::OpenStack::Storage::Real) ||
!storage.is_a?(Fog::OpenStack::Storage::Real)
raise StandardError, 'Carrierwave must be backed by OpenStack'
end
end
def fog_client!(uploader)
storage = uploader.new.send(:storage)
if !defined?(CarrierWave::Storage::Fog) ||
!storage.is_a?(CarrierWave::Storage::Fog)
raise StandardError, 'Carrierwave must be backed by a Fog provider'
end
storage.connection
end
# OpenStack Swift's COPY object command works across different buckets, but they still need
# to be on the same object store. This method tries to ensure that Carrierwave and ActiveStorage
# are indeed pointing to the same Swift store.
def ensure_active_storage_and_carrierwave_credetials_match(uploader)
auth_keys = [
:openstack_tenant,
:openstack_api_key,
:openstack_username,
:openstack_region,
:openstack_management_url
]
active_storage_creds = openstack_client!.credentials.slice(*auth_keys)
carrierwave_creds = fog_client!(uploader).credentials.slice(*auth_keys)
if active_storage_creds != carrierwave_creds
raise StandardError, "Active Storage and Carrierwave credentials must match"
end
end
# If identify is true, force ActiveStorage to examine the beginning of the file
# to determine its MIME type. This identification does not happen immediately,
# but when the first attachment that references this blob is created.
def make_blob(uploader, created_at, filename: nil, identify: false)
content_type = uploader.content_type
identified = content_type.present? && !identify
ActiveStorage::Blob.create(
filename: filename || uploader.filename,
content_type: content_type,
byte_size: uploader.size,
checksum: checksum(uploader),
created_at: created_at,
metadata: { identified: identified, virus_scan_result: ActiveStorage::VirusScanner::SAFE }
)
end
def make_empty_blob(uploader, created_at, filename: nil)
content_type = uploader.content_type || 'text/plain'
blob = ActiveStorage::Blob.build_after_upload(
io: StringIO.new('File not found when migrating from CarrierWave.'),
filename: filename || uploader.filename,
content_type: content_type || 'text/plain',
metadata: { virus_scan_result: ActiveStorage::VirusScanner::SAFE }
)
blob.created_at = created_at
blob.save!
blob
end
def checksum(uploader)
hex_to_base64(uploader.file.send(:file).etag)
end
def hex_to_base64(hexdigest)
[[hexdigest].pack("H*")].pack("m0")
end
def copy_from_carrierwave_to_active_storage!(source_name, blob)
openstack_client!.copy_object(
carrierwave_container_name,
source_name,
active_storage_container_name,
blob.key
)
fix_content_type(blob)
end
def carrierwave_container_name
Rails.application.secrets.fog[:directory]
end
def active_storage_container_name
ENV['FOG_ACTIVESTORAGE_DIRECTORY']
end
def delete_from_active_storage!(blob)
openstack_client!.delete_object(
active_storage_container_name,
blob.key
)
end
# Before calling this method, you must make sure the file has been uploaded for the blob.
# Otherwise, this method might fail if it needs to read the beginning of the file to
# update the blobs MIME type.
def make_attachment(model, attachment_name, blob)
attachment = ActiveStorage::Attachment.create(
name: attachment_name,
record_type: model.class.base_class.name,
record_id: model.id,
blob: blob,
created_at: model.updated_at.iso8601
)
# Making the attachment may have triggerred MIME type auto detection on the blob,
# so we make sure to sync that potentially new MIME type to the object in OpenStack
fix_content_type(blob)
attachment
end
def fix_content_type(blob, retry_delay: 5)
retries ||= 0
# In OpenStack, ActiveStorage cannot inject the MIME type on the fly during direct
# download. Instead, the MIME type needs to be stored statically on the file object
# in OpenStack. This is what this call does.
blob.service.change_content_type(blob.key, blob.content_type)
rescue
# When we quickly create a new attachment, and then change its content type,
# the Object Storage may not be synchronized yet. It this cas, it will return a
# "409 Conflict" error.
#
# Wait for a while, then try again twice (before giving up).
sleep(retry_delay)
retry if (retries += 1) < 3
raise
end
end