AWSTemplateFormatVersion: '2010-09-09' Description: Master template for Data Engineering Immersion Day # Note - if running this manually in Isengard, you can specify arbitrary values # for all of the parameters *except* the module Id and version which are needed # to pull assets properly from S3, *and* you should leave the SNS topic blank # so that, based on Conditions in this template, CloudFormation will avoid # subscribing resources to a non-existent SNS topic. Parameters: EEAPIBase: Description: "The Event Engine API Base URL in the form https://hostname:port/api" Type: String EEAPIToken: Description: "The API Token that the module uses for authentication" Type: String EEEventId: Description: "ID of the specific Event" Type: String EELifecycleTopicArn: Description: "ARN of the Event Engine Module lifecycle SNS topic" Type: String EEModuleId: Description: "Your module ID" Type: String EEModuleVersion: Description: "Your Module Version" Type: String EEAssetsBucket: Description: "Artifacts Bucket" Type: String Conditions: # Used to optionally exclude the subscription of Lambda functions (or other # resources) to the lifecycle SNS topic if its not provided in the template # to avoid deployment errors. This is specifically aimed at making it easier # to deploy your template in Isengard (outside of EE) for test/debug. LifecycleTopicIsNotEmpty: Fn::Not: - !Equals [!Ref EELifecycleTopicArn, ""] Resources: #------------------------------------------------------------------------------# # FUNCTION TO HANDLE EVENT LIFECYCLE EVENTS RECEIVED ON SNS TOPIC #------------------------------------------------------------------------------# HandleMasterLifecycleEventsFunction: Type: AWS::Lambda::Function Properties: Code: ZipFile: | import json import boto3 import os import traceback client = boto3.client('lambda') send_team_output_to_event_engine_function = os.environ['SEND_TEAM_OUTPUT_TO_EVENT_ENGINE_FUNCTION'] print('Loading function') def handler(event, context): try: print(event) message = json.loads(event['Records'][0]['Sns']['Message']) event_type = event['Records'][0]['Sns']['MessageAttributes']['event']['Value'] event_id = message['event-id'] module_id = message['module-id'] team_id = message['team-id'] cfn_outputs = {} if 'cfn-outputs' in message: cfn_outputs = message['cfn-outputs'] if event_type == 'eventengine:MODULE_DEPLOYING': print('Module {} for event {} for team {} is deploying...' .format(module_id, event_id, team_id) ) print('Nothing to do!') elif event_type == 'eventengine:MODULE_DEPLOYED': print('Module {} for event {} for team {} is deployed...' .format(module_id, event_id, team_id) ) if (len(cfn_outputs) == 0): print('Team module has no CloudFormation outputs to send to EE') else: send_initial_team_outputs_to_ee(team_id, cfn_outputs) if event_type == 'eventengine:MODULE_UNDEPLOYING': print('Module {} for event {} for team {} is undeploying...' .format(module_id, event_id, team_id) ) print('Nothing to do!') if event_type == 'eventengine:MODULE_UNDEPLOYED': print('Module {} for event {} for team {} is undeployed...' .format(module_id, event_id, team_id) ) print('Nothing to do!') else: print('Module {} for event {} for team {} has unhandled event {}...' .format(module_id, event_id, team_id, event_type) ) return { "status": 200, "message": "Done!" } except Exception as e: msg = "Unexpected error: {}".format(e) print(msg) traceback.print_exc() return { "status": 500, "message": msg } def send_initial_team_outputs_to_ee(team_id, cfn_outputs): print ('Sending team CloudFormation Outputs to Event Engine...') for key, value in cfn_outputs.items(): payload = { "team_id": team_id, "output_key": key, "output_value": value } print('Invoking Lambda {} with payload:\n'.format(send_team_output_to_event_engine_function) + json.dumps(payload, indent=2) ) response = client.invoke( FunctionName=send_team_output_to_event_engine_function, InvocationType='RequestResponse', Payload=json.dumps(payload), ) response_payload = json.loads(response['Payload'].read()) print('Invoke response status {}:\n{}'.format( response['StatusCode'], json.dumps(response_payload,indent=2) )) Environment: Variables: API_BASE: !Ref EEAPIBase API_TOKEN: !Ref EEAPIToken EVENT_REGION: !Ref AWS::Region EVENT_ID: !Ref EEEventId MODULE_ID: !Ref EEModuleId SEND_TEAM_OUTPUT_TO_EVENT_ENGINE_FUNCTION: !Ref SendTeamOutputToEventEngineFunction Handler: index.handler Role: !GetAtt HandleMasterLifecycleEventsRole.Arn Runtime: python3.7 Timeout: 120 HandleMasterLifecycleEventsSnsSubscription: Type: AWS::SNS::Subscription Condition: LifecycleTopicIsNotEmpty Properties: Endpoint: !GetAtt HandleMasterLifecycleEventsFunction.Arn Protocol: lambda TopicArn: !Ref EELifecycleTopicArn HandleMasterLifecycleEventsPermissionForSns: Type: AWS::Lambda::Permission Condition: LifecycleTopicIsNotEmpty Properties: Action: lambda:InvokeFunction Principal: sns.amazonaws.com SourceArn: !Ref EELifecycleTopicArn FunctionName: !Ref HandleMasterLifecycleEventsFunction HandleMasterLifecycleEventsRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - sts:AssumeRole Path: "/" Policies: - PolicyName: root PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - logs:* Resource: arn:aws:logs:*:*:* - Effect: Allow Action: - lambda:invokeFunction Resource: arn:aws:lambda:*:*:* #------------------------------------------------------------------------------# # FUNCTION TO SEND OUTPUTS TO EVENT ENGINE VIA AN API CALL #------------------------------------------------------------------------------# SendTeamOutputToEventEngineFunction: Type: AWS::Lambda::Function Properties: Code: S3Bucket: !Ref EEAssetsBucket S3Key: !Sub "modules/${EEModuleId}/v${EEModuleVersion}/lambda/send-team-output-to-event-engine.zip" Description: Sends team outputs to Event Engine. Environment: Variables: API_BASE: !Ref EEAPIBase API_TOKEN: !Ref EEAPIToken EVENT_REGION: !Ref AWS::Region EVENT_ID: !Ref EEEventId MODULE_ID: !Ref EEModuleId Handler: index.handler Role: !GetAtt SendOutputToEventEngineRole.Arn Runtime: python3.7 Timeout: 120 SendOutputToEventEngineRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - sts:AssumeRole Path: "/" Policies: - PolicyName: root PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - logs:* Resource: arn:aws:logs:*:*:*