Created
June 8, 2020 13:41
-
-
Save mcfantom-3xm/d72b6f63059218cb930ccaaa87736d47 to your computer and use it in GitHub Desktop.
CloudFormation Custom Resource
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
AWSTemplateFormatVersion: 2010-09-09 | |
Description: Rekognition Video Blog | |
Parameters: | |
ApplicationName: | |
Description: Name of the application deploying for the Social Media Analytics | |
Type: String | |
Default: RekognitionVideoBlog | |
EmailAddress: | |
Description: Email address used for SNS notification whenever people are found in video | |
Type: String | |
S3BucketName: | |
Description: Name for bucket | |
Type: String | |
AllowedPattern: (?=^.{3,63}$)(?!^(\d+\.)+\d+$)(^(([a-z0-9]|[a-z0-9][a-z0-9\-]*[a-z0-9])\.)*([a-z0-9]|[a-z0-9][a-z0-9\-]*[a-z0-9])$) | |
ConstraintDescription: S3 bucket name must be between 3 and 63 characters, be only lower-case, may contain numbers, periods, and dashes. Must start with a lowercase letter or number. Cannot contain underscore, end with a dash, contain consecutive periods, or dashes adyacent to periods. Cannot be formatted as an IP address. | |
Resources: | |
EventMetadataStream: | |
Type: 'AWS::Kinesis::Stream' | |
Properties: | |
Name: !Sub '${ApplicationName}-Stream' | |
ShardCount: 1 | |
SNSTopic: | |
Type: 'AWS::SNS::Topic' | |
Properties: | |
DisplayName: !Sub '${ApplicationName} SNS Topic' | |
Subscription: | |
- Endpoint: !Sub '${EmailAddress}' | |
Protocol: email | |
SNSPublishRole: | |
Type: 'AWS::IAM::Role' | |
Properties: | |
AssumeRolePolicyDocument: | |
Version: 2012-10-17 | |
Statement: | |
- Effect: Allow | |
Principal: | |
Service: lambda.amazonaws.com | |
Action: 'sts:AssumeRole' | |
Path: / | |
Policies: | |
- PolicyName: lambda-comprehend-policy | |
PolicyDocument: | |
Version: 2012-10-17 | |
Statement: | |
- Effect: Allow | |
Action: | |
- 'sns:Publish' | |
Resource: !Ref SNSTopic | |
- Effect: Allow | |
Action: | |
- 'kinesis:GetRecords' | |
- 'kinesis:GetShardIterator' | |
- 'kinesis:DescribeStream' | |
- 'kinesis:ListStreams' | |
Resource: !GetAtt EventMetadataStream.Arn | |
- Effect: Allow | |
Action: | |
- 'kinesis:ListStreams' | |
Resource: '*' | |
RekognitionVideoIAM: | |
Type: 'AWS::IAM::Role' | |
Properties: | |
AssumeRolePolicyDocument: | |
Version: 2012-10-17 | |
Statement: | |
- Effect: Allow | |
Principal: | |
Service: rekognition.amazonaws.com | |
Action: 'sts:AssumeRole' | |
- Effect: Allow | |
Principal: | |
Service: lambda.amazonaws.com | |
Action: 'sts:AssumeRole' | |
Path: / | |
Policies: | |
- PolicyName: lambda-comprehend-policy | |
PolicyDocument: | |
Version: 2012-10-17 | |
Statement: | |
- Effect: Allow | |
Action: | |
- 'kinesis:*' | |
Resource: !GetAtt EventMetadataStream.Arn | |
- Effect: Allow | |
Action: | |
- 'kinesisvideo:*' | |
Resource: '*' | |
- Effect: Allow | |
Action: | |
- 'rekognition:*' | |
Resource: '*' | |
- Effect: Allow | |
Action: | |
- 'logs:*' | |
Resource: '*' | |
- Effect: Allow | |
Action: | |
- 'lambda:TagResource' | |
- 'lambda:ListTags' | |
Resource: '*' | |
- Effect: Allow | |
Action: | |
- 's3:GetAccessPoint' | |
- 's3:PutAccountPublicAccessBlock' | |
- 's3:ListAccessPoints' | |
- 's3:ListJobs' | |
- 's3:GetAccountPublicAccessBlock' | |
- 's3:ListAllMyBuckets' | |
- 's3:CreateJob' | |
- 's3:HeadBucket' | |
Resource: '*' | |
- Effect: Allow | |
Action: | |
- 's3:*' | |
Resource: '*' | |
RekognitionVideoLambda: | |
Type: 'AWS::Lambda::Function' | |
Properties: | |
Environment: | |
Variables: | |
sns: !Ref SNSTopic | |
Handler: index.handler | |
Role: !GetAtt SNSPublishRole.Arn | |
Code: | |
S3Bucket: us-west-2.serverless-analytics | |
S3Key: KinesisVideoRekognitionVideo-blog/Lambda.zip | |
Runtime: nodejs10.x | |
Timeout: '120' | |
RekognitionLambdaKinesisMapping: | |
Type: 'AWS::Lambda::EventSourceMapping' | |
Properties: | |
BatchSize: 10 | |
Enabled: true | |
EventSourceArn: !GetAtt EventMetadataStream.Arn | |
FunctionName: !GetAtt RekognitionVideoLambda.Arn | |
StartingPosition: TRIM_HORIZON | |
LambdaExtraResourcesCfg: | |
Type: 'AWS::Lambda::Function' | |
Properties: | |
Code: | |
ZipFile: !Sub | | |
import os | |
import json | |
import logging | |
import signal | |
import boto3 | |
from urllib2 import build_opener, HTTPHandler, Request | |
LOGGER = logging.getLogger() | |
LOGGER.setLevel(logging.INFO) | |
def handler(event, context): | |
signal.alarm((context.get_remaining_time_in_millis() / 1000) - 1) | |
kinesisVideoStream = boto3.client('kinesisvideo') | |
rekognitionCollection = boto3.client('rekognition') | |
currentLambda = boto3.client('lambda') | |
try: | |
varStackName = str(os.environ['StackName']) | |
LOGGER.info('LAMBDA ARN: ' + str(context.invoked_function_arn)) | |
LOGGER.info('STACK NAME: ' + varStackName) | |
if event['RequestType'] == 'Create': | |
LOGGER.info('CREATED!') | |
response = kinesisVideoStream.create_stream(DeviceName=varStackName+'KinesisVideoStream', | |
StreamName=varStackName+'KinesisVideoStream', MediaType='video/h264', DataRetentionInHours=1, | |
Tags={'OwnedByStack': varStackName}) | |
stream_ARN = response['StreamARN'] | |
LOGGER.info('Kinesis Video Stream ARN: ' + str(stream_ARN)) | |
response = rekognitionCollection.create_collection(CollectionId=varStackName+'RekognitionCollection') | |
collection_ARN = response['CollectionArn'] | |
LOGGER.info('Rekognition Collection ARN: ' + str(collection_ARN)) | |
currentLambda.tag_resource(Resource=context.invoked_function_arn, Tags={'KinesisVideoStreamARN':stream_ARN, 'RekognitionCollectionARN':collection_ARN, 'RekognitionCollectionID':varStackName+'RekognitionCollection'}) | |
LOGGER.info('Lambda tagged succesfully.') | |
send_response(event, context, "SUCCESS", {"Message": "Resource creation successful!"}) | |
elif event['RequestType'] == 'Update': | |
LOGGER.info('UPDATE!') | |
send_response(event, context, "SUCCESS", {"Message": "Resource update successful!"}) | |
elif event['RequestType'] == 'Delete': | |
LOGGER.info('DELETE!') | |
lambdaTagsListResponse = currentLambda.list_tags(Resource=context.invoked_function_arn) | |
response = rekognitionCollection.delete_collection(CollectionId=lambdaTagsListResponse['Tags'].get('RekognitionCollectionID')) | |
LOGGER.info('Delete Rekognition Collection ID: ' + lambdaTagsListResponse['Tags'].get('RekognitionCollectionID') + ' Message: ' + str(response['StatusCode'])) | |
response = kinesisVideoStream.delete_stream(StreamARN=lambdaTagsListResponse['Tags'].get('KinesisVideoStreamARN')) | |
LOGGER.info('Delete Kinesis Video Stream ARN: ' + lambdaTagsListResponse['Tags'].get('KinesisVideoStreamARN') + ' Response: ' + str(response)) | |
send_response(event, context, "SUCCESS", {"Message": "Resource deletion successful!"}) | |
else: | |
LOGGER.info('FAILED!') | |
send_response(event, context, "FAILED", {"Message": "Unexpected event received from CF"}) | |
except Exception as error: | |
LOGGER.exception(error) | |
LOGGER.info('FAILED!') | |
send_response(event, context, "FAILED", {"Message": "Exception during lambda execution"}) | |
finally: | |
send_response(event, context, "SUCCESS", {"Message": "See previous logs to see the result of lambda execution."}) | |
def send_response(event, context, response_status, response_data): | |
response_body = json.dumps({"Status": response_status,"Reason": "See the details in CloudWatch Log Stream: " + context.log_stream_name, | |
"PhysicalResourceId": context.log_stream_name,"StackId": event['StackId'],"RequestId": event['RequestId'],"LogicalResourceId": event['LogicalResourceId'], | |
"Data": response_data}) | |
LOGGER.info('ResponseURL: ' + str(event['ResponseURL']) + ' - ResponseBody: ' + str(response_body)) | |
opener = build_opener(HTTPHandler) | |
request = Request(event['ResponseURL'], data=response_body) | |
request.add_header('Content-Type', '') | |
request.add_header('Content-Length', len(response_body)) | |
request.get_method = lambda: 'PUT' | |
response = opener.open(request) | |
LOGGER.info('Status code: ' + str(response.getcode()) + ' Status message: ' + str(response.msg)) | |
def timeout_handler(_signal, _frame): | |
raise Exception('Time exceeded') | |
signal.signal(signal.SIGALRM, timeout_handler) | |
Environment: | |
Variables: | |
StackName: !Ref 'AWS::StackName' | |
Description: Lambda function to create some extra resources (Kinesis Video Stream and Rekognition Collection) | |
FunctionName: lambda_function | |
Handler: index.handler | |
Role: !GetAtt RekognitionVideoIAM.Arn | |
Runtime: python2.7 | |
Timeout: '15' | |
Tags: | |
- Key: OwnedByStack | |
Value: !Ref 'AWS::StackName' | |
LambdaExtraResourcesCfgCaller: | |
Type: Custom::RekognitionVideoBlogLambdaInlineCaller | |
Properties: | |
ServiceToken: !GetAtt LambdaExtraResourcesCfg.Arn | |
S3BucketRekognitionCollection: | |
Type: AWS::S3::Bucket | |
Properties: | |
BucketName: !Ref S3BucketName | |
Outputs: | |
KinesisDataStreamArn: | |
Description: Kinesis Data Stream Arn (used in Stream Processer Input) | |
Value: !GetAtt EventMetadataStream.Arn | |
RekognitionVideoIAM: | |
Description: Rekognition Video Processing IAM Arn (used in Stream Processer Input) | |
Value: !GetAtt RekognitionVideoIAM.Arn | |
S3BucketRekognitionCollection: | |
Description: "Name of S3 bucket" | |
Value: !Join | |
- '' | |
- - 'https://' | |
- !GetAtt | |
- S3BucketRekognitionCollection | |
- DomainName |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment