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

support Azure Blob storage #363

Merged
merged 11 commits into from
Dec 12, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 40 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ gem "asset_sync"
gem "fog-aws"
```

Or, to use Azure Blob storage, configure as this.

``` ruby
gem "asset_sync"
gem "fog-azure-rm"
```


### Extended Installation (Faster sync with turbosprockets)

It's possible to improve **asset:precompile** time if you are using Rails 3.2.x
Expand Down Expand Up @@ -64,6 +72,12 @@ Or, to use Google Storage Cloud, configure as this.
config.action_controller.asset_host = "//#{ENV['FOG_DIRECTORY']}.storage.googleapis.com"
```

Or, to use Azure Blob storage, configure as this.

``` ruby
#config/environments/production.rb
config.action_controller.asset_host = "//#{ENV['AZURE_STORAGE_ACCOUNT_NAME']}.blob.core.windows.net/#{ENV['FOG_DIRECTORY']}"
```


On **HTTPS**: the exclusion of any protocol in the asset host declaration above will allow browsers to choose the transport mechanism on the fly. So if your application is available under both HTTP and HTTPS the assets will be served to match.
Expand All @@ -73,11 +87,20 @@ On **HTTPS**: the exclusion of any protocol in the asset host declaration above
``` ruby
config.action_controller.asset_host = "//s3.amazonaws.com/#{ENV['FOG_DIRECTORY']}"
```
Or

Or, to use Google Storage Cloud, configure as this.

``` ruby
config.action_controller.asset_host = "//storage.googleapis.com/#{ENV['FOG_DIRECTORY']}"
```

Or, to use Azure Blob storage, configure as this.

``` ruby
#config/environments/production.rb
config.action_controller.asset_host = "//#{ENV['AZURE_STORAGE_ACCOUNT_NAME']}.blob.core.windows.net/#{ENV['FOG_DIRECTORY']}"
```

On **non default S3 bucket region**: If your bucket is set to a region that is not the default US Standard (us-east-1) you must use the first style of url ``//#{ENV['FOG_DIRECTORY']}.s3.amazonaws.com`` or amazon will return a 301 permanently moved when assets are requested. Note the caveat above about bucket names and periods.

If you wish to have your assets sync to a sub-folder of your bucket instead of into the root add the following to your ``production.rb`` file
Expand Down Expand Up @@ -167,6 +190,7 @@ Run the included Rake task to generate a starting point.

rails g asset_sync:install --provider=Rackspace
rails g asset_sync:install --provider=AWS
rails g asset_sync:install --provider=AzureRM

The generator will create a Rails initializer at `config/initializers/asset_sync.rb`.

Expand Down Expand Up @@ -220,6 +244,7 @@ Run the included Rake task to generate a starting point.

rails g asset_sync:install --use-yml --provider=Rackspace
rails g asset_sync:install --use-yml --provider=AWS
rails g asset_sync:install --use-yml --provider=AzureRM

The generator will create a YAML file at `config/asset_sync.yml`.

Expand Down Expand Up @@ -316,12 +341,12 @@ end
The blocks are run when local files are being scanned and uploaded

#### Fog (Required)
* **fog\_provider**: your storage provider *AWS* (S3) or *Rackspace* (Cloud Files) or *Google* (Google Storage)
* **fog\_provider**: your storage provider *AWS* (S3) or *Rackspace* (Cloud Files) or *Google* (Google Storage) or *AzureRM* (Azure Blob)
* **fog\_directory**: your bucket name

#### Fog (Optional)

* **fog\_region**: the region your storage bucket is in e.g. *eu-west-1* (AWS), *ord* (Rackspace)
* **fog\_region**: the region your storage bucket is in e.g. *eu-west-1* (AWS), *ord* (Rackspace), *japanwest* (Azure Blob)
* **fog\_path\_style**: To use buckets with dot in names, check https://github.com/fog/fog/issues/2381#issuecomment-28088524

#### AWS
Expand All @@ -338,6 +363,10 @@ The blocks are run when local files are being scanned and uploaded
* **google\_storage\_access\_key\_id**: your Google Storage access key
* **google\_storage\_secret\_access\_key**: your Google Storage access secret

