diff --git a/provider-utils/awscloudformation/cloudformation-templates/ivs-helpers/IvsSnsSetup.template.ejs b/provider-utils/awscloudformation/cloudformation-templates/ivs-helpers/IvsSnsSetup.template.ejs new file mode 100644 index 00000000..9e1b4082 --- /dev/null +++ b/provider-utils/awscloudformation/cloudformation-templates/ivs-helpers/IvsSnsSetup.template.ejs @@ -0,0 +1,134 @@ +Description: + "IVS notifications sent to SNS and then forwarded to Lambda" + +Parameters: + env: + Type: String + Description: The environment name. e.g. Dev, Test, or Production. + Default: NONE + pS3: + Type: String + Description: Store template and lambda package + AllowedPattern: "[a-zA-Z][a-zA-Z0-9-_]*" + Default: amazonbooth + pSourceFolder: + Type: String + Description: Store template and lambda package + AllowedPattern: "[a-zA-Z][a-zA-Z0-9-_]*" + Default: vod-helpers + pSnsTopicName: + Type: String + Description: Topic Name + Default: default + pFunctionName: + Type: String + Description: Function Name + Default: IvsChannelStatusLambda + pFunctionHash: + Type: String + Description: FunctionHash + Default: default + +Resources: + + IvsNotificationsSNS: + Type: AWS::SNS::Topic + + IvsEventsRule: + Type: AWS::Events::Rule + Properties: + Description: "Event rule for IVS" + EventPattern: + source: + - aws.ivs + detail-type: + - "IVS Stream State Change" + State: ENABLED + Targets: + - Arn: !Ref IvsNotificationsSNS + Id: IvsNotificationsSNS + + rIvsNotificationsSNSPolicy: + Type: 'AWS::SNS::TopicPolicy' + Properties: + Topics: + - !Ref IvsNotificationsSNS + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Sid: "1" + Action: 'sns:Publish' + Resource: !Ref IvsNotificationsSNS + Principal: + AWS: '*' + Condition: + ArnLike: + AWS:SourceArn: !Sub 'arn:aws:*:*:${AWS::AccountId}:*' + - Effect: Allow + Sid: "2" + Action: "sns:Publish" + Principal: + Service : 'events.amazonaws.com' + Resource: !Ref IvsNotificationsSNS + +<% if (props.status.lambda) { -%> + rIvsStatusLambda: + Type: AWS::Lambda::Function + Properties: + FunctionName: !Ref pFunctionName + Description: Invoked on IVS status events + Handler: index.handler + Role: !GetAtt rIvsStatusLambdaRole.Arn + Runtime: nodejs14.x + Timeout: 30 + Code: + S3Bucket: !Ref pS3 + S3Key: !Sub + - ivs-helpers/IvsStatusLambda-${hash}.zip + - { hash: !Ref pFunctionHash } + + rIvsStatusLambdaRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - + Effect: Allow + Principal: + Service: + - lambda.amazonaws.com + Action: + - sts:AssumeRole + Policies: + - + PolicyName: !Sub "${AWS::AccountId}-ivs-status-processing-role" + PolicyDocument: + Statement: + - + Effect: Allow + Action: + - logs:CreateLogGroup + - logs:CreateLogStream + - logs:DescribeLogStreams + - logs:PutLogEvents + Resource: + - arn:aws:logs:*:*:* + + rSNSLambdaPermissions: + Type: AWS::Lambda::Permission + Properties: + FunctionName: !Ref rIvsStatusLambda + Action: lambda:InvokeFunction + Principal: sns.amazonaws.com + SourceArn: !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:* + + rSNSLambdaSubscription: + Type: AWS::SNS::Subscription + Properties: + Protocol: lambda + Endpoint: !GetAtt rIvsStatusLambda.Arn + TopicArn: !Ref IvsNotificationsSNS + +<% } -%> \ No newline at end of file diff --git a/provider-utils/awscloudformation/cloudformation-templates/ivs-helpers/LambdaFunctions/IvsStatusLambda/index.js b/provider-utils/awscloudformation/cloudformation-templates/ivs-helpers/LambdaFunctions/IvsStatusLambda/index.js new file mode 100644 index 00000000..3a897256 --- /dev/null +++ b/provider-utils/awscloudformation/cloudformation-templates/ivs-helpers/LambdaFunctions/IvsStatusLambda/index.js @@ -0,0 +1,9 @@ +console.log('Loading function'); + +exports.handler = async (event, context) => { + // console.log('Received event:', JSON.stringify(event, null, 2)); + const message = event.Records[0].Sns.Message; + console.log('From SNS:', message); + console.log('context ', context); + return message; +}; diff --git a/provider-utils/awscloudformation/cloudformation-templates/ivs-workflow-template.yaml.ejs b/provider-utils/awscloudformation/cloudformation-templates/ivs-workflow-template.yaml.ejs index 356b38c2..671fb6b8 100644 --- a/provider-utils/awscloudformation/cloudformation-templates/ivs-workflow-template.yaml.ejs +++ b/provider-utils/awscloudformation/cloudformation-templates/ivs-workflow-template.yaml.ejs @@ -28,6 +28,14 @@ Parameters: Type: String Description: Quality of channel Default: <%= props.channel.channelQuality %> +<% if (props.status.reportStatus) { -%> + pSnsTopicName: + Type: String + Description: Name of the SNS topic for MediaConvert status events + AllowedPattern: "[a-zA-Z][a-zA-Z0-9-_]*" + Default: AmplifyIvsChannelEvents +<% } -%> + Conditions: HasEnvironmentParameter: @@ -50,6 +58,36 @@ Resources: pLatencyMode: !Ref pLatencyMode pQuality: !Ref pQuality + + +<% if (props.status.reportStatus) { -%> + rSnsInvokeLambdaSetup: + Type: AWS::CloudFormation::Stack + Properties: + TemplateURL: !Sub "https://s3.amazonaws.com/${pS3}/${pSourceFolder}/IvsSnsSetup.template" + Parameters: + env: !Ref env + pS3: !Ref pS3 + pSourceFolder: !Ref pSourceFolder + pSnsTopicName: !Ref pSnsTopicName + pFunctionHash: "<%= props.hashes.IvsStatusLambda %>" + pFunctionName: + !If + - HasEnvironmentParameter + - !Join + - '-' + - - !Ref pProjectName + - !Ref env + - 'IvsChannelStatusLambda' + - !Join + - '-' + - - !Ref pProjectName + - 'IvsChannelStatusLambda' + +<% } -%> + + + Outputs: oVideoOutput: Value: !GetAtt rIVSChannel.Outputs.oVideoOutput diff --git a/provider-utils/awscloudformation/service-walkthroughs/ivs-push.js b/provider-utils/awscloudformation/service-walkthroughs/ivs-push.js index 663e0802..4a2b5198 100644 --- a/provider-utils/awscloudformation/service-walkthroughs/ivs-push.js +++ b/provider-utils/awscloudformation/service-walkthroughs/ivs-push.js @@ -79,8 +79,53 @@ async function serviceQuestions(context, options, defaultValuesFilename, resourc }, ]; + const statusReporting = [ + { + type: question.reportStatus.type, + name: question.reportStatus.key, + message: question.reportStatus.question, + choices: question.reportStatus.options, + default: question.reportStatus.default, + when(answers) { + return headlessMode.autoAnswer({ + context, + answers, + key: question.reportStatus.key, + value: args.reportStatus ? args.reportStatus : question.reportStatus.default, + }); + }, + }, + ]; + + const statusLambda = [ + { + type: question.reportStatusLambda.type, + name: question.reportStatusLambda.key, + message: question.reportStatusLambda.question, + choices: question.reportStatusLambda.options, + default: question.reportStatusLambda.default, + when(answers) { + return headlessMode.autoAnswer({ + context, + answers, + key: question.reportStatusLambda.key, + value: args.reportStatusLambda + ? args.reportStatusLambda : question.reportStatusLambda.default, + }); + }, + }, + ]; + const channelQuestions = await inquirer.prompt(createChannel); props.channel = channelQuestions; + const statusQuestions = await inquirer.prompt(statusReporting); + props.status = statusQuestions; + + if (statusQuestions.reportStatus === true) { + const statusQuestionsLambda = await inquirer.prompt(statusLambda); + props.status.lambda = statusQuestionsLambda.reportStatusLambda; + } + return props; } diff --git a/provider-utils/ivs-questions.json b/provider-utils/ivs-questions.json index e6ffae73..3871508d 100644 --- a/provider-utils/ivs-questions.json +++ b/provider-utils/ivs-questions.json @@ -46,5 +46,29 @@ ], "default": "LOW", "required": true + }, + "reportStatus" : { + "key": "reportStatus", + "question": "Report status of the channel", + "type": "confirm", + "default": "true", + "required": true, + "options" : [ + { + "value" : "true", + "next" : "reportStatusLambda" + }, + { + "value" : "false", + "next" : "" + } + ] + }, + "reportStatusLambda" : { + "key": "reportStatusLambda", + "question": "Invoke Lambda on channel status event", + "type": "confirm", + "default": "true", + "required": true } }