1
1
# -*- coding: utf-8 -*-
2
2
from __future__ import print_function
3
3
4
+ import hashlib
4
5
import json
5
6
import logging
6
7
import os
7
8
import sys
8
9
import time
10
+ from collections import defaultdict
9
11
from imp import load_source
10
12
from shutil import copy
11
13
from shutil import copyfile
14
+ from shutil import copytree
12
15
from tempfile import mkdtemp
13
16
14
17
import boto3
15
18
import botocore
16
19
import pip
17
20
import yaml
18
- import hashlib
19
21
20
22
from .helpers import archive
23
+ from .helpers import get_environment_variable_value
21
24
from .helpers import mkdir
22
25
from .helpers import read
23
26
from .helpers import timestamp
24
- from .helpers import get_environment_variable_value
25
27
26
28
29
+ ARN_PREFIXES = {
30
+ 'us-gov-west-1' : 'aws-us-gov' ,
31
+ }
32
+
27
33
log = logging .getLogger (__name__ )
28
34
29
35
@@ -47,11 +53,13 @@ def cleanup_old_versions(src, keep_last_versions):
47
53
aws_access_key_id = cfg .get ('aws_access_key_id' )
48
54
aws_secret_access_key = cfg .get ('aws_secret_access_key' )
49
55
50
- client = get_client ('lambda' , aws_access_key_id , aws_secret_access_key ,
51
- cfg .get ('region' ))
56
+ client = get_client (
57
+ 'lambda' , aws_access_key_id , aws_secret_access_key ,
58
+ cfg .get ('region' ),
59
+ )
52
60
53
61
response = client .list_versions_by_function (
54
- FunctionName = cfg .get ('function_name' )
62
+ FunctionName = cfg .get ('function_name' ),
55
63
)
56
64
versions = response .get ('Versions' )
57
65
if len (response .get ('Versions' )) < keep_last_versions :
@@ -63,7 +71,7 @@ def cleanup_old_versions(src, keep_last_versions):
63
71
try :
64
72
client .delete_function (
65
73
FunctionName = cfg .get ('function_name' ),
66
- Qualifier = version_number
74
+ Qualifier = version_number ,
67
75
)
68
76
except botocore .exceptions .ClientError as e :
69
77
print ('Skipping Version {}: {}'
@@ -144,6 +152,7 @@ def upload(src, requirements=False, local_package=None):
144
152
145
153
upload_s3 (cfg , path_to_zip_file )
146
154
155
+
147
156
def invoke (src , alt_event = None , verbose = False ):
148
157
"""Simulates a call to your function.
149
158
@@ -159,6 +168,11 @@ def invoke(src, alt_event=None, verbose=False):
159
168
path_to_config_file = os .path .join (src , 'config.yaml' )
160
169
cfg = read (path_to_config_file , loader = yaml .load )
161
170
171
+ # Load environment variables from the config file into the actual
172
+ # environment.
173
+ for key , value in cfg .get ('environment_variables' ).items ():
174
+ os .environ [key ] = value
175
+
162
176
# Load and parse event file.
163
177
if alt_event :
164
178
path_to_event_file = os .path .join (src , alt_event )
@@ -200,7 +214,8 @@ def init(src, minimal=False):
200
214
"""
201
215
202
216
templates_path = os .path .join (
203
- os .path .dirname (os .path .abspath (__file__ )), 'project_templates' )
217
+ os .path .dirname (os .path .abspath (__file__ )), 'project_templates' ,
218
+ )
204
219
for filename in os .listdir (templates_path ):
205
220
if (minimal and filename == 'event.json' ) or filename .endswith ('.pyc' ):
206
221
continue
@@ -236,22 +251,41 @@ def build(src, requirements=False, local_package=None):
236
251
output_filename = '{0}-{1}.zip' .format (timestamp (), function_name )
237
252
238
253
path_to_temp = mkdtemp (prefix = 'aws-lambda' )
239
- pip_install_to_target (path_to_temp ,
240
- requirements = requirements ,
241
- local_package = local_package )
254
+ pip_install_to_target (
255
+ path_to_temp ,
256
+ requirements = requirements ,
257
+ local_package = local_package ,
258
+ )
242
259
243
260
# Hack for Zope.
244
261
if 'zope' in os .listdir (path_to_temp ):
245
- print ('Zope packages detected; fixing Zope package paths to '
246
- 'make them importable.' )
262
+ print (
263
+ 'Zope packages detected; fixing Zope package paths to '
264
+ 'make them importable.' ,
265
+ )
247
266
# Touch.
248
267
with open (os .path .join (path_to_temp , 'zope/__init__.py' ), 'wb' ):
249
268
pass
250
269
251
270
# Gracefully handle whether ".zip" was included in the filename or not.
252
- output_filename = ('{0}.zip' .format (output_filename )
253
- if not output_filename .endswith ('.zip' )
254
- else output_filename )
271
+ output_filename = (
272
+ '{0}.zip' .format (output_filename )
273
+ if not output_filename .endswith ('.zip' )
274
+ else output_filename
275
+ )
276
+
277
+ # Allow definition of source code directories we want to build into our
278
+ # zipped package.
279
+ build_config = defaultdict (** cfg .get ('build' , {}))
280
+ build_source_directories = build_config .get ('source_directories' , '' )
281
+ build_source_directories = (
282
+ build_source_directories
283
+ if build_source_directories is not None
284
+ else ''
285
+ )
286
+ source_directories = [
287
+ d .strip () for d in build_source_directories .split (',' )
288
+ ]
255
289
256
290
files = []
257
291
for filename in os .listdir (src ):
@@ -262,14 +296,21 @@ def build(src, requirements=False, local_package=None):
262
296
continue
263
297
print ('Bundling: %r' % filename )
264
298
files .append (os .path .join (src , filename ))
299
+ elif os .path .isdir (filename ) and filename in source_directories :
300
+ print ('Bundling directory: %r' % filename )
301
+ files .append (os .path .join (src , filename ))
265
302
266
303
# "cd" into `temp_path` directory.
267
304
os .chdir (path_to_temp )
268
305
for f in files :
269
- _ , filename = os .path .split (f )
306
+ if os .path .isfile (f ):
307
+ _ , filename = os .path .split (f )
270
308
271
- # Copy handler file into root of the packages folder.
272
- copyfile (f , os .path .join (path_to_temp , filename ))
309
+ # Copy handler file into root of the packages folder.
310
+ copyfile (f , os .path .join (path_to_temp , filename ))
311
+ elif os .path .isdir (f ):
312
+ destination_folder = os .path .join (path_to_temp , f [len (src ) + 1 :])
313
+ copytree (f , destination_folder )
273
314
274
315
# Zip them together into a single file.
275
316
# TODO: Delete temp directory created once the archive has been compiled.
@@ -368,9 +409,10 @@ def pip_install_to_target(path, requirements=False, local_package=None):
368
409
_install_packages (path , packages )
369
410
370
411
371
- def get_role_name (account_id , role ):
412
+ def get_role_name (region , account_id , role ):
372
413
"""Shortcut to insert the `account_id` and `role` into the iam string."""
373
- return 'arn:aws:iam::{0}:role/{1}' .format (account_id , role )
414
+ prefix = ARN_PREFIXES .get (region , 'aws' )
415
+ return 'arn:{0}:iam::{1}:role/{2}' .format (prefix , account_id , role )
374
416
375
417
376
418
def get_account_id (aws_access_key_id , aws_secret_access_key ):
@@ -386,7 +428,7 @@ def get_client(client, aws_access_key_id, aws_secret_access_key, region=None):
386
428
client ,
387
429
aws_access_key_id = aws_access_key_id ,
388
430
aws_secret_access_key = aws_secret_access_key ,
389
- region_name = region
431
+ region_name = region ,
390
432
)
391
433
392
434
@@ -399,10 +441,15 @@ def create_function(cfg, path_to_zip_file, *use_s3, **s3_file):
399
441
aws_secret_access_key = cfg .get ('aws_secret_access_key' )
400
442
401
443
account_id = get_account_id (aws_access_key_id , aws_secret_access_key )
402
- role = get_role_name (account_id , cfg .get ('role' , 'lambda_basic_execution' ))
444
+ role = get_role_name (
445
+ cfg .get ('region' ), account_id ,
446
+ cfg .get ('role' , 'lambda_basic_execution' ),
447
+ )
403
448
404
- client = get_client ('lambda' , aws_access_key_id , aws_secret_access_key ,
405
- cfg .get ('region' ))
449
+ client = get_client (
450
+ 'lambda' , aws_access_key_id , aws_secret_access_key ,
451
+ cfg .get ('region' ),
452
+ )
406
453
407
454
# Do we prefer development variable over config?
408
455
buck_name = (
@@ -448,8 +495,8 @@ def create_function(cfg, path_to_zip_file, *use_s3, **s3_file):
448
495
key : get_environment_variable_value (value )
449
496
for key , value
450
497
in cfg .get ('environment_variables' ).items ()
451
- }
452
- }
498
+ },
499
+ },
453
500
)
454
501
455
502
client .create_function (** kwargs )
@@ -464,10 +511,15 @@ def update_function(cfg, path_to_zip_file, *use_s3, **s3_file):
464
511
aws_secret_access_key = cfg .get ('aws_secret_access_key' )
465
512
466
513
account_id = get_account_id (aws_access_key_id , aws_secret_access_key )
467
- role = get_role_name (account_id , cfg .get ('role' , 'lambda_basic_execution' ))
514
+ role = get_role_name (
515
+ cfg .get ('region' ), account_id ,
516
+ cfg .get ('role' , 'lambda_basic_execution' ),
517
+ )
468
518
469
- client = get_client ('lambda' , aws_access_key_id , aws_secret_access_key ,
470
- cfg .get ('region' ))
519
+ client = get_client (
520
+ 'lambda' , aws_access_key_id , aws_secret_access_key ,
521
+ cfg .get ('region' ),
522
+ )
471
523
472
524
# Do we prefer development variable over config?
473
525
buck_name = (
@@ -497,8 +549,8 @@ def update_function(cfg, path_to_zip_file, *use_s3, **s3_file):
497
549
'MemorySize' : cfg .get ('memory_size' , 512 ),
498
550
'VpcConfig' : {
499
551
'SubnetIds' : cfg .get ('subnet_ids' , []),
500
- 'SecurityGroupIds' : cfg .get ('security_group_ids' , [])
501
- }
552
+ 'SecurityGroupIds' : cfg .get ('security_group_ids' , []),
553
+ },
502
554
}
503
555
504
556
if 'environment_variables' in cfg :
@@ -508,8 +560,8 @@ def update_function(cfg, path_to_zip_file, *use_s3, **s3_file):
508
560
key : get_environment_variable_value (value )
509
561
for key , value
510
562
in cfg .get ('environment_variables' ).items ()
511
- }
512
- }
563
+ },
564
+ },
513
565
)
514
566
515
567
client .update_function_configuration (** kwargs )
@@ -520,17 +572,19 @@ def upload_s3(cfg, path_to_zip_file, *use_s3):
520
572
print ('Uploading your new Lambda function' )
521
573
aws_access_key_id = cfg .get ('aws_access_key_id' )
522
574
aws_secret_access_key = cfg .get ('aws_secret_access_key' )
523
- account_id = get_account_id ( aws_access_key_id , aws_secret_access_key )
524
- client = get_client ( 's3' , aws_access_key_id , aws_secret_access_key ,
525
- cfg .get ('region' ))
526
- role = get_role_name ( account_id , cfg . get ( 'role' , 'basic_s3_upload' ) )
575
+ client = get_client (
576
+ 's3' , aws_access_key_id , aws_secret_access_key ,
577
+ cfg .get ('region' ),
578
+ )
527
579
byte_stream = b''
528
580
with open (path_to_zip_file , mode = 'rb' ) as fh :
529
581
byte_stream = fh .read ()
530
582
s3_key_prefix = cfg .get ('s3_key_prefix' , '/dist' )
531
583
checksum = hashlib .new ('md5' , byte_stream ).hexdigest ()
532
584
timestamp = str (time .time ())
533
- filename = '{prefix}{checksum}-{ts}.zip' .format (prefix = s3_key_prefix , checksum = checksum , ts = timestamp )
585
+ filename = '{prefix}{checksum}-{ts}.zip' .format (
586
+ prefix = s3_key_prefix , checksum = checksum , ts = timestamp ,
587
+ )
534
588
535
589
# Do we prefer development variable over config?
536
590
buck_name = (
@@ -542,23 +596,37 @@ def upload_s3(cfg, path_to_zip_file, *use_s3):
542
596
kwargs = {
543
597
'Bucket' : '{}' .format (buck_name ),
544
598
'Key' : '{}' .format (filename ),
545
- 'Body' : byte_stream
599
+ 'Body' : byte_stream ,
546
600
}
547
601
548
602
client .put_object (** kwargs )
549
603
print ('Finished uploading {} to S3 bucket {}' .format (func_name , buck_name ))
550
604
if use_s3 == True :
551
605
return filename
552
606
607
+
553
608
def function_exists (cfg , function_name ):
554
609
"""Check whether a function exists or not"""
555
610
556
611
aws_access_key_id = cfg .get ('aws_access_key_id' )
557
612
aws_secret_access_key = cfg .get ('aws_secret_access_key' )
558
- client = get_client ('lambda' , aws_access_key_id , aws_secret_access_key ,
559
- cfg .get ('region' ))
560
- functions = client .list_functions ().get ('Functions' , [])
561
- for fn in functions :
562
- if fn .get ('FunctionName' ) == function_name :
563
- return True
564
- return False
613
+ client = get_client (
614
+ 'lambda' , aws_access_key_id , aws_secret_access_key ,
615
+ cfg .get ('region' ),
616
+ )
617
+
618
+ # Need to loop through until we get all of the lambda functions returned.
619
+ # It appears to be only returning 50 functions at a time.
620
+ functions = []
621
+ functions_resp = client .list_functions ()
622
+ functions .extend ([
623
+ f ['FunctionName' ] for f in functions_resp .get ('Functions' , [])
624
+ ])
625
+ while ('NextMarker' in functions_resp ):
626
+ functions_resp = client .list_functions (
627
+ Marker = functions_resp .get ('NextMarker' ),
628
+ )
629
+ functions .extend ([
630
+ f ['FunctionName' ] for f in functions_resp .get ('Functions' , [])
631
+ ])
632
+ return function_name in functions
0 commit comments