elasticity 5.0.1 → 5.0.2

Sign up to get free protection for your applications and to get access to all the features.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: elasticity
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.0.1
4
+ version: 5.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Slifka
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-04-13 00:00:00.000000000 Z
11
+ date: 2015-04-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rest-client
@@ -142,7 +142,10 @@ files:
142
142
  - Rakefile
143
143
  - elasticity.gemspec
144
144
  - lib/elasticity.rb
145
- - lib/elasticity/aws_request.rb
145
+ - lib/elasticity/aws_request_v2.rb
146
+ - lib/elasticity/aws_request_v4.rb
147
+ - lib/elasticity/aws_session.rb
148
+ - lib/elasticity/aws_utils.rb
146
149
  - lib/elasticity/bootstrap_action.rb
147
150
  - lib/elasticity/custom_jar_step.rb
148
151
  - lib/elasticity/emr.rb
@@ -163,7 +166,10 @@ files:
163
166
  - lib/elasticity/streaming_step.rb
164
167
  - lib/elasticity/sync_to_s3.rb
165
168
  - lib/elasticity/version.rb
166
- - spec/lib/elasticity/aws_request_spec.rb
169
+ - spec/lib/elasticity/aws_request_v2_spec.rb
170
+ - spec/lib/elasticity/aws_request_v4_spec.rb
171
+ - spec/lib/elasticity/aws_session_spec.rb
172
+ - spec/lib/elasticity/aws_utils_spec.rb
167
173
  - spec/lib/elasticity/bootstrap_action_spec.rb
168
174
  - spec/lib/elasticity/custom_jar_step_spec.rb
169
175
  - spec/lib/elasticity/emr_spec.rb
@@ -210,7 +216,10 @@ signing_key:
210
216
  specification_version: 4
211
217
  summary: Streamlined, programmatic access to Amazon's Elastic Map Reduce service.
212
218
  test_files:
213
- - spec/lib/elasticity/aws_request_spec.rb
219
+ - spec/lib/elasticity/aws_request_v2_spec.rb
220
+ - spec/lib/elasticity/aws_request_v4_spec.rb
221
+ - spec/lib/elasticity/aws_session_spec.rb
222
+ - spec/lib/elasticity/aws_utils_spec.rb
214
223
  - spec/lib/elasticity/bootstrap_action_spec.rb
215
224
  - spec/lib/elasticity/custom_jar_step_spec.rb
216
225
  - spec/lib/elasticity/emr_spec.rb
