massive 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +0 -2
- data/Gemfile.lock +1 -27
- data/README.md +7 -9
- data/lib/massive.rb +16 -22
- data/lib/massive/authenticators/s3.rb +37 -0
- data/lib/massive/file.rb +3 -21
- data/lib/massive/version.rb +1 -1
- data/spec/models/massive/authenticators/s3_spec.rb +119 -0
- data/spec/models/massive/file_spec.rb +15 -19
- metadata +4 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6add55169f9bbe782d2ee00b8a033a21aae4d9cb
|
4
|
+
data.tar.gz: 3e7ef6017201515489a3e8f04fb41b8669b701cd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a2470c8e5230e4b2f4e12a47e93722e16da720971c15c67179aa8f053a1907c1322309f19fdef0572c43211d0b56f62a7d159d6a0c25422ab61cd339deab9a18
|
7
|
+
data.tar.gz: 2e48d557a63fa84092f83acabca19ba70b3d21559bbb0f96f25dee3ee97c16b96e0daac07ccf26c40377046787184d8c5810ec1f00b9831ad54f100073a73b5f
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
massive (0.
|
4
|
+
massive (0.3.0)
|
5
5
|
active_model_serializers
|
6
6
|
file_processor (= 0.2.0)
|
7
7
|
mongoid (~> 4.0.0.beta)
|
@@ -37,26 +37,8 @@ GEM
|
|
37
37
|
debugger-ruby_core_source (1.3.4)
|
38
38
|
diff-lcs (1.2.5)
|
39
39
|
docile (1.1.3)
|
40
|
-
excon (0.33.0)
|
41
40
|
ffi (1.9.3)
|
42
41
|
file_processor (0.2.0)
|
43
|
-
fog (1.22.0)
|
44
|
-
fog-brightbox
|
45
|
-
fog-core (~> 1.21, >= 1.21.1)
|
46
|
-
fog-json
|
47
|
-
nokogiri (~> 1.5, >= 1.5.11)
|
48
|
-
fog-brightbox (0.0.2)
|
49
|
-
fog-core
|
50
|
-
fog-json
|
51
|
-
fog-core (1.22.0)
|
52
|
-
builder
|
53
|
-
excon (~> 0.33)
|
54
|
-
formatador (~> 0.2)
|
55
|
-
mime-types
|
56
|
-
net-scp (~> 1.1)
|
57
|
-
net-ssh (>= 2.1.3)
|
58
|
-
fog-json (1.0.0)
|
59
|
-
multi_json (~> 1.0)
|
60
42
|
formatador (0.2.4)
|
61
43
|
guard (2.6.1)
|
62
44
|
formatador (>= 0.2.4)
|
@@ -75,8 +57,6 @@ GEM
|
|
75
57
|
rb-inotify (>= 0.9)
|
76
58
|
lumberjack (1.0.5)
|
77
59
|
method_source (0.8.2)
|
78
|
-
mime-types (2.2)
|
79
|
-
mini_portile (0.6.0)
|
80
60
|
minitest (5.3.3)
|
81
61
|
mongoid (4.0.0.beta1)
|
82
62
|
activemodel (>= 4.0.0)
|
@@ -89,11 +69,6 @@ GEM
|
|
89
69
|
connection_pool (~> 2.0)
|
90
70
|
optionable (~> 0.2.0)
|
91
71
|
multi_json (1.10.0)
|
92
|
-
net-scp (1.2.1)
|
93
|
-
net-ssh (>= 2.6.5)
|
94
|
-
net-ssh (2.9.1)
|
95
|
-
nokogiri (1.6.2.1)
|
96
|
-
mini_portile (= 0.6.0)
|
97
72
|
optionable (0.2.0)
|
98
73
|
origin (2.1.1)
|
99
74
|
pry (0.9.12.6)
|
@@ -150,7 +125,6 @@ PLATFORMS
|
|
150
125
|
DEPENDENCIES
|
151
126
|
database_cleaner
|
152
127
|
debugger
|
153
|
-
fog
|
154
128
|
guard-rspec
|
155
129
|
massive!
|
156
130
|
rake
|
data/README.md
CHANGED
@@ -168,23 +168,21 @@ Notice that we didn't had to specify how the step would calculate the total coun
|
|
168
168
|
|
169
169
|
We also didn't have to specify how the job would iterate through each row, it is already defined. We just get a CSV::Row, which will be a Hash-like structure where the header of the CSV is the key, so we can just pass it to `User.create`. Of course this is a simple example, you should protect the attributes, or even pass only the ones you want from the CSV.
|
170
170
|
|
171
|
-
The `Massive::File` has support for
|
171
|
+
The `Massive::File` has support for getting files from Amazon AWS S3 service. To use it you must define the `storage_config`:
|
172
172
|
|
173
173
|
```ruby
|
174
|
-
Massive.
|
175
|
-
|
176
|
-
|
177
|
-
|
174
|
+
Massive.storage_config = {
|
175
|
+
key: 'INSERT-YOUR-AWS-KEY-HERE',
|
176
|
+
secret: 'INSERT-YOUR-AWS-SECRET-HERE'
|
177
|
+
directory: 'your-bucket-here', # defaults to 'massive'
|
178
|
+
expiration: 30.minutes # defaults to 1.hour
|
178
179
|
}
|
179
|
-
|
180
|
-
Massive.fog_directory = 'your-bucket-here' # defaults to 'massive'
|
181
|
-
Massive.fog_authenticated_url_expiration = 30.minutes # defaults to 1.hour
|
182
180
|
```
|
183
181
|
|
184
182
|
Then set the `filename` field when creating the `Massive::File` instead of setting its `url`. Notice that the filename should point to the full path within the bucket, not just the actual filename.
|
185
183
|
|
186
184
|
```ruby
|
187
|
-
process = Massive::FileProcess.new(file_attributes: { filename: '
|
185
|
+
process = Massive::FileProcess.new(file_attributes: { filename: 'path/to/my/file.csv' })
|
188
186
|
```
|
189
187
|
|
190
188
|
## Contributing
|
data/lib/massive.rb
CHANGED
@@ -23,38 +23,32 @@ module Massive
|
|
23
23
|
autoload :ProcessSerializer, 'massive/process_serializer'
|
24
24
|
autoload :StepSerializer, 'massive/step_serializer'
|
25
25
|
|
26
|
+
module Authenticators
|
27
|
+
autoload :S3, 'massive/authenticators/s3'
|
28
|
+
end
|
29
|
+
|
26
30
|
class Cancelled < StandardError; end
|
27
31
|
|
28
32
|
def self.redis
|
29
33
|
@redis ||= Resque.redis
|
30
34
|
end
|
31
35
|
|
32
|
-
def self.
|
33
|
-
@
|
34
|
-
end
|
35
|
-
|
36
|
-
def self.fog_credentials=(values=nil)
|
37
|
-
@fog_credentials = values
|
38
|
-
end
|
39
|
-
|
40
|
-
def self.fog_authenticated_url_expiration
|
41
|
-
@fog_authenticated_url_expiration
|
42
|
-
end
|
43
|
-
|
44
|
-
def self.fog_authenticated_url_expiration=(value=nil)
|
45
|
-
@fog_authenticated_url_expiration = value
|
46
|
-
end
|
47
|
-
|
48
|
-
def self.fog_directory
|
49
|
-
@fog_directory
|
36
|
+
def self.storage_config
|
37
|
+
@storage_config
|
50
38
|
end
|
51
39
|
|
52
|
-
def self.
|
53
|
-
@
|
40
|
+
def self.storage_config=(value)
|
41
|
+
@storage_config ||= {}
|
42
|
+
@storage_config.merge!(value)
|
54
43
|
end
|
55
44
|
|
56
|
-
self.
|
57
|
-
|
45
|
+
self.storage_config = {
|
46
|
+
directory: 'massive',
|
47
|
+
provider: Massive::Authenticators::S3,
|
48
|
+
key: nil,
|
49
|
+
secret: nil,
|
50
|
+
expiration: 1 * 60 * 60 # 1 hour
|
51
|
+
}
|
58
52
|
end
|
59
53
|
|
60
54
|
require "resque"
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Massive
|
2
|
+
module Authenticators
|
3
|
+
class S3
|
4
|
+
def initialize(filename)
|
5
|
+
@filename = filename
|
6
|
+
end
|
7
|
+
|
8
|
+
def url
|
9
|
+
"https://#{Massive.storage_config[:directory]}.s3.amazonaws.com/#{@filename}#{authentication_params}"
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def authentication_params
|
15
|
+
if Massive.storage_config[:key] && Massive.storage_config[:secret]
|
16
|
+
"?Expires=#{expiration}&AWSAccessKeyId=#{Massive.storage_config[:key]}&Signature=#{signature}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def expiration
|
21
|
+
@expiration ||= Time.now.to_i + Massive.storage_config[:expiration]
|
22
|
+
end
|
23
|
+
|
24
|
+
def signature
|
25
|
+
CGI.escape(
|
26
|
+
Base64.encode64(
|
27
|
+
OpenSSL::HMAC.digest(
|
28
|
+
OpenSSL::Digest.new('sha1'),
|
29
|
+
Massive.storage_config[:secret],
|
30
|
+
"GET\n\n\n#{expiration}\n/#{Massive.storage_config[:directory]}/#{@filename}".encode("UTF-8")
|
31
|
+
)
|
32
|
+
).gsub("\n", "")
|
33
|
+
)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/massive/file.rb
CHANGED
@@ -40,7 +40,7 @@ module Massive
|
|
40
40
|
end
|
41
41
|
|
42
42
|
def url
|
43
|
-
read_attribute(:url).presence ||
|
43
|
+
read_attribute(:url).presence || authenticator.url
|
44
44
|
end
|
45
45
|
|
46
46
|
private
|
@@ -59,26 +59,8 @@ module Massive
|
|
59
59
|
}
|
60
60
|
end
|
61
61
|
|
62
|
-
def
|
63
|
-
|
64
|
-
fog_file.respond_to?(:url) ? fog_file.url(Time.current.to_i + Massive.fog_authenticated_url_expiration) : fog_file.public_url
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
def can_use_fog?
|
69
|
-
filename && Massive.fog_credentials.present?
|
70
|
-
end
|
71
|
-
|
72
|
-
def fog_connection
|
73
|
-
@fog_connection ||= Fog::Storage.new(Massive.fog_credentials)
|
74
|
-
end
|
75
|
-
|
76
|
-
def fog_directory
|
77
|
-
@fog_directory ||= fog_connection.directories.get(Massive.fog_directory)
|
78
|
-
end
|
79
|
-
|
80
|
-
def fog_file
|
81
|
-
@fog_file ||= fog_directory.files.get(filename)
|
62
|
+
def authenticator
|
63
|
+
@authenticator ||= Massive.storage_config[:provider].new(filename)
|
82
64
|
end
|
83
65
|
end
|
84
66
|
end
|
data/lib/massive/version.rb
CHANGED
@@ -0,0 +1,119 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Massive::Authenticators::S3 do
|
4
|
+
let(:filename) { 'some/path/my-filename.png' }
|
5
|
+
subject(:authenticator) { described_class.new(filename) }
|
6
|
+
|
7
|
+
after do
|
8
|
+
Massive.storage_config = {
|
9
|
+
key: nil,
|
10
|
+
secret: nil
|
11
|
+
}
|
12
|
+
end
|
13
|
+
|
14
|
+
context 'when no key/secret are set' do
|
15
|
+
it "returns the given url without any authentication params" do
|
16
|
+
expect(authenticator.url).to eq("https://#{Massive.storage_config[:directory]}.s3.amazonaws.com/#{filename}")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context 'when key/secret are set' do
|
21
|
+
before do
|
22
|
+
Massive.storage_config[:key] = 'some-key'
|
23
|
+
Massive.storage_config[:secret] = 'some-secret'
|
24
|
+
Time.stub(:now).and_return(Time.parse("2014-05-15T13:25:45Z"))
|
25
|
+
end
|
26
|
+
|
27
|
+
def parsed_query(url)
|
28
|
+
CGI.parse(URI.parse(url).query || '').with_indifferent_access
|
29
|
+
end
|
30
|
+
|
31
|
+
it "returns a url pointing to the proper bucket" do
|
32
|
+
expect(URI.parse(authenticator.url).host).to eq("#{Massive.storage_config[:directory]}.s3.amazonaws.com")
|
33
|
+
end
|
34
|
+
|
35
|
+
it "returns a url using https" do
|
36
|
+
expect(URI.parse(authenticator.url).scheme).to eq("https")
|
37
|
+
end
|
38
|
+
|
39
|
+
it "returns a url with the filename as path" do
|
40
|
+
expect(URI.parse(authenticator.url).path).to eq("/#{filename}")
|
41
|
+
end
|
42
|
+
|
43
|
+
it "returns a url with the expiration as a query string parameter using a timestamped format" do
|
44
|
+
now = Time.now.tap { |now| Time.stub(:now).and_return(now) }
|
45
|
+
|
46
|
+
expect(parsed_query(authenticator.url)[:Expires].first.to_i).to eq(now.to_i + Massive.storage_config[:expiration])
|
47
|
+
end
|
48
|
+
|
49
|
+
it "returns a url with the AWSAccessKeyId as a query string parameter using the configured key" do
|
50
|
+
expect(parsed_query(authenticator.url)[:AWSAccessKeyId].first).to eq(Massive.storage_config[:key])
|
51
|
+
end
|
52
|
+
|
53
|
+
it "returns a url with the Signature as a query string parameter properly signing the GET request" do
|
54
|
+
expect(parsed_query(authenticator.url)[:Signature].first).to eq("FD38leXqSdFYGYrAXgNF8cX98os=")
|
55
|
+
end
|
56
|
+
|
57
|
+
context "when changing the expiration" do
|
58
|
+
before do
|
59
|
+
Time.stub(:now).and_return(Time.parse("2009-08-01T20:03:27Z"))
|
60
|
+
end
|
61
|
+
|
62
|
+
it "returns a url with the Signature properly signing based on the current time" do
|
63
|
+
expect(parsed_query(authenticator.url)[:Signature].first).to eq("ehM0n71BUPduV9WwWE73PIMmQYM=")
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
context "when changing the secret" do
|
68
|
+
before do
|
69
|
+
Massive.storage_config[:secret] = 'other-secret'
|
70
|
+
end
|
71
|
+
|
72
|
+
it "returns a url with the Signature based the configured secret" do
|
73
|
+
expect(parsed_query(authenticator.url)[:Signature].first).to eq("mE6pvT9pRLOUcufs+fX45vbbATQ=")
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context "when changing the key" do
|
78
|
+
before do
|
79
|
+
Massive.storage_config[:key] = 'other-key'
|
80
|
+
end
|
81
|
+
|
82
|
+
it "returns a url with the AWSAccessKeyId as a query string parameter using the configured key" do
|
83
|
+
expect(parsed_query(authenticator.url)[:AWSAccessKeyId].first).to eq(Massive.storage_config[:key])
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
context "when changing the directory" do
|
88
|
+
before do
|
89
|
+
Massive.storage_config[:directory] = 'other-directory'
|
90
|
+
end
|
91
|
+
|
92
|
+
it "returns a url with the expiration as a query string parameter using a timestamped format" do
|
93
|
+
now = Time.now.tap { |now| Time.stub(:now).and_return(now) }
|
94
|
+
|
95
|
+
expect(parsed_query(authenticator.url)[:Expires].first.to_i).to eq(now.to_i + Massive.storage_config[:expiration])
|
96
|
+
end
|
97
|
+
|
98
|
+
it "returns a url pointing to the proper bucket" do
|
99
|
+
expect(URI.parse(authenticator.url).host).to eq("#{Massive.storage_config[:directory]}.s3.amazonaws.com")
|
100
|
+
end
|
101
|
+
|
102
|
+
it "returns a url with the Signature based the configured directory" do
|
103
|
+
expect(parsed_query(authenticator.url)[:Signature].first).to eq("f37FDYE7lcewcpApQhFYBEUQjhs=")
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
context "when changing the filename" do
|
108
|
+
let(:filename) { 'some/other/file.txt' }
|
109
|
+
|
110
|
+
it "returns a url with the filename as path" do
|
111
|
+
expect(URI.parse(authenticator.url).path).to eq("/#{filename}")
|
112
|
+
end
|
113
|
+
|
114
|
+
it "returns a url with the Signature based the configured directory" do
|
115
|
+
expect(parsed_query(authenticator.url)[:Signature].first).to eq("fQCrfdk1FhSRZnecyZ2+Jye2HUY=")
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -18,7 +18,11 @@ describe Massive::File do
|
|
18
18
|
|
19
19
|
subject(:file) { process.file = Massive::File.new(url: url, encoding: encoding, col_sep: col_sep) }
|
20
20
|
|
21
|
-
|
21
|
+
def stub_processor
|
22
|
+
FileProcessor::CSV.stub(:new).with(file.url, expected_options).and_return(processor)
|
23
|
+
end
|
24
|
+
|
25
|
+
before { stub_processor }
|
22
26
|
|
23
27
|
describe "#processor" do
|
24
28
|
it "creates a new instance of the CSV file processor, enabling headers but without encoding and separator" do
|
@@ -50,28 +54,20 @@ describe Massive::File do
|
|
50
54
|
end
|
51
55
|
end
|
52
56
|
|
53
|
-
describe "when
|
54
|
-
let(:filename) { 'my-file.txt' }
|
55
|
-
let(:
|
56
|
-
let(:
|
57
|
-
let(:fog_file) { double('File') }
|
58
|
-
let(:authenticated_url) { 'http://my-auth.url.com' }
|
57
|
+
describe "when passing a filename" do
|
58
|
+
let(:filename) { 'my/path/my-file.txt' }
|
59
|
+
let(:url) { 'http://my-auth.url.com' }
|
60
|
+
let(:provider) { double(Massive::Authenticators::S3, url: url) }
|
59
61
|
|
60
|
-
subject(:file)
|
61
|
-
|
62
|
-
before do
|
63
|
-
Massive.fog_credentials = { provider: 'AWS', aws_access_key_id: 'some-key', aws_secret_access_key: 'some-secret' }
|
64
|
-
Massive.fog_authenticated_url_expiration = 1.hour
|
65
|
-
Massive.fog_directory = 'my-bucket'
|
62
|
+
subject(:file) { Massive::File.new(filename: filename, encoding: encoding, col_sep: col_sep) }
|
66
63
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
fog_file.stub(:url).with(Time.current.to_i + Massive.fog_authenticated_url_expiration).and_return(authenticated_url)
|
64
|
+
def stub_processor
|
65
|
+
Massive.storage_config[:provider].stub(:new).with(filename).and_return(provider)
|
66
|
+
FileProcessor::CSV.stub(:new).with(file.url, expected_options).and_return(processor)
|
71
67
|
end
|
72
68
|
|
73
|
-
it "creates a new instance of the CSV file processor, pointing its URL to the
|
74
|
-
FileProcessor::CSV.should_receive(:new).with(
|
69
|
+
it "creates a new instance of the CSV file processor, pointing its URL to the authenticator provider url" do
|
70
|
+
FileProcessor::CSV.should_receive(:new).with(url, expected_options).and_return(processor)
|
75
71
|
file.processor.should eq(processor)
|
76
72
|
end
|
77
73
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: massive
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Vicente Mundim
|
@@ -86,6 +86,7 @@ files:
|
|
86
86
|
- README.md
|
87
87
|
- Rakefile
|
88
88
|
- lib/massive.rb
|
89
|
+
- lib/massive/authenticators/s3.rb
|
89
90
|
- lib/massive/cancelling.rb
|
90
91
|
- lib/massive/file.rb
|
91
92
|
- lib/massive/file_job.rb
|
@@ -109,6 +110,7 @@ files:
|
|
109
110
|
- massive.gemspec
|
110
111
|
- spec/fixtures/custom_job.rb
|
111
112
|
- spec/fixtures/custom_step.rb
|
113
|
+
- spec/models/massive/authenticators/s3_spec.rb
|
112
114
|
- spec/models/massive/cancelling_spec.rb
|
113
115
|
- spec/models/massive/file_job_spec.rb
|
114
116
|
- spec/models/massive/file_spec.rb
|
@@ -155,6 +157,7 @@ summary: Parallelize processing of large files and/or data using Resque, Redis a
|
|
155
157
|
test_files:
|
156
158
|
- spec/fixtures/custom_job.rb
|
157
159
|
- spec/fixtures/custom_step.rb
|
160
|
+
- spec/models/massive/authenticators/s3_spec.rb
|
158
161
|
- spec/models/massive/cancelling_spec.rb
|
159
162
|
- spec/models/massive/file_job_spec.rb
|
160
163
|
- spec/models/massive/file_spec.rb
|