deployer.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. import os
  2. import boto3
  3. import mimetypes
  4. import json
  5. import requests
  6. import subprocess
  7. import tempfile
  8. import pathlib
  9. import shutil
  10. s3 = boto3.resource('s3')
  11. defaultContentType = 'application/octet-stream'
  12. def resource_handler(event, context):
  13. print(event)
  14. try:
  15. target_bucket = event['ResourceProperties']['TargetBucket']
  16. lambda_src = os.getcwd()
  17. acl = event['ResourceProperties']['Acl']
  18. cacheControl = 'max-age=' + \
  19. event['ResourceProperties']['CacheControlMaxAge']
  20. print(event['RequestType'])
  21. if event['RequestType'] == 'Create' or event['RequestType'] == 'Update':
  22. if 'Substitutions' in event['ResourceProperties'].keys():
  23. temp_dir = os.path.join(tempfile.mkdtemp(), context.aws_request_id)
  24. apply_substitutions(event['ResourceProperties']['Substitutions'], temp_dir)
  25. lambda_src = temp_dir
  26. print('uploading')
  27. upload(lambda_src, target_bucket, acl, cacheControl)
  28. elif event['RequestType'] == 'Delete':
  29. delete(lambda_src, target_bucket, s3)
  30. else:
  31. print('ignoring')
  32. if not lambda_src == os.getcwd():
  33. print('removing temporary', lambda_src)
  34. shutil.rmtree(lambda_src)
  35. send_result(event)
  36. except Exception as err:
  37. send_error(event, err)
  38. return event
  39. def upload(lambda_src, target_bucket, acl, cacheControl):
  40. for folder, subs, files in os.walk(lambda_src):
  41. for filename in files:
  42. source_file_path = os.path.join(folder, filename)
  43. destination_s3_key = os.path.relpath(source_file_path, lambda_src)
  44. contentType, encoding = mimetypes.guess_type(source_file_path)
  45. upload_file(source_file_path, target_bucket,
  46. destination_s3_key, s3, acl, cacheControl, contentType)
  47. def upload_file(source, bucket, key, s3lib, acl, cacheControl, contentType):
  48. print('uploading from {} {} {}'.format(source, bucket, key))
  49. contentType = contentType or defaultContentType
  50. s3lib.Object(bucket, key).put(ACL=acl, Body=open(source, 'rb'),
  51. CacheControl=cacheControl, ContentType=contentType)
  52. def delete(lambda_src, target_bucket, s3lib):
  53. for folder, subs, files in os.walk(lambda_src):
  54. for filename in files:
  55. source_file_path = os.path.join(folder, filename)
  56. destination_s3_key = os.path.relpath(source_file_path, lambda_src)
  57. print('deleting file {} from {}'.format(destination_s3_key, target_bucket))
  58. s3lib.Object(target_bucket, destination_s3_key).delete()
  59. def send_result(event):
  60. response_body = json.dumps({
  61. 'Status': 'SUCCESS',
  62. 'PhysicalResourceId': get_physical_resource_id(event),
  63. 'StackId': event['StackId'],
  64. 'RequestId': event['RequestId'],
  65. 'LogicalResourceId': event['LogicalResourceId']
  66. })
  67. print(response_body)
  68. requests.put(event['ResponseURL'], data=response_body)
  69. def send_error(event, error):
  70. response_body = json.dumps({
  71. 'Status': 'FAILED',
  72. 'Reason': str(error),
  73. 'PhysicalResourceId': get_physical_resource_id(event),
  74. 'StackId': event['StackId'],
  75. 'RequestId': event['RequestId'],
  76. 'LogicalResourceId': event['LogicalResourceId']
  77. })
  78. print(response_body)
  79. requests.put(event['ResponseURL'], data=response_body)
  80. def get_physical_resource_id(event):
  81. if 'PhysicalResourceId' in event.keys():
  82. return event['PhysicalResourceId']
  83. else:
  84. return event['RequestId']
  85. def apply_substitutions(substitutions, temp_dir):
  86. if not 'Values' in substitutions.keys():
  87. raise ValueError('Substitutions must contain Values')
  88. if not isinstance(substitutions['Values'], dict):
  89. raise ValueError('Substitutions.Values must be an Object')
  90. if len(substitutions['Values']) < 1:
  91. raise ValueError('Substitutions.Values must not be empty')
  92. if not 'FilePattern' in substitutions.keys():
  93. raise ValueError('Substitutions must contain FilePattern')
  94. if not isinstance(substitutions['FilePattern'], str):
  95. raise ValueError('Substitutions.FilePattern must be a String')
  96. if len(substitutions['FilePattern']) < 1:
  97. raise ValueError('Substitutions.FilePattern must not be empty')
  98. values = substitutions['Values']
  99. file_pattern = substitutions['FilePattern']
  100. subprocess.run(['cp', '-r', os.getcwd(), temp_dir])
  101. for full_path in pathlib.Path(temp_dir).glob(file_pattern):
  102. replace_with_command = lambda key: 's/\\${%s}/%s/g'% (sed_escape(key), sed_escape(values[key]))
  103. replacements = list(map(replace_with_command, values.keys()))
  104. sed_script = ';'.join(replacements)
  105. subprocess.run(['sed', sed_script, '-i', str(full_path)], cwd=tempfile.gettempdir(), check=True)
  106. def sed_escape(text):
  107. return text.replace('/', '\\/')