@@ -1,135 +0,0 @@
1
- module Elasticity
2
-
3
- class MissingKeyError < StandardError;
4
- end
5
- class MissingRegionError < StandardError;
6
- end
7
-
8
- class AwsRequest
9
-
10
- attr_reader :access_key
11
- attr_reader :secret_key
12
- attr_reader :host
13
- attr_reader :protocol
14
-
15
- # Supported values for options:
16
- # :region - AWS region (e.g. us-west-1)
17
- # :secure - true or false, default true.
18
- def initialize(access=nil, secret=nil, options={})
19
- # There is a cryptic error if this isn't set
20
- if options.has_key?(:region) && options[:region] == nil
21
- raise MissingRegionError, 'A valid :region is required to connect to EMR'
22
- end
23
-
24
- @access_key = get_access_key(access)
25
- @secret_key = get_secret_key(secret)
26
- @host = "elasticmapreduce.#{{:region => 'us-east-1'}.merge(options)[:region]}.amazonaws.com"
27
- @protocol = {:secure => true}.merge(options)[:secure] ? 'https' : 'http'
28
- end
29
-
30
- def submit(ruby_params)
31
- aws_params = AwsRequest.convert_ruby_to_aws(ruby_params)
32
- signed_params = sign_params(aws_params)
33
- begin
34
- RestClient.post("#@protocol://#@host", signed_params, :content_type => 'application/x-www-form-urlencoded; charset=utf-8')
35
- rescue RestClient::BadRequest => e
36
- raise ArgumentError, AwsRequest.parse_error_response(e.http_body)
37
- end
38
- end
39
-
40
- def ==(other)
41
- return false unless other.is_a? AwsRequest
42
- return false unless @access_key == other.access_key
43
- return false unless @secret_key == other.secret_key
44
- return false unless @host == other.host
45
- return false unless @protocol == other.protocol
46
- true
47
- end
48
-
49
- private
50
-
51
- def get_access_key(access)
52
- return access if access
53
- return ENV['AWS_ACCESS_KEY_ID'] if ENV['AWS_ACCESS_KEY_ID']
54
- raise MissingKeyError, 'Please provide an access key or set AWS_ACCESS_KEY_ID.'
55
- end
56
-
57
- def get_secret_key(secret)
58
- return secret if secret
59
- return ENV['AWS_SECRET_ACCESS_KEY'] if ENV['AWS_SECRET_ACCESS_KEY']
60
- raise MissingKeyError, 'Please provide a secret key or set AWS_SECRET_ACCESS_KEY.'
61
- end
62
-
63
- # (Used from RightScale's right_aws gem.)
64
- # EC2, SQS, SDB and EMR requests must be signed by this guy.
65
- # See: http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/index.html?REST_RESTAuth.html
66
- # http://developer.amazonwebservices.com/connect/entry.jspa?externalID=1928
67
- def sign_params(service_hash)
68
- service_hash.merge!({
69
- 'AWSAccessKeyId' => @access_key,
70
- 'Timestamp' => Time.now.utc.strftime('%Y-%m-%dT%H:%M:%S.000Z'),
71
- 'SignatureVersion' => '2',
72
- 'SignatureMethod' => 'HmacSHA256'
73
- })
74
- canonical_string = service_hash.keys.sort.map do |key|
75
- "#{AwsRequest.aws_escape(key)}=#{AwsRequest.aws_escape(service_hash[key])}"
76
- end.join('&')
77
- string_to_sign = "POST\n#{@host.downcase}\n/\n#{canonical_string}"
78
- signature = AwsRequest.aws_escape(Base64.encode64(OpenSSL::HMAC.digest("sha256", @secret_key, string_to_sign)).strip)
79
- "#{canonical_string}&Signature=#{signature}"
80
- end
81
-
82
- # (Used from RightScale's right_aws gem)
83
- # Escape a string according to Amazon's rules.
84
- # See: http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/index.html?REST_RESTAuth.html
85
- def self.aws_escape(param)
86
- param.to_s.gsub(/([^a-zA-Z0-9._~-]+)/n) do
87
- '%' + $1.unpack('H2' * $1.size).join('%').upcase
88
- end
89
- end
90
-
91
- # Since we use the same structure as AWS, we can generate AWS param names
92
- # from the Ruby versions of those names (and the param nesting).
93
- def self.convert_ruby_to_aws(params)
94
- result = {}
95
- params.each do |key, value|
96
- case value
97
- when Array
98
- prefix = "#{camelize(key.to_s)}.member"
99
- value.each_with_index do |item, index|
100
- if item.is_a?(String)
101
- result["#{prefix}.#{index+1}"] = item
102
- else
103
- convert_ruby_to_aws(item).each do |nested_key, nested_value|
104
- result["#{prefix}.#{index+1}.#{nested_key}"] = nested_value
105
- end
106
- end
107
- end
108
- when Hash
109
- prefix = "#{camelize(key.to_s)}"
110
- convert_ruby_to_aws(value).each do |nested_key, nested_value|
111
- result["#{prefix}.#{nested_key}"] = nested_value
112
- end
113
- else
114
- result[camelize(key.to_s)] = value
115
- end
116
- end
117
- result
118
- end
119
-
120
- # (Used from Rails' ActiveSupport)
121
- def self.camelize(word)
122
- word.to_s.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
123
- end
124
-
125
- # AWS error responses all follow the same form. Extract the message from
126
- # the error document.
127
- def self.parse_error_response(error_xml)
128
- xml_doc = Nokogiri::XML(error_xml)
129
- xml_doc.remove_namespaces!
130
- xml_doc.xpath("/ErrorResponse/Error/Message").text
131
- end
132
-
133
- end
134
-
135
- end
@@ -1,274 +0,0 @@
1
- describe Elasticity::AwsRequest do
2
-
3
- before do
4
- Timecop.freeze(Time.at(1302461096))
5
- end
6
-
7
- after do
8
- Timecop.return
9
- end
10
-
11
- subject do
12
- Elasticity::AwsRequest.new('access', 'secret')
13
- end
14
-
15
- describe '#initialize' do
16
-
17
- context 'when access and/or secret keys are provided' do
18
- it 'should set them to the provided values' do
19
- subject.access_key.should == 'access'
20
- subject.secret_key.should == 'secret'
21
- end
22
- end
23
-
24
- context 'when :region is nil' do
25
- it 'should be an error' do
26
- expect {
27
- Elasticity::AwsRequest.new('_', '_', :region => nil)
28
- }.to raise_error Elasticity::MissingRegionError, 'A valid :region is required to connect to EMR'
29
- end
30
- end
31
-
32
- context 'when either access or secret key is not provided or nil' do
33
-
34
- context 'when the proper environment variables are set' do
35
-
36
- context 'when access and secret key are not provided' do
37
- let(:default_values) { Elasticity::AwsRequest.new }
38
- before do
39
- ENV.stub(:[]).with('AWS_ACCESS_KEY_ID').and_return('ENV_ACCESS')
40
- ENV.stub(:[]).with('AWS_SECRET_ACCESS_KEY').and_return('ENV_SECRET')
41
- end
42
- it 'should set access and secret keys' do
43
- default_values.access_key.should == 'ENV_ACCESS'
44
- default_values.secret_key.should == 'ENV_SECRET'
45
- end
46
- end
47
-
48
- context 'when access and secret key are nil' do
49
- let(:nil_values) { Elasticity::AwsRequest.new(nil, nil) }
50
- before do
51
- ENV.stub(:[]).with('AWS_ACCESS_KEY_ID').and_return('ENV_ACCESS')
52
- ENV.stub(:[]).with('AWS_SECRET_ACCESS_KEY').and_return('ENV_SECRET')
53
- end
54
- it 'should set access and secret keys' do
55
- nil_values.access_key.should == 'ENV_ACCESS'
56
- nil_values.secret_key.should == 'ENV_SECRET'
57
- end
58
- end
59
-
60
- end
61
-
62
- context 'when the environment variables are not set' do
63
- let(:missing_something) { Elasticity::AwsRequest.new }
64
- context 'when the access key is not set' do
65
- before do
66
- ENV.stub(:[]).with('AWS_ACCESS_KEY_ID').and_return(nil)
67
- ENV.stub(:[]).with('AWS_SECRET_ACCESS_KEY').and_return('_')
68
- end
69
- it 'should raise an error' do
70
- expect {
71
- missing_something.access_key
72
- }.to raise_error(Elasticity::MissingKeyError, 'Please provide an access key or set AWS_ACCESS_KEY_ID.')
73
- end
74
- end
75
- context 'when the secret key is not set' do
76
- before do
77
- ENV.stub(:[]).with('AWS_ACCESS_KEY_ID').and_return('_')
78
- ENV.stub(:[]).with('AWS_SECRET_ACCESS_KEY').and_return(nil)
79
- end
80
- it 'should raise an error' do
81
- expect {
82
- missing_something.access_key
83
- }.to raise_error(Elasticity::MissingKeyError, 'Please provide a secret key or set AWS_SECRET_ACCESS_KEY.')
84
- end
85
- end
86
- end
87
-
88
- end
89
-
90
- end
91
-
92
- describe '#host' do
93
-
94
- context 'when the region is not specified' do
95
- its(:host) { should == 'elasticmapreduce.us-east-1.amazonaws.com' }
96
- end
97
-
98
- context 'when the region is specified' do
99
- let(:request_with_region) do
100
- Elasticity::AwsRequest.new('_', '_', {:region => 'us-west-1'})
101
- end
102
- it 'should incorporate the region into the hostname' do
103
- request_with_region.host.should == 'elasticmapreduce.us-west-1.amazonaws.com'
104
- end
105
- end
106
-
107
- end
108
-
109
- describe '#protocol' do
110
-
111
- context 'when :secure is not specified' do
112
- let(:default_request) { Elasticity::AwsRequest.new('_', '_') }
113
- it 'should be https by default' do
114
- default_request.protocol.should == 'https'
115
- end
116
- end
117
-
118
- context 'when :secure is specified' do
119
-
120
- context 'when :secure is truthy' do
121
- let(:secure_request) { Elasticity::AwsRequest.new('_', '_', {:secure => true}) }
122
- it 'should be https' do
123
- secure_request.protocol.should == 'https'
124
- end
125
- end
126
-
127
- context 'when :secure is falsey' do
128
- let(:insecure_request) { Elasticity::AwsRequest.new('_', '_', {:secure => false}) }
129
- it 'should be http' do
130
- insecure_request.protocol.should == 'http'
131
- end
132
- end
133
-
134
- end
135
-
136
- end
137
-
138
- describe '#sign_params' do
139
- it 'should sign according to AWS rules' do
140
- signed_params = subject.send(:sign_params, {})
141
- signed_params.should == 'AWSAccessKeyId=access&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2011-04-10T18%3A44%3A56.000Z&Signature=t%2BccC38VxCKyk2ROTKo9vnECsntKoU0RBAFklHWP5bE%3D'
142
- end
143
- end
144
-
145
- describe '#submit' do
146
-
147
- let(:request) do
148
- Elasticity::AwsRequest.new('_', '_').tap do |r|
149
- r.instance_variable_set(:@host, 'HOSTNAME')
150
- r.instance_variable_set(:@protocol, 'PROTOCOL')
151
- end
152
- end
153
-
154
- it 'should POST a properly assembled request' do
155
- ruby_params = {}
156
- aws_params = {}
157
- Elasticity::AwsRequest.should_receive(:convert_ruby_to_aws).with(ruby_params).and_return(ruby_params)
158
- request.should_receive(:sign_params).with(aws_params).and_return('SIGNED_PARAMS')
159
- RestClient.should_receive(:post).with('PROTOCOL://HOSTNAME', 'SIGNED_PARAMS', :content_type => 'application/x-www-form-urlencoded; charset=utf-8')
160
- request.submit(ruby_params)
161
- end
162
-
163
- context 'when there is an EMR error with the request' do
164
- let(:error_message) { 'ERROR_MESSAGE' }
165
- let(:error_xml) do
166
- <<-XML
167
- <ErrorResponse xmlns="http://elasticmapreduce.amazonaws.com/doc/2009-03-31">
168
- <Error>
169
- <Message>#{error_message}</Message>
170
- </Error>
171
- </ErrorResponse>
172
- XML
173
- end
174
- let(:error) do
175
- RestClient::BadRequest.new.tap do |error|
176
- error.stub(:http_body => error_xml)
177
- end
178
- end
179
-
180
- it 'should raise an Argument error with the body of the error' do
181
- RestClient.should_receive(:post).and_raise(error)
182
- expect {
183
- request.submit({})
184
- }.to raise_error(ArgumentError, error_message)
185
- end
186
- end
187
-
188
- end
189
-
190
- describe '#==' do
191
-
192
- describe 'basic equality checks with subject' do
193
- let(:same_object) { subject }
194
- let(:same_values) { Elasticity::AwsRequest.new('access', 'secret', {}) }
195
- let(:diff_type) { Object.new }
196
-
197
- it { should == same_object }
198
- it { should == same_values }
199
- it { should_not == diff_type }
200
- end
201
-
202
- describe 'deep comparisons' do
203
-
204
- it 'should fail on access key check' do
205
- Elasticity::AwsRequest.new('access', '_').should_not == Elasticity::AwsRequest.new('_', '_')
206
- end
207
-
208
- it 'should fail on secret key check' do
209
- Elasticity::AwsRequest.new('_', 'secret').should_not == Elasticity::AwsRequest.new('_', '_')
210
- end
211
-
212
- it 'should fail on host check' do
213
- aws1 = Elasticity::AwsRequest.new('_', '_', :region => 'us-east-1')
214
- aws2 = Elasticity::AwsRequest.new('_', '_', :region => 'us-west-1')
215
- aws1.should_not == aws2
216
- end
217
-
218
- it 'should fail on protocol check' do
219
- aws1 = Elasticity::AwsRequest.new('_', '_', :secure => true)
220
- aws2 = Elasticity::AwsRequest.new('_', '_', :secure => false)
221
- aws1.should_not == aws2
222
- end
223
-
224
- end
225
-
226
- end
227
-
228
- describe '.convert_ruby_to_aws' do
229
- it 'should convert the params' do
230
- add_jobflow_steps_params = {
231
- :job_flow_id => 'j-1',
232
- :steps => [
233
- {
234
- :action_on_failure => 'CONTINUE',
235
- :name => 'First New Job Step',
236
- :hadoop_jar_step => {
237
- :args => ['arg1', 'arg2', 'arg3',],
238
- :jar => 'first_step.jar',
239
- :main_class => 'first_class.jar'
240
- }
241
- },
242
- {
243
- :action_on_failure => 'CANCEL_AND_WAIT',
244
- :name => 'Second New Job Step',
245
- :hadoop_jar_step => {
246
- :args => ['arg4', 'arg5', 'arg6',],
247
- :jar => 'second_step.jar',
248
- :main_class => 'second_class.jar'
249
- }
250
- }
251
- ]
252
- }
253
- expected_result = {
254
- 'JobFlowId' => 'j-1',
255
- 'Steps.member.1.Name' => 'First New Job Step',
256
- 'Steps.member.1.ActionOnFailure' => 'CONTINUE',
257
- 'Steps.member.1.HadoopJarStep.Jar' => 'first_step.jar',
258
- 'Steps.member.1.HadoopJarStep.MainClass' => 'first_class.jar',
259
- 'Steps.member.1.HadoopJarStep.Args.member.1' => 'arg1',
260
- 'Steps.member.1.HadoopJarStep.Args.member.2' => 'arg2',
261
- 'Steps.member.1.HadoopJarStep.Args.member.3' => 'arg3',
262
- 'Steps.member.2.Name' => 'Second New Job Step',
263
- 'Steps.member.2.ActionOnFailure' => 'CANCEL_AND_WAIT',
264
- 'Steps.member.2.HadoopJarStep.Jar' => 'second_step.jar',
265
- 'Steps.member.2.HadoopJarStep.MainClass' => 'second_class.jar',
266
- 'Steps.member.2.HadoopJarStep.Args.member.1' => 'arg4',
267
- 'Steps.member.2.HadoopJarStep.Args.member.2' => 'arg5',
268
- 'Steps.member.2.HadoopJarStep.Args.member.3' => 'arg6'
269
- }
270
- Elasticity::AwsRequest.send(:convert_ruby_to_aws, add_jobflow_steps_params).should == expected_result
271
- end
272
- end
273
-
274
- end