dpl 1.7.12.travis.734.4 → 1.7.12.travis.742.4

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 CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- ZTdkMjZlMmE4NjI2MzhiODZiN2Y0NDUzOGQ0OWNlZDY0NzQ2NzA1Mg==
4
+ NTA2ZjVlMzYxNWY4YWJhOTc0MDYyNTkyYTRlZjQ3YWRmODljYjY4Mw==
5
5
  data.tar.gz: !binary |-
6
- OWZjMmRhYTI5N2QyY2MxNTAwMDUxMjI2ZjdiZjY2YTkwMTIyOTI3OQ==
6
+ YWRkNzBiMDZhN2ZhZDdhMDM5YTFlYjQwM2E4ZmQyMGU2YzdkNTBjMQ==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- ODFlODEzMThmZjU2NGEyMTMwNmNlZjE5NGYxNDZjZWQ3Yjg2ZmY0MmJhZjAw
10
- YjEwOTE4MmFhMmYyZGExODk2ZTA4YTg2OGI0ZmViNDAzZmI0MmEyOTZiZDA4
11
- OWQyNmU5Y2M1MTA2YTA0NTdjYTA3ZTBkNWE0NDFlMmFlNmFkYTQ=
9
+ NmUwZWY2NWZlOTgwZDQyYjQyZWNmMGFmYmE2MjAyMDBiYjFjMWM1Yzc5NmUz
10
+ NzE2MzBkNDI0ZTY4NzI2YTIzMjdiZjg2OTc4NzE3MGVlZmI0Nzk0MWE5MTg0
11
+ ZTk3YmFkMTI2NWY2MGZlMzQwOGU5NGJhNjg1ZGRjMjRiZjJmZGE=
12
12
  data.tar.gz: !binary |-
13
- ZmYzMzQ5MTI0N2YxMjlmM2E3NzE4NmY1YzY1NTRmZGViOTViNzA0N2JhZjFk
14
- MWE0MWU2ZDEzMzlmYjQ5MmFjODQ2MWQwNzZlNWZlOWM1YjE1ODBiZThjM2Vi
15
- NmNjMGRlYzc4ZjQ4ZWNiMTU2ZjEzYTc4NDdjZDQ2Yzk0ODBlYTQ=
13
+ ODUzM2RiMmIwNmVhYjkzZDczOTNhZWZlYWUwODQ0OTI2MDdkMzYzYTliYjc3
14
+ ZTBhYmM2ZTQ2ZWZhZDVjYTFjZTUwOGIzYjMzYjZmMzQ1NjA0YmNjYmExZmRk
15
+ ZmZjMjlhZTIwOTE1NTc0NTlhZDIzY2FhOGU0NzljOGI2M2RjM2M=
data/Gemfile CHANGED
@@ -31,7 +31,12 @@ group :sss do
31
31
  end
32
32
 
33
33
  group :code_deploy do
34
- gem 'aws-sdk', '2.0.13.pre'
34
+ gem 'aws-sdk', '>= 2.0.18.pre'
35
+ end
36
+
37
+ group :lambda do
38
+ gem 'aws-sdk', '>= 2.0.18.pre'
39
+ gem 'rubyzip'
35
40
  end
36
41
 
37
42
  group :cloud_files do
data/README.md CHANGED
@@ -32,6 +32,7 @@ Dpl supports the following providers:
32
32
  * [Puppet Forge](#puppet-forge)
33
33
  * [packagecloud](#packagecloud)
34
34
  * [Chef Supermarket](#chef-supermarket)
35
+ * [Lambda](#lambda)
35
36
 
36
37
  ## Installation:
37
38
 
@@ -456,3 +457,38 @@ For accounts using two factor authentication, you have to use an oauth token as
456
457
 
457
458
  dpl --provider=chef-supermarket --user-id=chef --client-key=.travis/client.pem --cookbook-category=Others
458
459
 
460
+ ### Lambda:
461
+
462
+ #### Options:
463
+
464
+ * **function_name**: Required. The name of the Lambda being created / updated.
465
+ * **role**: Required. The ARN of the IAM role to assign to this Lambda function.
466
+ * **handler_name**: Required. The function that Lambda calls to begin execution. For NodeJS, it is exported function for the module.
467
+ * **module_name**: Optional. The name of the module that exports the handler. Defaults to `index`.
468
+ * **zip**: Optional. Either a path to an existing packaged (zipped) Lambda, a directory to package, or a single file to package. Defaults to `Dir.pwd`.
469
+ * **description**: Optional. The description of the Lambda being created / updated. Defaults to "Deploy build #{context.env['TRAVIS_BUILD_NUMBER']} to AWS Lambda via Travis CI"
470
+ * **timeout**: Optional. The function execution time at which Lambda should terminate the function. Defaults to 3 (seconds).
471
+ * **memory_size**: Optional. The amount of memory in MB to allocate to this Lambda. Defaults to 128.
472
+
473
+ #### Examples:
474
+
475
+ Deploy contents of current working directory using default module:
476
+ ```
477
+ dpl --provider="lambda" \
478
+ --access_key_id="${AWS_ACCESS_KEY}" \
479
+ --secret_access_key="${AWS_SECRET_KEY}" \
480
+ --function_name="test-lambda" \
481
+ --role="${AWS_LAMBDA_ROLE}" \
482
+ --handler_name="handler";
483
+ ```
484
+ Deploy contents of a specific directory using specific module name:
485
+ ```
486
+ dpl --provider="lambda" \
487
+ --access_key_id="${AWS_ACCESS_KEY}" \
488
+ --secret_access_key="${AWS_SECRET_KEY}" \
489
+ --function_name="test-lambda" \
490
+ --role="${AWS_LAMBDA_ROLE}" \
491
+ --zip="${TRAVIS_BUILD_DIR}/dist" \
492
+ --module_name="copy" \
493
+ --handler_name="handler";
494
+ ```
data/lib/dpl/provider.rb CHANGED
@@ -18,6 +18,7 @@ module DPL
18
18
  autoload :CloudControl, 'dpl/provider/cloudcontrol'
19
19
  autoload :CloudFoundry, 'dpl/provider/cloud_foundry'
20
20
  autoload :CodeDeploy, 'dpl/provider/code_deploy'
21
+ autoload :Lambda, 'dpl/provider/lambda'
21
22
  autoload :PyPI, 'dpl/provider/pypi'
22
23
  autoload :Divshot, 'dpl/provider/divshot'
23
24
  autoload :CloudFiles, 'dpl/provider/cloud_files'
@@ -6,10 +6,7 @@ module DPL
6
6
  requires 'aws-sdk', pre: true
7
7
 
8
8
  def code_deploy
9
- @code_deploy ||= begin
10
- Aws.add_service('CodeDeploy', api: File.expand_path("../CodeDeploy.api.json", __FILE__)) unless defined? Aws::CodeDeploy
11
- Aws::CodeDeploy::Client.new(code_deploy_options)
12
- end
9
+ @code_deploy ||= Aws::CodeDeploy::Client.new(code_deploy_options)
13
10
  end
14
11
 
15
12
  def code_deploy_options
@@ -0,0 +1,142 @@
1
+ require 'json'
2
+ require 'tempfile'
3
+ require 'fileutils'
4
+
5
+ module DPL
6
+ class Provider
7
+ class Lambda < Provider
8
+ requires 'aws-sdk', pre: true
9
+ requires 'rubyzip', load: 'zip'
10
+
11
+ def lambda
12
+ @lambda ||= ::Aws::Lambda::Client.new(lambda_options)
13
+ end
14
+
15
+ def lambda_options
16
+ {
17
+ region: options[:region] || 'us-east-1',
18
+ credentials: ::Aws::Credentials.new(option(:access_key_id), option(:secret_access_key))
19
+ }
20
+ end
21
+
22
+ def push_app
23
+ response = lambda.upload_function({
24
+ function_name: options[:name] || option(:function_name),
25
+ description: options[:description] || default_description,
26
+ timeout: options[:timeout] || default_timeout,
27
+ memory_size: options[:memory_size] || deafult_memory_size,
28
+ role: option(:role),
29
+ handler: handler,
30
+ function_zip: function_zip,
31
+ runtime: default_runtime,
32
+ mode: default_mode
33
+ })
34
+
35
+ log "Uploaded lambda: #{response.function_name}."
36
+ rescue ::Aws::Lambda::Errors::ServiceException => exception
37
+ error(exception.message)
38
+ rescue ::Aws::Lambda::Errors::InvalidParameterValueException => exception
39
+ error(exception.message)
40
+ rescue ::Aws::Lambda::Errors::ResourceNotFoundException => exception
41
+ error(exception.message)
42
+ end
43
+
44
+ def handler
45
+ module_name = options[:module_name] || default_module_name
46
+ handler_name = option(:handler_name)
47
+
48
+ "#{module_name}.#{handler_name}"
49
+ end
50
+
51
+ def function_zip
52
+ target_zip_path = File.absolute_path(options[:zip] || Dir.pwd)
53
+ dest_file_path = output_file_path
54
+
55
+ if File.directory?(target_zip_path)
56
+ zip_directory(dest_file_path, target_zip_path)
57
+ elsif File.file?(target_zip_path)
58
+ zip_file(dest_file_path, target_zip_path)
59
+ else
60
+ error('Invalid zip option. If set, must be path to directory, js file, or a zip file.')
61
+ end
62
+
63
+ File.new(dest_file_path)
64
+ end
65
+
66
+ def zip_file(dest_file_path, target_file_path)
67
+ if File.extname(target_file_path) == '.zip'
68
+ # Just copy it to the destination right away, since it is already a zip.
69
+ FileUtils.cp(target_file_path, dest_file_path)
70
+ dest_file_path
71
+ else
72
+ # Zip up the file.
73
+ src_directory_path = File.dirname(target_file_path)
74
+ files = [ target_file_path ]
75
+
76
+ create_zip(dest_file_path, src_directory_path, files)
77
+ end
78
+ end
79
+
80
+ def zip_directory(dest_file_path, target_directory_path)
81
+ files = Dir[File.join(target_directory_path, '**', '**')]
82
+ create_zip(dest_file_path, target_directory_path, files)
83
+ end
84
+
85
+ def create_zip(dest_file_path, src_directory_path, files)
86
+ Zip::File.open(dest_file_path, Zip::File::CREATE) do |zipfile|
87
+ files.each do |file|
88
+ zipfile.add(file.sub(src_directory_path + File::SEPARATOR, ''), file)
89
+ end
90
+ end
91
+
92
+ dest_file_path
93
+ end
94
+
95
+ def needs_key?
96
+ false
97
+ end
98
+
99
+ def check_auth
100
+ log "Using Access Key: #{option(:access_key_id)[-4..-1].rjust(20, '*')}"
101
+ end
102
+
103
+ def output_file_path
104
+ @output_file_path ||= '/tmp/' + random_chars(8) + '-lambda.zip'
105
+ end
106
+
107
+ def default_runtime
108
+ 'nodejs'
109
+ end
110
+
111
+ def default_mode
112
+ 'event'
113
+ end
114
+
115
+ def default_timeout
116
+ 3 # seconds
117
+ end
118
+
119
+ def default_description
120
+ "Deploy build #{context.env['TRAVIS_BUILD_NUMBER']} to AWS Lambda via Travis CI"
121
+ end
122
+
123
+ def deafult_memory_size
124
+ 128
125
+ end
126
+
127
+ def default_module_name
128
+ 'index'
129
+ end
130
+
131
+ def random_chars(count=8)
132
+ (36**(count-1) + rand(36**count - 36**(count-1))).to_s(36)
133
+ end
134
+
135
+ def cleanup
136
+ end
137
+
138
+ def uncleanup
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,372 @@
1
+ require 'spec_helper'
2
+ require 'aws-sdk'
3
+ require 'dpl/error'
4
+ require 'dpl/provider'
5
+ require 'dpl/provider/lambda'
6
+
7
+ describe DPL::Provider::Lambda do
8
+
9
+ subject :provider do
10
+ described_class.new(DummyContext.new, :access_key_id => 'qwertyuiopasdfghjklz', :secret_access_key => 'qwertyuiopasdfghjklzqwertyuiopasdfghjklz')
11
+ end
12
+
13
+ describe '#lambda_options' do
14
+ context 'without region' do
15
+ example do
16
+ options = provider.lambda_options
17
+ expect(options[:region]).to eq('us-east-1')
18
+ end
19
+ end
20
+
21
+ context 'with region' do
22
+ example do
23
+ region = 'us-west-1'
24
+ provider.options.update(:region => region)
25
+ options = provider.lambda_options
26
+ expect(options[:region]).to eq(region)
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ describe DPL::Provider::Lambda do
33
+ access_key_id = 'someaccesskey'
34
+ secret_access_key = 'somesecretaccesskey'
35
+ region = 'us-east-1'
36
+
37
+ client_options = {
38
+ stub_responses: true,
39
+ region: region,
40
+ credentials: Aws::Credentials.new(access_key_id, secret_access_key)
41
+ }
42
+
43
+ subject :provider do
44
+ described_class.new(DummyContext.new, {
45
+ access_key_id: access_key_id,
46
+ secret_access_key: secret_access_key
47
+ })
48
+ end
49
+
50
+ before :each do
51
+ provider.stub(:lambda_options).and_return(client_options)
52
+ end
53
+
54
+ describe '#lambda' do
55
+ example do
56
+ expect(Aws::Lambda::Client).to receive(:new).with(client_options).once
57
+ provider.lambda
58
+ end
59
+ end
60
+
61
+ describe '#push_app' do
62
+ lambda_options = {
63
+ function_name: 'test-function',
64
+ role: 'some-role',
65
+ module_name: 'index',
66
+ handler_name: 'handler'
67
+ }
68
+
69
+ example_response = {
70
+ function_name: 'test-function',
71
+ role: 'some-role',
72
+ handler: 'index.handler'
73
+ }
74
+
75
+ before(:each) do
76
+ old_options = provider.options
77
+ provider.stub(:options) { old_options.merge(lambda_options) }
78
+ end
79
+
80
+ context 'with a successful response' do
81
+ before do
82
+ provider.lambda.stub_responses(:upload_function, example_response)
83
+ end
84
+
85
+ example do
86
+ expect(provider).to receive(:log).with(/Uploaded lambda: #{lambda_options[:function_name]}\./)
87
+ provider.push_app
88
+ end
89
+ end
90
+
91
+ context 'with a ServiceException response' do
92
+ before do
93
+ provider.lambda.stub_responses(:upload_function, 'ServiceException')
94
+ end
95
+
96
+ example do
97
+ expect(provider).to receive(:error).once
98
+ provider.push_app
99
+ end
100
+ end
101
+
102
+ context 'with a InvalidParameterValueException response' do
103
+ before do
104
+ provider.lambda.stub_responses(:upload_function, 'InvalidParameterValueException')
105
+ end
106
+
107
+ example do
108
+ expect(provider).to receive(:error).once
109
+ provider.push_app
110
+ end
111
+ end
112
+
113
+ context 'with a ResourceNotFoundException response' do
114
+ before do
115
+ provider.lambda.stub_responses(:upload_function, 'ResourceNotFoundException')
116
+ end
117
+
118
+ example do
119
+ expect(provider).to receive(:error).once
120
+ provider.push_app
121
+ end
122
+ end
123
+ end
124
+
125
+ describe "#handler" do
126
+ context "without a module name" do
127
+ module_name = 'index'
128
+ handler_name = 'HandlerName'
129
+ expected_handler = "#{module_name}.#{handler_name}"
130
+
131
+ before do
132
+ expect(provider.options).to receive(:[]).with(:module_name).and_return(nil)
133
+ expect(provider.options).to receive(:fetch).with(:handler_name).and_return(handler_name)
134
+ end
135
+
136
+ example do
137
+ expect(provider.handler).to eq(expected_handler)
138
+ end
139
+ end
140
+
141
+ context "with a module name" do
142
+ module_name = 'ModuleName'
143
+ handler_name = 'HandlerName'
144
+ expected_handler = "#{module_name}.#{handler_name}"
145
+
146
+ before do
147
+ expect(provider.options).to receive(:[]).with(:module_name).and_return(module_name)
148
+ expect(provider.options).to receive(:fetch).with(:handler_name).and_return(handler_name)
149
+ end
150
+
151
+ example do
152
+ expect(provider.handler).to eq(expected_handler)
153
+ end
154
+ end
155
+ end
156
+
157
+ describe '#function_zip' do
158
+ context 'when zip is not specified' do
159
+ path = Dir.pwd
160
+ output_file_path = '/some/path.zip'
161
+
162
+ before do
163
+ expect(provider.options).to receive(:[]).with(:zip).and_return(nil)
164
+ expect(provider).to receive(:output_file_path).and_return(output_file_path)
165
+ expect(File).to receive(:directory?).with(path).and_return(true)
166
+ expect(provider).to receive(:zip_directory).with(output_file_path, path)
167
+ expect(File).to receive(:new).with(output_file_path)
168
+ end
169
+
170
+ example do
171
+ provider.function_zip
172
+ end
173
+ end
174
+
175
+ context 'when zip is a file path' do
176
+ path = '/some/file/path.zip'
177
+ output_file_path = '/some/path.zip'
178
+
179
+ before do
180
+ expect(provider.options).to receive(:[]).with(:zip).and_return(path)
181
+ expect(provider).to receive(:output_file_path).and_return(output_file_path)
182
+ expect(File).to receive(:directory?).with(path).and_return(false)
183
+ expect(File).to receive(:file?).with(path).and_return(true)
184
+ expect(provider).to receive(:zip_file).with(output_file_path, path)
185
+ expect(File).to receive(:new).with(output_file_path)
186
+ end
187
+
188
+ example do
189
+ provider.function_zip
190
+ end
191
+ end
192
+
193
+ context 'when zip is a directory' do
194
+ path = '/some/dir/path'
195
+ output_file_path = '/some/path.zip'
196
+
197
+ before do
198
+ expect(provider.options).to receive(:[]).with(:zip).and_return(path)
199
+ expect(provider).to receive(:output_file_path).and_return(output_file_path)
200
+ expect(File).to receive(:directory?).with(path).and_return(true)
201
+ expect(provider).to receive(:zip_directory).with(output_file_path, path)
202
+ expect(File).to receive(:new).with(output_file_path)
203
+ end
204
+
205
+ example do
206
+ provider.function_zip
207
+ end
208
+ end
209
+
210
+ context 'with an invalid zip option' do
211
+ path = '/some/file/path.zip'
212
+ output_file_path = '/some/path.zip'
213
+ error = 'Invalid zip option. If set, must be path to directory, js file, or a zip file.'
214
+
215
+ before do
216
+ expect(provider.options).to receive(:[]).with(:zip).and_return(path)
217
+ expect(provider).to receive(:output_file_path).and_return(output_file_path)
218
+ expect(File).to receive(:directory?).with(path).and_return(false)
219
+ expect(File).to receive(:file?).with(path).and_return(false)
220
+ end
221
+
222
+ example do
223
+ expect { provider.function_zip }.to raise_error(DPL::Error, error)
224
+ end
225
+ end
226
+ end
227
+
228
+ describe '#zip_file' do
229
+ dest = '/some/path/to/write.zip'
230
+
231
+ context 'when zip is a file path' do
232
+ dir = '/some/target'
233
+ target = File.join(dir, 'file.js')
234
+
235
+ before do
236
+ expect(File).to receive(:extname).with(target).and_return('.js')
237
+ expect(provider).to receive(:create_zip).with(dest, dir, [ target ])
238
+ end
239
+
240
+ example do
241
+ provider.zip_file(dest, target)
242
+ end
243
+ end
244
+
245
+ context 'when zip is an existing zip file' do
246
+ dir = '/some/target'
247
+ target = File.join(dir, 'file.js')
248
+
249
+ before do
250
+ expect(File).to receive(:extname).with(target).and_return('.zip')
251
+ expect(FileUtils).to receive(:cp).with(target, dest)
252
+ end
253
+
254
+ example do
255
+ provider.zip_file(dest, target)
256
+ end
257
+ end
258
+ end
259
+
260
+ describe '#zip_directory' do
261
+ dest = '/some/path/to/write.zip'
262
+ target = '/some/dir'
263
+ glob = File.join(target, '**', '**')
264
+ files = %w[ 'one' 'two' ]
265
+
266
+ before do
267
+ expect(Dir).to receive(:[]).with(glob).and_return(files)
268
+ expect(provider).to receive(:create_zip).with(dest, target, files)
269
+ end
270
+
271
+ example do
272
+ provider.zip_directory(dest, target)
273
+ end
274
+ end
275
+
276
+ describe '#create_zip' do
277
+ dest = '/some/dest.zip'
278
+ src = '/some/src/dir'
279
+ file_one = 'one.js'
280
+ file_two = 'two.js'
281
+ files = [
282
+ File.join(src, file_one),
283
+ File.join(src, file_two)
284
+ ]
285
+
286
+ before do
287
+ zip_file = double(Zip::File)
288
+ expect(Zip::File).to receive(:open).with(dest, Zip::File::CREATE).and_yield(zip_file)
289
+ expect(zip_file).to receive(:add).once.with(file_one, File.join(src, file_one))
290
+ expect(zip_file).to receive(:add).once.with(file_two, File.join(src, file_two))
291
+ end
292
+
293
+ example do
294
+ provider.create_zip(dest, src, files)
295
+ end
296
+ end
297
+
298
+ describe '#needs_key?' do
299
+ example do
300
+ expect(provider.needs_key?).to eq(false)
301
+ end
302
+ end
303
+
304
+ describe '#check_auth' do
305
+ example do
306
+ expect(provider).to receive(:log).with("Using Access Key: #{access_key_id[-4..-1].rjust(20, '*')}")
307
+ provider.check_auth
308
+ end
309
+ end
310
+
311
+ describe '#output_file_path' do
312
+ example do
313
+ expect(provider.output_file_path).to match(/tmp\/\w{8}\-lambda\.zip/)
314
+ end
315
+ end
316
+
317
+ describe '#default_runtime' do
318
+ example do
319
+ expect(provider.default_runtime).to eq('nodejs')
320
+ end
321
+ end
322
+
323
+ describe '#default_mode' do
324
+ example do
325
+ expect(provider.default_mode).to eq('event')
326
+ end
327
+ end
328
+
329
+ describe '#default_timeout' do
330
+ example do
331
+ expect(provider.default_timeout).to eq(3)
332
+ end
333
+ end
334
+
335
+ describe '#default_description' do
336
+ build_number = 2
337
+
338
+ before do
339
+ provider.context.env.stub(:[]).with('TRAVIS_BUILD_NUMBER').and_return(build_number)
340
+ end
341
+
342
+ let(:build_number) { provider.context.env['TRAVIS_BUILD_NUMBER'] }
343
+
344
+ example do
345
+ expect(provider.default_description).to eq(
346
+ "Deploy build #{build_number} to AWS Lambda via Travis CI"
347
+ )
348
+ end
349
+ end
350
+
351
+ describe '#deafult_memory_size' do
352
+ example do
353
+ expect(provider.deafult_memory_size).to eq(128)
354
+ end
355
+ end
356
+
357
+ describe '#random_chars' do
358
+ context 'without specifying count' do
359
+ example do
360
+ expect(provider.random_chars.length).to eq(8)
361
+ end
362
+ end
363
+
364
+ context 'with specified count' do
365
+ count = 4
366
+ example do
367
+ expect(provider.random_chars(count).length).to eq(count)
368
+ end
369
+ end
370
+ end
371
+
372
+ end