paperclip-azure 1.0.0 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Manifest.txt +27 -1
- data/README.txt +25 -7
- data/Rakefile +7 -1
- data/lib/paperclip-azure.rb +0 -1
- data/lib/paperclip/azure.rb +1 -1
- data/lib/paperclip/storage/azure.rb +43 -57
- data/lib/paperclip/storage/azure/environment.rb +2 -2
- data/spec/database.yml +3 -0
- data/spec/paperclip/storage/azure_spec.rb +435 -0
- data/spec/spec_helper.rb +29 -3
- data/spec/support/fake_model.rb +25 -0
- data/spec/support/fake_rails.rb +12 -0
- data/spec/support/fixtures/12k.png +0 -0
- data/spec/support/fixtures/50x50.png +0 -0
- data/spec/support/fixtures/5k.png +0 -0
- data/spec/support/fixtures/animated +0 -0
- data/spec/support/fixtures/animated.gif +0 -0
- data/spec/support/fixtures/animated.unknown +0 -0
- data/spec/support/fixtures/azure.yml +8 -0
- data/spec/support/fixtures/bad.png +1 -0
- data/spec/support/fixtures/empty.html +1 -0
- data/spec/support/fixtures/empty.xlsx +0 -0
- data/spec/support/fixtures/fog.yml +8 -0
- data/spec/support/fixtures/rotated.jpg +0 -0
- data/spec/support/fixtures/spaced file.jpg +0 -0
- data/spec/support/fixtures/spaced file.png +0 -0
- data/spec/support/fixtures/text.txt +1 -0
- data/spec/support/fixtures/twopage.pdf +0 -0
- data/spec/support/fixtures/uppercase.PNG +0 -0
- data/spec/support/mock_attachment.rb +24 -0
- data/spec/support/mock_interpolator.rb +24 -0
- data/spec/support/mock_url_generator_builder.rb +27 -0
- data/spec/support/model_reconstruction.rb +68 -0
- data/spec/support/test_data.rb +13 -0
- data/spec/support/version_helper.rb +9 -0
- metadata +116 -6
- data/lib/azure/core/auth/shared_access_signature.rb +0 -84
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 911cdf6bd98931b2d25cb0dc5db1f0ce60b0bb9a
|
4
|
+
data.tar.gz: bc3c27455a1b7f9aba2e217eed8a8d245a2c1e55
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 858ac4cd36c47aa4bd052ed076af97357f57e407f25815ff0804ab5da2976e78200e749d5290d20a53e34397c51355617f8ef62fe608195fa5b0c5eb472b1648
|
7
|
+
data.tar.gz: 08744fa9bfadc893045c65966b35ddddb3dc3318490fe15f08771432d1d5904022e85d4a6aa6c72db814dbb0836cf75df2989a8ae55f6c8f4f6be62b9a6e31dc
|
data/Manifest.txt
CHANGED
@@ -3,10 +3,36 @@ History.txt
|
|
3
3
|
Manifest.txt
|
4
4
|
README.txt
|
5
5
|
Rakefile
|
6
|
-
lib/azure/core/auth/shared_access_signature.rb
|
7
6
|
lib/paperclip/storage/azure/environment.rb
|
8
7
|
lib/paperclip/storage/azure.rb
|
9
8
|
lib/paperclip/azure.rb
|
10
9
|
lib/paperclip-azure.rb
|
11
10
|
spec/paperclip/storage/azure/environment_spec.rb
|
11
|
+
spec/paperclip/storage/azure_spec.rb
|
12
|
+
spec/support/fixtures/12k.png
|
13
|
+
spec/support/fixtures/50x50.png
|
14
|
+
spec/support/fixtures/5k.png
|
15
|
+
spec/support/fixtures/animated
|
16
|
+
spec/support/fixtures/animated.gif
|
17
|
+
spec/support/fixtures/animated.unknown
|
18
|
+
spec/support/fixtures/azure.yml
|
19
|
+
spec/support/fixtures/bad.png
|
20
|
+
spec/support/fixtures/empty.html
|
21
|
+
spec/support/fixtures/empty.xlsx
|
22
|
+
spec/support/fixtures/fog.yml
|
23
|
+
spec/support/fixtures/rotated.jpg
|
24
|
+
spec/support/fixtures/spaced file.jpg
|
25
|
+
spec/support/fixtures/spaced file.png
|
26
|
+
spec/support/fixtures/text.txt
|
27
|
+
spec/support/fixtures/twopage.pdf
|
28
|
+
spec/support/fixtures/uppercase.PNG
|
29
|
+
spec/support/fake_model.rb
|
30
|
+
spec/support/fake_rails.rb
|
31
|
+
spec/support/mock_attachment.rb
|
32
|
+
spec/support/mock_interpolator.rb
|
33
|
+
spec/support/mock_url_generator_builder.rb
|
34
|
+
spec/support/model_reconstruction.rb
|
35
|
+
spec/support/test_data.rb
|
36
|
+
spec/support/version_helper.rb
|
37
|
+
spec/database.yml
|
12
38
|
spec/spec_helper.rb
|
data/README.txt
CHANGED
@@ -18,12 +18,11 @@ Paperclip-Azure is a [Paperclip](https://github.com/thoughtbot/paperclip) storag
|
|
18
18
|
The Azure storage engine has been developed to work as similarly to S3 storage configuration as is possible. This gem can be configured in a Paperclip initializer or environment file as follows:
|
19
19
|
|
20
20
|
Paperclip::Attachment.default_options[:storage] = :azure
|
21
|
-
Paperclip::Attachment.default_options[:url]
|
22
|
-
Paperclip::Attachment.default_options[:path]
|
23
|
-
Paperclip::Attachment.default_options[:storage] = :azure
|
21
|
+
Paperclip::Attachment.default_options[:url] = ':azure_path_url'
|
22
|
+
Paperclip::Attachment.default_options[:path] = ':class/:attachment/:id/:style/:filename'
|
24
23
|
Paperclip::Attachment.default_options[:azure_credentials] = {
|
25
24
|
storage_account_name: ENV['AZURE_STORAGE_ACCOUNT'],
|
26
|
-
|
25
|
+
storage_access_key: ENV['AZURE_STORAGE_ACCESS_KEY'],
|
27
26
|
container: ENV['AZURE_CONTAINER_NAME']
|
28
27
|
}
|
29
28
|
|
@@ -33,10 +32,29 @@ Or, at the level of the model such as in the following example:
|
|
33
32
|
storage: :azure,
|
34
33
|
azure_credentials: {
|
35
34
|
storage_account_name: ENV['AZURE_STORAGE_ACCOUNT'],
|
36
|
-
|
35
|
+
storage_access_key: ENV['AZURE_STORAGE_ACCESS_KEY'],
|
37
36
|
container: ENV['AZURE_CONTAINER_NAME']
|
38
37
|
}
|
39
38
|
|
39
|
+
Additionally, you can also supply credentials using a path or a File that contains the +storage_access_key+ and +storage_account_name+ that Azure gives you. You can 'environment-space' this just like you do to your `database.yml` file, so different environments can use different accounts:
|
40
|
+
|
41
|
+
development:
|
42
|
+
storage_account_name: foo
|
43
|
+
storage_access_key: 123...
|
44
|
+
test:
|
45
|
+
storage_account_name: foo
|
46
|
+
storage_access_key: abc...
|
47
|
+
production:
|
48
|
+
storage_account_name: foo
|
49
|
+
storage_access_key: 456...
|
50
|
+
|
51
|
+
This is not required, however, and the file may simply look like this:
|
52
|
+
|
53
|
+
storage_account_name: foo
|
54
|
+
storage_access_key: 456...
|
55
|
+
|
56
|
+
In which case, those access keys will be used in all environments. You can also put your container name in this file, instead of adding it to the code directly. This is useful when you want the same account but a different container for development versus production.
|
57
|
+
|
40
58
|
=== Private Blob Access
|
41
59
|
|
42
60
|
In the even that are using a Blob that has been configured for Private access, you will need to use the Shared Access Signature functionality of Azure. This functionality has been baked in to the `Attachment#expiring_url` method. Simply specify a time and a style and you will get a proper URL as follows:
|
@@ -51,7 +69,7 @@ Microsoft offers specialized Azure implementations for special circumstances sho
|
|
51
69
|
|
52
70
|
Paperclip::Attachment.default_options[:azure_credentials] = {
|
53
71
|
storage_account_name: ENV['AZURE_STORAGE_ACCOUNT'],
|
54
|
-
|
72
|
+
storage_access_key: ENV['AZURE_STORAGE_ACCESS_KEY'],
|
55
73
|
container: ENV['AZURE_CONTAINER_NAME'],
|
56
74
|
region: :de
|
57
75
|
}
|
@@ -62,7 +80,7 @@ Or, in the instance where the credentials are specified at the model level:
|
|
62
80
|
storage: :azure,
|
63
81
|
azure_credentials: {
|
64
82
|
storage_account_name: ENV['AZURE_STORAGE_ACCOUNT'],
|
65
|
-
|
83
|
+
storage_access_key: ENV['AZURE_STORAGE_ACCESS_KEY'],
|
66
84
|
container: ENV['AZURE_CONTAINER_NAME'],
|
67
85
|
region: :cn
|
68
86
|
}
|
data/Rakefile
CHANGED
@@ -13,12 +13,18 @@ Hoe.spec "paperclip-azure" do
|
|
13
13
|
developer("hireross.com", "help@hireross.com")
|
14
14
|
license "MIT" # this should match the license in the README
|
15
15
|
|
16
|
-
extra_deps << ['azure', '~> 0.
|
16
|
+
extra_deps << ['azure-storage', '~> 0.12']
|
17
17
|
extra_deps << ['hashie', '~> 3.5']
|
18
18
|
extra_deps << ['addressable', '~> 2.5']
|
19
19
|
|
20
|
+
extra_dev_deps << ['paperclip', '>= 4.3.6']
|
21
|
+
extra_dev_deps << ['sqlite3', '~> 1.3.8']
|
20
22
|
extra_dev_deps << ['rspec', '~> 3.0']
|
21
23
|
extra_dev_deps << ['simplecov', '~> 0.14']
|
24
|
+
extra_dev_deps << ['activerecord', '>= 4.2.0']
|
25
|
+
extra_dev_deps << ['activerecord-import', '~> 0.19']
|
26
|
+
extra_dev_deps << ['activemodel', '>= 4.2.0']
|
27
|
+
extra_dev_deps << ['activesupport', '>= 4.2.0']
|
22
28
|
end
|
23
29
|
|
24
30
|
# vim: syntax=ruby
|
data/lib/paperclip-azure.rb
CHANGED
data/lib/paperclip/azure.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'azure/storage'
|
1
2
|
require 'paperclip/storage/azure/environment'
|
2
3
|
|
3
4
|
module Paperclip
|
@@ -5,25 +6,23 @@ module Paperclip
|
|
5
6
|
# Azure's container file hosting service is a scalable, easy place to store files for
|
6
7
|
# distribution. You can find out more about it at http://azure.microsoft.com/en-us/services/storage/
|
7
8
|
#
|
8
|
-
# To use Paperclip with Azure, include the +azure+ gem in your Gemfile:
|
9
|
-
# gem 'azure'
|
10
9
|
# There are a few Azure-specific options for has_attached_file:
|
11
10
|
# * +azure_credentials+: Takes a path, a File, a Hash or a Proc. The path (or File) must point
|
12
|
-
# to a YAML file containing the +
|
11
|
+
# to a YAML file containing the +storage_access_key+ and +storage_account_name+ that azure
|
13
12
|
# gives you. You can 'environment-space' this just like you do to your
|
14
13
|
# database.yml file, so different environments can use different accounts:
|
15
14
|
# development:
|
16
15
|
# storage_account_name: foo
|
17
|
-
#
|
16
|
+
# storage_access_key: 123...
|
18
17
|
# test:
|
19
18
|
# storage_account_name: foo
|
20
|
-
#
|
19
|
+
# storage_access_key: abc...
|
21
20
|
# production:
|
22
21
|
# storage_account_name: foo
|
23
|
-
#
|
22
|
+
# storage_access_key: 456...
|
24
23
|
# This is not required, however, and the file may simply look like this:
|
25
24
|
# storage_account_name: foo
|
26
|
-
#
|
25
|
+
# storage_access_key: 456...
|
27
26
|
# In which case, those access keys will be used in all environments. You can also
|
28
27
|
# put your container name in this file, instead of adding it to the code directly.
|
29
28
|
# This is useful when you want the same account but a different container for
|
@@ -37,7 +36,7 @@ module Paperclip
|
|
37
36
|
# :azure_credentials => Proc.new{|a| a.instance.azure_credentials }
|
38
37
|
#
|
39
38
|
# def azure_credentials
|
40
|
-
# { :container => "xxx", :storage_account_name => "xxx", :
|
39
|
+
# { :container => "xxx", :storage_account_name => "xxx", :storage_access_key => "xxx" }
|
41
40
|
# end
|
42
41
|
# end
|
43
42
|
#
|
@@ -64,26 +63,35 @@ module Paperclip
|
|
64
63
|
|
65
64
|
base.instance_eval do
|
66
65
|
@azure_options = @options[:azure_options] || {}
|
66
|
+
|
67
|
+
unless @options[:url].to_s.match(/\A:azure.*url\z/) || @options[:url] == ":asset_host".freeze
|
68
|
+
@options[:path] = path_option.gsub(/:url/, @options[:url]).sub(/\A:rails_root\/public\/system/, "".freeze)
|
69
|
+
@options[:url] = ":azure_path_url".freeze
|
70
|
+
end
|
71
|
+
@options[:url] = @options[:url].inspect if @options[:url].is_a?(Symbol)
|
72
|
+
|
73
|
+
@http_proxy = @options[:http_proxy] || nil
|
67
74
|
end
|
68
75
|
|
69
76
|
Paperclip.interpolates(:azure_path_url) do |attachment, style|
|
70
77
|
attachment.azure_uri(style)
|
71
78
|
end unless Paperclip::Interpolations.respond_to? :azure_path_url
|
79
|
+
Paperclip.interpolates(:asset_host) do |attachment, style|
|
80
|
+
"#{attachment.path(style).sub(%r{\A/}, "".freeze)}"
|
81
|
+
end unless Paperclip::Interpolations.respond_to? :asset_host
|
72
82
|
end
|
73
83
|
|
74
84
|
def expiring_url(time = 3600, style_name = default_style)
|
75
85
|
if path(style_name)
|
76
|
-
uri = azure_uri(style_name)
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
)
|
86
|
-
signer.sign
|
86
|
+
uri = URI azure_uri(style_name)
|
87
|
+
generator = ::Azure::Storage::Core::Auth::SharedAccessSignature.new azure_account_name,
|
88
|
+
azure_credentials[:storage_access_key]
|
89
|
+
|
90
|
+
generator.signed_uri uri, false, service: 'b',
|
91
|
+
resource: 'b',
|
92
|
+
permissions: 'r',
|
93
|
+
start: (Time.now - (5 * 60)).utc.iso8601,
|
94
|
+
expiry: (Time.now + time).utc.iso8601
|
87
95
|
else
|
88
96
|
url(style_name)
|
89
97
|
end
|
@@ -115,7 +123,7 @@ module Paperclip
|
|
115
123
|
@azure_interface ||= begin
|
116
124
|
config = {}
|
117
125
|
|
118
|
-
[:storage_account_name, :
|
126
|
+
[:storage_account_name, :storage_access_key, :container].each do |opt|
|
119
127
|
config[opt] = azure_credentials[opt] if azure_credentials[opt]
|
120
128
|
end
|
121
129
|
|
@@ -123,54 +131,32 @@ module Paperclip
|
|
123
131
|
end
|
124
132
|
end
|
125
133
|
|
126
|
-
def
|
127
|
-
|
134
|
+
def azure_storage_client
|
135
|
+
config = {}
|
128
136
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
require 'azure/core/http/retry_policy' # For Some Reason, All Other Loading Locations Fail
|
134
|
-
service.filters << ::Azure::Core::Http::RetryPolicy.new do |response, retry_data|
|
135
|
-
status_code = case
|
136
|
-
when !response.nil?
|
137
|
-
response.status_code
|
138
|
-
when !retry_data[:error].nil?
|
139
|
-
retry_data[:error].status_code
|
140
|
-
else
|
141
|
-
500
|
142
|
-
end
|
143
|
-
status_code = 500 if status_code == 0
|
144
|
-
retry_data[:count] ||= 0
|
145
|
-
|
146
|
-
if (!response.nil? && response.success? && retry_data[:error].nil?) ||
|
147
|
-
(status_code >= 300 && status_code < 500 && status_code != 408) ||
|
148
|
-
status_code == 501 ||
|
149
|
-
status_code == 505 ||
|
150
|
-
(!retry_data[:error].nil? && retry_data[:error].description == 'Blob type of the blob reference doesn\'t match blob type of the blob.') ||
|
151
|
-
retry_data[:count] >= 5
|
152
|
-
retry_data[:count] = 0
|
153
|
-
else
|
154
|
-
retry_data[:count] += 1
|
137
|
+
[:storage_account_name, :storage_access_key].each do |opt|
|
138
|
+
config[opt] = azure_credentials[opt] if azure_credentials[opt]
|
139
|
+
end
|
155
140
|
|
156
|
-
|
157
|
-
|
141
|
+
@azure_storage_client ||= ::Azure::Storage::Client.create config
|
142
|
+
end
|
158
143
|
|
159
|
-
|
160
|
-
|
144
|
+
def obtain_azure_instance_for(options)
|
145
|
+
instances = (Thread.current[:paperclip_azure_instances] ||= {})
|
146
|
+
return instances[options] if instance[options]
|
161
147
|
|
162
|
-
|
163
|
-
|
148
|
+
service = ::Azure::Storage::Blob::BlobService.new(client: azure_storage_client)
|
149
|
+
service.with_filter ::Azure::Storage::Core::Filter::ExponentialRetryPolicyFilter.new
|
164
150
|
|
165
|
-
instances[options]
|
151
|
+
instances[options] = service
|
166
152
|
end
|
167
153
|
|
168
154
|
def azure_uri(style_name = default_style)
|
169
|
-
"
|
155
|
+
"https://#{azure_base_url}/#{container_name}/#{path(style_name).gsub(%r{\A/}, '')}"
|
170
156
|
end
|
171
157
|
|
172
158
|
def azure_base_url
|
173
|
-
Environment.url_for azure_account_name,
|
159
|
+
Environment.url_for azure_account_name, azure_credentials[:region]
|
174
160
|
end
|
175
161
|
|
176
162
|
def azure_container
|
@@ -10,8 +10,8 @@ module Paperclip
|
|
10
10
|
usgovt: 'core.usgovcloudapi.net'
|
11
11
|
}
|
12
12
|
|
13
|
-
def self.url_for(account_name, region =
|
14
|
-
"#{account_name}.blob.#{ENVIRONMENT_SUFFIX[region]}"
|
13
|
+
def self.url_for(account_name, region = nil)
|
14
|
+
"#{account_name}.blob.#{ENVIRONMENT_SUFFIX[region || :global]}"
|
15
15
|
end
|
16
16
|
end
|
17
17
|
end
|
data/spec/database.yml
ADDED
@@ -0,0 +1,435 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require "base64"
|
3
|
+
|
4
|
+
describe Paperclip::Storage::Azure do
|
5
|
+
let(:storage_access_key) { 'kiaY4+GkLMVxnfOK2X+eCJOE06J8QtHC6XNuXVwt8Pp4kMezYaa7cNjtYnZr4/b732RKdz5pZwl8RN9yb8gBCg==' }
|
6
|
+
|
7
|
+
describe "#parse_credentials" do
|
8
|
+
let(:credentials) {{
|
9
|
+
'production' => {key: '12345'},
|
10
|
+
development: {key: "54321"}
|
11
|
+
}}
|
12
|
+
|
13
|
+
before do
|
14
|
+
@proxy_settings = {host: "127.0.0.1", port: 8888, user: "foo", password: "bar"}
|
15
|
+
rebuild_model storage: :azure,
|
16
|
+
container: "testing",
|
17
|
+
azure_credentials: {not: :important}
|
18
|
+
@dummy = Dummy.new
|
19
|
+
@avatar = @dummy.avatar
|
20
|
+
end
|
21
|
+
|
22
|
+
it "gets the correct credentials when RAILS_ENV is production" do
|
23
|
+
rails_env("production") do
|
24
|
+
expect(@avatar.parse_credentials(credentials)).to eq({key: "12345"})
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
it "gets the correct credentials when RAILS_ENV is development" do
|
29
|
+
rails_env("development") do
|
30
|
+
expect(@avatar.parse_credentials(credentials)).to eq({key: "54321"})
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
it "returns the argument if the key does not exist" do
|
35
|
+
rails_env("not really an env") do
|
36
|
+
expect(@avatar.parse_credentials(test: "12345")).to eq({test: "12345"})
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
describe '#container_name' do
|
43
|
+
describe ":container option via :azure_credentials" do
|
44
|
+
before do
|
45
|
+
rebuild_model storage: :azure,
|
46
|
+
azure_credentials: {container: 'testing'}
|
47
|
+
@dummy = Dummy.new
|
48
|
+
end
|
49
|
+
|
50
|
+
it "populates #container_name" do
|
51
|
+
expect(@dummy.avatar.container_name).to eq('testing')
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe ":container option" do
|
56
|
+
before do
|
57
|
+
rebuild_model storage: :azure,
|
58
|
+
container: "testing",
|
59
|
+
azure_credentials: {}
|
60
|
+
@dummy = Dummy.new
|
61
|
+
end
|
62
|
+
|
63
|
+
it "populates #container_name" do
|
64
|
+
expect(@dummy.avatar.container_name).to eq('testing')
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe "missing :container option" do
|
69
|
+
before do
|
70
|
+
rebuild_model storage: :azure,
|
71
|
+
azure_credentials: {not: :important}
|
72
|
+
|
73
|
+
@dummy = Dummy.new
|
74
|
+
@dummy.avatar = stringy_file
|
75
|
+
end
|
76
|
+
|
77
|
+
it "raises an argument error" do
|
78
|
+
expect{ @dummy.avatar.container_name }.to raise_error(ArgumentError, /missing required :container option/)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe "" do
|
84
|
+
before do
|
85
|
+
rebuild_model storage: :azure,
|
86
|
+
azure_credentials: {
|
87
|
+
storage_account_name: 'storage',
|
88
|
+
storage_access_key: storage_access_key
|
89
|
+
},
|
90
|
+
container: "container",
|
91
|
+
path: ":attachment/:basename:dotextension",
|
92
|
+
url: ":azure_path_url"
|
93
|
+
|
94
|
+
@dummy = Dummy.new
|
95
|
+
@dummy.avatar = stringy_file
|
96
|
+
|
97
|
+
allow(@dummy).to receive(:new_record?).and_return(false)
|
98
|
+
end
|
99
|
+
|
100
|
+
it "returns urls based on Azure paths" do
|
101
|
+
expect(@dummy.avatar.url).to match(%r{^https://storage.blob.core.windows.net/container/avatars/data[^\.]})
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
describe "An attachment that uses Azure for storage and has styles that return different file types" do
|
106
|
+
before do
|
107
|
+
rebuild_model storage: :azure,
|
108
|
+
styles: { large: ['500x500#', :jpg] },
|
109
|
+
container: "container",
|
110
|
+
path: ":attachment/:basename:dotextension",
|
111
|
+
azure_credentials: {
|
112
|
+
'access_key_id' => "12345",
|
113
|
+
'secret_access_key' => "54321"
|
114
|
+
}
|
115
|
+
|
116
|
+
File.open(fixture_file('5k.png'), 'rb') do |file|
|
117
|
+
@dummy = Dummy.new
|
118
|
+
@dummy.avatar = file
|
119
|
+
|
120
|
+
allow(@dummy).to receive(:new_record?).and_return(false)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
it "returns a url containing the correct original file mime type" do
|
125
|
+
expect(@dummy.avatar.url).to match(/.+\/5k.png/)
|
126
|
+
end
|
127
|
+
|
128
|
+
it "returns a url containing the correct processed file mime type" do
|
129
|
+
expect(@dummy.avatar.url(:large)).to match(/.+\/5k.jpg/)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
describe "An attachment that uses Azure for storage and has spaces in file name" do
|
134
|
+
before do
|
135
|
+
rebuild_model storage: :azure,
|
136
|
+
styles: { large: ["500x500#", :jpg] },
|
137
|
+
container: "container",
|
138
|
+
azure_credentials: {
|
139
|
+
"storage_access_key" => "54321"
|
140
|
+
}
|
141
|
+
|
142
|
+
File.open(fixture_file("spaced file.png"), "rb") do |file|
|
143
|
+
@dummy = Dummy.new
|
144
|
+
@dummy.avatar = file
|
145
|
+
|
146
|
+
allow(@dummy).to receive(:new_record?).and_return(false)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
it "returns a replaced version for path" do
|
151
|
+
expect(@dummy.avatar.path).to match(/.+\/spaced_file\.png/)
|
152
|
+
end
|
153
|
+
|
154
|
+
it "returns a replaced version for url" do
|
155
|
+
expect(@dummy.avatar.url).to match(/.+\/spaced_file\.png/)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
describe "An attachment that uses Azure for storage and has a question mark in file name" do
|
160
|
+
before do
|
161
|
+
rebuild_model storage: :azure,
|
162
|
+
styles: { large: ['500x500#', :jpg] },
|
163
|
+
container: "container",
|
164
|
+
azure_credentials: {
|
165
|
+
'storage_access_key' => "54321"
|
166
|
+
}
|
167
|
+
|
168
|
+
stringio = stringy_file
|
169
|
+
class << stringio
|
170
|
+
def original_filename
|
171
|
+
"question?mark.png"
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
file = Paperclip.io_adapters.for(stringio)
|
176
|
+
|
177
|
+
@dummy = Dummy.new
|
178
|
+
@dummy.avatar = file
|
179
|
+
@dummy.save
|
180
|
+
|
181
|
+
allow(@dummy).to receive(:new_record?).and_return(false)
|
182
|
+
end
|
183
|
+
|
184
|
+
it "returns a replaced version for path" do
|
185
|
+
expect(@dummy.avatar.path).to match(/.+\/question_mark\.png/)
|
186
|
+
end
|
187
|
+
|
188
|
+
it "returns a replaced version for url" do
|
189
|
+
expect(@dummy.avatar.url).to match(/.+\/question_mark\.png/)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
describe ":asset_host path Interpolations" do
|
194
|
+
before do
|
195
|
+
rebuild_model storage: :azure,
|
196
|
+
azure_credentials: {},
|
197
|
+
container: "container",
|
198
|
+
path: ":attachment/:basename:dotextension",
|
199
|
+
url: ":asset_host"
|
200
|
+
@dummy = Dummy.new
|
201
|
+
@dummy.avatar = stringy_file
|
202
|
+
allow(@dummy).to receive(:new_record?).and_return(false)
|
203
|
+
end
|
204
|
+
|
205
|
+
it "returns a relative URL for Rails to calculate assets host" do
|
206
|
+
expect(@dummy.avatar.url).to match(%r{^avatars/data[^\.]})
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
describe "#expiring_url" do
|
211
|
+
before { @dummy = Dummy.new }
|
212
|
+
|
213
|
+
describe "with no attachment" do
|
214
|
+
before { expect(@dummy.avatar.exists?).to be_falsey }
|
215
|
+
|
216
|
+
it "returns the default URL" do
|
217
|
+
expect(@dummy.avatar.expiring_url).to eq(@dummy.avatar.url)
|
218
|
+
end
|
219
|
+
|
220
|
+
it 'generates a url for a style when a file does not exist' do
|
221
|
+
expect(@dummy.avatar.expiring_url(3600, :thumb)).to eq(@dummy.avatar.url(:thumb))
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
describe "Generating a url with an expiration for each style" do
|
227
|
+
before do
|
228
|
+
rebuild_model storage: :azure,
|
229
|
+
azure_credentials: {
|
230
|
+
production: {
|
231
|
+
storage_account_name: 'prod_storage',
|
232
|
+
storage_access_key: 'YWNjZXNzLWtleQ==',
|
233
|
+
container: "prod_container"
|
234
|
+
},
|
235
|
+
development: {
|
236
|
+
storage_account_name: 'dev_storage',
|
237
|
+
storage_access_key: 'YWNjZXNzLWtleQ==',
|
238
|
+
container: "dev_container"
|
239
|
+
}
|
240
|
+
},
|
241
|
+
path: ":attachment/:style/:basename:dotextension"
|
242
|
+
|
243
|
+
rails_env("production") do
|
244
|
+
@dummy = Dummy.new
|
245
|
+
@dummy.avatar = stringy_file
|
246
|
+
end
|
247
|
+
|
248
|
+
allow(::Azure::Storage::Core::Auth::SharedAccessSignature).to receive(:new).and_call_original
|
249
|
+
allow(::Azure::Storage::Core::Auth::SharedAccessSignatureSigner).to receive(:new).and_call_original
|
250
|
+
end
|
251
|
+
|
252
|
+
it "generates a url for the thumb" do
|
253
|
+
rails_env("production") do
|
254
|
+
expect { @dummy.avatar.expiring_url(1800, :thumb) }.not_to raise_error
|
255
|
+
end
|
256
|
+
|
257
|
+
expect(::Azure::Storage::Core::Auth::SharedAccessSignature).to have_received(:new)
|
258
|
+
.with('prod_storage', anything)
|
259
|
+
end
|
260
|
+
|
261
|
+
it "generates a url for the default style" do
|
262
|
+
rails_env("production") do
|
263
|
+
expect { @dummy.avatar.expiring_url(1800) }.not_to raise_error
|
264
|
+
end
|
265
|
+
|
266
|
+
expect(::Azure::Storage::Core::Auth::SharedAccessSignature).to have_received(:new)
|
267
|
+
.with('prod_storage', anything)
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
context "Parsing Azure credentials with a container in them" do
|
272
|
+
before do
|
273
|
+
rebuild_model storage: :azure,
|
274
|
+
azure_credentials: {
|
275
|
+
production: { container: "prod_container" },
|
276
|
+
development: { container: "dev_container" }
|
277
|
+
}
|
278
|
+
@dummy = Dummy.new
|
279
|
+
end
|
280
|
+
|
281
|
+
it "gets the right container in production" do
|
282
|
+
rails_env("production") do
|
283
|
+
expect(@dummy.avatar.container_name).to eq("prod_container")
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
it "gets the right container in development" do
|
288
|
+
rails_env("development") do
|
289
|
+
expect(@dummy.avatar.container_name).to eq("dev_container")
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
context "An attachment with Azure storage" do
|
295
|
+
before do
|
296
|
+
rebuild_model storage: :azure,
|
297
|
+
container: "testing",
|
298
|
+
path: ":attachment/:style/:basename:dotextension",
|
299
|
+
azure_credentials: {
|
300
|
+
storage_account_name: 'storage',
|
301
|
+
storage_access_key: storage_access_key
|
302
|
+
}
|
303
|
+
end
|
304
|
+
|
305
|
+
it "is extended by the Azure module" do
|
306
|
+
expect(Dummy.new.avatar).to be_a(Paperclip::Storage::Azure)
|
307
|
+
end
|
308
|
+
|
309
|
+
it "won't be extended by the Filesystem module" do
|
310
|
+
expect(Dummy.new.avatar).not_to be_a(Paperclip::Storage::Filesystem)
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
describe "An attachment with Azure storage and container defined as a Proc" do
|
315
|
+
before do
|
316
|
+
rebuild_model storage: :azure,
|
317
|
+
container: lambda { |attachment| "container_#{attachment.instance.other}" },
|
318
|
+
azure_credentials: {not: :important}
|
319
|
+
end
|
320
|
+
|
321
|
+
it "gets the right container name" do
|
322
|
+
expect(Dummy.new(other: 'a').avatar.container_name).to eq("container_a")
|
323
|
+
expect(Dummy.new(other: 'b').avatar.container_name).to eq("container_b")
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
context "An attachment with Azure storage and Azure credentials defined as a Proc" do
|
328
|
+
before do
|
329
|
+
rebuild_model storage: :azure,
|
330
|
+
container: {not: :important},
|
331
|
+
azure_credentials: lambda { |attachment|
|
332
|
+
Hash['storage_access_key' => "secret#{attachment.instance.other}"]
|
333
|
+
}
|
334
|
+
end
|
335
|
+
|
336
|
+
it "gets the right credentials" do
|
337
|
+
expect(Dummy.new(other: '1234').avatar.azure_credentials[:storage_access_key]).to eq("secret1234")
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
context "An attachment with Azure storage and Azure credentials in an unsupported manor" do
|
342
|
+
before do
|
343
|
+
rebuild_model storage: :azure,
|
344
|
+
container: "testing",
|
345
|
+
azure_credentials: ["unsupported"]
|
346
|
+
@dummy = Dummy.new
|
347
|
+
end
|
348
|
+
|
349
|
+
it "does not accept the credentials" do
|
350
|
+
expect { @dummy.avatar.azure_credentials }.to raise_error(ArgumentError)
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
context "An attachment with Azure storage and Azure credentials not supplied" do
|
355
|
+
before do
|
356
|
+
rebuild_model storage: :azure, container: "testing"
|
357
|
+
@dummy = Dummy.new
|
358
|
+
end
|
359
|
+
|
360
|
+
it "does not parse any credentials" do
|
361
|
+
expect(@dummy.avatar.azure_credentials).to eq({})
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
describe "with Azure credentials supplied as Pathname" do
|
366
|
+
before do
|
367
|
+
ENV['AZURE_CONTAINER'] = 'pathname_container'
|
368
|
+
ENV['AZURE_STORAGE_ACCOUNT'] = 'pathname_storage_account'
|
369
|
+
ENV['AZURE_STORAGE_ACCESS_KEY'] = storage_access_key
|
370
|
+
|
371
|
+
rails_env('test') do
|
372
|
+
rebuild_model storage: :azure,
|
373
|
+
azure_credentials: Pathname.new(fixture_file('azure.yml'))
|
374
|
+
|
375
|
+
Dummy.delete_all
|
376
|
+
@dummy = Dummy.new
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
it "parses the credentials" do
|
381
|
+
expect(@dummy.avatar.container_name).to eq('pathname_container')
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
describe "with Azure credentials in a YAML file" do
|
386
|
+
before do
|
387
|
+
ENV['AZURE_CONTAINER'] = 'pathname_container'
|
388
|
+
ENV['AZURE_STORAGE_ACCOUNT'] = 'pathname_storage_account'
|
389
|
+
ENV['AZURE_STORAGE_ACCESS_KEY'] = storage_access_key
|
390
|
+
|
391
|
+
rails_env('test') do
|
392
|
+
rebuild_model storage: :azure,
|
393
|
+
azure_credentials: File.new(fixture_file('azure.yml'))
|
394
|
+
|
395
|
+
Dummy.delete_all
|
396
|
+
|
397
|
+
@dummy = Dummy.new
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
it "runs the file through ERB" do
|
402
|
+
expect(@dummy.avatar.container_name).to eq('pathname_container')
|
403
|
+
end
|
404
|
+
end
|
405
|
+
|
406
|
+
describe "path is a proc" do
|
407
|
+
before do
|
408
|
+
rebuild_model storage: :azure,
|
409
|
+
path: ->(attachment) { attachment.instance.attachment_path }
|
410
|
+
|
411
|
+
@dummy = Dummy.new
|
412
|
+
@dummy.class_eval do
|
413
|
+
def attachment_path
|
414
|
+
'/some/dynamic/path'
|
415
|
+
end
|
416
|
+
end
|
417
|
+
@dummy.avatar = stringy_file
|
418
|
+
end
|
419
|
+
|
420
|
+
it "returns a correct path" do
|
421
|
+
expect(@dummy.avatar.path).to eq('/some/dynamic/path')
|
422
|
+
end
|
423
|
+
end
|
424
|
+
|
425
|
+
private
|
426
|
+
|
427
|
+
def rails_env(env)
|
428
|
+
stored_env, Rails.env = Rails.env, env
|
429
|
+
begin
|
430
|
+
yield
|
431
|
+
ensure
|
432
|
+
Rails.env = stored_env
|
433
|
+
end
|
434
|
+
end
|
435
|
+
end
|