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

Sign up to get free protection for your applications and to get access to all the features.
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