httpimagestore 1.8.1 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +15 -0
- data/Gemfile +7 -7
- data/Gemfile.lock +20 -20
- data/README.md +165 -37
- data/Rakefile +7 -2
- data/VERSION +1 -1
- data/bin/httpimagestore +74 -41
- data/lib/httpimagestore/configuration/file.rb +20 -11
- data/lib/httpimagestore/configuration/handler.rb +96 -257
- data/lib/httpimagestore/configuration/handler/source_store_base.rb +37 -0
- data/lib/httpimagestore/configuration/handler/statement.rb +114 -0
- data/lib/httpimagestore/configuration/identify.rb +17 -9
- data/lib/httpimagestore/configuration/output.rb +33 -61
- data/lib/httpimagestore/configuration/path.rb +2 -2
- data/lib/httpimagestore/configuration/request_state.rb +131 -0
- data/lib/httpimagestore/configuration/s3.rb +41 -29
- data/lib/httpimagestore/configuration/thumbnailer.rb +189 -96
- data/lib/httpimagestore/configuration/validate_hmac.rb +170 -0
- data/lib/httpimagestore/error_reporter.rb +6 -1
- data/lib/httpimagestore/ruby_string_template.rb +10 -19
- metadata +40 -102
- data/.rspec +0 -1
- data/features/cache-control.feature +0 -41
- data/features/compatibility.feature +0 -165
- data/features/data-uri.feature +0 -55
- data/features/encoding.feature +0 -103
- data/features/error-reporting.feature +0 -281
- data/features/flexi.feature +0 -259
- data/features/health-check.feature +0 -29
- data/features/request-matching.feature +0 -211
- data/features/rewrite.feature +0 -122
- data/features/s3-store-and-thumbnail.feature +0 -82
- data/features/source-failover.feature +0 -71
- data/features/step_definitions/httpimagestore_steps.rb +0 -203
- data/features/storage.feature +0 -198
- data/features/support/env.rb +0 -116
- data/features/support/test-large.jpg +0 -0
- data/features/support/test.empty +0 -0
- data/features/support/test.jpg +0 -0
- data/features/support/test.png +0 -0
- data/features/support/test.txt +0 -1
- data/features/support/tiny.png +0 -0
- data/features/xid-forwarding.feature +0 -49
- data/httpimagestore.gemspec +0 -145
- data/load_test/load_test.1k.23a022f6e.m1.small-comp.csv +0 -3
- data/load_test/load_test.1k.ec9bde794.m1.small.csv +0 -4
- data/load_test/load_test.jmx +0 -317
- data/load_test/thumbnail_specs.csv +0 -11
- data/load_test/thumbnail_specs_v2.csv +0 -10
- data/spec/configuration_file_spec.rb +0 -333
- data/spec/configuration_handler_spec.rb +0 -255
- data/spec/configuration_identify_spec.rb +0 -67
- data/spec/configuration_output_spec.rb +0 -821
- data/spec/configuration_path_spec.rb +0 -138
- data/spec/configuration_s3_spec.rb +0 -911
- data/spec/configuration_source_failover_spec.rb +0 -101
- data/spec/configuration_spec.rb +0 -90
- data/spec/configuration_thumbnailer_spec.rb +0 -483
- data/spec/ruby_string_template_spec.rb +0 -61
- data/spec/spec_helper.rb +0 -89
- data/spec/support/compute.jpg +0 -0
- data/spec/support/cuba_response_env.rb +0 -40
- data/spec/support/full.cfg +0 -183
- data/spec/support/utf_string.txt +0 -1
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
require_relative 'spec_helper'
|
|
2
|
-
require 'httpimagestore/configuration'
|
|
3
|
-
MemoryLimit.logger = Configuration::Scope.logger = RootLogger.new('/dev/null')
|
|
4
|
-
|
|
5
|
-
require 'httpimagestore/configuration/handler'
|
|
6
|
-
require 'httpimagestore/configuration/path'
|
|
7
|
-
|
|
8
|
-
describe Configuration do
|
|
9
|
-
describe 'path rendering' do
|
|
10
|
-
it 'should load paths form single line and multi line declarations and render spec templates' do
|
|
11
|
-
subject = Configuration.read(<<-'EOF')
|
|
12
|
-
path "uri" "#{path}"
|
|
13
|
-
path "hash" "#{input_digest}.#{extension}"
|
|
14
|
-
path {
|
|
15
|
-
"hash-name" "#{input_digest}/#{image_name}.#{extension}"
|
|
16
|
-
"structured" "#{dirname}/#{input_digest}/#{basename}.#{extension}"
|
|
17
|
-
"structured-name" "#{dirname}/#{input_digest}/#{basename}-#{image_name}.#{extension}"
|
|
18
|
-
}
|
|
19
|
-
EOF
|
|
20
|
-
|
|
21
|
-
subject.paths['uri'].render(path: 'test/abc.jpg').should == 'test/abc.jpg'
|
|
22
|
-
subject.paths['hash'].render(input_digest: '2cf24dba5fb0a30e', extension: 'jpg').should == '2cf24dba5fb0a30e.jpg'
|
|
23
|
-
subject.paths['hash-name'].render(input_digest: '2cf24dba5fb0a30e', image_name: 'xbrna', extension: 'jpg').should == '2cf24dba5fb0a30e/xbrna.jpg'
|
|
24
|
-
subject.paths['structured'].render(dirname: 'test', input_digest: '2cf24dba5fb0a30e', basename: 'abc', extension: 'jpg').should == 'test/2cf24dba5fb0a30e/abc.jpg'
|
|
25
|
-
subject.paths['structured-name'].render(dirname: 'test', input_digest: '2cf24dba5fb0a30e', basename: 'abc', extension: 'jpg', image_name: 'xbrna').should == 'test/2cf24dba5fb0a30e/abc-xbrna.jpg'
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
describe 'error handling' do
|
|
29
|
-
it 'should raise NoValueError on missing path name' do
|
|
30
|
-
expect {
|
|
31
|
-
Configuration.read(<<-'EOF')
|
|
32
|
-
path
|
|
33
|
-
EOF
|
|
34
|
-
}.to raise_error Configuration::NoValueError, %{syntax error while parsing 'path': expected path name}
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
it 'should raise NoValueError on missing path template' do
|
|
38
|
-
expect {
|
|
39
|
-
Configuration.read(<<-'EOF')
|
|
40
|
-
path {
|
|
41
|
-
"blah"
|
|
42
|
-
}
|
|
43
|
-
EOF
|
|
44
|
-
}.to raise_error Configuration::NoValueError, %{syntax error while parsing '"blah"': expected path template}
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
it 'should raise PathNotDefinedError if path lookup fails' do
|
|
48
|
-
subject = Configuration.read('')
|
|
49
|
-
|
|
50
|
-
expect {
|
|
51
|
-
subject.paths['blah']
|
|
52
|
-
}.to raise_error Configuration::PathNotDefinedError, "path 'blah' not defined"
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
it 'should raise NoValueForPathTemplatePlaceholerError if locals value is not found' do
|
|
56
|
-
subject = Configuration.read(<<-'EOF')
|
|
57
|
-
path {
|
|
58
|
-
"test" "#{abc}#{xyz}"
|
|
59
|
-
}
|
|
60
|
-
EOF
|
|
61
|
-
|
|
62
|
-
expect {
|
|
63
|
-
subject.paths['test'].render
|
|
64
|
-
}.to raise_error Configuration::NoValueForPathTemplatePlaceholerError, %q{cannot generate path 'test' from template '#{abc}#{xyz}': no value for '#{abc}'}
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
describe 'rendering from RequestState' do
|
|
69
|
-
let :state do
|
|
70
|
-
Configuration::RequestState.new(
|
|
71
|
-
'test',
|
|
72
|
-
{operation: 'pad'},
|
|
73
|
-
'test/abc.jpg',
|
|
74
|
-
{width: '123', height: '321'}
|
|
75
|
-
)
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
subject do
|
|
79
|
-
Configuration.read(<<-'EOF')
|
|
80
|
-
path "uri" "#{path}"
|
|
81
|
-
path "hash" "#{input_digest}.#{extension}"
|
|
82
|
-
path {
|
|
83
|
-
"hash-name" "#{input_digest}/#{image_name}.#{extension}"
|
|
84
|
-
"structured" "#{dirname}/#{input_digest}/#{basename}.#{extension}"
|
|
85
|
-
"structured-name" "#{dirname}/#{input_digest}/#{basename}-#{image_name}.#{extension}"
|
|
86
|
-
}
|
|
87
|
-
path "name" "#{image_name}"
|
|
88
|
-
path "base" "#{basename}"
|
|
89
|
-
EOF
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
it 'should render path using meta variables and locals' do
|
|
93
|
-
|
|
94
|
-
subject.paths['uri'].render(state).should == 'test/abc.jpg'
|
|
95
|
-
subject.paths['hash'].render(state).should == '9f86d081884c7d65.jpg'
|
|
96
|
-
subject.paths['hash-name'].render(state.with_locals(image_name: 'xbrna')).should == '9f86d081884c7d65/xbrna.jpg'
|
|
97
|
-
subject.paths['structured'].render(state).should == 'test/9f86d081884c7d65/abc.jpg'
|
|
98
|
-
subject.paths['structured-name'].render(state.with_locals(image_name: 'xbrna')).should == 'test/9f86d081884c7d65/abc-xbrna.jpg'
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
describe 'error handling' do
|
|
102
|
-
let :state do
|
|
103
|
-
Configuration::RequestState.new(
|
|
104
|
-
'',
|
|
105
|
-
{operation: 'pad'},
|
|
106
|
-
'test/abc.jpg',
|
|
107
|
-
{width: '123', height: '321'}
|
|
108
|
-
)
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
it 'should raise PathRenderingError if body was expected but not provided' do
|
|
112
|
-
expect {
|
|
113
|
-
subject.paths['hash'].render(state)
|
|
114
|
-
}.to raise_error Configuration::PathRenderingError, %q{cannot generate path 'hash' from template '#{input_digest}.#{extension}': need not empty request body to generate value for 'input_digest'}
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
it 'should raise PathRenderingError if variable not defined' do
|
|
118
|
-
expect {
|
|
119
|
-
subject.paths['name'].render(state)
|
|
120
|
-
}.to raise_error Configuration::PathRenderingError, %q{cannot generate path 'name' from template '#{image_name}': variable 'image_name' not defined}
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
it 'should raise PathRenderingError if meta variable dependent variable not defined' do
|
|
124
|
-
state = Configuration::RequestState.new(
|
|
125
|
-
'',
|
|
126
|
-
{operation: 'pad'},
|
|
127
|
-
nil,
|
|
128
|
-
{width: '123', height: '321'}
|
|
129
|
-
)
|
|
130
|
-
expect {
|
|
131
|
-
subject.paths['base'].render(state)
|
|
132
|
-
}.to raise_error Configuration::PathRenderingError, %q{cannot generate path 'base' from template '#{basename}': need 'path' variable to generate value for 'basename'}
|
|
133
|
-
end
|
|
134
|
-
end
|
|
135
|
-
end
|
|
136
|
-
end
|
|
137
|
-
end
|
|
138
|
-
|
|
@@ -1,911 +0,0 @@
|
|
|
1
|
-
require_relative 'spec_helper'
|
|
2
|
-
require 'aws-sdk'
|
|
3
|
-
require 'httpimagestore/configuration'
|
|
4
|
-
MemoryLimit.logger = Configuration::Scope.logger = RootLogger.new('/dev/null')
|
|
5
|
-
|
|
6
|
-
require 'httpimagestore/configuration/output'
|
|
7
|
-
require 'httpimagestore/configuration/s3'
|
|
8
|
-
|
|
9
|
-
unless ENV['AWS_ACCESS_KEY_ID'] and ENV['AWS_SECRET_ACCESS_KEY'] and ENV['AWS_S3_TEST_BUCKET']
|
|
10
|
-
puts "AWS_ACCESS_KEY_ID or AWS_SECRET_ACCESS_KEY or AWS_S3_TEST_BUCKET environment variables not set - Skipping S3 specs"
|
|
11
|
-
else
|
|
12
|
-
describe Configuration do
|
|
13
|
-
describe Configuration::S3 do
|
|
14
|
-
subject do
|
|
15
|
-
Configuration.read(<<-EOF)
|
|
16
|
-
s3 key="#{ENV['AWS_ACCESS_KEY_ID']}" secret="#{ENV['AWS_SECRET_ACCESS_KEY']}"
|
|
17
|
-
EOF
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
it 'should provide S3 key and secret' do
|
|
21
|
-
subject.s3.config.access_key_id.should == ENV['AWS_ACCESS_KEY_ID']
|
|
22
|
-
subject.s3.config.secret_access_key.should == ENV['AWS_SECRET_ACCESS_KEY']
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
it 'should use SSL by default' do
|
|
26
|
-
subject.s3.config.use_ssl.should be_true
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
it 'should allow disabling SSL' do
|
|
30
|
-
subject = Configuration.read(<<-EOF)
|
|
31
|
-
s3 key="#{ENV['AWS_ACCESS_KEY_ID']}" secret="#{ENV['AWS_SECRET_ACCESS_KEY']}" ssl=false
|
|
32
|
-
EOF
|
|
33
|
-
|
|
34
|
-
subject.s3.config.use_ssl.should be_false
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
it 'should provide S3 client' do
|
|
38
|
-
subject.s3.should be_a AWS::S3
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
describe 'error handling' do
|
|
42
|
-
it 'should raise StatementCollisionError on duplicate s3 statement' do
|
|
43
|
-
expect {
|
|
44
|
-
Configuration.read(<<-EOF)
|
|
45
|
-
s3 key="#{ENV['AWS_ACCESS_KEY_ID']}" secret="#{ENV['AWS_SECRET_ACCESS_KEY']}"
|
|
46
|
-
s3 key="#{ENV['AWS_ACCESS_KEY_ID']}" secret="#{ENV['AWS_SECRET_ACCESS_KEY']}"
|
|
47
|
-
EOF
|
|
48
|
-
}.to raise_error Configuration::StatementCollisionError, %{syntax error while parsing 's3 key="#{ENV['AWS_ACCESS_KEY_ID']}" secret="#{ENV['AWS_SECRET_ACCESS_KEY']}"': only one s3 type statement can be specified within context}
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
it 'should raise NoAttributeError on missing key attribute' do
|
|
52
|
-
expect {
|
|
53
|
-
Configuration.read(<<-EOF)
|
|
54
|
-
s3 secret="#{ENV['AWS_SECRET_ACCESS_KEY']}"
|
|
55
|
-
EOF
|
|
56
|
-
}.to raise_error Configuration::NoAttributeError, %{syntax error while parsing 's3 secret="#{ENV['AWS_SECRET_ACCESS_KEY']}"': expected 'key' attribute to be set}
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
it 'should raise NoAttributeError on missing secret attribute' do
|
|
60
|
-
expect {
|
|
61
|
-
Configuration.read(<<-EOF)
|
|
62
|
-
s3 key="#{ENV['AWS_ACCESS_KEY_ID']}"
|
|
63
|
-
EOF
|
|
64
|
-
}.to raise_error Configuration::NoAttributeError, %{syntax error while parsing 's3 key="#{ENV['AWS_ACCESS_KEY_ID']}"': expected 'secret' attribute to be set}
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
describe Configuration::S3SourceStoreBase::CacheRoot do
|
|
70
|
-
subject do
|
|
71
|
-
Configuration::S3SourceStoreBase::CacheRoot.new('/tmp')
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
before do
|
|
75
|
-
@cache_file = Pathname.new('/tmp/0d/bf/50c256d6b6efe55d11d0b6b50600')
|
|
76
|
-
@cache_file.dirname.mkpath
|
|
77
|
-
@cache_file.open('w') do |io|
|
|
78
|
-
io.write 'abc'
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
test2 = Pathname.new('/tmp/46/b9/7a454d831d7570abbb833330d9fb')
|
|
82
|
-
test2.unlink if test2.exist?
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
it 'should build cache file location for storage location from bucket and key' do
|
|
86
|
-
cache_file = subject.cache_file('mybucket', 'hello/world.jpg')
|
|
87
|
-
cache_file.should be_a Configuration::S3SourceStoreBase::CacheRoot::CacheFile
|
|
88
|
-
cache_file.to_s.should == "/tmp/0d/bf/50c256d6b6efe55d11d0b6b50600"
|
|
89
|
-
end
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
describe Configuration::S3Source do
|
|
93
|
-
let :state do
|
|
94
|
-
Configuration::RequestState.new('abc', {test_image: 'test.jpg'})
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
subject do
|
|
98
|
-
Configuration.read(<<-EOF)
|
|
99
|
-
s3 key="#{ENV['AWS_ACCESS_KEY_ID']}" secret="#{ENV['AWS_SECRET_ACCESS_KEY']}"
|
|
100
|
-
path "hash" "\#{test_image}"
|
|
101
|
-
get {
|
|
102
|
-
source_s3 "original" bucket="#{ENV['AWS_S3_TEST_BUCKET']}" path="hash"
|
|
103
|
-
}
|
|
104
|
-
EOF
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
before :all do
|
|
108
|
-
@test_data = (support_dir + 'compute.jpg').read.force_encoding('ASCII-8BIT')
|
|
109
|
-
|
|
110
|
-
s3_client = AWS::S3.new(use_ssl: false)
|
|
111
|
-
s3_test_bucket = s3_client.buckets[ENV['AWS_S3_TEST_BUCKET']]
|
|
112
|
-
s3_test_bucket.objects['test.jpg'].write(@test_data, content_type: 'image/jpeg')
|
|
113
|
-
s3_test_bucket.objects['test_prefix/test.jpg'].write(@test_data, content_type: 'image/jpeg')
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
it 'should source image from S3 using path spec' do
|
|
117
|
-
subject.handlers[0].sources[0].should be_a Configuration::S3Source
|
|
118
|
-
subject.handlers[0].sources[0].realize(state)
|
|
119
|
-
|
|
120
|
-
state.images['original'].data.should == @test_data
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
it 'should use S3 object content type for mime type' do
|
|
124
|
-
subject.handlers[0].sources[0].realize(state)
|
|
125
|
-
|
|
126
|
-
state.images['original'].mime_type.should == 'image/jpeg'
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
it 'should provide source path and HTTPS url' do
|
|
130
|
-
subject.handlers[0].sources[0].realize(state)
|
|
131
|
-
|
|
132
|
-
state.images['original'].source_path.should == "test.jpg"
|
|
133
|
-
state.images['original'].source_url.to_s.should start_with "https://"
|
|
134
|
-
state.images['original'].source_url.to_s.should include ENV['AWS_S3_TEST_BUCKET']
|
|
135
|
-
state.images['original'].source_url.to_s.should include "/test.jpg"
|
|
136
|
-
state.images['original'].source_url.to_s.should include ENV['AWS_ACCESS_KEY_ID']
|
|
137
|
-
status(state.images['original'].source_url).should == 200
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
describe 'storage prefix' do
|
|
141
|
-
subject do
|
|
142
|
-
Configuration.read(<<-EOF)
|
|
143
|
-
s3 key="#{ENV['AWS_ACCESS_KEY_ID']}" secret="#{ENV['AWS_SECRET_ACCESS_KEY']}"
|
|
144
|
-
path "hash" "\#{test_image}"
|
|
145
|
-
get {
|
|
146
|
-
source_s3 "original" bucket="#{ENV['AWS_S3_TEST_BUCKET']}" path="hash" prefix="test_prefix/"
|
|
147
|
-
}
|
|
148
|
-
EOF
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
it 'should still provide valid HTTPS URL incliding prefix' do
|
|
152
|
-
subject.handlers[0].sources[0].realize(state)
|
|
153
|
-
|
|
154
|
-
state.images['original'].source_url.to_s.should start_with "https://"
|
|
155
|
-
state.images['original'].source_url.to_s.should include ENV['AWS_S3_TEST_BUCKET']
|
|
156
|
-
state.images['original'].source_url.to_s.should include "/test_prefix/test.jpg"
|
|
157
|
-
state.images['original'].source_url.to_s.should include ENV['AWS_ACCESS_KEY_ID']
|
|
158
|
-
status(state.images['original'].source_url).should == 200
|
|
159
|
-
end
|
|
160
|
-
|
|
161
|
-
it 'should provide source path without prefix' do
|
|
162
|
-
subject.handlers[0].sources[0].realize(state)
|
|
163
|
-
|
|
164
|
-
state.images['original'].source_path.should == "test.jpg"
|
|
165
|
-
end
|
|
166
|
-
end
|
|
167
|
-
|
|
168
|
-
describe 'object cache' do
|
|
169
|
-
let :state do
|
|
170
|
-
Configuration::RequestState.new('abc', {test_image: 'test/ghost.jpg'})
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
subject do
|
|
174
|
-
Configuration.read(<<-EOF)
|
|
175
|
-
s3 key="#{ENV['AWS_ACCESS_KEY_ID']}" secret="#{ENV['AWS_SECRET_ACCESS_KEY']}"
|
|
176
|
-
path "hash" "\#{test_image}"
|
|
177
|
-
get {
|
|
178
|
-
source_s3 "original" bucket="#{ENV['AWS_S3_TEST_BUCKET']}" path="hash" cache-root="/tmp"
|
|
179
|
-
source_s3 "original_cached" bucket="#{ENV['AWS_S3_TEST_BUCKET']}" path="hash" cache-root="/tmp"
|
|
180
|
-
source_s3 "original_cached_public" bucket="#{ENV['AWS_S3_TEST_BUCKET']}" path="hash" cache-root="/tmp" public="true"
|
|
181
|
-
source_s3 "original_cached_public2" bucket="#{ENV['AWS_S3_TEST_BUCKET']}" path="hash" cache-root="/tmp" public="true"
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
post {
|
|
185
|
-
store_s3 "input" bucket="#{ENV['AWS_S3_TEST_BUCKET']}" path="hash" cache-root="/tmp"
|
|
186
|
-
}
|
|
187
|
-
EOF
|
|
188
|
-
end
|
|
189
|
-
|
|
190
|
-
before do
|
|
191
|
-
@cache_file = Pathname.new('/tmp/ce/26/b196585e28aa99f55b1260b629e2')
|
|
192
|
-
@cache_file.dirname.mkpath
|
|
193
|
-
@cache_file.open('wb') do |io|
|
|
194
|
-
header = MessagePack.pack(
|
|
195
|
-
'private_url' => 'https://s3-eu-west-1.amazonaws.com/test/ghost.jpg?' + ENV['AWS_ACCESS_KEY_ID'],
|
|
196
|
-
'public_url' => 'https://s3-eu-west-1.amazonaws.com/test/ghost.jpg',
|
|
197
|
-
'content_type' => 'image/jpeg'
|
|
198
|
-
)
|
|
199
|
-
io.write [header.length].pack('L') # header length
|
|
200
|
-
io.write header
|
|
201
|
-
io.write 'abc'
|
|
202
|
-
end
|
|
203
|
-
end
|
|
204
|
-
|
|
205
|
-
it 'should use cache when configured and object in cache' do
|
|
206
|
-
subject.handlers[0].sources[0].should be_a Configuration::S3Source
|
|
207
|
-
subject.handlers[0].sources[0].realize(state)
|
|
208
|
-
|
|
209
|
-
state.images['original'].data.should == 'abc'
|
|
210
|
-
end
|
|
211
|
-
|
|
212
|
-
it 'should keep mime type' do
|
|
213
|
-
subject.handlers[0].sources[0].realize(state)
|
|
214
|
-
|
|
215
|
-
state.images['original'].mime_type.should == 'image/jpeg'
|
|
216
|
-
end
|
|
217
|
-
|
|
218
|
-
it 'should keep private source URL' do
|
|
219
|
-
subject.handlers[0].sources[0].realize(state)
|
|
220
|
-
|
|
221
|
-
state.images['original'].source_url.should be_a Addressable::URI
|
|
222
|
-
state.images['original'].source_url.to_s.should == 'https://s3-eu-west-1.amazonaws.com/test/ghost.jpg?' + ENV['AWS_ACCESS_KEY_ID']
|
|
223
|
-
end
|
|
224
|
-
|
|
225
|
-
it 'should keep public source URL' do
|
|
226
|
-
subject = Configuration.read(<<-EOF)
|
|
227
|
-
s3 key="#{ENV['AWS_ACCESS_KEY_ID']}" secret="#{ENV['AWS_SECRET_ACCESS_KEY']}"
|
|
228
|
-
path "hash" "\#{test_image}"
|
|
229
|
-
get {
|
|
230
|
-
source_s3 "original" bucket="#{ENV['AWS_S3_TEST_BUCKET']}" path="hash" cache-root="/tmp" public=true
|
|
231
|
-
}
|
|
232
|
-
EOF
|
|
233
|
-
subject.handlers[0].sources[0].realize(state)
|
|
234
|
-
|
|
235
|
-
state.images['original'].source_url.should be_a Addressable::URI
|
|
236
|
-
state.images['original'].source_url.to_s.should == 'https://s3-eu-west-1.amazonaws.com/test/ghost.jpg'
|
|
237
|
-
end
|
|
238
|
-
|
|
239
|
-
describe 'read-through' do
|
|
240
|
-
it 'shluld use object stored in S3 when not found in the cache' do
|
|
241
|
-
cache_file = Pathname.new('/tmp/af/a3/5eaf0a614693e2d1ed455ac1cedb')
|
|
242
|
-
cache_file.unlink if cache_file.exist?
|
|
243
|
-
|
|
244
|
-
state = Configuration::RequestState.new('abc', {test_image: 'test.jpg'})
|
|
245
|
-
subject.handlers[0].sources[0].realize(state)
|
|
246
|
-
|
|
247
|
-
cache_file.should exist
|
|
248
|
-
end
|
|
249
|
-
|
|
250
|
-
it 'should write cache on read and be able to use it on next read' do
|
|
251
|
-
cache_file = Pathname.new('/tmp/af/a3/5eaf0a614693e2d1ed455ac1cedb')
|
|
252
|
-
cache_file.unlink if cache_file.exist?
|
|
253
|
-
|
|
254
|
-
state = Configuration::RequestState.new('abc', {test_image: 'test.jpg'})
|
|
255
|
-
subject.handlers[0].sources[0].realize(state)
|
|
256
|
-
|
|
257
|
-
cache_file.should exist
|
|
258
|
-
|
|
259
|
-
subject.handlers[0].sources[1].realize(state)
|
|
260
|
-
|
|
261
|
-
state.images['original'].data.should == @test_data
|
|
262
|
-
state.images['original'].mime_type.should == 'image/jpeg'
|
|
263
|
-
|
|
264
|
-
state.images['original_cached'].data.should == @test_data
|
|
265
|
-
state.images['original_cached'].mime_type.should == 'image/jpeg'
|
|
266
|
-
end
|
|
267
|
-
|
|
268
|
-
it 'should update cached object with new properties read from S3' do
|
|
269
|
-
cache_file = Pathname.new('/tmp/af/a3/5eaf0a614693e2d1ed455ac1cedb')
|
|
270
|
-
cache_file.unlink if cache_file.exist?
|
|
271
|
-
|
|
272
|
-
state = Configuration::RequestState.new('abc', {test_image: 'test.jpg'})
|
|
273
|
-
|
|
274
|
-
## cache with private URL
|
|
275
|
-
subject.handlers[0].sources[0].realize(state)
|
|
276
|
-
|
|
277
|
-
cache_file.should exist
|
|
278
|
-
sum = Digest::SHA2.new.update(cache_file.read).to_s
|
|
279
|
-
|
|
280
|
-
## read from cache with private URL
|
|
281
|
-
subject.handlers[0].sources[1].realize(state)
|
|
282
|
-
|
|
283
|
-
# no change
|
|
284
|
-
Digest::SHA2.new.update(cache_file.read).to_s.should == sum
|
|
285
|
-
|
|
286
|
-
## read from cache; add public URL
|
|
287
|
-
subject.handlers[0].sources[2].realize(state)
|
|
288
|
-
|
|
289
|
-
# should get updated
|
|
290
|
-
Digest::SHA2.new.update(cache_file.read).to_s.should_not == sum
|
|
291
|
-
|
|
292
|
-
sum = Digest::SHA2.new.update(cache_file.read).to_s
|
|
293
|
-
## read from cahce
|
|
294
|
-
subject.handlers[0].sources[3].realize(state)
|
|
295
|
-
|
|
296
|
-
# no change
|
|
297
|
-
Digest::SHA2.new.update(cache_file.read).to_s.should == sum
|
|
298
|
-
end
|
|
299
|
-
|
|
300
|
-
describe 'error handling' do
|
|
301
|
-
let :state do
|
|
302
|
-
Configuration::RequestState.new('abc', {test_image: 'test.jpg'})
|
|
303
|
-
end
|
|
304
|
-
|
|
305
|
-
before :each do
|
|
306
|
-
@cache_file = Pathname.new('/tmp/af/a3/5eaf0a614693e2d1ed455ac1cedb')
|
|
307
|
-
@cache_file.dirname.mkpath
|
|
308
|
-
@cache_file.open('wb') do |io|
|
|
309
|
-
header = 'xyz'
|
|
310
|
-
io.write [header.length].pack('L') # header length
|
|
311
|
-
io.write header
|
|
312
|
-
io.write 'abc'
|
|
313
|
-
end
|
|
314
|
-
end
|
|
315
|
-
|
|
316
|
-
it 'should rewrite cached object when corrupted' do
|
|
317
|
-
subject.handlers[0].sources[0].realize(state)
|
|
318
|
-
state.images['original'].data.should == @test_data
|
|
319
|
-
|
|
320
|
-
cache = @cache_file.read.force_encoding('ASCII-8BIT')
|
|
321
|
-
cache.should_not include 'xyz'
|
|
322
|
-
cache.should include @test_data
|
|
323
|
-
end
|
|
324
|
-
|
|
325
|
-
it 'should use S3 object when cache file is not accessible' do
|
|
326
|
-
@cache_file.chmod(0000)
|
|
327
|
-
begin
|
|
328
|
-
subject.handlers[0].sources[0].realize(state)
|
|
329
|
-
state.images['original'].data.should == @test_data
|
|
330
|
-
ensure
|
|
331
|
-
@cache_file.chmod(0644)
|
|
332
|
-
|
|
333
|
-
cache = @cache_file.read.force_encoding('ASCII-8BIT')
|
|
334
|
-
cache.should include 'xyz'
|
|
335
|
-
cache.should_not include @test_data
|
|
336
|
-
end
|
|
337
|
-
end
|
|
338
|
-
|
|
339
|
-
it 'should use S3 object when cache direcotry is not accessible' do
|
|
340
|
-
@cache_file.dirname.chmod(0000)
|
|
341
|
-
begin
|
|
342
|
-
subject.handlers[0].sources[0].realize(state)
|
|
343
|
-
state.images['original'].data.should == @test_data
|
|
344
|
-
ensure
|
|
345
|
-
@cache_file.dirname.chmod(0755)
|
|
346
|
-
|
|
347
|
-
cache = @cache_file.read.force_encoding('ASCII-8BIT')
|
|
348
|
-
cache.should include 'xyz'
|
|
349
|
-
cache.should_not include @test_data
|
|
350
|
-
end
|
|
351
|
-
end
|
|
352
|
-
|
|
353
|
-
it 'should not store cache file for S3 objects that does not exist' do
|
|
354
|
-
cache_file = Pathname.new('/tmp/a2/fd/4261e9a7586ed772d0c78bb51c9d')
|
|
355
|
-
cache_file.unlink if cache_file.exist?
|
|
356
|
-
|
|
357
|
-
state = Configuration::RequestState.new('abc', {test_image: 'bogus.jpg'})
|
|
358
|
-
|
|
359
|
-
expect {
|
|
360
|
-
subject.handlers[0].sources[0].realize(state)
|
|
361
|
-
}.to raise_error Configuration::S3NoSuchKeyError
|
|
362
|
-
|
|
363
|
-
cache_file.should_not exist
|
|
364
|
-
end
|
|
365
|
-
end
|
|
366
|
-
end
|
|
367
|
-
|
|
368
|
-
describe 'write-through' do
|
|
369
|
-
let :state do
|
|
370
|
-
Configuration::RequestState.new(@test_data, {test_image: 'test_cache.jpg'})
|
|
371
|
-
end
|
|
372
|
-
|
|
373
|
-
before :each do
|
|
374
|
-
end
|
|
375
|
-
|
|
376
|
-
it 'should cache S3 object during write' do
|
|
377
|
-
cache_file = Pathname.new('/tmp/31/f6/d48147b9981bb880fb1861539e3f')
|
|
378
|
-
cache_file.unlink if cache_file.exist?
|
|
379
|
-
|
|
380
|
-
subject.handlers[1].sources[0].realize(state)
|
|
381
|
-
state.images['input'].mime_type = 'image/jpeg'
|
|
382
|
-
subject.handlers[1].stores[0].realize(state)
|
|
383
|
-
|
|
384
|
-
# we have cache
|
|
385
|
-
cache_file.should exist
|
|
386
|
-
|
|
387
|
-
# but delete S3 so it will fail if cache was not used fully
|
|
388
|
-
s3_client = AWS::S3.new(use_ssl: false)
|
|
389
|
-
s3_test_bucket = s3_client.buckets[ENV['AWS_S3_TEST_BUCKET']]
|
|
390
|
-
s3_test_bucket.objects['test_cache.jpg'].delete
|
|
391
|
-
|
|
392
|
-
state = Configuration::RequestState.new('', {test_image: 'test_cache.jpg'})
|
|
393
|
-
expect {
|
|
394
|
-
subject.handlers[0].sources[0].realize(state)
|
|
395
|
-
}.not_to raise_error
|
|
396
|
-
state.images['original'].data.should == @test_data
|
|
397
|
-
end
|
|
398
|
-
end
|
|
399
|
-
end
|
|
400
|
-
|
|
401
|
-
describe 'non encrypted connection mode' do
|
|
402
|
-
subject do
|
|
403
|
-
Configuration.read(<<-EOF)
|
|
404
|
-
s3 key="#{ENV['AWS_ACCESS_KEY_ID']}" secret="#{ENV['AWS_SECRET_ACCESS_KEY']}" ssl=false
|
|
405
|
-
path "hash" "\#{test_image}"
|
|
406
|
-
get {
|
|
407
|
-
source_s3 "original" bucket="#{ENV['AWS_S3_TEST_BUCKET']}" path="hash"
|
|
408
|
-
}
|
|
409
|
-
EOF
|
|
410
|
-
end
|
|
411
|
-
|
|
412
|
-
it 'should source image from S3 using path spec' do
|
|
413
|
-
subject.handlers[0].sources[0].should be_a Configuration::S3Source
|
|
414
|
-
subject.handlers[0].sources[0].realize(state)
|
|
415
|
-
|
|
416
|
-
state.images['original'].data.should == @test_data
|
|
417
|
-
end
|
|
418
|
-
|
|
419
|
-
it 'should provide source HTTP url' do
|
|
420
|
-
subject.handlers[0].sources[0].realize(state)
|
|
421
|
-
state.images['original'].source_url.to_s.should start_with "http://"
|
|
422
|
-
state.images['original'].source_url.to_s.should include ENV['AWS_S3_TEST_BUCKET']
|
|
423
|
-
state.images['original'].source_url.to_s.should include "/test.jpg"
|
|
424
|
-
state.images['original'].source_url.to_s.should include ENV['AWS_ACCESS_KEY_ID']
|
|
425
|
-
status(state.images['original'].source_url).should == 200
|
|
426
|
-
end
|
|
427
|
-
end
|
|
428
|
-
|
|
429
|
-
describe 'context locals' do
|
|
430
|
-
before :all do
|
|
431
|
-
s3_client = AWS::S3.new(use_ssl: false)
|
|
432
|
-
s3_test_bucket = s3_client.buckets[ENV['AWS_S3_TEST_BUCKET']]
|
|
433
|
-
s3_test_bucket.objects['test-image-name.jpg'].write('hello world', content_type: 'image/jpeg')
|
|
434
|
-
s3_test_bucket.objects["#{ENV['AWS_S3_TEST_BUCKET']}.jpg"].write('hello bucket', content_type: 'image/jpeg')
|
|
435
|
-
end
|
|
436
|
-
|
|
437
|
-
subject do
|
|
438
|
-
Configuration.read(<<-EOF)
|
|
439
|
-
s3 key="#{ENV['AWS_ACCESS_KEY_ID']}" secret="#{ENV['AWS_SECRET_ACCESS_KEY']}" ssl=false
|
|
440
|
-
path "image_name" "\#{image_name}.jpg"
|
|
441
|
-
path "bucket" "\#{bucket}.jpg"
|
|
442
|
-
get {
|
|
443
|
-
source_s3 "test-image-name" bucket="#{ENV['AWS_S3_TEST_BUCKET']}" path="image_name"
|
|
444
|
-
source_s3 "bucket" bucket="#{ENV['AWS_S3_TEST_BUCKET']}" path="bucket"
|
|
445
|
-
}
|
|
446
|
-
EOF
|
|
447
|
-
end
|
|
448
|
-
|
|
449
|
-
it 'should provide image name to be used as #{image_name}' do
|
|
450
|
-
subject.handlers[0].sources[0].realize(state)
|
|
451
|
-
state.images['test-image-name'].source_path.should == 'test-image-name.jpg'
|
|
452
|
-
state.images['test-image-name'].data.should == 'hello world'
|
|
453
|
-
end
|
|
454
|
-
|
|
455
|
-
it 'should provide bucket to be used as #{bucket}' do
|
|
456
|
-
subject.handlers[0].sources[1].realize(state)
|
|
457
|
-
state.images['bucket'].source_path.should == "#{ENV['AWS_S3_TEST_BUCKET']}.jpg"
|
|
458
|
-
state.images['bucket'].data.should == 'hello bucket'
|
|
459
|
-
end
|
|
460
|
-
end
|
|
461
|
-
|
|
462
|
-
describe 'error handling' do
|
|
463
|
-
it 'should raise NoValueError on missing image name' do
|
|
464
|
-
expect {
|
|
465
|
-
Configuration.read(<<-EOF)
|
|
466
|
-
get "test" {
|
|
467
|
-
source_s3 bucket="#{ENV['AWS_S3_TEST_BUCKET']}" path="hash"
|
|
468
|
-
}
|
|
469
|
-
EOF
|
|
470
|
-
}.to raise_error Configuration::NoValueError, %{syntax error while parsing 'source_s3 bucket="#{ENV['AWS_S3_TEST_BUCKET']}" path="hash"': expected image name}
|
|
471
|
-
end
|
|
472
|
-
|
|
473
|
-
it 'should raise NoAttributeError on missing bucket name' do
|
|
474
|
-
expect {
|
|
475
|
-
Configuration.read(<<-EOF)
|
|
476
|
-
get "test" {
|
|
477
|
-
source_s3 "original" path="hash"
|
|
478
|
-
}
|
|
479
|
-
EOF
|
|
480
|
-
}.to raise_error Configuration::NoAttributeError, %{syntax error while parsing 'source_s3 "original" path="hash"': expected 'bucket' attribute to be set}
|
|
481
|
-
end
|
|
482
|
-
|
|
483
|
-
it 'should raise NoAttributeError on missing path' do
|
|
484
|
-
expect {
|
|
485
|
-
Configuration.read(<<-EOF)
|
|
486
|
-
get "test" {
|
|
487
|
-
source_s3 "original" bucket="#{ENV['AWS_S3_TEST_BUCKET']}"
|
|
488
|
-
}
|
|
489
|
-
EOF
|
|
490
|
-
}.to raise_error Configuration::NoAttributeError, %{syntax error while parsing 'source_s3 "original" bucket="#{ENV['AWS_S3_TEST_BUCKET']}"': expected 'path' attribute to be set}
|
|
491
|
-
end
|
|
492
|
-
|
|
493
|
-
it 'should raise S3NotConfiguredError if used but no s3 statement was used' do
|
|
494
|
-
subject = Configuration.read(<<-'EOF')
|
|
495
|
-
path "hash" "#{test_image}"
|
|
496
|
-
get "test" {
|
|
497
|
-
source_s3 "original" bucket="#{ENV['AWS_S3_TEST_BUCKET']}" path="hash"
|
|
498
|
-
}
|
|
499
|
-
EOF
|
|
500
|
-
expect {
|
|
501
|
-
subject.handlers[0].sources[0].realize(state)
|
|
502
|
-
}.to raise_error Configuration::S3NotConfiguredError, 'S3 client not configured'
|
|
503
|
-
end
|
|
504
|
-
|
|
505
|
-
it 'should raise S3NoSuchBucketError if bucket was not found on S3' do
|
|
506
|
-
subject = Configuration.read(<<-EOF)
|
|
507
|
-
s3 key="#{ENV['AWS_ACCESS_KEY_ID']}" secret="#{ENV['AWS_SECRET_ACCESS_KEY']}" ssl=false
|
|
508
|
-
path "hash" "\#{test_image}"
|
|
509
|
-
get "test" {
|
|
510
|
-
source_s3 "original" bucket="#{ENV['AWS_S3_TEST_BUCKET']}X" path="hash"
|
|
511
|
-
}
|
|
512
|
-
EOF
|
|
513
|
-
expect {
|
|
514
|
-
subject.handlers[0].sources[0].realize(state)
|
|
515
|
-
}.to raise_error Configuration::S3NoSuchBucketError, %{S3 bucket '#{ENV['AWS_S3_TEST_BUCKET']}X' does not exist}
|
|
516
|
-
end
|
|
517
|
-
|
|
518
|
-
it 'should raise S3NoSuchKeyError if object was not found on S3' do
|
|
519
|
-
subject = Configuration.read(<<-EOF)
|
|
520
|
-
s3 key="#{ENV['AWS_ACCESS_KEY_ID']}" secret="#{ENV['AWS_SECRET_ACCESS_KEY']}" ssl=false
|
|
521
|
-
path "hash" "blah"
|
|
522
|
-
get "test" {
|
|
523
|
-
source_s3 "original" bucket="#{ENV['AWS_S3_TEST_BUCKET']}" path="hash"
|
|
524
|
-
}
|
|
525
|
-
EOF
|
|
526
|
-
expect {
|
|
527
|
-
subject.handlers[0].sources[0].realize(state)
|
|
528
|
-
}.to raise_error Configuration::S3NoSuchKeyError, %{S3 bucket '#{ENV['AWS_S3_TEST_BUCKET']}' does not contain key 'blah'}
|
|
529
|
-
end
|
|
530
|
-
|
|
531
|
-
it 'should raise S3AccessDenied if bucket was not found on S3' do
|
|
532
|
-
subject = Configuration.read(<<-EOF)
|
|
533
|
-
s3 key="#{ENV['AWS_ACCESS_KEY_ID']}" secret="#{ENV['AWS_SECRET_ACCESS_KEY']}" ssl=false
|
|
534
|
-
path "hash" "\#{test_image}"
|
|
535
|
-
get "test" {
|
|
536
|
-
source_s3 "original" bucket="blah" path="hash"
|
|
537
|
-
}
|
|
538
|
-
EOF
|
|
539
|
-
expect {
|
|
540
|
-
subject.handlers[0].sources[0].realize(state)
|
|
541
|
-
}.to raise_error Configuration::S3AccessDenied, %{access to S3 bucket 'blah' or key 'test.jpg' was denied}
|
|
542
|
-
end
|
|
543
|
-
end
|
|
544
|
-
|
|
545
|
-
describe 'memory limit' do
|
|
546
|
-
let :state do
|
|
547
|
-
Configuration::RequestState.new('abc', {test_image: 'test.jpg'}, '', {}, MemoryLimit.new(10))
|
|
548
|
-
end
|
|
549
|
-
|
|
550
|
-
it 'should raise MemoryLimit::MemoryLimitedExceededError when sourcing bigger image than limit' do
|
|
551
|
-
expect {
|
|
552
|
-
subject.handlers[0].sources[0].realize(state)
|
|
553
|
-
}.to raise_error MemoryLimit::MemoryLimitedExceededError
|
|
554
|
-
end
|
|
555
|
-
end
|
|
556
|
-
|
|
557
|
-
context 'in failover context' do
|
|
558
|
-
subject do
|
|
559
|
-
Configuration.read(<<-EOF)
|
|
560
|
-
s3 key="#{ENV['AWS_ACCESS_KEY_ID']}" secret="#{ENV['AWS_SECRET_ACCESS_KEY']}"
|
|
561
|
-
path "hash" "\#{test_image}"
|
|
562
|
-
path "bogus" "bogus"
|
|
563
|
-
get {
|
|
564
|
-
source_failover {
|
|
565
|
-
source_s3 "first_fail_1" bucket="#{ENV['AWS_S3_TEST_BUCKET']}" path="bogus"
|
|
566
|
-
source_s3 "first_fail_2" bucket="#{ENV['AWS_S3_TEST_BUCKET']}" path="hash"
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
EOF
|
|
570
|
-
end
|
|
571
|
-
|
|
572
|
-
it 'should source second image' do
|
|
573
|
-
subject.handlers[0].sources[0].should be_a Configuration::SourceFailover
|
|
574
|
-
subject.handlers[0].sources[0].realize(state)
|
|
575
|
-
|
|
576
|
-
state.images.keys.should == ['first_fail_2']
|
|
577
|
-
state.images['first_fail_2'].should_not be_nil
|
|
578
|
-
state.images['first_fail_2'].data.should == @test_data
|
|
579
|
-
end
|
|
580
|
-
end
|
|
581
|
-
end
|
|
582
|
-
|
|
583
|
-
describe Configuration::S3Store do
|
|
584
|
-
let :state do
|
|
585
|
-
Configuration::RequestState.new(@test_data, {test_image: 'test_out.jpg'})
|
|
586
|
-
end
|
|
587
|
-
|
|
588
|
-
subject do
|
|
589
|
-
Configuration.read(<<-EOF)
|
|
590
|
-
s3 key="#{ENV['AWS_ACCESS_KEY_ID']}" secret="#{ENV['AWS_SECRET_ACCESS_KEY']}"
|
|
591
|
-
path "hash" "\#{test_image}"
|
|
592
|
-
post {
|
|
593
|
-
store_s3 "input" bucket="#{ENV['AWS_S3_TEST_BUCKET']}" path="hash"
|
|
594
|
-
}
|
|
595
|
-
EOF
|
|
596
|
-
end
|
|
597
|
-
|
|
598
|
-
before :all do
|
|
599
|
-
@test_data = (support_dir + 'compute.jpg').read.force_encoding('ASCII-8BIT')
|
|
600
|
-
|
|
601
|
-
s3_client = AWS::S3.new(use_ssl: false)
|
|
602
|
-
s3_test_bucket = s3_client.buckets[ENV['AWS_S3_TEST_BUCKET']]
|
|
603
|
-
@test_object = s3_test_bucket.objects['test_out.jpg']
|
|
604
|
-
@test_object.delete
|
|
605
|
-
test_object = s3_test_bucket.objects['test_prefix/test_out.jpg']
|
|
606
|
-
test_object.delete
|
|
607
|
-
end
|
|
608
|
-
|
|
609
|
-
before :each do
|
|
610
|
-
subject.handlers[0].sources[0].realize(state)
|
|
611
|
-
end
|
|
612
|
-
|
|
613
|
-
it 'should source image from S3 using path spec' do
|
|
614
|
-
subject.handlers[0].stores[0].should be_a Configuration::S3Store
|
|
615
|
-
subject.handlers[0].stores[0].realize(state)
|
|
616
|
-
|
|
617
|
-
@test_object.read.should == @test_data
|
|
618
|
-
end
|
|
619
|
-
|
|
620
|
-
it 'should use image mime type as S3 object content type' do
|
|
621
|
-
state.images['input'].mime_type = 'image/jpeg'
|
|
622
|
-
subject.handlers[0].stores[0].realize(state)
|
|
623
|
-
|
|
624
|
-
@test_object.head[:content_type].should == 'image/jpeg'
|
|
625
|
-
end
|
|
626
|
-
|
|
627
|
-
it 'should provide source path and HTTPS url' do
|
|
628
|
-
subject.handlers[0].stores[0].realize(state)
|
|
629
|
-
|
|
630
|
-
state.images['input'].store_path.should == "test_out.jpg"
|
|
631
|
-
state.images['input'].store_url.to_s.should start_with "https://"
|
|
632
|
-
state.images['input'].store_url.to_s.should include ENV['AWS_S3_TEST_BUCKET']
|
|
633
|
-
state.images['input'].store_url.to_s.should include "/test_out.jpg"
|
|
634
|
-
state.images['input'].store_url.to_s.should include ENV['AWS_ACCESS_KEY_ID']
|
|
635
|
-
status(state.images['input'].store_url).should == 200
|
|
636
|
-
end
|
|
637
|
-
|
|
638
|
-
describe 'storage prefix' do
|
|
639
|
-
subject do
|
|
640
|
-
Configuration.read(<<-EOF)
|
|
641
|
-
s3 key="#{ENV['AWS_ACCESS_KEY_ID']}" secret="#{ENV['AWS_SECRET_ACCESS_KEY']}"
|
|
642
|
-
path "hash" "\#{test_image}"
|
|
643
|
-
post {
|
|
644
|
-
store_s3 "input" bucket="#{ENV['AWS_S3_TEST_BUCKET']}" path="hash" prefix="test_prefix/"
|
|
645
|
-
}
|
|
646
|
-
EOF
|
|
647
|
-
end
|
|
648
|
-
|
|
649
|
-
it 'should still provide valid HTTPS URL incliding prefix' do
|
|
650
|
-
subject.handlers[0].stores[0].realize(state)
|
|
651
|
-
|
|
652
|
-
state.images['input'].store_url.to_s.should start_with "https://"
|
|
653
|
-
state.images['input'].store_url.to_s.should include ENV['AWS_S3_TEST_BUCKET']
|
|
654
|
-
state.images['input'].store_url.to_s.should include "test_prefix/test_out.jpg"
|
|
655
|
-
state.images['input'].store_url.to_s.should include ENV['AWS_ACCESS_KEY_ID']
|
|
656
|
-
status(state.images['input'].store_url).should == 200
|
|
657
|
-
end
|
|
658
|
-
|
|
659
|
-
it 'should provide storage path without prefix' do
|
|
660
|
-
subject.handlers[0].stores[0].realize(state)
|
|
661
|
-
|
|
662
|
-
state.images['input'].store_path.should == "test_out.jpg"
|
|
663
|
-
end
|
|
664
|
-
end
|
|
665
|
-
|
|
666
|
-
describe 'non encrypted connection mode' do
|
|
667
|
-
subject do
|
|
668
|
-
Configuration.read(<<-EOF)
|
|
669
|
-
s3 key="#{ENV['AWS_ACCESS_KEY_ID']}" secret="#{ENV['AWS_SECRET_ACCESS_KEY']}" ssl=false
|
|
670
|
-
path "hash" "\#{test_image}"
|
|
671
|
-
post {
|
|
672
|
-
store_s3 "input" bucket="#{ENV['AWS_S3_TEST_BUCKET']}" path="hash"
|
|
673
|
-
}
|
|
674
|
-
EOF
|
|
675
|
-
end
|
|
676
|
-
|
|
677
|
-
it 'should source image from S3 using path spec' do
|
|
678
|
-
subject.handlers[0].stores[0].should be_a Configuration::S3Store
|
|
679
|
-
subject.handlers[0].stores[0].realize(state)
|
|
680
|
-
|
|
681
|
-
@test_object.read.should == @test_data
|
|
682
|
-
end
|
|
683
|
-
|
|
684
|
-
it 'should provide source HTTP url' do
|
|
685
|
-
subject.handlers[0].stores[0].realize(state)
|
|
686
|
-
|
|
687
|
-
state.images['input'].store_url.to_s.should start_with "http://"
|
|
688
|
-
state.images['input'].store_url.to_s.should include ENV['AWS_S3_TEST_BUCKET']
|
|
689
|
-
state.images['input'].store_url.to_s.should include "/test_out.jpg"
|
|
690
|
-
state.images['input'].store_url.to_s.should include ENV['AWS_ACCESS_KEY_ID']
|
|
691
|
-
status(state.images['input'].store_url).should == 200
|
|
692
|
-
end
|
|
693
|
-
end
|
|
694
|
-
|
|
695
|
-
describe 'permission control' do
|
|
696
|
-
it 'should store images that are not accessible by public by default' do
|
|
697
|
-
subject.handlers[0].stores[0].realize(state)
|
|
698
|
-
status(state.images['input'].store_url.to_s[/^[^\?]*/]).should == 403
|
|
699
|
-
end
|
|
700
|
-
|
|
701
|
-
describe 'public' do
|
|
702
|
-
subject do
|
|
703
|
-
Configuration.read(<<-EOF)
|
|
704
|
-
s3 key="#{ENV['AWS_ACCESS_KEY_ID']}" secret="#{ENV['AWS_SECRET_ACCESS_KEY']}"
|
|
705
|
-
path "hash" "\#{test_image}"
|
|
706
|
-
post {
|
|
707
|
-
store_s3 "input" bucket="#{ENV['AWS_S3_TEST_BUCKET']}" path="hash" public=true
|
|
708
|
-
}
|
|
709
|
-
EOF
|
|
710
|
-
end
|
|
711
|
-
|
|
712
|
-
it 'should store image accessible for public' do
|
|
713
|
-
subject.handlers[0].stores[0].realize(state)
|
|
714
|
-
|
|
715
|
-
get(state.images['input'].store_url).should == @test_data
|
|
716
|
-
end
|
|
717
|
-
|
|
718
|
-
it 'should provide public source HTTPS url' do
|
|
719
|
-
subject.handlers[0].stores[0].realize(state)
|
|
720
|
-
|
|
721
|
-
state.images['input'].store_url.to_s.should start_with "https://"
|
|
722
|
-
state.images['input'].store_url.to_s.should include ENV['AWS_S3_TEST_BUCKET']
|
|
723
|
-
state.images['input'].store_url.to_s.should include "/test_out.jpg"
|
|
724
|
-
state.images['input'].store_url.to_s.should_not include ENV['AWS_ACCESS_KEY_ID']
|
|
725
|
-
status(state.images['input'].store_url).should == 200
|
|
726
|
-
end
|
|
727
|
-
|
|
728
|
-
describe 'non encrypted connection mode' do
|
|
729
|
-
subject do
|
|
730
|
-
Configuration.read(<<-EOF)
|
|
731
|
-
s3 key="#{ENV['AWS_ACCESS_KEY_ID']}" secret="#{ENV['AWS_SECRET_ACCESS_KEY']}" ssl=false
|
|
732
|
-
path "hash" "\#{test_image}"
|
|
733
|
-
post {
|
|
734
|
-
store_s3 "input" bucket="#{ENV['AWS_S3_TEST_BUCKET']}" path="hash" public=true
|
|
735
|
-
}
|
|
736
|
-
EOF
|
|
737
|
-
end
|
|
738
|
-
|
|
739
|
-
it 'should provide public source HTTP url' do
|
|
740
|
-
subject.handlers[0].stores[0].realize(state)
|
|
741
|
-
|
|
742
|
-
state.images['input'].store_url.to_s.should start_with "http://"
|
|
743
|
-
state.images['input'].store_url.to_s.should include ENV['AWS_S3_TEST_BUCKET']
|
|
744
|
-
state.images['input'].store_url.to_s.should include "/test_out.jpg"
|
|
745
|
-
state.images['input'].store_url.to_s.should_not include ENV['AWS_ACCESS_KEY_ID']
|
|
746
|
-
status(state.images['input'].store_url).should == 200
|
|
747
|
-
end
|
|
748
|
-
end
|
|
749
|
-
end
|
|
750
|
-
end
|
|
751
|
-
|
|
752
|
-
describe 'cache control' do
|
|
753
|
-
it 'should have no cache control set by default' do
|
|
754
|
-
subject.handlers[0].stores[0].realize(state)
|
|
755
|
-
headers(state.images['input'].store_url)["Cache-Control"].should be_nil
|
|
756
|
-
end
|
|
757
|
-
|
|
758
|
-
describe 'set' do
|
|
759
|
-
subject do
|
|
760
|
-
Configuration.read(<<-EOF)
|
|
761
|
-
s3 key="#{ENV['AWS_ACCESS_KEY_ID']}" secret="#{ENV['AWS_SECRET_ACCESS_KEY']}"
|
|
762
|
-
path "hash" "\#{test_image}"
|
|
763
|
-
post {
|
|
764
|
-
store_s3 "input" bucket="#{ENV['AWS_S3_TEST_BUCKET']}" path="hash" public=true cache-control="public, max-age=3600"
|
|
765
|
-
}
|
|
766
|
-
EOF
|
|
767
|
-
end
|
|
768
|
-
|
|
769
|
-
it 'should have given cahce control header set on the object' do
|
|
770
|
-
subject.handlers[0].stores[0].realize(state)
|
|
771
|
-
headers(state.images['input'].store_url)["Cache-Control"].should == 'public, max-age=3600'
|
|
772
|
-
end
|
|
773
|
-
end
|
|
774
|
-
end
|
|
775
|
-
|
|
776
|
-
describe 'conditional inclusion support' do
|
|
777
|
-
let :state do
|
|
778
|
-
Configuration::RequestState.new(@test_data, {test_image: 'test_out.jpg', list: 'input,input2'})
|
|
779
|
-
end
|
|
780
|
-
|
|
781
|
-
subject do
|
|
782
|
-
Configuration.read(<<-EOF)
|
|
783
|
-
post {
|
|
784
|
-
store_s3 "input" bucket="#{ENV['AWS_S3_TEST_BUCKET']}" path="hash" if-image-name-on="\#{list}"
|
|
785
|
-
store_s3 "input1" bucket="#{ENV['AWS_S3_TEST_BUCKET']}" path="hash" if-image-name-on="\#{list}"
|
|
786
|
-
store_s3 "input2" bucket="#{ENV['AWS_S3_TEST_BUCKET']}" path="hash" if-image-name-on="\#{list}"
|
|
787
|
-
}
|
|
788
|
-
EOF
|
|
789
|
-
end
|
|
790
|
-
|
|
791
|
-
it 'should mark sores to be included when image name match if-image-name-on list' do
|
|
792
|
-
subject.handlers[0].stores[0].excluded?(state).should be_false
|
|
793
|
-
subject.handlers[0].stores[1].excluded?(state).should be_true
|
|
794
|
-
subject.handlers[0].stores[2].excluded?(state).should be_false
|
|
795
|
-
end
|
|
796
|
-
end
|
|
797
|
-
|
|
798
|
-
describe 'context locals' do
|
|
799
|
-
subject do
|
|
800
|
-
Configuration.read(<<-EOF)
|
|
801
|
-
s3 key="#{ENV['AWS_ACCESS_KEY_ID']}" secret="#{ENV['AWS_SECRET_ACCESS_KEY']}" ssl=false
|
|
802
|
-
path "image_name" "\#{image_name}"
|
|
803
|
-
path "bucket" "\#{bucket}"
|
|
804
|
-
path "image_mime_extension" "\#{image_mime_extension}"
|
|
805
|
-
post {
|
|
806
|
-
store_s3 "input" bucket="#{ENV['AWS_S3_TEST_BUCKET']}" path="image_name"
|
|
807
|
-
store_s3 "input" bucket="#{ENV['AWS_S3_TEST_BUCKET']}" path="bucket"
|
|
808
|
-
store_s3 "input" bucket="#{ENV['AWS_S3_TEST_BUCKET']}" path="image_mime_extension"
|
|
809
|
-
}
|
|
810
|
-
EOF
|
|
811
|
-
end
|
|
812
|
-
|
|
813
|
-
it 'should provide image name to be used as #{image_name}' do
|
|
814
|
-
subject.handlers[0].stores[0].realize(state)
|
|
815
|
-
|
|
816
|
-
state.images['input'].store_path.should == 'input'
|
|
817
|
-
end
|
|
818
|
-
|
|
819
|
-
it 'should provide bucket to be used as #{bucket}' do
|
|
820
|
-
subject.handlers[0].stores[1].realize(state)
|
|
821
|
-
|
|
822
|
-
state.images['input'].store_path.should == ENV['AWS_S3_TEST_BUCKET']
|
|
823
|
-
end
|
|
824
|
-
|
|
825
|
-
it 'should provide image mime type based file extension to be used as #{image_mime_extension}' do
|
|
826
|
-
state.images['input'].mime_type = 'image/jpeg'
|
|
827
|
-
subject.handlers[0].stores[2].realize(state)
|
|
828
|
-
|
|
829
|
-
state.images['input'].store_path.should == 'jpg'
|
|
830
|
-
end
|
|
831
|
-
|
|
832
|
-
it 'should raise PathRenderingError if there is on mime type for image defined and path contains #{image_mime_extension}' do
|
|
833
|
-
expect {
|
|
834
|
-
subject.handlers[0].stores[2].realize(state)
|
|
835
|
-
}.to raise_error Configuration::PathRenderingError, %q{cannot generate path 'image_mime_extension' from template '#{image_mime_extension}': image 'input' does not have data for variable 'image_mime_extension'}
|
|
836
|
-
end
|
|
837
|
-
end
|
|
838
|
-
|
|
839
|
-
describe 'error handling' do
|
|
840
|
-
it 'should raise NoValueError on missing image name' do
|
|
841
|
-
expect {
|
|
842
|
-
Configuration.read(<<-EOF)
|
|
843
|
-
post "test" {
|
|
844
|
-
store_s3 bucket="#{ENV['AWS_S3_TEST_BUCKET']}" path="hash"
|
|
845
|
-
}
|
|
846
|
-
EOF
|
|
847
|
-
}.to raise_error Configuration::NoValueError, %{syntax error while parsing 'store_s3 bucket="#{ENV['AWS_S3_TEST_BUCKET']}" path="hash"': expected image name}
|
|
848
|
-
end
|
|
849
|
-
|
|
850
|
-
it 'should raise NoAttributeError on missing bucket name' do
|
|
851
|
-
expect {
|
|
852
|
-
Configuration.read(<<-EOF)
|
|
853
|
-
post "test" {
|
|
854
|
-
store_s3 "input" path="hash"
|
|
855
|
-
}
|
|
856
|
-
EOF
|
|
857
|
-
}.to raise_error Configuration::NoAttributeError, %{syntax error while parsing 'store_s3 "input" path="hash"': expected 'bucket' attribute to be set}
|
|
858
|
-
end
|
|
859
|
-
|
|
860
|
-
it 'should raise NoAttributeError on missing path' do
|
|
861
|
-
expect {
|
|
862
|
-
Configuration.read(<<-EOF)
|
|
863
|
-
post "test" {
|
|
864
|
-
store_s3 "input" bucket="#{ENV['AWS_S3_TEST_BUCKET']}"
|
|
865
|
-
}
|
|
866
|
-
EOF
|
|
867
|
-
}.to raise_error Configuration::NoAttributeError, %{syntax error while parsing 'store_s3 "input" bucket="#{ENV['AWS_S3_TEST_BUCKET']}"': expected 'path' attribute to be set}
|
|
868
|
-
end
|
|
869
|
-
|
|
870
|
-
it 'should raise S3NotConfiguredError if used but no s3 statement was used' do
|
|
871
|
-
subject = Configuration.read(<<-EOF)
|
|
872
|
-
path "hash" "\#{test_image}"
|
|
873
|
-
post "test" {
|
|
874
|
-
store_s3 "input" bucket="#{ENV['AWS_S3_TEST_BUCKET']}" path="hash"
|
|
875
|
-
}
|
|
876
|
-
EOF
|
|
877
|
-
expect {
|
|
878
|
-
subject.handlers[0].stores[0].realize(state)
|
|
879
|
-
}.to raise_error Configuration::S3NotConfiguredError, 'S3 client not configured'
|
|
880
|
-
end
|
|
881
|
-
|
|
882
|
-
it 'should raise S3NoSuchBucketError if bucket was not found on S3' do
|
|
883
|
-
subject = Configuration.read(<<-EOF)
|
|
884
|
-
s3 key="#{ENV['AWS_ACCESS_KEY_ID']}" secret="#{ENV['AWS_SECRET_ACCESS_KEY']}" ssl=false
|
|
885
|
-
path "hash" "\#{test_image}"
|
|
886
|
-
post "test" {
|
|
887
|
-
store_s3 "input" bucket="#{ENV['AWS_S3_TEST_BUCKET']}X" path="hash"
|
|
888
|
-
}
|
|
889
|
-
EOF
|
|
890
|
-
expect {
|
|
891
|
-
subject.handlers[0].stores[0].realize(state)
|
|
892
|
-
}.to raise_error Configuration::S3NoSuchBucketError, %{S3 bucket '#{ENV['AWS_S3_TEST_BUCKET']}X' does not exist}
|
|
893
|
-
end
|
|
894
|
-
|
|
895
|
-
it 'should raise S3AccessDenied if bucket was not found on S3' do
|
|
896
|
-
subject = Configuration.read(<<-EOF)
|
|
897
|
-
s3 key="#{ENV['AWS_ACCESS_KEY_ID']}" secret="#{ENV['AWS_SECRET_ACCESS_KEY']}" ssl=false
|
|
898
|
-
path "hash" "\#{test_image}"
|
|
899
|
-
post "test" {
|
|
900
|
-
store_s3 "input" bucket="blah" path="hash"
|
|
901
|
-
}
|
|
902
|
-
EOF
|
|
903
|
-
expect {
|
|
904
|
-
subject.handlers[0].stores[0].realize(state)
|
|
905
|
-
}.to raise_error Configuration::S3AccessDenied, %{access to S3 bucket 'blah' or key 'test_out.jpg' was denied}
|
|
906
|
-
end
|
|
907
|
-
end
|
|
908
|
-
end
|
|
909
|
-
end
|
|
910
|
-
end
|
|
911
|
-
|