Cloudfront + Carrierwave - ruby-on-rails

So this seems like it should be quite easy... everyone is saying just to use config.asset_host. When I set that though, all the links inside my app still point to S3.
CarrierWave.configure do |config|
config.storage = :fog
config.fog_credentials = {
:provider => 'AWS',
:aws_access_key_id => AWS_ACCESS_KEY_ID,
:aws_secret_access_key => AWS_SECRET_ACCESS_KEY,
:region => 'us-east-1'
}
config.fog_authenticated_url_expiration = 3.hours
config.asset_host = "http://xyz123.cloudfront.net"
config.fog_directory = S3_BUCKET_NAME
config.fog_public = false
config.fog_attributes = {
'Cache-Control' => "max-age=#{1.year.to_i}"
}
end
here is how I call my files...
image_tag book.attachments.first.filename.file.authenticated_url(:thumb175)
It looks to me like public_url prepends the proper host, but it takes 0 arguments... so how am I supposed to pass the proper response-content-disposition and response-content-type and the link expire time?

I had the same problem and spent far too long figuring out the answer! It turns out that when you set fog_public = false CarrierWave will ignore config.asset_host. You can demo this by setting config.fog_public = true: your URLs will now be CloudFront URLs rather than S3 URLs. This issue has been raised previously:
https://github.com/carrierwaveuploader/carrierwave/issues/1158
https://github.com/carrierwaveuploader/carrierwave/issues/1215
In a recent project I was happy using CarrierWave to handle uploads to S3, but wanted it to return a signed CloudFront URL when using Model.attribute_url. I came up with the following (admittedly ugly) workaround that I hope others can benefit from or improve upon:
Add the 'cloudfront-signer' gem to your project and configure it per the instructions. Then add the following override of /lib/carrierwave/uploader/url.rb in a new file in config/initializers (note the multiple insertions of AWS::CF::Signer.sign_url):
module CarrierWave
module Uploader
module Url
extend ActiveSupport::Concern
include CarrierWave::Uploader::Configuration
include CarrierWave::Utilities::Uri
##
# === Parameters
#
# [Hash] optional, the query params (only AWS)
#
# === Returns
#
# [String] the location where this file is accessible via a url
#
def url(options = {})
if file.respond_to?(:url) and not file.url.blank?
file.method(:url).arity == 0 ? AWS::CF::Signer.sign_url(file.url) : AWS::CF::Signer.sign_url(file.url(options))
elsif file.respond_to?(:path)
path = encode_path(file.path.gsub(File.expand_path(root), ''))
if host = asset_host
if host.respond_to? :call
AWS::CF::Signer.sign_url("#{host.call(file)}#{path}")
else
AWS::CF::Signer.sign_url("#{host}#{path}")
end
else
AWS::CF::Signer.sign_url((base_path || "") + path)
end
end
end
end # Url
end # Uploader
end # CarrierWave
Then override /lib/carrierwave/storage/fog.rb by adding the following to the bottom of the same file:
require "fog"
module CarrierWave
module Storage
class Fog < Abstract
class File
include CarrierWave::Utilities::Uri
def url
# Delete 'if statement' related to fog_public
public_url
end
end
end
end
end
Lastly, in config/initializers/carrierwave.rb:
config.asset_host = "http://d12345678.cloudfront.net"
config.fog_public = false
That's it. You can now use Model.attribute_url and it will return a signed CloudFront URL to a private file uploaded by CarrierWave to your S3 bucket.

I think you found this for yourself, but public urls will not expire. If you want that you'll need to use authenticated urls. For public urls I think you can simply get the url and append whatever query params you would like, at least for now. If that works well for you, we can certainly see about patching things to do the right thing.

Related

Can carrierwave direct upload to local storage?

Sometimes I am away from internet and still need to work on upload pages. Carrierwave Direct seems to force storage :fog; with no way of overriding in dev.
Is it possible to tell Carrierwave Direct to use local storage (:file) and simply fallback to Carrierwave's development config settings?
Setting storage :file in carrierwave initializer under development config settings doesnt work...carrierwave_direct errors with "is not a recognized provider" from "<%= direct_upload_form_for #uploader do |f| %>".
I have attempted to work around carrierwave direct, but between forcing :fog, expecting a redirect url and expecting the direct_upload_form_for form method...carrierwave_direct is pretty much in charge.
Using storage :file in development would be a welcome feature for the carrierwave_direct gem. Does anyone know how to cleanly do this?
I think it can be done as follows:
CarrierWave.configure do |config|
if Rails.env.development? || Rails.env.test?
config.storage = :file
config.asset_host = ENV["dev_url"]
else
config.fog_provider = 'fog/aws' # required
config.fog_credentials = {
provider: 'AWS', # required
aws_access_key_id: ENV["aws_id"], # required
aws_secret_access_key: ENV["aws_key"], # required
region: ENV["aws_zone"] # optional, defaults to 'us-east-1'
}
config.fog_directory = ENV["aws_bucket"] # required
config.max_file_size = 600.megabytes # defaults to 5.megabytes
config.use_action_status = true
config.fog_public = false # optional, defaults to true
config.fog_attributes = { cache_control: "public, max-age=#{365.day.to_i}" } # optional, defaults to {}
end
end
And in your Uploader add in:
class SomeUploader < CarrierWave::Uploader::Base
if Rails.env.development? || Rails.env.test?
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
end
end

Carrierwave fog local storage full attachment path

I am building a rails app with carrierwave and fog for attachment storage. In my test environment, I am using fog local storage.
I am looking for a way to get the full attachment path with this configuration.
CarrierWave.configure do |config|
config.fog_credentials = {
provider: 'Local',
local_root: '/Users/me/fog',
endpoint: '/Users/me/fog',
}
config.fog_directory = 'test.myapp.com
config.fog_public = false
config.fog_attributes = { 'Cache-Control' => 'max-age=315576000' }
end
When I use any other storage options (like AWS S3), I can get the full url to an attachment just by doing my_object.my_attachment_url or my_object.my_attachment.path.
However, when using Local storage, I only get a relative path to my configuration options like my_object/my_attachment/1/test.jpg.
Is there any way through carrierwave or fog to get the full path to this local file?
For my example, the output I am looking for would be: /Users/me/fog/test.myapp.com/my_object/my_attachment/1/test.jpg
For me, the answer was modifying to carrierwave uploader class.
I had
def store_dir
"#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
which worked fine for AWS S3 as all the S3 specific info was inserted before this string. However, to get this to work with fog Local as well, I added:
if Rails.env.test?
def base_path
"#{File.expand_path(CONFIG.fog_local_root)}/#{CONFIG.fog_directory}/"
end
else
def base_path
''
end
end

Carrierwave + Fog+ caching

Scenario: We have a few users on the site who have previously uploaded a logo for their site. Recently, we changed the dimensions of this logo and would like all accounts to reflect this change (we've also removed retina_rails from our app). So we plan on making a migration to remove retina rails while at the same time looping through each account and re uploading the logos to normalize across all logos.
Currently, this is what the migration looks like:
class RemoveRetinaDimensionsFromAccounts < ActiveRecord::Migration
def change
remove_column :accounts, :retina_dimensions, :text
end
ActsAsTenant.configure.require_tenant = false
Account.all.each do |account|
if account.logo?
account.logo.cache_stored_file!
account.logo.retrieve_from_cache!(account.logo.cache_name)
account.logo.recreate_versions!(:small, :small)
account.save!
end
end
ActsAsTenant.configure.require_tenant = true
end
This is what our carrierwave.rb file looks like:
CarrierWave.configure do |config|
if Rails.env.test?
config.storage = :file
config.enable_processing = false
elsif Rails.env.development?
config.storage = :file
config.cache_dir = "#{Rails.root}/tmp/uploads"
elsif Rails.env.staging?
config.storage = :fog
config.cache_dir = "#{Rails.root}/tmp/uploads"
config.fog_credentials = {
:provider => 'AWS', # required
:aws_access_key_id => Rails.application.secrets.aws_access_key_id, # required
:aws_secret_access_key => Rails.application.secrets.aws_secret_access_key, # required
:region => 'us-west-2' # optional, defaults to 'us-east-1'
}
config.fog_directory = 'blvd-staging' # required
config.fog_public = false
end
end
I've tried to follow the advice mentioned in this link https://github.com/carrierwaveuploader/carrierwave/wiki/How-to%3A-Recreate-and-reprocess-your-files-stored-on-fog but it is not working. I've tested to make sure the cache is saving files, and it is. However, when I try and retrieve_from_cache! I'm unable to do so (as the cached file does not have a name).
This is what my cached files look like:
tmp
uploads
##########-#####-####
Thank you.
Turns out I did not run the desired code block within the change block inside the migration so the code was never being executed.

How to display files uploaded using carrierwave, fog and local storage with rails application?

I am using carrierwave with fog to create a simple image uploading process on my server.
The goal is to have images stored in my server in this folder :
/opt/myapp/uploads/
I have configured carrierwave and fog with that parameters and the upload is working very well :
CarrierWave.configure do |config|
config.fog_credentials = {
:provider => 'Local',
:local_root => '/opt/myapp/'
}
config.fog_public = false
config.fog_directory = 'uploads/'
config.storage = :fog
config.asset_host = proc do |file|
'/opt/myapp/uploads/'
end
end
When I upload an image I can see that it is stored in the corresponding folder. But how can I display them in my web pages ?
The generated url is
http://localhost:3000/opt/myapp/uploads/<path-to-my-image>.png
So my application tries to get images from an opt/ folder in my rails application directory, but how could I tell it to retrieve them from the server filesystem instead ?
Ok,that was easy to do :
First add a route for the corresponding urls :
match '/opt/myapp/uploads/:file_name' => 'files#serve'
The create a FilesController with a serve method :
class FilesController < ApplicationController
def serve
before_filter :authenticate_user! #used with Devise to protect the access to the images
path = "/opt/myapp/uploads/#{params[:file_name]}.png"
send_file( path,
:disposition => 'inline',
:type => 'image/png',
:x_sendfile => true )
end
end
Then I needed to add this line in my development.rb and production.rb configuration files :
config.action_dispatch.x_sendfile_header = "X-Accel-Redirect" #to use with Thin and Nginx

Carrierwave+fog+s3 not working with Cloud Front URLs

I'm having this problem with carrierwave+fog+s3 with Amazon cloud front. With the following setup I can upload files to s3 but after uploaded, the S3 Objects URLs I get from my rails app doesn't have the assets_host based URLs i.e I'm expeting the URLs to be looks like this format https://mycloudfrontname.cloudfront.net/uploads/myfile.mp3
But they all appear in this format https://mybucketname.s3.amazonaws.com/uploads/myfile.mp3
What might be wrong here?
CarrierWave.configure do |config|
config.fog_credentials = {
:provider => 'AWS',
:aws_access_key_id => 'XXXX',
:aws_secret_access_key => 'XXXX',
:region => 'us-east-1'
}
config.fog_directory = 'mybucketname'
config.asset_host = 'https://mycloudfrontname.cloudfront.net'
config.fog_public = false
config.fog_attributes = {'Cache-Control' => 'max-age=315576000'}
end
UPDATE:
I found this code bit from Carrierwave's /lib/carrierwave/storage/fog.rb - So if we set the asset_host as on above code snippet this must work right? or is there any other configuration I must do as well?
def public_url
if host = #uploader.asset_host
if host.respond_to? :call
"#{host.call(self)}/#{path}"
else
"#{host}/#{path}"
end
else
# AWS/Google optimized for speed over correctness
case #uploader.fog_credentials[:provider]
when 'AWS'
# if directory is a valid subdomain, use that style for access
if #uploader.fog_directory.to_s =~ /^(?:[a-z]|\d(?!\d{0,2}(?:\d{1,3}){3}$))(?:[a-z0-9\.]|(?![\-])|\-(?![\.])){1,61}[a-z0-9]$/
"https://#{#uploader.fog_directory}.s3.amazonaws.com/#{path}"
else
# directory is not a valid subdomain, so use path style for access
"https://s3.amazonaws.com/#{#uploader.fog_directory}/#{path}"
end
when 'Google'
"https://commondatastorage.googleapis.com/#{#uploader.fog_directory}/#{path}"
else
# avoid a get by just using local reference
directory.files.new(:key => path).public_url
end
end
end
Change config.fog_public to true and add config.asset_host = 'YOUR_CND_ADDRESS'. The asset_host doesn't work when fog_public is false
In your environment file you need to set the asset host. Just add the line below to your config/environments/production.rb file, and you should be okay. Also may want to make sure you're using the latest version of carrierwave and fog gems.
-- config/environments/production.rb
Myapp::Application.configure do
# Use Content Delivery Network for assets
config.action_controller.asset_host = 'https://mycloudfrontname.cloudfront.net'
end
Don't use asset_host. The asset_host setting is for files served by the rails asset helpers. CarrierWave files are handled in a different manner. The config you are looking for is config.fog_host
config.fog_host = 'https://mycloudfrontname.cloudfront.net'

Resources