delete_github_branch_ebs.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. #
  2. # Copyright (c) Contributors to the Open 3D Engine Project.
  3. # For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. #
  5. # SPDX-License-Identifier: Apache-2.0 OR MIT
  6. #
  7. #
  8. import os
  9. import boto3
  10. import json
  11. import hmac
  12. import hashlib
  13. def delete_volumes(repository_name, branch_name):
  14. """
  15. Trigger lambda function that deletes EBS volumes.
  16. :param repository_name: Full repository name.
  17. :param branch_name: Branch name that is deleted.
  18. :return: Number of EBS volumes that are deleted successfully, number of EBS volumes that are not deleted.
  19. """
  20. client = boto3.client('lambda')
  21. payload = {
  22. 'repository_name': repository_name,
  23. 'branch_name': branch_name
  24. }
  25. response = client.invoke(
  26. FunctionName='delete_branch_ebs',
  27. Payload=json.dumps(payload),
  28. )
  29. status = response['Payload'].read()
  30. response_json = json.loads(status.decode())
  31. return response_json['success'], response_json['failure']
  32. def verify_signature(headers, payload):
  33. """
  34. Validate POST request headers and payload to only receive the expected GitHub webhook requests.
  35. :param headers: Headers from POST request.
  36. :param payload: Payload from POST request.
  37. :return: True if request is verified, otherwise, return False.
  38. """
  39. # secret is stored in AWS Secret Manager
  40. secret_name = os.environ.get('GITHUB_WEBHOOK_SECRET_NAME', '')
  41. client = boto3.client(service_name='secretsmanager')
  42. response = client.get_secret_value(SecretId=secret_name)
  43. secret = response['SecretString']
  44. # Using X-Hub-Signature-256 is recommended by https://docs.github.com/en/developers/webhooks-and-events/securing-your-webhooks
  45. signature = headers.get('X-Hub-Signature-256', '')
  46. computed_hash = hmac.new(secret.encode(), payload.encode(), hashlib.sha256).hexdigest()
  47. computed_signature = f'sha256={computed_hash}'
  48. return hmac.compare_digest(computed_signature.encode(), signature.encode())
  49. def create_response(status, success=0, failure=0, repository_name=None, branch_name=None):
  50. """
  51. :param status: Status of EBS deletion request.
  52. :param success: Number of EBS volumes that are deleted successfully.
  53. :param failure: Number of EBS volumes that are not deleted.
  54. :param repository_name: Full repository name.
  55. :param branch_name: Branch name that is deleted.
  56. :return: JSON response.
  57. """
  58. response = {
  59. 'success': {
  60. 'statusCode': 200,
  61. 'body': f'[SUCCESS] All {success + failure} EBS volumes are deleted for branch {branch_name} in repository {repository_name}',
  62. 'isBase64Encoded': 'false'
  63. },
  64. 'failure': {
  65. 'statusCode': 500,
  66. 'body': f'[FAILURE] Failed to delete {failure}/{success + failure} EBS volumes for branch {branch_name} in repository {repository_name}',
  67. 'isBase64Encoded': 'false'
  68. },
  69. 'unauthorized': {
  70. 'statusCode': 401,
  71. 'body': 'Unauthorized',
  72. 'isBase64Encoded': 'false'
  73. },
  74. 'unsupported': {
  75. 'statusCode': 204,
  76. 'isBase64Encoded': 'false'
  77. }
  78. }
  79. return response[status]
  80. def lambda_handler(event, context):
  81. # This function is triggered by AWS API Gateway,
  82. if event.get('resource', '') == '/delete-github-branch-ebs':
  83. headers = event['headers']
  84. payload = event['body']
  85. # Validate github webhook request here since request body cannot be passed to API Gateway lambda authorizer.
  86. if verify_signature(headers, payload):
  87. # Convert payload from string type to json to get repository name and branch name
  88. payload = json.loads(payload)
  89. repository_name = payload['repository']['full_name']
  90. if headers['X-GitHub-Event'] == 'delete':
  91. # On Github branch/tag delete event
  92. branch_name = payload['ref']
  93. elif headers['X-GitHub-Event'] == 'pull_request' and payload['action'] == 'closed':
  94. # On Github pull request closed event
  95. pull_request_number = payload['number']
  96. branch_name = f'PR-{pull_request_number}'
  97. else:
  98. return create_response('unsupported')
  99. (success, failure) = delete_volumes(repository_name, branch_name)
  100. if not failure:
  101. return create_response('success', success, failure, repository_name, branch_name)
  102. else:
  103. return create_response('failure', success, failure, repository_name, branch_name)
  104. else:
  105. return create_response('unauthorized')