#### Azure Blob
* **azure\_storage\_account\_name**: your Azure Blob access key
* **azure\_storage\_access\_key**: your Azure Blob access secret

#### Rackspace (Optional)

* **rackspace\_auth\_url**: Rackspace auth URL, for Rackspace London use: `https://lon.identity.api.rackspacecloud.com/v2.0`
Expand Down Expand Up @@ -494,9 +523,17 @@ end

Make sure you have a .env file with these details:-

# for AWS provider
AWS_ACCESS_KEY_ID=<yourkeyid>
AWS_SECRET_ACCESS_KEY=<yoursecretkey>
FOG_DIRECTORY=<yourbucket>
FOG_REGION=<youbucketregion>

# for AzureRM provider
AZURE_STORAGE_ACCOUNT_NAME=<youraccountname>
AZURE_STORAGE_ACCESS_KEY=<youraccesskey>
FOG_DIRECTORY=<yourcontainer>
FOG_REGION=<yourcontainerregion>

Make sure the bucket has read/write permissions. Then to run the tests:-

Expand Down
1 change: 1 addition & 0 deletions asset_sync.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Gem::Specification.new do |s|
s.add_development_dependency "jeweler"

s.add_development_dependency "fog-aws"
s.add_development_dependency "fog-azure-rm"

s.add_development_dependency "uglifier"
s.add_development_dependency "appraisal"
Expand Down
25 changes: 21 additions & 4 deletions lib/asset_sync/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ class Invalid < StandardError; end
# Google Storage
attr_accessor :google_storage_secret_access_key, :google_storage_access_key_id

# Azure Blob with Fog::AzureRM
attr_accessor :azure_storage_account_name
attr_accessor :azure_storage_access_key

validates :existing_remote_files, :inclusion => { :in => %w(keep delete ignore) }

validates :fog_provider, :presence => true
Expand Down Expand Up @@ -120,6 +124,10 @@ def google?
fog_provider =~ /google/i
end

def azure_rm?
fog_provider =~ /azurerm/i
end

def cache_asset_regexp=(cache_asset_regexp)
self.cache_asset_regexps = [cache_asset_regexp]
end
Expand Down Expand Up @@ -175,6 +183,9 @@ def load_yml!
self.cdn_distribution_id = yml['cdn_distribution_id'] if yml.has_key?("cdn_distribution_id")
self.cache_asset_regexps = yml['cache_asset_regexps'] if yml.has_key?("cache_asset_regexps")

self.azure_storage_account_name = yml['azure_storage_account_name'] if yml.has_key?("azure_storage_account_name")
self.azure_storage_access_key = yml['azure_storage_access_key'] if yml.has_key?("azure_storage_access_key")

# TODO deprecate the other old style config settings. FML.
self.aws_access_key_id = yml["aws_access_key"] if yml.has_key?("aws_access_key")
self.aws_secret_access_key = yml["aws_access_secret"] if yml.has_key?("aws_access_secret")
Expand Down Expand Up @@ -208,25 +219,31 @@ def fog_options
options.merge!({:scheme => fog_scheme}) if fog_scheme
options.merge!({:aws_signature_version => aws_signature_version}) if aws_signature_version
options.merge!({:path_style => fog_path_style}) if fog_path_style
options.merge!({:region => fog_region}) if fog_region
elsif rackspace?
options.merge!({
:rackspace_username => rackspace_username,
:rackspace_api_key => rackspace_api_key
})
options.merge!({
:rackspace_region => fog_region
}) if fog_region
options.merge!({ :rackspace_region => fog_region }) if fog_region
options.merge!({ :rackspace_auth_url => rackspace_auth_url }) if rackspace_auth_url
elsif google?
options.merge!({
:google_storage_secret_access_key => google_storage_secret_access_key,
:google_storage_access_key_id => google_storage_access_key_id
})
options.merge!({:region => fog_region}) if fog_region
elsif azure_rm?
require 'fog/azurerm'
options.merge!({
:azure_storage_account_name => azure_storage_account_name,
:azure_storage_access_key => azure_storage_access_key,
})
options.merge!({:environment => fog_region}) if fog_region
else
raise ArgumentError, "AssetSync Unknown provider: #{fog_provider} only AWS, Rackspace and Google are supported currently."
end

