--- AWSTemplateFormatVersion: "2010-09-09" Description: Amazon Transcribe News Media Analysis (uksb-1potrt4cg) Transform: AWS::Serverless-2016-10-31 Globals: Api: Cors: AllowMethods: "'GET,POST,OPTIONS,DELETE,PUT'" AllowHeaders: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'" AllowOrigin: "'*'" Function: Runtime: nodejs14.x Environment: Variables: VERSION: '1.6' Parameters: MaxTasksCapacity: Description: The maximum number of Fargate Tasks to be allocated Type: Number Default: 10 RetryThreshold: Description: Number of times to retry transcription if an error is encountered Type: Number Default: 3 PublicSubnetIpBlocks: Description: Comma-delimited list of CIDR blocks for the public subnets Type: CommaDelimitedList Default: "10.0.12.0/22, 10.0.16.0/22, 10.0.20.0/22" VpcIpBlock: Description: CIDR block for the VPC Type: String Default: 10.0.0.0/16 AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(1[6-9]|2[0-8]))$ TaskName: Description: Name for the transcriber task family in ECS Type: String Default: transcriber CreateCloudFrontDistribution: Description: Creates a CloudFront distribution for accessing the web interface of the demo. This must be enabled if S3 Block Public Access is enabled at an account level. Type: String Default: "false" AllowedValues: - "true" - "false" Outputs: url: Value: !If - WithCloudFront - !Sub 'https://${CloudFrontDistribution.DomainName}' - !Sub 'https://${WebUIBucket.RegionalDomainName}/index.html' Description: WebUI URL Mappings: Regions: ap-northeast-2: HasThreeAZs: true ap-south-1: HasThreeAZs: true ap-southeast-1: HasThreeAZs: true ap-southeast-2: HasThreeAZs: true ca-central-1: HasThreeAZs: false eu-central-1: HasThreeAZs: true eu-west-1: HasThreeAZs: true eu-west-2: HasThreeAZs: true us-east-1: HasThreeAZs: true us-east-2: HasThreeAZs: true us-west-2: HasThreeAZs: true Conditions: HasThreeAZs: !Equals [!FindInMap [Regions, !Ref "AWS::Region", HasThreeAZs], true] WithCloudFront: !Equals [!Ref CreateCloudFrontDistribution, 'true'] Resources: # Network configuration VPC: Type: AWS::EC2::VPC Properties: EnableDnsSupport: true EnableDnsHostnames: true CidrBlock: !Ref VpcIpBlock Tags: - Key: Name Value: !Sub ${AWS::StackName} VPC GatewayToInternet: Type: AWS::EC2::VPCGatewayAttachment Properties: VpcId: !Ref VPC InternetGatewayId: !Ref InternetGateway InternetGateway: Type: AWS::EC2::InternetGateway PublicNetworkAcl: Type: AWS::EC2::NetworkAcl Properties: VpcId: !Ref VPC PublicNetworkAclEntryInAllowAll: Type: AWS::EC2::NetworkAclEntry Properties: NetworkAclId: !Ref PublicNetworkAcl RuleNumber: 99 Protocol: -1 RuleAction: allow Egress: false CidrBlock: 0.0.0.0/0 PublicNetworkAclEntryOutAllowAll: Type: AWS::EC2::NetworkAclEntry Properties: NetworkAclId: !Ref PublicNetworkAcl RuleNumber: 99 Protocol: -1 RuleAction: allow Egress: true CidrBlock: 0.0.0.0/0 PublicRoute: Type: AWS::EC2::Route DependsOn: GatewayToInternet Properties: RouteTableId: !Ref PublicRouteTable DestinationCidrBlock: 0.0.0.0/0 GatewayId: !Ref InternetGateway PublicRouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref VPC PublicRouteTableAssociation1: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PublicSubnet1 RouteTableId: !Ref PublicRouteTable PublicRouteTableAssociation2: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PublicSubnet2 RouteTableId: !Ref PublicRouteTable PublicRouteTableAssociation3: Type: AWS::EC2::SubnetRouteTableAssociation Condition: HasThreeAZs Properties: SubnetId: !Ref PublicSubnet3 RouteTableId: !Ref PublicRouteTable PublicSubnet1: Type: AWS::EC2::Subnet Properties: VpcId: !Ref VPC CidrBlock: !Select [0, !Ref PublicSubnetIpBlocks] AvailabilityZone: !Select [0, !GetAZs ''] MapPublicIpOnLaunch: true Tags: - Key: Name Value: !Sub ${AWS::StackName} Public Subnet 1 PublicSubnet2: Type: AWS::EC2::Subnet Properties: VpcId: !Ref VPC CidrBlock: !Select [1, !Ref PublicSubnetIpBlocks] AvailabilityZone: !Select [1, !GetAZs ''] MapPublicIpOnLaunch: true Tags: - Key: Name Value: !Sub ${AWS::StackName} Public Subnet 2 PublicSubnet3: Type: AWS::EC2::Subnet Condition: HasThreeAZs Properties: VpcId: !Ref VPC CidrBlock: !Select [2, !Ref PublicSubnetIpBlocks] AvailabilityZone: !Select [2, !GetAZs ''] MapPublicIpOnLaunch: true Tags: - Key: Name Value: !Sub ${AWS::StackName} Public Subnet 3 PublicSubnetNetworkAclAssociation1: Type: AWS::EC2::SubnetNetworkAclAssociation Properties: SubnetId: !Ref PublicSubnet1 NetworkAclId: !Ref PublicNetworkAcl PublicSubnetNetworkAclAssociation2: Type: AWS::EC2::SubnetNetworkAclAssociation Properties: SubnetId: !Ref PublicSubnet2 NetworkAclId: !Ref PublicNetworkAcl PublicSubnetNetworkAclAssociation3: Type: AWS::EC2::SubnetNetworkAclAssociation Condition: HasThreeAZs Properties: SubnetId: !Ref PublicSubnet3 NetworkAclId: !Ref PublicNetworkAcl # Custom Resources LambdaSetup: Type: AWS::Serverless::Function Properties: FunctionName: MediaAnalysisSetup Handler: index.handler CodeUri: ../backend/functions/setup/dist/ Layers: - !Ref AWSSDKLayer Description: Custom Lambda resource for the Media Analysis Cloudformation Stack MemorySize: 256 Environment: Variables: API_GATEWAY: !Sub https://${RestApi}.execute-api.${AWS::Region}.amazonaws.com/PROD BUILD_BUCKET: !Ref CodeBuildArtifactBucket COGNITO_IDENTITY_POOL: !Ref CognitoIdentityPool CREATE_CLOUDFRONT_DISTRIBUTION: !Ref CreateCloudFrontDistribution FROM_BUCKET: !Sub solution-builders-${AWS::Region} MAX_TASKS: !Ref MaxTasksCapacity REGION: !Ref AWS::Region REPOSITORY: !Ref ECRRepository WEBUI_BUCKET: !Ref WebUIBucket Timeout: 900 Policies: - AWSLambdaBasicExecutionRole - Version: "2012-10-17" Statement: - Effect: Allow Action: - 's3:*' - 'ecr:*' Resource: '*' SetupWebUIAndDockerImage: Type: Custom::Setup DependsOn: CodePipeline Properties: ServiceToken: !GetAtt LambdaSetup.Arn Region: !Ref AWS::Region # Computing & Containers AWSSDKLayer: Type: AWS::Serverless::LayerVersion Properties: LayerName: MediaAnalysisAWSSDK Description: Latest confirmed compatible AWS SDK ContentUri: ../backend/functions/layers/aws_sdk/ CompatibleRuntimes: - nodejs12.x - nodejs14.x FPLayer: Type: AWS::Serverless::LayerVersion Properties: LayerName: MediaAnalysisFP Description: Layer containing functional programming libs ContentUri: ../backend/functions/layers/fp/ CompatibleRuntimes: - nodejs12.x - nodejs14.x Orchestrator: Type: AWS::Serverless::Function Properties: Handler: index.handler CodeUri: ../backend/functions/orchestrator/dist/ Timeout: 60 MemorySize: 512 Layers: - !Ref AWSSDKLayer - !Ref FPLayer Policies: - AmazonDynamoDBFullAccess - AmazonECS_FullAccess - AWSLambdaBasicExecutionRole - AWSLambdaInvocation-DynamoDB Environment: Variables: CLUSTER: !Ref ECSCluster RETRY_THRESHOLD: !Ref RetryThreshold SUBNETS: !If - HasThreeAZs - !Sub ${PublicSubnet1},${PublicSubnet2},${PublicSubnet3} - !Sub ${PublicSubnet1},${PublicSubnet2} TASKS_TABLE_NAME: !Ref TasksDynamoTable TASK_NAME: !Ref TaskName Events: Stream: Type: DynamoDB Properties: Stream: !GetAtt TasksDynamoTable.StreamArn BatchSize: 100 StartingPosition: TRIM_HORIZON ECSCluster: Type: AWS::ECS::Cluster ECRRepository: Type: AWS::ECR::Repository Properties: RepositoryName: !Ref TaskName CodeBuildArtifactBucket: Type: AWS::S3::Bucket Properties: VersioningConfiguration: Status: Enabled CodeBuildServiceRole: Type: AWS::IAM::Role Properties: Path: / AssumeRolePolicyDocument: Statement: - Effect: Allow Principal: Service: codebuild.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: root PolicyDocument: Statement: - Resource: "*" Effect: Allow Action: - logs:CreateLogGroup - logs:CreateLogStream - logs:PutLogEvents - ecr:GetAuthorizationToken - Resource: !Sub arn:aws:s3:::${CodeBuildArtifactBucket}/* Effect: Allow Action: - s3:* - Resource: !Sub arn:aws:ecr:${AWS::Region}:${AWS::AccountId}:repository/${ECRRepository} Effect: Allow Action: - ecr:GetDownloadUrlForLayer - ecr:BatchGetImage - ecr:BatchCheckLayerAvailability - ecr:PutImage - ecr:InitiateLayerUpload - ecr:UploadLayerPart - ecr:CompleteLayerUpload CodeBuildProject: Type: AWS::CodeBuild::Project Properties: Artifacts: Type: CODEPIPELINE Source: Type: CODEPIPELINE BuildSpec: | version: 0.2 phases: pre_build: commands: - $(aws ecr get-login --no-include-email) - IMAGE_URI="${REPOSITORY_URI}" build: commands: - docker build --tag "$IMAGE_URI" . post_build: commands: - docker push "$IMAGE_URI" Environment: ComputeType: BUILD_GENERAL1_SMALL Image: aws/codebuild/docker:18.09.0 Type: LINUX_CONTAINER EnvironmentVariables: - Name: AWS_DEFAULT_REGION Value: !Ref AWS::Region - Name: REPOSITORY_URI Value: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${ECRRepository} Name: !Ref AWS::StackName ServiceRole: !Ref CodeBuildServiceRole CodePipelineServiceRole: Type: AWS::IAM::Role Properties: Path: / AssumeRolePolicyDocument: Statement: - Effect: Allow Principal: Service: codepipeline.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: root PolicyDocument: Statement: - Resource: "*" Effect: Allow Action: - ecs:DescribeServices - ecs:DescribeTaskDefinition - ecs:DescribeTasks - ecs:ListTasks - ecs:RegisterTaskDefinition - ecs:UpdateService - codebuild:StartBuild - codebuild:BatchGetBuilds - iam:PassRole - s3:* CodePipeline: Type: AWS::CodePipeline::Pipeline Properties: RoleArn: !GetAtt CodePipelineServiceRole.Arn ArtifactStore: Type: S3 Location: !Ref CodeBuildArtifactBucket Stages: - Name: Source Actions: - Name: App ActionTypeId: Category: Source Owner: AWS Version: "1" Provider: S3 Configuration: S3Bucket: !Ref CodeBuildArtifactBucket S3ObjectKey: build/transcriber.zip OutputArtifacts: - Name: Transcriber RunOrder: 1 - Name: Build Actions: - Name: Build ActionTypeId: Category: Build Owner: AWS Version: "1" Provider: CodeBuild Configuration: ProjectName: !Ref CodeBuildProject InputArtifacts: - Name: Transcriber OutputArtifacts: - Name: BuildOutput RunOrder: 1 EcsTaskExecutionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Statement: - Effect: Allow Principal: Service: - ecs-tasks.amazonaws.com Action: - sts:AssumeRole Path: / ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy TranscriberLogGroup: Type: AWS::Logs::LogGroup Properties: LogGroupName: !Sub /ecs/${TaskName} RetentionInDays: 7 TranscriberTaskIAMRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Statement: - Effect: Allow Principal: Service: ecs-tasks.amazonaws.com Action: sts:AssumeRole Path: / ManagedPolicyArns: - arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess - arn:aws:iam::aws:policy/AmazonTranscribeFullAccess TranscriberTaskDefinition: Type: AWS::ECS::TaskDefinition Properties: TaskRoleArn: !Ref TranscriberTaskIAMRole ExecutionRoleArn: !GetAtt EcsTaskExecutionRole.Arn NetworkMode: awsvpc Memory: '4096' Cpu: '2048' Family: !Ref TaskName RequiresCompatibilities: - FARGATE ContainerDefinitions: - Name: !Ref TaskName Essential: true Image: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${ECRRepository}:latest LogConfiguration: LogDriver: awslogs Options: awslogs-group: !Ref TranscriberLogGroup awslogs-region: !Ref 'AWS::Region' awslogs-stream-prefix: !Ref 'AWS::StackName' Environment: - Name: TRANSCRIPTS_DYNAMO_DB_TABLE Value: !Ref TranscriptDynamoTable - Name: RETRY_THRESHOLD Value: !Ref RetryThreshold - Name: TASKS_DYNAMO_DB_TABLE Value: !Ref TasksDynamoTable - Name: CLUSTER Value: !Ref ECSCluster - Name: AWS_REGION Value: !Ref 'AWS::Region' # Data DbReadRole: Type: AWS::IAM::Role Properties: ManagedPolicyArns: - arn:aws:iam::aws:policy/AmazonDynamoDBReadOnlyAccess AssumeRolePolicyDocument: Statement: - Effect: Allow Principal: Service: - apigateway.amazonaws.com Action: - sts:AssumeRole DbWriteRole: Type: AWS::IAM::Role Properties: ManagedPolicyArns: - arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess AssumeRolePolicyDocument: Statement: - Effect: Allow Principal: Service: - apigateway.amazonaws.com Action: - sts:AssumeRole TasksDynamoTable: Type: AWS::DynamoDB::Table Properties: TableName: MediaAnalysisTasks AttributeDefinitions: - AttributeName: MediaUrl AttributeType: S KeySchema: - AttributeName: MediaUrl KeyType: HASH BillingMode: PAY_PER_REQUEST StreamSpecification: StreamViewType: NEW_IMAGE TranscriptDynamoTable: Type: AWS::DynamoDB::Table Properties: TableName: MediaAnalysisTranscript StreamSpecification: StreamViewType: NEW_IMAGE AttributeDefinitions: - AttributeName: MediaUrl AttributeType: S - AttributeName: ResultId AttributeType: S - AttributeName: FragmentTimestamp AttributeType: N KeySchema: - AttributeName: MediaUrl KeyType: HASH - AttributeName: ResultId KeyType: RANGE BillingMode: PAY_PER_REQUEST LocalSecondaryIndexes: - IndexName: MediaAnalysisLSI KeySchema: - AttributeName: MediaUrl KeyType: HASH - AttributeName: FragmentTimestamp KeyType: RANGE Projection: ProjectionType: ALL # Rest API & Frontend ApiGatewayInvokeRole: Type: AWS::IAM::Role Properties: ManagedPolicyArns: - arn:aws:iam::aws:policy/AmazonAPIGatewayInvokeFullAccess AssumeRolePolicyDocument: Statement: - Effect: Allow Principal: Federated: - cognito-identity.amazonaws.com Action: sts:AssumeRoleWithWebIdentity Condition: StringEquals: "cognito-identity.amazonaws.com:aud": !Ref CognitoIdentityPool ForAnyValue:StringLike: "cognito-identity.amazonaws.com:amr": "unauthenticated" CognitoIdentityPool: Type: AWS::Cognito::IdentityPool Properties: IdentityPoolName: MediaAnalysisIdentityPool AllowUnauthenticatedIdentities: true CognitoIdentityPoolRole: Type: AWS::Cognito::IdentityPoolRoleAttachment Properties: IdentityPoolId: !Ref CognitoIdentityPool Roles: authenticated: !GetAtt ApiGatewayInvokeRole.Arn unauthenticated: !GetAtt ApiGatewayInvokeRole.Arn ApiGatewayCloudWatchRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Statement: - Effect: Allow Principal: Service: - apigateway.amazonaws.com Action: sts:AssumeRole Path: / ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs Account: Type: AWS::ApiGateway::Account Properties: CloudWatchRoleArn: !GetAtt ApiGatewayCloudWatchRole.Arn RestApi: Type: AWS::Serverless::Api Name: MediaAnalysisAPI Properties: StageName: PROD EndpointConfiguration: REGIONAL MethodSettings: - LoggingLevel: INFO ResourcePath: "/*" HttpMethod: "*" DefinitionBody: swagger: 2.0 info: version: 1.0 title: MediaAnalysisAPI basePath: /PROD paths: /poll: get: consumes: - application/json produces: - application/json responses: "200": description: 200 response headers: Access-Control-Allow-Origin: type: string Access-Control-Allow-Headers: type: string "400": description: 400 response headers: Access-Control-Allow-Origin: type: string Access-Control-Allow-Headers: type: string "500": description: 500 response headers: Access-Control-Allow-Origin: type: string Access-Control-Allow-Headers: type: string x-amazon-apigateway-integration: credentials: !GetAtt DbReadRole.Arn uri: !Sub arn:aws:apigateway:${AWS::Region}:dynamodb:action/Query responses: 4\d{2}: statusCode: "400" responseParameters: method.response.header.Access-Control-Allow-Headers: "'Content-Type'" method.response.header.Access-Control-Allow-Origin: "'*'" 5\d{2}: statusCode: "500" responseParameters: method.response.header.Access-Control-Allow-Headers: "'Content-Type'" method.response.header.Access-Control-Allow-Origin: "'*'" "200": statusCode: "200" responseParameters: method.response.header.Access-Control-Allow-Origin: "'*'" method.response.header.Access-Control-Allow-Headers: "'Content-Type'" responseTemplates: application/json: | #set($inputRoot = $input.path('$')) #set($c = 0) { "fragments": [ #foreach($elem in $inputRoot.Items) { #set($c = $c + 1) "timestamp": $elem.FragmentTimestamp.N, "transcript": "$elem.Transcript.S", "isPartial": $elem.IsPartial.BOOL }#if($foreach.hasNext && $c < 1000),#end #end ] } requestTemplates: application/json: !Sub | { "TableName": "${TranscriptDynamoTable}", "IndexName": "MediaAnalysisLSI", "KeyConditionExpression": "MediaUrl = :murl AND FragmentTimestamp BETWEEN :from AND :to", "ProjectionExpression": "FragmentTimestamp,Transcript,IsPartial", "ExpressionAttributeValues": { ":murl": { "S": "$util.urlDecode($input.params().querystring.get('mediaUrl'))" }, ":from": { "N": "$input.params().querystring.get('from')" }, ":to": { "N": "$input.params().querystring.get('to')" } } } passthroughBehavior: when_no_match httpMethod: POST type: aws security: - sigv4: [] /tasks: get: consumes: - application/json produces: - application/json responses: "200": description: 200 response headers: Access-Control-Allow-Origin: type: string Access-Control-Allow-Headers: type: string "400": description: 400 response headers: Access-Control-Allow-Origin: type: string Access-Control-Allow-Headers: type: string "500": description: 500 response headers: Access-Control-Allow-Origin: type: string Access-Control-Allow-Headers: type: string security: - sigv4: [] x-amazon-apigateway-integration: credentials: !GetAtt DbReadRole.Arn uri: !Sub arn:aws:apigateway:${AWS::Region}:dynamodb:action/Scan responses: 4\d{2}: statusCode: "400" responseParameters: method.response.header.Access-Control-Allow-Headers: "'Content-Type'" method.response.header.Access-Control-Allow-Origin: "'*'" "200": statusCode: "200" responseParameters: method.response.header.Access-Control-Allow-Headers: "'Content-Type'" method.response.header.Access-Control-Allow-Origin: "'*'" responseTemplates: application/json: | #set($inputRoot = $input.path('$')) { "tasks": [ #foreach($elem in $inputRoot.Items) { "mediaDescription": "$util.escapeJavaScript($elem.MediaDescription.S).replaceAll("\\'","'")", "mediaTitle": "$util.escapeJavaScript($elem.MediaTitle.S).replaceAll("\\'","'")", "mediaUrl": "$elem.MediaUrl.S", "taskStatus": "$elem.TaskStatus.S", "taskArn": "$elem.TaskArn.S" }#if($foreach.hasNext),#end #end ] } 5\d{2}: statusCode: "500" responseParameters: method.response.header.Access-Control-Allow-Headers: "'Content-Type'" method.response.header.Access-Control-Allow-Origin: "'*'" passthroughBehavior: when_no_match httpMethod: POST requestTemplates: application/json: !Sub | { "TableName": "${TasksDynamoTable}", "ProjectionExpression": "MediaDescription, MediaTitle, MediaUrl, TaskStatus, TaskArn", "ConsistentRead": true } type: aws /tasks/{mediaUrl}: get: consumes: - application/json produces: - application/json responses: "200": description: 200 response headers: Access-Control-Allow-Origin: type: string Access-Control-Allow-Headers: type: string "400": description: 400 response headers: Access-Control-Allow-Origin: type: string Access-Control-Allow-Headers: type: string "500": description: 500 response headers: Access-Control-Allow-Origin: type: string Access-Control-Allow-Headers: type: string x-amazon-apigateway-integration: credentials: !GetAtt DbWriteRole.Arn uri: !Sub arn:aws:apigateway:${AWS::Region}:dynamodb:action/Query responses: 4\d{2}: statusCode: "400" responseParameters: method.response.header.Access-Control-Allow-Headers: "'Content-Type'" method.response.header.Access-Control-Allow-Origin: "'*'" 5\d{2}: statusCode: "500" responseParameters: method.response.header.Access-Control-Allow-Headers: "'Content-Type'" method.response.header.Access-Control-Allow-Origin: "'*'" "200": statusCode: "200" responseParameters: method.response.header.Access-Control-Allow-Origin: "'*'" method.response.header.Access-Control-Allow-Headers: "'Content-Type'" responseTemplates: application/json: | #set($inputRoot = $input.path('$')) #foreach($elem in $inputRoot.Items) { "mediaDescription": "$util.escapeJavaScript($elem.MediaDescription.S).replaceAll("\\'","'")", "mediaTitle": "$util.escapeJavaScript($elem.MediaTitle.S).replaceAll("\\'","'")", "mediaUrl": "$elem.MediaUrl.S", "taskStatus": "$elem.TaskStatus.S", "taskArn": "$elem.TaskArn.S" } #end requestTemplates: application/json: !Sub | { "TableName": "${TasksDynamoTable}", "KeyConditionExpression": "MediaUrl = :murl", "ExpressionAttributeValues": { ":murl": { "S": "$util.urlDecode($input.params('mediaUrl'))" } }, "ConsistentRead": true } passthroughBehavior: when_no_match httpMethod: POST type: aws security: - sigv4: [] delete: consumes: - application/json produces: - application/json responses: "200": description: 200 response headers: Access-Control-Allow-Origin: type: string Access-Control-Allow-Headers: type: string "400": description: 400 response headers: Access-Control-Allow-Origin: type: string Access-Control-Allow-Headers: type: string "500": description: 500 response headers: Access-Control-Allow-Origin: type: string Access-Control-Allow-Headers: type: string x-amazon-apigateway-integration: credentials: !GetAtt DbWriteRole.Arn uri: !Sub arn:aws:apigateway:${AWS::Region}:dynamodb:action/UpdateItem responses: 4\d{2}: statusCode: "400" responseParameters: method.response.header.Access-Control-Allow-Headers: "'Content-Type'" method.response.header.Access-Control-Allow-Origin: "'*'" 5\d{2}: statusCode: "500" responseParameters: method.response.header.Access-Control-Allow-Headers: "'Content-Type'" method.response.header.Access-Control-Allow-Origin: "'*'" "200": statusCode: "200" responseParameters: method.response.header.Access-Control-Allow-Origin: "'*'" method.response.header.Access-Control-Allow-Headers: "'Content-Type'" responseTemplates: application/json: | { "ok": true } requestTemplates: application/json: !Sub | { "TableName": "${TasksDynamoTable}", "Key": { "MediaUrl": { "S": "$util.urlDecode($input.params('mediaUrl'))" } }, "UpdateExpression": "set TaskStatus = :newStatus", "ExpressionAttributeValues": { ":newStatus": { "S": "TERMINATING" } } } passthroughBehavior: when_no_match httpMethod: POST type: aws security: - sigv4: [] put: consumes: - application/json produces: - application/json responses: "200": description: 200 response headers: Access-Control-Allow-Origin: type: string Access-Control-Allow-Headers: type: string "400": description: 400 response headers: Access-Control-Allow-Origin: type: string Access-Control-Allow-Headers: type: string "500": description: 500 response headers: Access-Control-Allow-Origin: type: string Access-Control-Allow-Headers: type: string x-amazon-apigateway-integration: credentials: !GetAtt DbWriteRole.Arn uri: !Sub arn:aws:apigateway:${AWS::Region}:dynamodb:action/PutItem responses: 4\d{2}: statusCode: "400" responseParameters: method.response.header.Access-Control-Allow-Headers: "'Content-Type'" method.response.header.Access-Control-Allow-Origin: "'*'" 5\d{2}: statusCode: "500" responseParameters: method.response.header.Access-Control-Allow-Headers: "'Content-Type'" method.response.header.Access-Control-Allow-Origin: "'*'" "200": statusCode: "200" responseParameters: method.response.header.Access-Control-Allow-Origin: "'*'" method.response.header.Access-Control-Allow-Headers: "'Content-Type'" responseTemplates: application/json: | { "ok": true } requestTemplates: application/json: !Sub | #set($inputRoot = $input.path('$')) #set($mediaTitle = $inputRoot.mediaTitle) #set($mediaDescription = $inputRoot.mediaDescription) { "TableName": "${TasksDynamoTable}", "Item": { #if("$mediaDescription" != "") "MediaDescription": { "S": "$mediaDescription" }, #end "MediaUrl": { "S": "$util.urlDecode($input.params('mediaUrl'))" }, "MediaTitle": { "S": "$mediaTitle" }, "TaskStatus": { "S": "WAITING" } } } passthroughBehavior: when_no_match httpMethod: POST type: aws security: - sigv4: [] securityDefinitions: sigv4: type: apiKey name: Authorization in: header x-amazon-apigateway-authtype: awsSigv4 DependsOn: - Account WebUIBucket: Type: AWS::S3::Bucket Properties: CorsConfiguration: CorsRules: - AllowedHeaders: ['*'] AllowedMethods: [GET] AllowedOrigins: ['*'] Id: MediaAnalysisCorsRule MaxAge: 3600 WebUIBucketReadPolicy: Type: AWS::S3::BucketPolicy Condition: WithCloudFront Properties: Bucket: !Ref WebUIBucket PolicyDocument: Statement: - Action: s3:GetObject Effect: Allow Resource: !Sub arn:aws:s3:::${WebUIBucket}/* Principal: CanonicalUser: !GetAtt CloudFrontOriginAccessIdentity.S3CanonicalUserId CloudFrontOriginAccessIdentity: Type: AWS::CloudFront::CloudFrontOriginAccessIdentity Condition: WithCloudFront Properties: CloudFrontOriginAccessIdentityConfig: Comment: !Ref WebUIBucket CloudFrontDistribution: Type: AWS::CloudFront::Distribution Condition: WithCloudFront Properties: DistributionConfig: Origins: - DomainName: !GetAtt WebUIBucket.RegionalDomainName Id: myS3Origin-MediaAnalysis S3OriginConfig: OriginAccessIdentity: !Sub origin-access-identity/cloudfront/${CloudFrontOriginAccessIdentity} Enabled: true HttpVersion: http2 Comment: The Distribution for the Media Analysis Web UI DefaultRootObject: index.html DefaultCacheBehavior: AllowedMethods: - HEAD - GET - OPTIONS TargetOriginId: myS3Origin-MediaAnalysis ForwardedValues: QueryString: false Cookies: Forward: none ViewerProtocolPolicy: redirect-to-https PriceClass: PriceClass_All ViewerCertificate: CloudFrontDefaultCertificate: true