elasticity 5.0.1 → 5.0.2

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.
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