options.merge!({:region => fog_region}) if fog_region && !rackspace?
return options
end

Expand Down
3 changes: 3 additions & 0 deletions lib/asset_sync/engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ class Engine < Rails::Engine
config.google_storage_access_key_id = ENV['GOOGLE_STORAGE_ACCESS_KEY_ID'] if ENV.has_key?('GOOGLE_STORAGE_ACCESS_KEY_ID')
config.google_storage_secret_access_key = ENV['GOOGLE_STORAGE_SECRET_ACCESS_KEY'] if ENV.has_key?('GOOGLE_STORAGE_SECRET_ACCESS_KEY')

config.azure_storage_account_name = ENV['AZURE_STORAGE_ACCOUNT_NAME'] if ENV.has_key?('AZURE_STORAGE_ACCOUNT_NAME')
config.azure_storage_access_key = ENV['AZURE_STORAGE_ACCESS_KEY'] if ENV.has_key?('AZURE_STORAGE_ACCESS_KEY')

config.enabled = (ENV['ASSET_SYNC_ENABLED'] == 'true') if ENV.has_key?('ASSET_SYNC_ENABLED')

config.existing_remote_files = ENV['ASSET_SYNC_EXISTING_REMOTE_FILES'] || "keep"
Expand Down
6 changes: 6 additions & 0 deletions lib/asset_sync/storage.rb
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,12 @@ def upload_file(f)
})
end

if config.azure_rm?
# converts content_type from MIME::Type to String.
# because Azure::Storage (called from Fog::AzureRM) expects content_type as a String like "application/json; charset=utf-8"
file[:content_type] = file[:content_type].content_type if file[:content_type].is_a?(::MIME::Type)
end

bucket.files.create( file ) unless ignore
file_handle.close
gzip_file_handle.close if gzip_file_handle
Expand Down
14 changes: 13 additions & 1 deletion lib/generators/asset_sync/install_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ class InstallGenerator < Rails::Generators::Base

# Commandline options can be defined here using Thor-like options:
class_option :use_yml, :type => :boolean, :default => false, :desc => "Use YML file instead of Rails Initializer"
class_option :provider, :type => :string, :default => "AWS", :desc => "Generate with support for 'AWS', 'Rackspace', or 'Google'"
class_option :provider, :type => :string, :default => "AWS", :desc => "Generate with support for 'AWS', 'Rackspace', 'Google', or 'AzureRM"

def self.source_root
@source_root ||= File.join(File.dirname(__FILE__), 'templates')
Expand All @@ -23,6 +23,10 @@ def rackspace?
options[:provider] == 'Rackspace'
end

def azure_rm?
options[:provider] == 'AzureRM'
end

def aws_access_key_id
"<%= ENV['AWS_ACCESS_KEY_ID'] %>"
end
Expand All @@ -47,6 +51,14 @@ def rackspace_api_key
"<%= ENV['RACKSPACE_API_KEY'] %>"
end

def azure_storage_account_name
"<%= ENV['AZURE_STORAGE_ACCOUNT_NAME'] %>"
end

def azure_storage_access_key
"<%= ENV['AZURE_STORAGE_ACCESS_KEY'] %>"
end

def app_name
@app_name ||= Rails.application.is_a?(Rails::Application) && Rails.application.class.name.sub(/::Application$/, "").downcase
end
Expand Down
6 changes: 6 additions & 0 deletions lib/generators/asset_sync/templates/asset_sync.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@

# if you need to change rackspace_auth_url (e.g. if you need to use Rackspace London)
# config.rackspace_auth_url = "lon.auth.api.rackspacecloud.com"
<%- elsif azure_rm? -%>
config.fog_provider = 'AzureRM'
config.azure_storage_account_name = ENV['AZURE_STORAGE_ACCOUNT_NAME']
config.azure_storage_access_key = ENV['AZURE_STORAGE_ACCESS_KEY']

# config.fog_directory specifies container name of Azure Blob storage
<%- end -%>
config.fog_directory = ENV['FOG_DIRECTORY']

Expand Down
6 changes: 6 additions & 0 deletions lib/generators/asset_sync/templates/asset_sync.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ defaults: &defaults
rackspace_api_key: "<%= rackspace_api_key %>"
# if you need to change rackspace_auth_url (e.g. if you need to use Rackspace London)
# rackspace_auth_url: "https://lon.identity.api.rackspacecloud.com/v2.0"
<%- elsif azure_rm? -%>
fog_provider: 'AzureRM'
azure_storage_account_name: "<%= azure_storage_account_name %>"
azure_storage_access_key: "<%= azure_storage_access_key %>"

# fog_directory specifies container name of Azure Blob storage
<%- end -%>
fog_directory: "<%= app_name %>-assets"
# You may need to specify what region your storage bucket is in
Expand Down
19 changes: 19 additions & 0 deletions spec/fixtures/azure_rm_with_yml/config/asset_sync.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
defaults: &defaults
fog_provider: "AzureRM"
azure_storage_account_name: 'xxxx'
azure_storage_access_key: 'zzzz'

development:
<<: *defaults
fog_directory: "rails_app_development"
existing_remote_files: keep

test:
<<: *defaults
fog_directory: "rails_app_test"
existing_remote_files: keep

production:
<<: *defaults
fog_directory: "rails_app_production"
existing_remote_files: delete
74 changes: 74 additions & 0 deletions spec/integration/azure_rm_integration_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
require File.dirname(__FILE__) + '/../spec_helper'
require "fog/azurerm"

def bucket(name)
options = {
:provider => 'AzureRM',
:azure_storage_account_name => ENV['AZURE_STORAGE_ACCOUNT_NAME'],
:azure_storage_access_key => ENV['AZURE_STORAGE_ACCESS_KEY']
}
options.merge!({ :environment => ENV['FOG_REGION'] }) if ENV.has_key?('FOG_REGION')

connection = Fog::Storage.new(options)
connection.directories.get(ENV['FOG_DIRECTORY'], :prefix => name)
end

def execute(command)
app_path = File.expand_path("../../dummy_app", __FILE__)
Dir.chdir app_path
`#{command}`
end

describe "AssetSync" do

before(:each) do
@prefix = SecureRandom.hex(6)
end

let(:app_js_regex){
/#{@prefix}\/application-[a-zA-Z0-9]*.js$/
}

let(:app_js_gz_regex){
/#{@prefix}\/application-[a-zA-Z0-9]*.js.gz$/
}

let(:files){ bucket(@prefix).files }


after(:each) do
@directory = bucket(@prefix)
@directory.files.each do |f|
f.destroy
end
end

it "sync" do
execute "rake ASSET_SYNC_PREFIX=#{@prefix} assets:precompile"

files = bucket(@prefix).files

app_js = files.select{ |f| f.key =~ app_js_regex }.first
expect(app_js.content_type).to eq("application/javascript")

app_js_gz = files.select{ |f| f.key =~ app_js_gz_regex }.first
expect(app_js_gz.content_type).to eq("application/javascript")
expect(app_js_gz.content_encoding).to eq("gzip")
end

it "sync with enabled=false" do
execute "rake ASSET_SYNC_PREFIX=#{@prefix} ASSET_SYNC_ENABLED=false assets:precompile"
expect(bucket(@prefix).files.size).to eq(0)
end

it "sync with gzip_compression=true" do
execute "rake ASSET_SYNC_PREFIX=#{@prefix} ASSET_SYNC_GZIP_COMPRESSION=true assets:precompile"
# bucket(@prefix).files.size.should == 3

app_js_path = files.select{ |f| f.key =~ app_js_regex }.first
app_js = files.get( app_js_path.key )
expect(app_js.content_type).to eq("application/javascript")
end

end

Loading