AWSTemplateFormatVersion: "2010-09-09"

Description: Media2Cloud Backend Stack - create backend resources such as Amazon DynamoDB tables, AWS Step Functions, AWS Lambda functions and layers, Amazon EventBridge, Amazon CloudWatch Events, Amazon SNS, Amazon S3 notification, and so forth. Version %%VERSION%%

Mappings:
    Workflow:
        # Backlog workflow
        BacklogStatusUpdater:
            Package: "%%PKG_BACKLOG_STATUS_UPDATER%%"
            Name: backlog-status-updater
        BacklogStreamConnector:
            Package: "%%PKG_BACKLOG_STREAM_CONNECTOR%%"
            Name: backlog-stream-connector
        BacklogCustomLabels:
            Package: "%%PKG_BACKLOG_CUSTOMLABELS%%"
            Name: backlog-custom-labels
        # Ingest workflow
        IngestFixity:
            Package: "%%PKG_INGEST_FIXITY%%"
            Name: ingest-fixity
        IngestVideo:
            Package: "%%PKG_INGEST_VIDEO%%"
            Name: ingest-video
        IngestAudio:
            Package: "%%PKG_INGEST_AUDIO%%"
            Name: ingest-audio
        IngestImage:
            Package: "%%PKG_INGEST_IMAGE%%"
            Name: ingest-image
        IngestDocument:
            Package: "%%PKG_INGEST_DOCUMENT%%"
            Name: ingest-document
        IngestMain:
            Package: "%%PKG_INGEST_MAIN%%"
            Name: ingest-main
        IngestStatusUpdater:
            Package: "%%PKG_INGEST_STATUS_UPDATER%%"
            Name: ingest-status-updater
        # Analysis workflow
        AnalysisVideo:
            Package: "%%PKG_ANALYSIS_VIDEO%%"
            Name: analysis-video
        AnalysisAudio:
            Package: "%%PKG_ANALYSIS_AUDIO%%"
            Name: analysis-audio
        AnalysisImage:
            Package: "%%PKG_ANALYSIS_IMAGE%%"
            Name: analysis-image
        # Docker image containerized lambda
        BlipModel:
            Name: blip-model
        AnalysisDocument:
            Package: "%%PKG_ANALYSIS_DOCUMENT%%"
            Name: analysis-document
        AnalysisMain:
            Package: "%%PKG_ANALYSIS_MAIN%%"
            Name: analysis-main
        AnalysisStatusUpdater:
            Package: "%%PKG_ANALYSIS_STATUS_UPDATER%%"
            Name: analysis-status-updater
        # Main Workflow
        Main:
            Name: main
        StateMachineErrorHandler:
            Package: "%%PKG_ERROR_HANDLER%%"
            Name: main-error-handler
        IngestS3Event:
            Package: "%%PKG_MAIN_S3EVENT%%"
            Name: main-s3event
    Layer:
        MediaInfoLib:
            Package: "%%LAYER_MEDIAINFO%%"
            Name: mediainfo
        ImageProcessLib:
            Package: "%%LAYER_IMAGE_PROCESS%%"
            Name: image-process-lib
        FixityLib:
            Package: "%%LAYER_FIXITY_LIB%%"
            Name: fixity-lib
        PDFLib:
            Package: "%%LAYER_PDF_LIB%%"
            Name: pdf-lib
        CanvasLib:
            Package: "%%LAYER_CANVAS_LIB%%"
            Name: canvas-lib
        BacklogLib:
            Package: "%%LAYER_BACKLOG%%"
            Name: service-backlog-lib
    DynamoDB:
        Backlog:
            Suffix: service-backlog
            PartitionKey: id
            SortKey: serviceApi
            # status-timestamp global secondary index
            GSIStatusIndex: gsi-status-timestamp
            GSIStatusPartitionKey: status
            GSIStatusSortKey: timestamp
            # jobId global secondary index
            GSIJobIdIndex: gsi-jobId
            GSIJobIdPartitionKey: jobId
        AtomicLock:
            Suffix: atomic-lock
            PartitionKey: lockId
        ServiceToken:
            Suffix: service-token
            PartitionKey: uuid
            SortKey: keyword
        Ingest:
            Suffix: ingest
            PartitionKey: uuid
            # schemaversion-timestamp global secondary index
            SchemaVersionIndexName: gsi-schemaversion-timestamp
            SchemaVersionIndexKey: schemaVersion
            GSISortKey: timestamp
            # type-timestamp global secondary index
            TypeIndexName: gsi-type-timestamp
            TypeIndexKey: type
            # group-timestamp global secondary index
            GroupIndexName: gsi-group-timestamp
            GroupIndexKey: group
            # overallstatus-timestamp global secondary index
            StatusIndexName: gsi-overallstatus-timestamp
            StatusIndexKey: overallStatus
        Analysis:
            Suffix: aiml
            PartitionKey: uuid
            SortKey: type
    EventBridge:
        Bus:
            Name: service-backlog
        Rule:
            Name: StatusChange
            Detail: Service Backlog Status Change
            Source: custom.servicebacklog
    SNS:
        Topic:
            Name: status
            DisplayName: M2CStatus
        BacklogTopic:
            Name: service-backlog-topic
            DisplayName: BacklogSNS
    Node:
        Runtime:
            Version: nodejs14.x

Parameters:
    S3Bucket:
        Type: String
        Description: S3Bucket
    KeyPrefix:
        Type: String
        Description: KeyPrefix
    SolutionId:
        Type: String
        Description: SolutionId
    SolutionLowerCaseId:
        Type: String
        Description: SolutionLowerCaseId
    ResourcePrefix:
        Type: String
        Description: ResourcePrefix
    CustomUserAgent:
        Type: String
        Description: CustomUserAgent
    AnonymousUsage:
        Type: String
        Description: AnonymousUsage
    CustomResourcesLambdaArn:
        Type: String
        Description: CustomResourcesLambdaArn
    CustomResourcesRoleName:
        Type: String
        Description: CustomResourcesRoleName
    AwsSdkLayer:
        Type: String
        Description: AwsSdkLayer
    CoreLibLayer:
        Type: String
        Description: CoreLibLayer
    SolutionUuid:
        Type: String
        Description: SolutionUuid
    IotHost:
        Type: String
        Description: IotHost
    IotTopic:
        Type: String
        Description: IotTopic
    IngestBucket:
        Type: String
        Description: IngestBucket
    ProxyBucket:
        Type: String
        Description: ProxyBucket
    OpenSearchDomainName:
        Type: String
        Description: OpenSearchDomainName
    OpenSearchDomainEndpoint:
        Type: String
        Description: OpenSearchDomainEndpoint
    DefaultAIOptions:
        Type: String
        Description: DefaultAIOptions
    DefaultMinConfidence:
        Type: String
        Description: DefaultMinConfidence
    MediaConvertEndpoint:
        Type: String
        Description: MediaConvertEndpoint
    StartOnObjectCreation:
        Type: String
        Description: StartOnObjectCreation
    AIOptionsS3Key:
        Type: String
        Description: AIOptionsS3Key
    BlipImageArn:
        Type: String
        Description: BlipImageArn

Conditions:
    bStartOnObjectCreation: !Equals
        - !Ref StartOnObjectCreation
        - "YES"
    bBlipImageArn: !Not
        - !Equals
            - !Ref BlipImageArn
            - ""

Resources:
    ################################################################################
    #
    # Lambda Layers for backend
    # * MediaInfoLayer
    # * ImageProcessLayer
    # * FixityLibLayer
    # * PDFLibLayer
    # * CanvasLibLayer
    # * BacklogLayer
    #
    ################################################################################
    MediaInfoLayer:
        Type: AWS::Lambda::LayerVersion
        Properties:
            LayerName: !Sub
                - ${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Layer
                    - MediaInfoLib
                    - Name
            CompatibleRuntimes:
                - !FindInMap
                    - Node
                    - Runtime
                    - Version
            Content:
                S3Bucket: !Ref S3Bucket
                S3Key: !Sub
                    - ${KeyPrefix}/${package}
                    - package: !FindInMap
                        - Layer
                        - MediaInfoLib
                        - Package
            Description: !Sub (${SolutionLowerCaseId}) mediainfo library layer
            LicenseInfo: Apache-2.0

    ImageProcessLayer:
        Type: AWS::Lambda::LayerVersion
        Properties:
            LayerName: !Sub
                - ${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Layer
                    - ImageProcessLib
                    - Name
            CompatibleRuntimes:
                - !FindInMap
                    - Node
                    - Runtime
                    - Version
            Content:
                S3Bucket: !Ref S3Bucket
                S3Key: !Sub
                    - ${KeyPrefix}/${package}
                    - package: !FindInMap
                        - Layer
                        - ImageProcessLib
                        - Package
            Description: !Sub (${SolutionLowerCaseId}) image process library layer (EXIFtool and JIMP)
            LicenseInfo: Apache-2.0

    FixityLibLayer:
        Type: AWS::Lambda::LayerVersion
        Properties:
            LayerName: !Sub
                - ${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Layer
                    - FixityLib
                    - Name
            CompatibleRuntimes:
                - !FindInMap
                    - Node
                    - Runtime
                    - Version
            Content:
                S3Bucket: !Ref S3Bucket
                S3Key: !Sub
                    - ${KeyPrefix}/${package}
                    - package: !FindInMap
                        - Layer
                        - FixityLib
                        - Package
            Description: !Sub (${SolutionLowerCaseId}) fixity library layer (SPARK and RUSHA)
            LicenseInfo: Apache-2.0

    PDFLibLayer:
        Type: AWS::Lambda::LayerVersion
        Properties:
            LayerName: !Sub
                - ${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Layer
                    - PDFLib
                    - Name
            CompatibleRuntimes:
                - !FindInMap
                    - Node
                    - Runtime
                    - Version
            Content:
                S3Bucket: !Ref S3Bucket
                S3Key: !Sub
                    - ${KeyPrefix}/${package}
                    - package: !FindInMap
                        - Layer
                        - PDFLib
                        - Package
            Description: !Sub (${SolutionLowerCaseId}) pdf library layer (PDF.JS)
            LicenseInfo: Apache-2.0

    CanvasLibLayer:
        Type: AWS::Lambda::LayerVersion
        Properties:
            LayerName: !Sub
                - ${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Layer
                    - CanvasLib
                    - Name
            CompatibleRuntimes:
                - !FindInMap
                    - Node
                    - Runtime
                    - Version
            Content:
                S3Bucket: !Ref S3Bucket
                S3Key: !Sub
                    - ${KeyPrefix}/${package}
                    - package: !FindInMap
                        - Layer
                        - CanvasLib
                        - Package
            Description: !Sub (${SolutionLowerCaseId}) nodeJS canvas layer
            LicenseInfo: Apache-2.0

    BacklogLibLayer:
        Type: AWS::Lambda::LayerVersion
        Properties:
            LayerName: !Sub
                - ${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Layer
                    - BacklogLib
                    - Name
            CompatibleRuntimes:
                - !FindInMap
                    - Node
                    - Runtime
                    - Version
            Content:
                S3Bucket: !Ref S3Bucket
                S3Key: !Sub
                    - ${KeyPrefix}/${package}
                    - package: !FindInMap
                        - Layer
                        - BacklogLib
                        - Package
            Description: !Sub (${SolutionLowerCaseId}) service backlog queue management library layer
            LicenseInfo: Apache-2.0

    ################################################################################
    #
    # Amazon DynamoDB tables
    # * BacklogTable: Queue service requests
    # * AtomicLockTable: Serialize service request, used by Custom Labels workflow
    # * IngestTable: Stores ingested metadata
    # * ServiceTokenTable: Part of the Service Integration, temporarily stores
    #   Step Functions execution token to resume
    # * AnalysisTable: Stores AI/ML metadata (pointers to S3 locations)
    #
    ################################################################################
    BacklogTable:
        Type: AWS::DynamoDB::Table
        Metadata:
            cfn_nag:
                rules_to_suppress:
                    -
                        id: W28
                        reason: Tables are explicitly assigned with predefined names
        Properties:
            TableName: !Sub
                - ${ResourcePrefix}-${suffix}
                - suffix: !FindInMap
                    - DynamoDB
                    - Backlog
                    - Suffix
            BillingMode: PAY_PER_REQUEST
            AttributeDefinitions:
                -
                    AttributeName: !FindInMap
                        - DynamoDB
                        - Backlog
                        - PartitionKey
                    AttributeType: S
                -
                    AttributeName: !FindInMap
                        - DynamoDB
                        - Backlog
                        - SortKey
                    AttributeType: S
                -
                    AttributeName: !FindInMap
                        - DynamoDB
                        - Backlog
                        - GSIStatusPartitionKey
                    AttributeType: S
                -
                    AttributeName: !FindInMap
                        - DynamoDB
                        - Backlog
                        - GSIStatusSortKey
                    AttributeType: N
                -
                    AttributeName: !FindInMap
                        - DynamoDB
                        - Backlog
                        - GSIJobIdPartitionKey
                    AttributeType: S
            KeySchema:
                -
                    AttributeName: !FindInMap
                        - DynamoDB
                        - Backlog
                        - PartitionKey
                    KeyType: HASH
                -
                    AttributeName: !FindInMap
                        - DynamoDB
                        - Backlog
                        - SortKey
                    KeyType: RANGE
            SSESpecification:
                SSEEnabled: true
            PointInTimeRecoverySpecification:
                PointInTimeRecoveryEnabled: true
            TimeToLiveSpecification:
                AttributeName: ttl
                Enabled: true
            GlobalSecondaryIndexes:
                -
                    IndexName: !FindInMap
                        - DynamoDB
                        - Backlog
                        - GSIStatusIndex
                    KeySchema:
                        -
                            AttributeName: !FindInMap
                                - DynamoDB
                                - Backlog
                                - GSIStatusPartitionKey
                            KeyType: HASH
                        -
                            AttributeName: !FindInMap
                                - DynamoDB
                                - Backlog
                                - GSIStatusSortKey
                            KeyType: RANGE                            
                    Projection:
                        ProjectionType: INCLUDE
                        NonKeyAttributes:
                            - serviceParams
                -
                    IndexName: !FindInMap
                        - DynamoDB
                        - Backlog
                        - GSIJobIdIndex
                    KeySchema:
                        -
                            AttributeName: !FindInMap
                                - DynamoDB
                                - Backlog
                                - GSIJobIdPartitionKey
                            KeyType: HASH
                    Projection:
                        ProjectionType: INCLUDE
                        NonKeyAttributes:
                            - serviceParams
            StreamSpecification:
                StreamViewType: NEW_AND_OLD_IMAGES

    AtomicLockTable:
        Type: AWS::DynamoDB::Table
        Metadata:
            cfn_nag:
                rules_to_suppress:
                    -
                        id: W28
                        reason: Tables are explicitly assigned with predefined names
        Properties:
            TableName: !Sub
                - ${ResourcePrefix}-${suffix}
                - suffix: !FindInMap
                    - DynamoDB
                    - AtomicLock
                    - Suffix
            BillingMode: PAY_PER_REQUEST
            AttributeDefinitions:
                -
                    AttributeName: !FindInMap
                        - DynamoDB
                        - AtomicLock
                        - PartitionKey
                    AttributeType: S
            KeySchema:
                -
                    AttributeName: !FindInMap
                        - DynamoDB
                        - AtomicLock
                        - PartitionKey
                    KeyType: HASH
            SSESpecification:
                SSEEnabled: true
            PointInTimeRecoverySpecification:
                PointInTimeRecoveryEnabled: true
            TimeToLiveSpecification:
                AttributeName: ttl
                Enabled: true

    ServiceTokenTable:
        Type: AWS::DynamoDB::Table
        Metadata:
            cfn_nag:
                rules_to_suppress:
                    -
                        id: W28
                        reason: Tables are explicitly assigned with predefined names
        Properties:
            TableName: !Sub
                - ${ResourcePrefix}-${suffix}
                - suffix: !FindInMap
                    - DynamoDB
                    - ServiceToken
                    - Suffix
            BillingMode: PAY_PER_REQUEST
            AttributeDefinitions:
                -
                    AttributeName: !FindInMap
                        - DynamoDB
                        - ServiceToken
                        - PartitionKey
                    AttributeType: S
                -
                    AttributeName: !FindInMap
                        - DynamoDB
                        - ServiceToken
                        - SortKey
                    AttributeType: S
            KeySchema:
                -
                    AttributeName: !FindInMap
                        - DynamoDB
                        - ServiceToken
                        - PartitionKey
                    KeyType: HASH
                -
                    AttributeName: !FindInMap
                        - DynamoDB
                        - ServiceToken
                        - SortKey
                    KeyType: RANGE
            SSESpecification:
                SSEEnabled: true
            PointInTimeRecoverySpecification:
                PointInTimeRecoveryEnabled: true
            TimeToLiveSpecification:
                AttributeName: ttl
                Enabled: true

    IngestTable:
        Type: AWS::DynamoDB::Table
        Metadata:
            cfn_nag:
                rules_to_suppress:
                    -
                        id: W28
                        reason: Tables are explicitly assigned with predefined names
        Properties:
            TableName: !Sub
                - ${ResourcePrefix}-${suffix}
                - suffix: !FindInMap
                    - DynamoDB
                    - Ingest
                    - Suffix
            BillingMode: PAY_PER_REQUEST
            AttributeDefinitions:
                -
                    AttributeName: !FindInMap
                        - DynamoDB
                        - Ingest
                        - PartitionKey
                    AttributeType: S
                -
                    AttributeName: !FindInMap
                        - DynamoDB
                        - Ingest
                        - SchemaVersionIndexKey
                    AttributeType: N
                -
                    AttributeName: !FindInMap
                        - DynamoDB
                        - Ingest
                        - GSISortKey
                    AttributeType: N
                -
                    AttributeName: !FindInMap
                        - DynamoDB
                        - Ingest
                        - TypeIndexKey
                    AttributeType: S
                -
                    AttributeName: !FindInMap
                        - DynamoDB
                        - Ingest
                        - GroupIndexKey
                    AttributeType: S
                -
                    AttributeName: !FindInMap
                        - DynamoDB
                        - Ingest
                        - StatusIndexKey
                    AttributeType: S
            KeySchema:
                -
                    AttributeName: !FindInMap
                        - DynamoDB
                        - Ingest
                        - PartitionKey
                    KeyType: HASH
            SSESpecification:
                SSEEnabled: true
            PointInTimeRecoverySpecification:
                PointInTimeRecoveryEnabled: true
            GlobalSecondaryIndexes:
                -
                    IndexName: !FindInMap
                        - DynamoDB
                        - Ingest
                        - SchemaVersionIndexName
                    KeySchema:
                        -
                            AttributeName: !FindInMap
                                - DynamoDB
                                - Ingest
                                - SchemaVersionIndexKey
                            KeyType: HASH
                        -
                            AttributeName: !FindInMap
                                - DynamoDB
                                - Ingest
                                - GSISortKey
                            KeyType: RANGE
                    Projection:
                        ProjectionType: INCLUDE
                        NonKeyAttributes:
                            - !FindInMap
                                - DynamoDB
                                - Ingest
                                - TypeIndexKey
                -
                    IndexName: !FindInMap
                        - DynamoDB
                        - Ingest
                        - TypeIndexName
                    KeySchema:
                        -
                            AttributeName: !FindInMap
                                - DynamoDB
                                - Ingest
                                - TypeIndexKey
                            KeyType: HASH
                        -
                            AttributeName: !FindInMap
                                - DynamoDB
                                - Ingest
                                - GSISortKey
                            KeyType: RANGE
                    Projection:
                        ProjectionType: KEYS_ONLY
                -
                    IndexName: !FindInMap
                        - DynamoDB
                        - Ingest
                        - GroupIndexName
                    KeySchema:
                        -
                            AttributeName: !FindInMap
                                - DynamoDB
                                - Ingest
                                - GroupIndexKey
                            KeyType: HASH
                        -
                            AttributeName: !FindInMap
                                - DynamoDB
                                - Ingest
                                - GSISortKey
                            KeyType: RANGE
                    Projection:
                        ProjectionType: KEYS_ONLY
                -
                    IndexName: !FindInMap
                        - DynamoDB
                        - Ingest
                        - StatusIndexName
                    KeySchema:
                        -
                            AttributeName: !FindInMap
                                - DynamoDB
                                - Ingest
                                - StatusIndexKey
                            KeyType: HASH
                        -
                            AttributeName: !FindInMap
                                - DynamoDB
                                - Ingest
                                - GSISortKey
                            KeyType: RANGE
                    Projection:
                        ProjectionType: KEYS_ONLY
            StreamSpecification:
                StreamViewType: NEW_AND_OLD_IMAGES

    AnalysisTable:
        Type: AWS::DynamoDB::Table
        Metadata:
            cfn_nag:
                rules_to_suppress:
                    -
                        id: W28
                        reason: Tables are explicitly assigned with predefined names
        Properties:
            TableName: !Sub
                - ${ResourcePrefix}-${suffix}
                - suffix: !FindInMap
                    - DynamoDB
                    - Analysis
                    - Suffix
            BillingMode: PAY_PER_REQUEST
            AttributeDefinitions:
                -
                    AttributeName: !FindInMap
                        - DynamoDB
                        - Analysis
                        - PartitionKey
                    AttributeType: S
                -
                    AttributeName: !FindInMap
                        - DynamoDB
                        - Analysis
                        - SortKey
                    AttributeType: S
            KeySchema:
                -
                    AttributeName: !FindInMap
                        - DynamoDB
                        - Analysis
                        - PartitionKey
                    KeyType: HASH
                -
                    AttributeName: !FindInMap
                        - DynamoDB
                        - Analysis
                        - SortKey
                    KeyType: RANGE
            SSESpecification:
                SSEEnabled: true
            PointInTimeRecoverySpecification:
                PointInTimeRecoveryEnabled: true

    ################################################################################
    #
    # Service Data Access Role
    # * Read/Write to Bucket (Amazon Transcribe, Amazon Comprehend, AWS Elemental
    #   MediaConvert)
    #
    ################################################################################
    ServiceDataAccessRole:
        Type: AWS::IAM::Role
        Properties:
            AssumeRolePolicyDocument:
                Version: "2012-10-17"
                Statement:
                    -
                        Effect: Allow
                        Action: sts:AssumeRole
                        Principal:
                            Service:
                                - comprehend.amazonaws.com
                                - transcribe.amazonaws.com
                                - mediaconvert.amazonaws.com
            # Note: MediaConvert only takes '/' or '/service-role/' paths.
            # '/some-random-string/' will throw an error 'Invalid role provided in RESOURCE_ROLE_ARN' with code 1432
            Path: /service-role/
            Policies:
                -
                    PolicyName: !Sub ${ResourcePrefix}-data-access
                    PolicyDocument:
                        Version: "2012-10-17"
                        Statement:
                            # S3 - read from ingest bucket
                            -
                                Effect: Allow
                                Action: s3:GetObject
                                Resource: !Sub arn:aws:s3:::${IngestBucket}/*
                            # S3 list/read/write to proxy bucket
                            -
                                Effect: Allow
                                Action: s3:ListBucket
                                Resource: !Sub arn:aws:s3:::${ProxyBucket}
                            -
                                Effect: Allow
                                Action:
                                    - s3:GetObject
                                    - s3:PutObject
                                Resource: !Sub arn:aws:s3:::${ProxyBucket}/*

    ################################################################################
    #
    # Amazon EventBridge
    # * an EventBridge bus that interfaces with upstream/downstream processes
    #
    ################################################################################
    EventBridgeLogGroup:
        Type: AWS::Logs::LogGroup
        Metadata:
            cfn_nag:
                rules_to_suppress:
                    -
                        id: W84
                        reason: Use default encryption. Disable additional KMS encryption requirement. https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html
        Properties:
            LogGroupName: !Sub
                - /aws/events/${ResourcePrefix}-${name}
                - name: !FindInMap
                    - EventBridge
                    - Bus
                    - Name
            RetentionInDays: 7

    EventBridgeBus:
        Type: AWS::Events::EventBus
        Properties:
            Name: !Sub
                - ${ResourcePrefix}-${name}
                - name: !FindInMap
                    - EventBridge
                    - Bus
                    - Name

    EventBridgeRole:
        Type: AWS::IAM::Role
        Properties:
            AssumeRolePolicyDocument:
                Version: "2012-10-17"
                Statement:
                    -
                        Effect: Allow
                        Action: sts:AssumeRole
                        Principal:
                            Service: events.amazonaws.com
            Path: !Sub /${ResourcePrefix}/
            Policies:
                -
                    PolicyName: !Sub ${ResourcePrefix}-eventbridge
                    PolicyDocument:
                        Version: "2012-10-17"
                        Statement:
                            -
                                Effect: Allow
                                Action:
                                    - logs:CreateLogGroup
                                    - logs:CreateLogStream
                                    - logs:PutLogEvents
                                Resource: !GetAtt EventBridgeLogGroup.Arn

    EventBridgeRule:
        Type: AWS::Events::Rule
        Properties:
            Description: !FindInMap
                - EventBridge
                - Rule
                - Detail
            EventBusName: !GetAtt EventBridgeBus.Name
            EventPattern:
                detail-type:
                    - !FindInMap
                        - EventBridge
                        - Rule
                        - Detail
                source:
                    - !FindInMap
                        - EventBridge
                        - Rule
                        - Source
                account:
                    - !Ref AWS::AccountId
                region:
                    - !Ref AWS::Region
            Name: !FindInMap
                - EventBridge
                - Rule
                - Name
            RoleArn: !GetAtt EventBridgeRole.Arn
            State: ENABLED
            Targets:
                -
                    Arn: !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:${EventBridgeLogGroup}
                    Id: !Sub ${ResourcePrefix}-loggroup-target

    ################################################################################
    #
    # Amazon Simple Notification Service (SNS)
    #
    ################################################################################
    SNSTopic:
        Type: AWS::SNS::Topic
        Properties:
            TopicName: !Sub
                - ${ResourcePrefix}-${name}
                - name: !FindInMap
                    - SNS
                    - Topic
                    - Name
            DisplayName: !FindInMap
                - SNS
                - Topic
                - DisplayName
            KmsMasterKeyId: alias/aws/sns

    SNSTopicPolicy:
        Type: AWS::SNS::TopicPolicy
        Properties:
            Topics:
                - !Ref SNSTopic
            PolicyDocument:
                Id: !Sub ${ResourcePrefix}-status-policy
                Version: "2012-10-17"
                Statement:
                    -
                        Sid: AllowLamdaToPublish
                        Effect: Allow
                        Principal:
                            Service: lambda.amazonaws.com
                        Action: sns:Publish
                        Resource: !Ref SNSTopic

    ################################################################################
    #
    # Backlog Service SNS Topic and Data Access
    # * SNS Topic (Amazon Rekognition, Amazon Textract)
    #
    ################################################################################
    BacklogTopic:
        Type: AWS::SNS::Topic
        Properties:
            TopicName: !Sub
                - ${ResourcePrefix}-${name}
                - name: !FindInMap
                    - SNS
                    - BacklogTopic
                    - Name
            DisplayName: !FindInMap
                - SNS
                - BacklogTopic
                - DisplayName
            KmsMasterKeyId: alias/aws/sns

    BacklogTopicPolicy:
        Type: AWS::SNS::TopicPolicy
        Properties:
            Topics:
                - !Ref BacklogTopic
            PolicyDocument:
                Id: !Sub ${ResourcePrefix}-backlog-topic-policy
                Version: "2012-10-17"
                Statement:
                    -
                        Sid: allow-services-publish-sns
                        Effect: Allow
                        Principal:
                            Service:
                                - rekognition.amazonaws.com
                                - textract.amazonaws.com
                        Action: sns:Publish
                        Resource: !Ref BacklogTopic

    BacklogTopicRole:
        Type: AWS::IAM::Role
        Properties:
            AssumeRolePolicyDocument:
                Version: "2012-10-17"
                Statement:
                    -
                        Effect: Allow
                        Action: sts:AssumeRole
                        Principal:
                            Service:
                                - rekognition.amazonaws.com
                                - textract.amazonaws.com
            Path: !Sub /${ResourcePrefix}/
            Policies:
                -
                    PolicyName: !Sub ${ResourcePrefix}-service-backlog-topic-role
                    PolicyDocument:
                        Version: "2012-10-17"
                        Statement:
                            -
                                Effect: Allow
                                Action: sns:Publish
                                Resource: !Ref BacklogTopic

    ################################################################################
    #
    # Backlog Custom Labels State Machine
    #
    ################################################################################
    BacklogCustomLabelsLogGroup:
        Type: AWS::Logs::LogGroup
        Metadata:
            cfn_nag:
                rules_to_suppress:
                    -
                        id: W84
                        reason: Use default encryption. Disable additional KMS encryption requirement. https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html
        Properties:
            LogGroupName: !Sub
                - /aws/lambda/${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Workflow
                    - BacklogCustomLabels
                    - Name
            RetentionInDays: 7

    BacklogCustomLabelsRole:
        Type: AWS::IAM::Role
        Properties:
            AssumeRolePolicyDocument:
                Version: "2012-10-17"
                Statement:
                    -
                        Effect: Allow
                        Action: sts:AssumeRole
                        Principal:
                            Service: lambda.amazonaws.com
            Path: !Sub /${ResourcePrefix}/
            ManagedPolicyArns:
                - arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess
            Policies:
                -
                    PolicyName: !Sub ${ResourcePrefix}-backlog-customlabels
                    PolicyDocument:
                        Version: "2012-10-17"
                        Statement:
                            # CloudWatch Logs
                            -
                                Effect: Allow
                                Action:
                                    - logs:CreateLogGroup
                                    - logs:CreateLogStream
                                    - logs:PutLogEvents
                                Resource: !GetAtt BacklogCustomLabelsLogGroup.Arn
                            # S3
                            -
                                Effect: Allow
                                Action:
                                    - s3:GetObject
                                    - s3:PutObject
                                    - s3:PutObjectAcl
                                Resource: !Sub arn:aws:s3:::${ProxyBucket}/*
                            # DynamoDB
                            -
                                Effect: Allow
                                Action:
                                    - dynamodb:Scan
                                    - dynamodb:Query
                                    - dynamodb:PutItem
                                    - dynamodb:UpdateItem
                                    - dynamodb:DeleteItem
                                Resource: !GetAtt AtomicLockTable.Arn
                            # Rekognition Custom Labels
                            -
                                Effect: Allow
                                Action: rekognition:DescribeProjectVersions
                                Resource: !Sub arn:aws:rekognition:${AWS::Region}:${AWS::AccountId}:project/*/*
                            -
                                Effect: Allow
                                Action:
                                    - rekognition:DetectCustomLabels
                                    - rekognition:StartProjectVersion
                                Resource: !Sub arn:aws:rekognition:${AWS::Region}:${AWS::AccountId}:project/*/version/*/*

    BacklogCustomLabelsLambda:
        Type: AWS::Lambda::Function
        Metadata:
            cfn_nag:
                rules_to_suppress:
                    -
                        id: W89
                        reason: Workflow not using VPC
                    -
                        id: W92
                        reason: Workflow not limiting simultaneous executions
        Properties:
            FunctionName: !Sub
                - ${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Workflow
                    - BacklogCustomLabels
                    - Name
            Description: !Sub (${SolutionLowerCaseId}) Backlog Custom Labels state machine lambda (128MB)
            Runtime: !FindInMap
                - Node
                - Runtime
                - Version
            MemorySize: 128
            Timeout: 900
            Handler: index.handler
            Role: !GetAtt BacklogCustomLabelsRole.Arn
            Code:
                S3Bucket: !Ref S3Bucket
                S3Key: !Sub
                    - ${KeyPrefix}/${package}
                    - package: !FindInMap
                        - Workflow
                        - BacklogCustomLabels
                        - Package
            Layers:
                - !Ref AwsSdkLayer
                - !Ref BacklogLibLayer
            TracingConfig:
                Mode: Active
            Environment:
                Variables:
                    ENV_EXPECTED_BUCKET_OWNER: !Ref AWS::AccountId
                    ENV_CUSTOM_USER_AGENT: !Ref CustomUserAgent
                    ENV_ANONYMOUS_USAGE: !Ref AnonymousUsage
                    ENV_SOLUTION_UUID: !Ref SolutionUuid
                    ENV_SOLUTION_ID: !Ref SolutionId
                    ENV_RESOURCE_PREFIX: !Ref ResourcePrefix
                    # Service Backlog
                    ENV_BACKLOG_EB_BUS: !GetAtt EventBridgeBus.Name
                    ENV_BACKLOG_TABLE: !Ref BacklogTable
                    ENV_ATOMICLOCK_TABLE: !Ref AtomicLockTable

    BacklogCustomLabelsStateMachineServiceRole:
        Type: AWS::IAM::Role
        Properties:
            AssumeRolePolicyDocument:
                Version: "2012-10-17"
                Statement:
                    -
                        Effect: Allow
                        Action: sts:AssumeRole
                        Principal:
                            Service: !Sub states.${AWS::Region}.amazonaws.com
            Path: !Sub /${ResourcePrefix}/
            Policies:
                -
                    PolicyName: !Sub ${ResourcePrefix}-customlabels-service-role
                    PolicyDocument:
                        Version: "2012-10-17"
                        Statement:
                            -
                                Effect: Allow
                                Action: lambda:InvokeFunction
                                Resource: !GetAtt BacklogCustomLabelsLambda.Arn

    BacklogCustomLabelsStateMachine:
        Type: AWS::StepFunctions::StateMachine
        Properties:
            StateMachineName: !Sub
                - ${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Workflow
                    - BacklogCustomLabels
                    - Name
            RoleArn: !GetAtt BacklogCustomLabelsStateMachineServiceRole.Arn
            DefinitionString:
                !Sub
                    - |-
                        {
                            "StartAt": "Check project version status",
                            "States": {
                                "Check project version status": {
                                    "Type": "Task",
                                    "Resource": "${x0}",
                                    "Parameters": {
                                        "operation": "check-project-version-status",
                                        "jobTag.$": "$.jobTag",
                                        "input.$": "$.input",
                                        "output.$": "$.output"
                                    },
                                    "Next": "Project version started?",
                                    "Retry": [
                                        {
                                            "ErrorEquals": [
                                                "States.ALL"
                                            ],
                                            "IntervalSeconds": 1,
                                            "MaxAttempts": 2,
                                            "BackoffRate": 1.2
                                        }
                                    ]
                                },
                                "Project version started?": {
                                    "Type": "Choice",
                                    "Choices": [
                                        {
                                            "Or": [
                                                {
                                                    "Variable": "$.output['check-project-version-status'].status",
                                                    "StringEquals": "TRAINING_FAILED"
                                                },
                                                {
                                                    "Variable": "$.output['check-project-version-status'].status",
                                                    "StringEquals": "FAILED"
                                                },
                                                {
                                                    "Variable": "$.output['check-project-version-status'].status",
                                                    "StringEquals": "DELETING"
                                                }
                                            ],
                                            "Next": "Project version failed"
                                        },
                                        {
                                            "Variable": "$.output['check-project-version-status'].status",
                                            "StringEquals": "RUNNING",
                                            "Next": "Detect custom labels"
                                        },
                                        {
                                            "Or": [
                                                {
                                                    "Variable": "$.output['check-project-version-status'].status",
                                                    "StringEquals": "TRAINING_COMPLETED"
                                                },
                                                {
                                                    "Variable": "$.output['check-project-version-status'].status",
                                                    "StringEquals": "STOPPED"
                                                }
                                            ],
                                            "Next": "Start project version"
                                        }
                                    ],
                                    "Default": "Wait for project version status (3mins)"
                                },
                                "Wait for project version status (3mins)": {
                                    "Type": "Wait",
                                    "Seconds": 180,
                                    "Next": "Check project version status"
                                },
                                "Start project version": {
                                    "Type": "Task",
                                    "Resource": "${x0}",
                                    "Parameters": {
                                        "operation": "start-project-version",
                                        "jobTag.$": "$.jobTag",
                                        "input.$": "$.input",
                                        "output.$": "$.output"
                                    },
                                    "Retry": [
                                        {
                                            "ErrorEquals": [
                                                "States.ALL"
                                            ],
                                            "IntervalSeconds": 1,
                                            "MaxAttempts": 2,
                                            "BackoffRate": 1.2
                                        }
                                    ],
                                    "Next": "Wait for project version status (3mins)"
                                },
                                "Detect custom labels": {
                                    "Type": "Task",
                                    "Resource": "${x0}",
                                    "Parameters": {
                                        "operation": "detect-custom-labels",
                                        "jobTag.$": "$.jobTag",
                                        "input.$": "$.input",
                                        "output.$": "$.output"
                                    },
                                    "Retry": [
                                        {
                                            "ErrorEquals": [
                                                "States.ALL"
                                            ],
                                            "IntervalSeconds": 1,
                                            "MaxAttempts": 4,
                                            "BackoffRate": 1.2
                                        }
                                    ],
                                    "Next": "More custom labels?"
                                },
                                "More custom labels?": {
                                    "Type": "Choice",
                                    "Choices": [
                                        {
                                            "Variable": "$.output['detect-custom-labels'].status",
                                            "StringEquals": "completed",
                                            "Next": "Custom labels completed"
                                        }
                                    ],
                                    "Default": "Detect custom labels"
                                },
                                "Custom labels completed": {
                                    "Type": "Succeed"
                                },
                                "Project version failed": {
                                    "Type": "Fail"
                                }
                            }
                        }
                    -
                        {
                            x0: !GetAtt BacklogCustomLabelsLambda.Arn
                        }

    ################################################################################
    #
    # Backlog Stream connector
    # * Data Access Role (Amazon Transcribe, Amazon Comprehend
    # * Connector lambda role
    # * Connector lambda
    # * Configure Lambda Event Source Mapping to trigger on DDB Stream
    #
    ################################################################################
    BacklogStreamConnectorLogGroup:
        Type: AWS::Logs::LogGroup
        Metadata:
            cfn_nag:
                rules_to_suppress:
                    -
                        id: W84
                        reason: Use default encryption. Disable additional KMS encryption requirement. https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html
        Properties:
            LogGroupName: !Sub
                - /aws/lambda/${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Workflow
                    - BacklogStreamConnector
                    - Name
            RetentionInDays: 7

    BacklogStreamConnectorRole:
        Type: AWS::IAM::Role
        Metadata:
            cfn_nag:
                rules_to_suppress:
                    -
                        id: W11
                        reason: Wildcard character is prefixed with the solution and stack id
                    -
                        id: W76
                        reason: Suppress SPCM higher than 25 warning
        Properties:
            AssumeRolePolicyDocument:
              Version: "2012-10-17"
              Statement:
                -
                    Effect: Allow
                    Principal:
                        Service: lambda.amazonaws.com
                    Action: sts:AssumeRole
            Path: !Sub /${ResourcePrefix}/
            ManagedPolicyArns:
                - arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess
            Policies:
                -
                    PolicyName: !Sub ${ResourcePrefix}-service-backlog-stream
                    PolicyDocument:
                        Version: "2012-10-17"
                        Statement:
                            # Logs
                            -
                                Effect: Allow
                                Action:
                                    - logs:CreateLogGroup
                                    - logs:CreateLogStream
                                    - logs:PutLogEvents
                                Resource: !GetAtt BacklogStreamConnectorLogGroup.Arn
                            # DynamoDB
                            -
                                Effect: Allow
                                Action:
                                    - dynamodb:Query
                                    - dynamodb:UpdateItem
                                Resource:
                                    - !GetAtt BacklogTable.Arn
                                    - !Sub
                                        - ${BacklogTable.Arn}/index/${index}
                                        - index: !FindInMap
                                            - DynamoDB
                                            - Backlog
                                            - GSIStatusIndex
                            # DynamoDB stream permission
                            -
                                Effect: Allow
                                Action:
                                    - dynamodb:DescribeStream
                                    - dynamodb:GetRecords
                                    - dynamodb:GetShardIterator
                                    - dynamodb:ListStreams
                                Resource: !GetAtt BacklogTable.StreamArn
                            # EventBridge
                            -
                                Effect: Allow
                                Action: events:PutEvents
                                Resource: !GetAtt EventBridgeBus.Arn
                            # Rekognition
                            -
                                Effect: Allow
                                Action:
                                    - rekognition:StartContentModeration
                                    - rekognition:StartCelebrityRecognition
                                    - rekognition:StartFaceDetection
                                    - rekognition:StartFaceSearch
                                    - rekognition:StartLabelDetection
                                    - rekognition:StartPersonTracking
                                    - rekognition:StartSegmentDetection
                                    - rekognition:StartTextDetection
                                Resource: "*"
                            -
                                Effect: Allow
                                Action: rekognition:StopProjectVersion
                                Resource: !Sub arn:aws:rekognition:${AWS::Region}:${AWS::AccountId}:project/*/version/*/*
                            # Textract
                            -
                                Effect: Allow
                                Action:
                                    - textract:StartDocumentAnalysis
                                    - textract:StartDocumentTextDetection
                                Resource: "*"
                            # Comprehend
                            -
                                Effect: Allow
                                Action:
                                    - comprehend:StartEntitiesDetectionJob
                                    - comprehend:StartKeyPhrasesDetectionJob
                                    - comprehend:StartDominantLanguageDetectionJob
                                    - comprehend:StartSentimentDetectionJob
                                    - comprehend:StartTopicsDetectionJob
                                Resource: "*"
                            # Transcribe
                            -
                                Effect: Allow
                                Action: transcribe:StartTranscriptionJob
                                Resource: "*"
                            # MediaConvert
                            -
                                Effect: Allow
                                Action: mediaConvert:CreateJob
                                Resource: !Sub arn:aws:mediaconvert:${AWS::Region}:${AWS::AccountId}:*
                            # S3 - StreamConnectorLambda starts AI/ML job on behalf
                            #      which requires GetObject when the services assume role
                            -
                                Effect: Allow
                                Action: s3:GetObject
                                Resource: !Sub arn:aws:s3:::${IngestBucket}/*
                            -
                                Effect: Allow
                                Action: s3:ListBucket
                                Resource: !Sub arn:aws:s3:::${ProxyBucket}
                            -
                                Effect: Allow
                                Action:
                                    - s3:GetObject
                                    - s3:PutObject
                                Resource: !Sub arn:aws:s3:::${ProxyBucket}/*
                            # Step Functions (Custom Labels)
                            -
                                Effect: Allow
                                Action: states:StartExecution
                                Resource: !Ref BacklogCustomLabelsStateMachine
                            # DynamoDB - AtomicLock table to acquire lock
                            -
                                Effect: Allow
                                Action:
                                    - dynamodb:Query
                                    - dynamodb:DeleteItem
                                    - dynamodb:PutItem
                                    - dynamodb:UpdateItem
                                Resource: !GetAtt AtomicLockTable.Arn
                            # IAM - Rekognition/Textract pass SNS topic role for notification
                            -
                                Effect: Allow
                                Action: iam:PassRole
                                Resource: !GetAtt BacklogTopicRole.Arn
                            # IAM - Comprehend/Transcribe pass data access role
                            -
                                Effect: Allow
                                Action:
                                    - iam:GetRole
                                    - iam:PassRole
                                Resource: !GetAtt ServiceDataAccessRole.Arn

    BacklogStreamConnectorLambda:
        Type: AWS::Lambda::Function
        Metadata:
            cfn_nag:
                rules_to_suppress:
                    -
                        id: W89
                        reason: Workflow not using VPC
                    -
                        id: W92
                        reason: Workflow not limiting simultaneous executions
        Properties:
            FunctionName: !Sub
                - ${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Workflow
                    - BacklogStreamConnector
                    - Name
            Description: !Sub (${SolutionLowerCaseId}) backlog stream connector (128MB)
            Runtime: !FindInMap
                - Node
                - Runtime
                - Version
            MemorySize: 128
            Timeout: 900
            Handler: index.handler
            Role: !GetAtt BacklogStreamConnectorRole.Arn
            Code:
                S3Bucket: !Ref S3Bucket
                S3Key: !Sub
                    - ${KeyPrefix}/${package}
                    - package: !FindInMap
                        - Workflow
                        - BacklogStreamConnector
                        - Package
            Layers:
                - !Ref AwsSdkLayer
                - !Ref BacklogLibLayer
            TracingConfig:
                Mode: Active
            Environment:
                Variables:
                    ENV_EXPECTED_BUCKET_OWNER: !Ref AWS::AccountId
                    ENV_CUSTOM_USER_AGENT: !Ref CustomUserAgent
                    ENV_ANONYMOUS_USAGE: !Ref AnonymousUsage
                    ENV_SOLUTION_UUID: !Ref SolutionUuid
                    ENV_SOLUTION_ID: !Ref SolutionId
                    ENV_RESOURCE_PREFIX: !Ref ResourcePrefix
                    ENV_BACKLOG_TABLE: !Ref BacklogTable
                    ENV_BACKLOG_EB_BUS: !GetAtt EventBridgeBus.Name
                    ENV_BACKLOG_TOPIC_ARN: !Ref BacklogTopic
                    ENV_BACKLOG_TOPIC_ROLE_ARN: !GetAtt BacklogTopicRole.Arn
                    ENV_DATA_ACCESS_ROLE: !GetAtt ServiceDataAccessRole.Arn
                    ENV_ATOMICLOCK_TABLE: !Ref AtomicLockTable
                    ENV_MEDIACONVERT_HOST: !Ref MediaConvertEndpoint

    BacklogStreamEventSource:
        Type: AWS::Lambda::EventSourceMapping
        Properties:
            Enabled: true
            EventSourceArn: !GetAtt BacklogTable.StreamArn
            FunctionName: !Ref BacklogStreamConnectorLambda
            BatchSize: 1
            MaximumRetryAttempts: 10
            StartingPosition: LATEST

    ################################################################################
    #
    # Backlog Status Updater
    # * Updater lambda role
    # * Updater lambda
    # * Amazon SNS (Amazon Rekognition, Amazon Textract)
    # * Amazon EventBridge / CloudWatch Event
    #   * AWS Elemental MediaConvert
    #   * Amazon Transcribe
    #   * Amazon Step Functions of Custom Labels
    #
    ################################################################################
    BacklogStatusUpdaterLogGroup:
        Type: AWS::Logs::LogGroup
        Metadata:
            cfn_nag:
                rules_to_suppress:
                    -
                        id: W84
                        reason: Use default encryption. Disable additional KMS encryption requirement. https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html
        Properties:
            LogGroupName: !Sub
                - /aws/lambda/${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Workflow
                    - BacklogStatusUpdater
                    - Name
            RetentionInDays: 7

    BacklogStatusUpdaterRole:
        Type: AWS::IAM::Role
        Properties:
            AssumeRolePolicyDocument:
                Version: "2012-10-17"
                Statement:
                    -
                        Effect: Allow
                        Action: sts:AssumeRole
                        Principal:
                            Service: lambda.amazonaws.com
            Path: !Sub /${ResourcePrefix}/
            ManagedPolicyArns:
                - arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess
            Policies:
                -
                    PolicyName: !Sub ${ResourcePrefix}-service-backlog-status-updater
                    PolicyDocument:
                        Version: "2012-10-17"
                        Statement:
                            # CloudWatch Logs
                            -
                                Effect: Allow
                                Action:
                                    - logs:CreateLogGroup
                                    - logs:CreateLogStream
                                    - logs:PutLogEvents
                                Resource: !GetAtt BacklogStatusUpdaterLogGroup.Arn
                            # DynamoDB - query and delete item from backlog table / jobId index
                            -
                                Effect: Allow
                                Action:
                                    - dynamodb:DeleteItem
                                    - dynamodb:Query
                                Resource:
                                    - !GetAtt BacklogTable.Arn
                                    - !Sub
                                        - ${BacklogTable.Arn}/index/${index}
                                        - index: !FindInMap
                                            - DynamoDB
                                            - Backlog
                                            - GSIJobIdIndex
                            # EventBridge
                            -
                                Effect: Allow
                                Action: events:PutEvents
                                Resource: !GetAtt EventBridgeBus.Arn
                            # DynamoDB - Atomic Lock Table to delete lock
                            -
                                Effect: Allow
                                Action:
                                    - dynamodb:DeleteItem
                                    - dynamodb:Query
                                Resource: !GetAtt AtomicLockTable.Arn
                            # Step Functions - describe custom labels execution
                            -
                                Effect: Allow
                                Action: states:DescribeExecution
                                Resource: !Sub arn:aws:states:${AWS::Region}:${AWS::AccountId}:execution:${BacklogCustomLabelsStateMachine.Name}:*

    BacklogStatusUpdaterLambda:
        Type: AWS::Lambda::Function
        Metadata:
            cfn_nag:
                rules_to_suppress:
                    -
                        id: W89
                        reason: Workflow not using VPC
                    -
                        id: W92
                        reason: Workflow not limiting simultaneous executions
        Properties:
            FunctionName: !Sub
                - ${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Workflow
                    - BacklogStatusUpdater
                    - Name
            Description: !Sub (${SolutionLowerCaseId}) service backlog status updater lambda (128MB)
            Runtime: !FindInMap
                - Node
                - Runtime
                - Version
            MemorySize: 128
            Timeout: 900
            Handler: index.handler
            Role: !GetAtt BacklogStatusUpdaterRole.Arn
            Code:
                S3Bucket: !Ref S3Bucket
                S3Key: !Sub
                    - ${KeyPrefix}/${package}
                    - package: !FindInMap
                        - Workflow
                        - BacklogStatusUpdater
                        - Package
            Layers:
                - !Ref AwsSdkLayer
                - !Ref BacklogLibLayer
            TracingConfig:
                Mode: Active
            Environment:
                Variables:
                    ENV_EXPECTED_BUCKET_OWNER: !Ref AWS::AccountId
                    ENV_CUSTOM_USER_AGENT: !Ref CustomUserAgent
                    ENV_ANONYMOUS_USAGE: !Ref AnonymousUsage
                    ENV_SOLUTION_UUID: !Ref SolutionUuid
                    ENV_SOLUTION_ID: !Ref SolutionId
                    ENV_RESOURCE_PREFIX: !Ref ResourcePrefix
                    ENV_BACKLOG_TABLE: !Ref BacklogTable
                    ENV_BACKLOG_EB_BUS: !GetAtt EventBridgeBus.Name
                    ENV_BACKLOG_TOPIC_ARN: !Ref BacklogTopic
                    ENV_BACKLOG_TOPIC_ROLE_ARN: !GetAtt BacklogTopicRole.Arn
                    ENV_ATOMICLOCK_TABLE: !Ref AtomicLockTable

    ################################################################################
    # Backlog Rekognition / Textract SNS Notification
    BacklogTopicSubscription:
        Type: AWS::SNS::Subscription
        Properties:
            Protocol: lambda
            TopicArn: !Ref BacklogTopic
            Endpoint: !GetAtt BacklogStatusUpdaterLambda.Arn

    BacklogTopicPermission:
        Type: AWS::Lambda::Permission
        Properties:
            FunctionName: !Ref BacklogStatusUpdaterLambda
            Action: lambda:InvokeFunction
            Principal: sns.amazonaws.com
            SourceArn: !Ref BacklogTopic

    ################################################################################
    # Backlog MediaConvert Job Status Change Event
    BacklogMediaConvertStatusChangeEvent:
        Type: AWS::Events::Rule
        Properties:
            Name: !Sub ${ResourcePrefix}-MediaConvertJobStatusChangeEventBacklog
            Description: !Sub "(${SolutionLowerCaseId}) MediaConvert Job Status Change Event (Backlog)"
            EventPattern:
                source:
                    - aws.mediaconvert
                region:
                    - !Ref AWS::Region
                detail-type:
                    - MediaConvert Job State Change
                detail:
                    status:
                        - COMPLETE
                        - CANCELED
                        - ERROR
                    userMetadata:
                        solutionUuid:
                            - !Ref SolutionUuid
            State: ENABLED
            Targets:
                -
                    Id: !Sub Id-${BacklogStatusUpdaterLambda}
                    Arn: !GetAtt BacklogStatusUpdaterLambda.Arn

    BacklogMediaConvertStatusChangePermission:
        Type: AWS::Lambda::Permission
        Properties:
            FunctionName: !Ref BacklogStatusUpdaterLambda
            Action: lambda:InvokeFunction
            Principal: events.amazonaws.com
            SourceArn: !GetAtt BacklogMediaConvertStatusChangeEvent.Arn

    ################################################################################
    # Backlog Transcribe Job Status Change Event
    BacklogTranscribeStatusChangeEvent:
        Type: AWS::Events::Rule
        Properties:
            Name: !Sub ${ResourcePrefix}-TranscribeJobStateChangeBacklog
            Description: !Sub (${SolutionLowerCaseId}) Transcribe Job State Change Event (Backlog)
            EventPattern:
                source:
                    - aws.transcribe
                region:
                    - !Ref AWS::Region
                detail-type:
                    - Transcribe Job State Change
                detail:
                    TranscriptionJobStatus:
                        - COMPLETED
                        - FAILED
                    TranscriptionJobName:
                        - prefix: !Sub ${SolutionUuid}
            State: ENABLED
            Targets:
                -
                    Id: !Sub Id-${BacklogStatusUpdaterLambda}
                    Arn: !GetAtt BacklogStatusUpdaterLambda.Arn

    BacklogTranscribeStatusChangePermission:
        Type: AWS::Lambda::Permission
        Properties:
            FunctionName: !Ref BacklogStatusUpdaterLambda
            Action: lambda:InvokeFunction
            Principal: events.amazonaws.com
            SourceArn: !GetAtt BacklogTranscribeStatusChangeEvent.Arn

    ################################################################################
    # Backlog Custom Labels State Machine Event
    BacklogCustomLabelsStateMachineStatusEvent:
        Type: AWS::Events::Rule
        Properties:
            Name: !Sub
                - ${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Workflow
                    - BacklogCustomLabels
                    - Name
            Description: !Sub (${SolutionLowerCaseId}) backlog custom labels state machine status change
            EventPattern:
                source:
                    - aws.states
                detail-type:
                    - Step Functions Execution Status Change
                detail:
                    status:
                        - FAILED
                        - ABORTED
                        - TIMED_OUT
                        - SUCCEEDED
                    stateMachineArn:
                        - !Ref BacklogCustomLabelsStateMachine
            State: ENABLED
            Targets:
                -
                    Id: !Sub Id-${BacklogStatusUpdaterLambda}
                    Arn: !GetAtt BacklogStatusUpdaterLambda.Arn

    BacklogCustomLabelsStateMachineStatusPermission:
        Type: AWS::Lambda::Permission
        Properties:
            FunctionName: !Ref BacklogStatusUpdaterLambda
            Action: lambda:InvokeFunction
            Principal: events.amazonaws.com
            SourceArn: !GetAtt BacklogCustomLabelsStateMachineStatusEvent.Arn

    ################################################################################
    #
    # Ingest Workflow
    # * State Machine Service Role
    # * Fixity (Nested) State Machine
    # * Audio (Nested) State Machine
    # * Video (Nested) State Machine
    # * Image (Nested) State Machine
    # * Document (Nested) State Machine
    # * Ingest (Main) State Machine
    #
    ################################################################################
    IngestStateMachineServiceRole:
        Type: AWS::IAM::Role
        Properties:
            AssumeRolePolicyDocument:
                Version: "2012-10-17"
                Statement:
                    -
                        Effect: Allow
                        Action: sts:AssumeRole
                        Principal:
                            Service: !Sub states.${AWS::Region}.amazonaws.com
            Path: !Sub /${ResourcePrefix}/
            Policies:
                -
                    PolicyName: !Sub ${ResourcePrefix}-ingest-statemachine-role
                    PolicyDocument:
                        Version: "2012-10-17"
                        Statement:
                            -
                                Effect: Allow
                                Action: lambda:InvokeFunction
                                Resource:
                                    - !GetAtt IngestFixityLambda.Arn
                                    - !GetAtt IngestVideoLambda.Arn
                                    - !GetAtt IngestAudioLambda.Arn
                                    - !GetAtt IngestImageLambda.Arn
                                    - !GetAtt IngestDocumentLambda.Arn
                                    - !GetAtt IngestMainLambda.Arn
                            # Below polices are needed for Service Integration of nested workflows
                            # https://docs.aws.amazon.com/step-functions/latest/dg/stepfunctions-iam.html
                            -
                                Effect: Allow
                                Action: states:StartExecution
                                Resource:
                                    - !Sub
                                        - arn:aws:states:${AWS::Region}:${AWS::AccountId}:stateMachine:${ResourcePrefix}-${name}
                                        - name: !FindInMap
                                            - Workflow
                                            - IngestFixity
                                            - Name
                                    - !Sub
                                        - arn:aws:states:${AWS::Region}:${AWS::AccountId}:stateMachine:${ResourcePrefix}-${name}
                                        - name: !FindInMap
                                            - Workflow
                                            - IngestVideo
                                            - Name
                                    - !Sub
                                        - arn:aws:states:${AWS::Region}:${AWS::AccountId}:stateMachine:${ResourcePrefix}-${name}
                                        - name: !FindInMap
                                            - Workflow
                                            - IngestAudio
                                            - Name
                                    - !Sub
                                        - arn:aws:states:${AWS::Region}:${AWS::AccountId}:stateMachine:${ResourcePrefix}-${name}
                                        - name: !FindInMap
                                            - Workflow
                                            - IngestImage
                                            - Name
                                    - !Sub
                                        - arn:aws:states:${AWS::Region}:${AWS::AccountId}:stateMachine:${ResourcePrefix}-${name}
                                        - name: !FindInMap
                                            - Workflow
                                            - IngestDocument
                                            - Name
                            -
                                Effect: Allow
                                Action:
                                    - states:DescribeExecution
                                    - states:StopExecution
                                Resource:
                                    - !Sub
                                        - arn:aws:states:${AWS::Region}:${AWS::AccountId}:execution:${ResourcePrefix}-${name}:*
                                        - name: !FindInMap
                                            - Workflow
                                            - IngestFixity
                                            - Name
                                    - !Sub
                                        - arn:aws:states:${AWS::Region}:${AWS::AccountId}:execution:${ResourcePrefix}-${name}:*
                                        - name: !FindInMap
                                            - Workflow
                                            - IngestVideo
                                            - Name
                                    - !Sub
                                        - arn:aws:states:${AWS::Region}:${AWS::AccountId}:execution:${ResourcePrefix}-${name}:*
                                        - name: !FindInMap
                                            - Workflow
                                            - IngestAudio
                                            - Name
                                    - !Sub
                                        - arn:aws:states:${AWS::Region}:${AWS::AccountId}:execution:${ResourcePrefix}-${name}:*
                                        - name: !FindInMap
                                            - Workflow
                                            - IngestImage
                                            - Name
                                    - !Sub
                                        - arn:aws:states:${AWS::Region}:${AWS::AccountId}:execution:${ResourcePrefix}-${name}:*
                                        - name: !FindInMap
                                            - Workflow
                                            - IngestDocument
                                            - Name
                            -
                                Effect: Allow
                                Action:
                                    - events:PutTargets
                                    - events:PutRule
                                    - events:DescribeRule
                                Resource: !Sub arn:aws:events:${AWS::Region}:${AWS::AccountId}:rule/StepFunctionsGetEventsForStepFunctionsExecutionRule

    ################################################################################
    # Ingest Fixty State Machine resources
    IngestFixityLogGroup:
        Type: AWS::Logs::LogGroup
        Metadata:
            cfn_nag:
                rules_to_suppress:
                    -
                        id: W84
                        reason: Use default encryption. Disable additional KMS encryption requirement. https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html
        Properties:
            LogGroupName: !Sub
                - /aws/lambda/${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Workflow
                    - IngestFixity
                    - Name
            RetentionInDays: 7

    IngestFixityRole:
        Type: AWS::IAM::Role
        Properties:
            AssumeRolePolicyDocument:
                Version: "2012-10-17"
                Statement:
                    -
                        Effect: Allow
                        Action: sts:AssumeRole
                        Principal:
                            Service: lambda.amazonaws.com
            Path: !Sub /${ResourcePrefix}/
            ManagedPolicyArns:
                - arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess
            Policies:
                -
                    PolicyName: !Sub ${ResourcePrefix}-ingest-fixity
                    PolicyDocument:
                        Version: "2012-10-17"
                        Statement:
                            # CloudWatch Logs
                            -
                                Effect: Allow
                                Action:
                                    - logs:CreateLogGroup
                                    - logs:CreateLogStream
                                    - logs:PutLogEvents
                                Resource: !GetAtt IngestFixityLogGroup.Arn
                            # S3
                            -
                                Effect: Allow
                                Action:
                                    - s3:GetObject
                                    - s3:GetObjectTagging
                                    - s3:GetObjectVersionTagging
                                    - s3:PutObject
                                    - s3:PutObjectTagging
                                    - s3:PutObjectVersionTagging
                                    - s3:RestoreObject
                                Resource: !Sub arn:aws:s3:::${IngestBucket}/*

    IngestFixityLambda:
        Type: AWS::Lambda::Function
        Metadata:
            cfn_nag:
                rules_to_suppress:
                    -
                        id: W89
                        reason: Workflow not using VPC
                    -
                        id: W92
                        reason: Workflow not limiting simultaneous executions
        Properties:
            FunctionName: !Sub
                - ${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Workflow
                    - IngestFixity
                    - Name
            Description: !Sub (${SolutionLowerCaseId}) ingest fixity state machine lambda (2048MB)
            Runtime: !FindInMap
                - Node
                - Runtime
                - Version
            MemorySize: 2048
            Timeout: 900
            Handler: index.handler
            Role: !GetAtt IngestFixityRole.Arn
            Code:
                S3Bucket: !Ref S3Bucket
                S3Key: !Sub
                    - ${KeyPrefix}/${package}
                    - package: !FindInMap
                        - Workflow
                        - IngestFixity
                        - Package
            Layers:
                - !Ref AwsSdkLayer
                - !Ref CoreLibLayer
                - !Ref FixityLibLayer
            TracingConfig:
                Mode: Active
            Environment:
                Variables:
                    ENV_EXPECTED_BUCKET_OWNER: !Ref AWS::AccountId
                    ENV_CUSTOM_USER_AGENT: !Ref CustomUserAgent
                    ENV_ANONYMOUS_USAGE: !Ref AnonymousUsage
                    ENV_SOLUTION_UUID: !Ref SolutionUuid
                    ENV_SOLUTION_ID: !Ref SolutionId
                    ENV_RESOURCE_PREFIX: !Ref ResourcePrefix
                    ENV_IOT_HOST: !Ref IotHost
                    ENV_IOT_TOPIC: !Ref IotTopic
                    ENV_INGEST_BUCKET: !Ref IngestBucket

    IngestFixityStateMachine:
        Type: AWS::StepFunctions::StateMachine
        Properties:
            StateMachineName: !Sub
                - ${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Workflow
                    - IngestFixity
                    - Name
            RoleArn: !GetAtt IngestStateMachineServiceRole.Arn
            DefinitionString:
                !Sub
                    - |-
                        {
                            "Comment": "Ingest Fixity state machine auto restores s3 object from GLAICER or DEEP_ARCHIVE and to run checksum",
                            "StartAt": "Check restore status",
                            "States": {
                                "Check restore status": {
                                    "Type": "Task",
                                    "Resource": "${x0}",
                                    "Parameters": {
                                        "operation": "check-restore-status",
                                        "uuid.$": "$.uuid",
                                        "status.$": "$.status",
                                        "progress.$": "$.progress",
                                        "input.$": "$.input",
                                        "data.$": "$.data"
                                    },
                                    "Next": "Restore completed?",
                                    "Retry": [
                                        {
                                            "ErrorEquals": [
                                                "States.ALL"
                                            ],
                                            "IntervalSeconds": 1,
                                            "MaxAttempts": 4,
                                            "BackoffRate": 1.2
                                        }
                                    ]
                                },
                                "Restore completed?": {
                                    "Type": "Choice",
                                    "Choices": [
                                        {
                                            "Variable": "$.status",
                                            "StringEquals": "COMPLETED",
                                            "Next": "Compute checksum"
                                        },
                                        {
                                            "Variable": "$.data.restore.tier",
                                            "StringEquals": "Expedited",
                                            "Next": "Wait 4 mins"
                                        },
                                        {
                                            "And": [
                                                {
                                                    "Variable": "$.data.restore.storageClass",
                                                    "StringEquals": "DEEP_ARCHIVE"
                                                },
                                                {
                                                    "Variable": "$.data.restore.tier",
                                                    "StringEquals": "Bulk"
                                                }
                                            ],
                                            "Next": "Wait 12 hrs"
                                        }
                                    ],
                                    "Default": "Wait 4 hrs"
                                },
                                "Wait 4 mins": {
                                    "Type": "Wait",
                                    "Seconds": 240,
                                    "Next": "Check restore status"
                                },
                                "Wait 4 hrs": {
                                    "Type": "Wait",
                                    "Seconds": 14400,
                                    "Next": "Check restore status"
                                },
                                "Wait 12 hrs": {
                                    "Type": "Wait",
                                    "Seconds": 43200,
                                    "Next": "Check restore status"
                                },
                                "Compute checksum": {
                                    "Type": "Task",
                                    "Resource": "${x0}",
                                    "Parameters": {
                                        "operation": "compute-checksum",
                                        "uuid.$": "$.uuid",
                                        "status.$": "$.status",
                                        "progress.$": "$.progress",
                                        "input.$": "$.input",
                                        "data.$": "$.data"
                                    },
                                    "Next": "More data?",
                                    "Retry": [
                                        {
                                            "ErrorEquals": [
                                                "States.ALL"
                                            ],
                                            "IntervalSeconds": 1,
                                            "MaxAttempts": 4,
                                            "BackoffRate": 1.2
                                        }
                                    ]
                                },
                                "More data?": {
                                    "Type": "Choice",
                                    "Choices": [
                                        {
                                            "Variable": "$.status",
                                            "StringEquals": "COMPLETED",
                                            "Next": "Validate checksum"
                                        }
                                    ],
                                    "Default": "Compute checksum"
                                },
                                "Validate checksum": {
                                    "Type": "Task",
                                    "Resource": "${x0}",
                                    "Parameters": {
                                        "operation": "validate-checksum",
                                        "uuid.$": "$.uuid",
                                        "status": "NOT_STARTED",
                                        "progress": 0,
                                        "input.$": "$.input",
                                        "data.$": "$.data"
                                    },
                                    "End": true,
                                    "Retry": [
                                        {
                                            "ErrorEquals": [
                                                "States.ALL"
                                            ],
                                            "IntervalSeconds": 1,
                                            "MaxAttempts": 4,
                                            "BackoffRate": 1.2
                                        }
                                    ]
                                }
                            }
                        }
                    -
                        {
                            x0: !GetAtt IngestFixityLambda.Arn
                        }

    ################################################################################
    # Ingest Video State Machine resources
    IngestVideoLogGroup:
        Type: AWS::Logs::LogGroup
        Metadata:
            cfn_nag:
                rules_to_suppress:
                    -
                        id: W84
                        reason: Use default encryption. Disable additional KMS encryption requirement. https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html
        Properties:
            LogGroupName: !Sub
                - /aws/lambda/${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Workflow
                    - IngestVideo
                    - Name
            RetentionInDays: 7

    IngestVideoRole:
        Type: AWS::IAM::Role
        Properties:
            AssumeRolePolicyDocument:
                Version: "2012-10-17"
                Statement:
                    -
                        Effect: Allow
                        Action: sts:AssumeRole
                        Principal:
                            Service: lambda.amazonaws.com
            Path: !Sub /${ResourcePrefix}/
            ManagedPolicyArns:
                - arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess
            Policies:
                -
                    PolicyName: !Sub ${ResourcePrefix}-ingest-video
                    PolicyDocument:
                        Version: "2012-10-17"
                        Statement:
                            # CloudWatch Logs
                            -
                                Effect: Allow
                                Action:
                                    - logs:CreateLogGroup
                                    - logs:CreateLogStream
                                    - logs:PutLogEvents
                                Resource: !GetAtt IngestVideoLogGroup.Arn
                            # S3 - ingest bucket
                            -
                                Effect: Allow
                                Action: s3:ListBucket
                                Resource: !Sub arn:aws:s3:::${IngestBucket}
                            -
                                Effect: Allow
                                Action: s3:GetObject
                                Resource: !Sub arn:aws:s3:::${IngestBucket}/*
                            # S3 - proxy bucket
                            -
                                Effect: Allow
                                Action: s3:ListBucket
                                Resource: !Sub arn:aws:s3:::${ProxyBucket}
                            -
                                Effect: Allow
                                Action:
                                    - s3:GetObject
                                    - s3:PutObject
                                Resource: !Sub arn:aws:s3:::${ProxyBucket}/*
                            # MediaConvert
                            -
                                Effect: Allow
                                Action:
                                    - mediaConvert:CreateJob
                                    - mediaConvert:GetJob
                                Resource: !Sub arn:aws:mediaconvert:${AWS::Region}:${AWS::AccountId}:*
                            # IAM - pass data access role to MediaConvert
                            -
                                Effect: Allow
                                Action:
                                    - iam:GetRole
                                    - iam:PassRole
                                Resource: !GetAtt ServiceDataAccessRole.Arn
                            # DynamoDB
                            -
                                Effect: Allow
                                Action:
                                    - dynamodb:Scan
                                    - dynamodb:Query
                                    - dynamodb:PutItem
                                    - dynamodb:UpdateItem
                                    - dynamodb:DeleteItem
                                Resource: !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${ResourcePrefix}-*
                            # EventBridge (ServiceBacklog)
                            -
                                Effect: Allow
                                Action: events:PutEvents
                                Resource: !GetAtt EventBridgeBus.Arn

    IngestVideoLambda:
        Type: AWS::Lambda::Function
        Metadata:
            cfn_nag:
                rules_to_suppress:
                    -
                        id: W89
                        reason: Workflow not using VPC
                    -
                        id: W92
                        reason: Workflow not limiting simultaneous executions
        Properties:
            FunctionName: !Sub
                - ${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Workflow
                    - IngestVideo
                    - Name
            Description: !Sub (${SolutionLowerCaseId}) ingest video state machine lambda (2048MB)
            Runtime: !FindInMap
                - Node
                - Runtime
                - Version
            MemorySize: 2048
            Timeout: 900
            Handler: index.handler
            Role: !GetAtt IngestVideoRole.Arn
            Code:
                S3Bucket: !Ref S3Bucket
                S3Key: !Sub
                    - ${KeyPrefix}/${package}
                    - package: !FindInMap
                        - Workflow
                        - IngestVideo
                        - Package
            Layers:
                - !Ref AwsSdkLayer
                - !Ref CoreLibLayer
                - !Ref MediaInfoLayer
                - !Ref BacklogLibLayer
            TracingConfig:
                Mode: Active
            Environment:
                Variables:
                    ENV_EXPECTED_BUCKET_OWNER: !Ref AWS::AccountId
                    ENV_CUSTOM_USER_AGENT: !Ref CustomUserAgent
                    ENV_ANONYMOUS_USAGE: !Ref AnonymousUsage
                    ENV_SOLUTION_UUID: !Ref SolutionUuid
                    ENV_SOLUTION_ID: !Ref SolutionId
                    ENV_RESOURCE_PREFIX: !Ref ResourcePrefix
                    ENV_IOT_HOST: !Ref IotHost
                    ENV_IOT_TOPIC: !Ref IotTopic
                    ENV_INGEST_BUCKET: !Ref IngestBucket
                    ENV_PROXY_BUCKET: !Ref ProxyBucket
                    ENV_MEDIACONVERT_HOST: !Ref MediaConvertEndpoint
                    ENV_DATA_ACCESS_ROLE: !GetAtt ServiceDataAccessRole.Arn
                    ## Service Backlog ##
                    ENV_BACKLOG_EB_BUS: !GetAtt EventBridgeBus.Name
                    ENV_BACKLOG_TABLE: !Ref BacklogTable
                    ENV_BACKLOG_TOPIC_ARN: !Ref BacklogTopic
                    ENV_BACKLOG_TOPIC_ROLE_ARN: !GetAtt BacklogTopicRole.Arn

    IngestVideoStateMachine:
        Type: AWS::StepFunctions::StateMachine
        Properties:
            StateMachineName: !Sub
                - ${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Workflow
                    - IngestVideo
                    - Name
            RoleArn: !GetAtt IngestStateMachineServiceRole.Arn
            DefinitionString:
                !Sub
                    - |-
                        {
                            "Comment": "video ingest state machine to run mediainfo and start mediaconvert job to create proxy",
                            "StartAt": "Run mediainfo",
                            "States": {
                                "Run mediainfo": {
                                    "Type": "Task",
                                    "Resource": "${x0}",
                                    "Parameters": {
                                        "operation": "run-mediainfo",
                                        "uuid.$": "$.uuid",
                                        "status": "NOT_STARTED",
                                        "progress": 0,
                                        "input.$": "$.input",
                                        "data.$": "$.data"
                                    },
                                    "Next": "Start and wait for mediaconvert job",
                                    "Retry": [
                                        {
                                            "ErrorEquals": [
                                                "States.ALL"
                                            ],
                                            "IntervalSeconds": 1,
                                            "MaxAttempts": 2,
                                            "BackoffRate": 1.2
                                        }
                                    ]
                                },
                                "Start and wait for mediaconvert job": {
                                    "Type": "Task",
                                    "Resource":"arn:aws:states:::lambda:invoke.waitForTaskToken",
                                    "Parameters": {
                                        "FunctionName": "${a0}",
                                        "Payload": {
                                            "token.$":"$$.Task.Token",
                                            "operation": "start-transcode",
                                            "uuid.$": "$.uuid",
                                            "status": "NOT_STARTED",
                                            "progress": 0,
                                            "input.$": "$.input",
                                            "data.$": "$.data"
                                        }
                                    },
                                    "TimeoutSeconds": 86400,
                                    "End": true
                                }
                            }
                        }
                    -
                        {
                            x0: !GetAtt IngestVideoLambda.Arn,
                            a0: !Ref IngestVideoLambda
                        }

    ################################################################################
    # Ingest Audio State Machine resources
    IngestAudioLogGroup:
        Type: AWS::Logs::LogGroup
        Metadata:
            cfn_nag:
                rules_to_suppress:
                    -
                        id: W84
                        reason: Use default encryption. Disable additional KMS encryption requirement. https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html
        Properties:
            LogGroupName: !Sub
                - /aws/lambda/${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Workflow
                    - IngestAudio
                    - Name
            RetentionInDays: 7

    IngestAudioRole:
        Type: AWS::IAM::Role
        Properties:
            AssumeRolePolicyDocument:
                Version: "2012-10-17"
                Statement:
                    -
                        Effect: Allow
                        Action: sts:AssumeRole
                        Principal:
                            Service: lambda.amazonaws.com
            Path: !Sub /${ResourcePrefix}/
            ManagedPolicyArns:
                - arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess
            Policies:
                -
                    PolicyName: !Sub ${ResourcePrefix}-ingest-audio
                    PolicyDocument:
                        Version: "2012-10-17"
                        Statement:
                            # CloudWatch Logs
                            -
                                Effect: Allow
                                Action:
                                    - logs:CreateLogGroup
                                    - logs:CreateLogStream
                                    - logs:PutLogEvents
                                Resource: !GetAtt IngestAudioLogGroup.Arn
                            # S3 - ingest bucket
                            -
                                Effect: Allow
                                Action: s3:ListBucket
                                Resource: !Sub arn:aws:s3:::${IngestBucket}
                            -
                                Effect: Allow
                                Action: s3:GetObject
                                Resource: !Sub arn:aws:s3:::${IngestBucket}/*
                            # S3 - proxy bucket
                            -
                                Effect: Allow
                                Action: s3:ListBucket
                                Resource: !Sub arn:aws:s3:::${ProxyBucket}
                            -
                                Effect: Allow
                                Action:
                                    - s3:GetObject
                                    - s3:PutObject
                                Resource: !Sub arn:aws:s3:::${ProxyBucket}/*
                            # MediaConvert
                            -
                                Effect: Allow
                                Action:
                                    - mediaConvert:CreateJob
                                    - mediaConvert:GetJob
                                Resource: !Sub arn:aws:mediaconvert:${AWS::Region}:${AWS::AccountId}:*
                            # IAM - pass data access role to MediaConvert
                            -
                                Effect: Allow
                                Action:
                                    - iam:GetRole
                                    - iam:PassRole
                                Resource: !GetAtt ServiceDataAccessRole.Arn
                            # DynamoDB
                            -
                                Effect: Allow
                                Action:
                                    - dynamodb:Scan
                                    - dynamodb:Query
                                    - dynamodb:PutItem
                                    - dynamodb:UpdateItem
                                    - dynamodb:DeleteItem
                                Resource: !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${ResourcePrefix}-*
                            # EventBridge (ServiceBacklog)
                            -
                                Effect: Allow
                                Action: events:PutEvents
                                Resource: !GetAtt EventBridgeBus.Arn

    IngestAudioLambda:
        Type: AWS::Lambda::Function
        Metadata:
            cfn_nag:
                rules_to_suppress:
                    -
                        id: W89
                        reason: Workflow not using VPC
                    -
                        id: W92
                        reason: Workflow not limiting simultaneous executions
        Properties:
            FunctionName: !Sub
                - ${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Workflow
                    - IngestAudio
                    - Name
            Description: !Sub (${SolutionLowerCaseId}) ingest audio state machine lambda (256MB)
            Runtime: !FindInMap
                - Node
                - Runtime
                - Version
            MemorySize: 256
            Timeout: 900
            Handler: index.handler
            Role: !GetAtt IngestAudioRole.Arn
            Code:
                S3Bucket: !Ref S3Bucket
                S3Key: !Sub
                    - ${KeyPrefix}/${package}
                    - package: !FindInMap
                        - Workflow
                        - IngestAudio
                        - Package
            Layers:
                - !Ref AwsSdkLayer
                - !Ref CoreLibLayer
                - !Ref MediaInfoLayer
                - !Ref BacklogLibLayer
            TracingConfig:
                Mode: Active
            Environment:
                Variables:
                    ENV_EXPECTED_BUCKET_OWNER: !Ref AWS::AccountId
                    ENV_CUSTOM_USER_AGENT: !Ref CustomUserAgent
                    ENV_ANONYMOUS_USAGE: !Ref AnonymousUsage
                    ENV_SOLUTION_UUID: !Ref SolutionUuid
                    ENV_SOLUTION_ID: !Ref SolutionId
                    ENV_RESOURCE_PREFIX: !Ref ResourcePrefix
                    ENV_IOT_HOST: !Ref IotHost
                    ENV_IOT_TOPIC: !Ref IotTopic
                    ENV_INGEST_BUCKET: !Ref IngestBucket
                    ENV_PROXY_BUCKET: !Ref ProxyBucket
                    ENV_MEDIACONVERT_HOST: !Ref MediaConvertEndpoint
                    ENV_DATA_ACCESS_ROLE: !GetAtt ServiceDataAccessRole.Arn
                    ## Service Backlog ##
                    ENV_BACKLOG_EB_BUS: !GetAtt EventBridgeBus.Name
                    ENV_BACKLOG_TABLE: !Ref BacklogTable
                    ENV_BACKLOG_TOPIC_ARN: !Ref BacklogTopic
                    ENV_BACKLOG_TOPIC_ROLE_ARN: !GetAtt BacklogTopicRole.Arn

    IngestAudioStateMachine:
        Type: AWS::StepFunctions::StateMachine
        Properties:
            StateMachineName: !Sub
                - ${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Workflow
                    - IngestAudio
                    - Name
            RoleArn: !GetAtt IngestStateMachineServiceRole.Arn
            DefinitionString:
                !Sub
                    - |-
                        {
                            "Comment": "audio ingest state machine to run mediainfo and start elastictranscode job to create proxy",
                            "StartAt": "Run mediainfo",
                            "States": {
                                "Run mediainfo": {
                                    "Type": "Task",
                                    "Resource": "${x0}",
                                    "Parameters": {
                                        "operation": "run-mediainfo",
                                        "uuid.$": "$.uuid",
                                        "status": "NOT_STARTED",
                                        "progress": 0,
                                        "input.$": "$.input",
                                        "data.$": "$.data"
                                    },
                                    "Next": "Start and wait for transcode job",
                                    "Retry": [
                                        {
                                            "ErrorEquals": [
                                                "States.ALL"
                                            ],
                                            "IntervalSeconds": 1,
                                            "MaxAttempts": 2,
                                            "BackoffRate": 1.2
                                        }
                                    ]
                                },
                                "Start and wait for transcode job": {
                                    "Type": "Task",
                                    "Resource":"arn:aws:states:::lambda:invoke.waitForTaskToken",
                                    "Parameters": {
                                        "FunctionName": "${a0}",
                                        "Payload": {
                                            "token.$":"$$.Task.Token",
                                            "operation": "start-transcode",
                                            "uuid.$": "$.uuid",
                                            "status": "NOT_STARTED",
                                            "progress": 0,
                                            "input.$": "$.input",
                                            "data.$": "$.data"
                                        }
                                    },
                                    "TimeoutSeconds": 86400,
                                    "End": true
                                }
                            }
                        }
                    -
                        {
                            x0: !GetAtt IngestAudioLambda.Arn,
                            a0: !Ref IngestAudioLambda
                        }

    ################################################################################
    # Ingest Image State Machine resources
    IngestImageLogGroup:
        Type: AWS::Logs::LogGroup
        Metadata:
            cfn_nag:
                rules_to_suppress:
                    -
                        id: W84
                        reason: Use default encryption. Disable additional KMS encryption requirement. https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html
        Properties:
            LogGroupName: !Sub
                - /aws/lambda/${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Workflow
                    - IngestImage
                    - Name
            RetentionInDays: 7

    IngestImageRole:
        Type: AWS::IAM::Role
        Properties:
            AssumeRolePolicyDocument:
                Version: "2012-10-17"
                Statement:
                    -
                        Effect: Allow
                        Action: sts:AssumeRole
                        Principal:
                            Service: lambda.amazonaws.com
            Path: !Sub /${ResourcePrefix}/
            ManagedPolicyArns:
                - arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess
            Policies:
                -
                    PolicyName: !Sub ${ResourcePrefix}-ingest-image
                    PolicyDocument:
                        Version: "2012-10-17"
                        Statement:
                            # CloudWatch Logs
                            -
                                Effect: Allow
                                Action:
                                    - logs:CreateLogGroup
                                    - logs:CreateLogStream
                                    - logs:PutLogEvents
                                Resource: !GetAtt IngestImageLogGroup.Arn
                            # S3 - ingest bucket
                            -
                                Effect: Allow
                                Action: s3:ListBucket
                                Resource: !Sub arn:aws:s3:::${IngestBucket}
                            -
                                Effect: Allow
                                Action: s3:GetObject
                                Resource: !Sub arn:aws:s3:::${IngestBucket}/*
                            # S3 - proxy bucket
                            -
                                Effect: Allow
                                Action: s3:ListBucket
                                Resource: !Sub arn:aws:s3:::${ProxyBucket}
                            -
                                Effect: Allow
                                Action:
                                    - s3:GetObject
                                    - s3:PutObject
                                Resource: !Sub arn:aws:s3:::${ProxyBucket}/*
                            # DynamoDB
                            -
                                Effect: Allow
                                Action:
                                    - dynamodb:Scan
                                    - dynamodb:Query
                                    - dynamodb:UpdateItem
                                    - dynamodb:DeleteItem
                                Resource: !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${ResourcePrefix}-*

    IngestImageLambda:
        Type: AWS::Lambda::Function
        Metadata:
            cfn_nag:
                rules_to_suppress:
                    -
                        id: W89
                        reason: Workflow not using VPC
                    -
                        id: W92
                        reason: Workflow not limiting simultaneous executions
        Properties:
            FunctionName: !Sub
                - ${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Workflow
                    - IngestImage
                    - Name
            Description: !Sub (${SolutionLowerCaseId}) ingest image state machine lambda (1024MB)
            Runtime: !FindInMap
                - Node
                - Runtime
                - Version
            MemorySize: 3008
            Timeout: 900
            Handler: index.handler
            Role: !GetAtt IngestImageRole.Arn
            Code:
                S3Bucket: !Ref S3Bucket
                S3Key: !Sub
                    - ${KeyPrefix}/${package}
                    - package: !FindInMap
                        - Workflow
                        - IngestImage
                        - Package
            Layers:
                - !Ref AwsSdkLayer
                - !Ref CoreLibLayer
                - !Ref ImageProcessLayer
            TracingConfig:
                Mode: Active
            Environment:
                Variables:
                    ENV_EXPECTED_BUCKET_OWNER: !Ref AWS::AccountId
                    ENV_CUSTOM_USER_AGENT: !Ref CustomUserAgent
                    ENV_ANONYMOUS_USAGE: !Ref AnonymousUsage
                    ENV_SOLUTION_UUID: !Ref SolutionUuid
                    ENV_SOLUTION_ID: !Ref SolutionId
                    ENV_RESOURCE_PREFIX: !Ref ResourcePrefix
                    ENV_IOT_HOST: !Ref IotHost
                    ENV_IOT_TOPIC: !Ref IotTopic
                    ENV_INGEST_BUCKET: !Ref IngestBucket
                    ENV_PROXY_BUCKET: !Ref ProxyBucket

    IngestImageStateMachine:
        Type: AWS::StepFunctions::StateMachine
        Properties:
            StateMachineName: !Sub
                - ${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Workflow
                    - IngestImage
                    - Name
            RoleArn: !GetAtt IngestStateMachineServiceRole.Arn
            DefinitionString:
                !Sub
                    - |-
                        {
                            "Comment": "image ingest state machine to run exiftool and extract thumbnail image",
                            "StartAt": "Run imageinfo",
                            "States": {
                                "Run imageinfo": {
                                    "Type": "Task",
                                    "Resource": "${x0}",
                                    "Parameters": {
                                        "operation": "run-imageinfo",
                                        "uuid.$": "$.uuid",
                                        "status": "NOT_STARTED",
                                        "progress": 0,
                                        "input.$": "$.input",
                                        "data.$": "$.data"
                                    },
                                    "End": true,
                                    "Retry": [
                                        {
                                            "ErrorEquals": [
                                                "States.ALL"
                                            ],
                                            "IntervalSeconds": 1,
                                            "MaxAttempts": 2,
                                            "BackoffRate": 1.2
                                        }
                                    ]
                                }
                            }
                        }
                    -
                        {
                            x0: !GetAtt IngestImageLambda.Arn
                        }

    ################################################################################
    # Ingest Document State Machine resources
    IngestDocumentLogGroup:
        Type: AWS::Logs::LogGroup
        Metadata:
            cfn_nag:
                rules_to_suppress:
                    -
                        id: W84
                        reason: Use default encryption. Disable additional KMS encryption requirement. https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html
        Properties:
            LogGroupName: !Sub
                - /aws/lambda/${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Workflow
                    - IngestDocument
                    - Name
            RetentionInDays: 7

    IngestDocumentRole:
        Type: AWS::IAM::Role
        Properties:
            AssumeRolePolicyDocument:
                Version: "2012-10-17"
                Statement:
                    -
                        Effect: Allow
                        Action: sts:AssumeRole
                        Principal:
                            Service: lambda.amazonaws.com
            Path: !Sub /${ResourcePrefix}/
            ManagedPolicyArns:
                - arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess
            Policies:
                -
                    PolicyName: !Sub ${ResourcePrefix}-ingest-document
                    PolicyDocument:
                        Version: "2012-10-17"
                        Statement:
                            # CloudWatch Logs
                            -
                                Effect: Allow
                                Action:
                                    - logs:CreateLogGroup
                                    - logs:CreateLogStream
                                    - logs:PutLogEvents
                                Resource: !GetAtt IngestDocumentLogGroup.Arn
                            # S3 - ingest bucket
                            -
                                Effect: Allow
                                Action: s3:ListBucket
                                Resource: !Sub arn:aws:s3:::${IngestBucket}
                            -
                                Effect: Allow
                                Action: s3:GetObject
                                Resource: !Sub arn:aws:s3:::${IngestBucket}/*
                            # S3 - proxy bucket
                            -
                                Effect: Allow
                                Action: s3:ListBucket
                                Resource: !Sub arn:aws:s3:::${ProxyBucket}
                            -
                                Effect: Allow
                                Action:
                                    - s3:GetObject
                                    - s3:PutObject
                                Resource: !Sub arn:aws:s3:::${ProxyBucket}/*
                            # DynamoDB
                            -
                                Effect: Allow
                                Action:
                                    - dynamodb:Scan
                                    - dynamodb:Query
                                    - dynamodb:UpdateItem
                                    - dynamodb:DeleteItem
                                Resource: !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${ResourcePrefix}-*

    IngestDocumentLambda:
        Type: AWS::Lambda::Function
        Metadata:
            cfn_nag:
                rules_to_suppress:
                    -
                        id: W89
                        reason: Workflow not using VPC
                    -
                        id: W92
                        reason: Workflow not limiting simultaneous executions
        Properties:
            FunctionName: !Sub
                - ${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Workflow
                    - IngestDocument
                    - Name
            Description: !Sub (${SolutionLowerCaseId}) ingest document state machine lambda (1024MB)
            Runtime: !FindInMap
                - Node
                - Runtime
                - Version
            MemorySize: 1024
            Timeout: 900
            Handler: index.handler
            Role: !GetAtt IngestDocumentRole.Arn
            Code:
                S3Bucket: !Ref S3Bucket
                S3Key: !Sub
                    - ${KeyPrefix}/${package}
                    - package: !FindInMap
                        - Workflow
                        - IngestDocument
                        - Package
            Layers:
                - !Ref AwsSdkLayer
                - !Ref CoreLibLayer
                - !Ref PDFLibLayer
                - !Ref CanvasLibLayer
            TracingConfig:
                Mode: Active
            Environment:
                Variables:
                    ENV_EXPECTED_BUCKET_OWNER: !Ref AWS::AccountId
                    ENV_CUSTOM_USER_AGENT: !Ref CustomUserAgent
                    ENV_ANONYMOUS_USAGE: !Ref AnonymousUsage
                    ENV_SOLUTION_UUID: !Ref SolutionUuid
                    ENV_SOLUTION_ID: !Ref SolutionId
                    ENV_RESOURCE_PREFIX: !Ref ResourcePrefix
                    ENV_IOT_HOST: !Ref IotHost
                    ENV_IOT_TOPIC: !Ref IotTopic
                    ENV_INGEST_BUCKET: !Ref IngestBucket
                    ENV_PROXY_BUCKET: !Ref ProxyBucket

    IngestDocumentStateMachine:
        Type: AWS::StepFunctions::StateMachine
        Properties:
            StateMachineName: !Sub
                - ${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Workflow
                    - IngestDocument
                    - Name
            RoleArn: !GetAtt IngestStateMachineServiceRole.Arn
            DefinitionString:
                !Sub
                    - |-
                        {
                            "Comment": "document ingest state machine lambda to run pdf info to extract metadata and convert pages to PNG images",
                            "StartAt": "Run PDFInfo and extract pages",
                            "States": {
                                "Run PDFInfo and extract pages": {
                                    "Type": "Task",
                                    "Resource": "${x0}",
                                    "Parameters": {
                                        "operation": "run-docinfo",
                                        "uuid.$": "$.uuid",
                                        "status.$": "$.status",
                                        "progress.$": "$.progress",
                                        "input.$": "$.input",
                                        "data.$": "$.data"
                                    },
                                    "Next": "More pages?",
                                    "Retry": [
                                        {
                                            "ErrorEquals": [
                                                "States.ALL"
                                            ],
                                            "IntervalSeconds": 1,
                                            "MaxAttempts": 2,
                                            "BackoffRate": 1.2
                                        }
                                    ]
                                },
                                "More pages?": {
                                    "Type": "Choice",
                                    "Choices": [
                                        {
                                            "Variable": "$.status",
                                            "StringEquals": "COMPLETED",
                                            "Next": "PDFInfo completed"
                                        }
                                    ],
                                    "Default": "Run PDFInfo and extract pages"
                                },
                                "PDFInfo completed": {
                                    "Type": "Succeed"
                                }
                            }
                        }
                    -
                        {
                            x0: !GetAtt IngestDocumentLambda.Arn
                        }

    ################################################################################
    # Ingest Main State Machine resources
    IngestMainLogGroup:
        Type: AWS::Logs::LogGroup
        Metadata:
            cfn_nag:
                rules_to_suppress:
                    -
                        id: W84
                        reason: Use default encryption. Disable additional KMS encryption requirement. https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html
        Properties:
            LogGroupName: !Sub
                - /aws/lambda/${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Workflow
                    - IngestMain
                    - Name
            RetentionInDays: 7

    IngestMainRole:
        Type: AWS::IAM::Role
        Properties:
            AssumeRolePolicyDocument:
                Version: "2012-10-17"
                Statement:
                    -
                        Effect: Allow
                        Action: sts:AssumeRole
                        Principal:
                            Service: lambda.amazonaws.com
            Path: !Sub /${ResourcePrefix}/
            ManagedPolicyArns:
                - arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess
            Policies:
                -
                    PolicyName: !Sub ${ResourcePrefix}-ingest-main
                    PolicyDocument:
                        Version: "2012-10-17"
                        Statement:
                            # CloudWatch Logs
                            -
                                Effect: Allow
                                Action:
                                    - logs:CreateLogGroup
                                    - logs:CreateLogStream
                                    - logs:PutLogEvents
                                Resource: !GetAtt IngestMainLogGroup.Arn
                            # S3 - ingest bucket
                            -
                                Effect: Allow
                                Action: s3:ListBucket
                                Resource: !Sub arn:aws:s3:::${IngestBucket}
                            -
                                Effect: Allow
                                Action:
                                    - s3:GetObject
                                    - s3:GetObjectTagging
                                    - s3:GetObjectVersionTagging
                                    - s3:PutObjectTagging
                                    - s3:PutObjectVersionTagging
                                Resource: !Sub arn:aws:s3:::${IngestBucket}/*
                            # S3 - proxy bucket
                            -
                                Effect: Allow
                                Action: s3:ListBucket
                                Resource: !Sub arn:aws:s3:::${ProxyBucket}
                            -
                                Effect: Allow
                                Action:
                                    - s3:GetObject
                                    - s3:PutObject
                                Resource: !Sub arn:aws:s3:::${ProxyBucket}/*
                            # DynamoDB
                            -
                                Effect: Allow
                                Action:
                                    - dynamodb:Scan
                                    - dynamodb:Query
                                    - dynamodb:UpdateItem
                                    - dynamodb:DeleteItem
                                Resource: !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${ResourcePrefix}-*
                            # IoT
                            -
                                Effect: Allow
                                Action: iot:Publish
                                Resource: !Sub arn:aws:iot:${AWS::Region}:${AWS::AccountId}:topic/${IotTopic}
                            # OpenSearch
                            -
                                Effect: Allow
                                Action:
                                    - es:ESHttpGet
                                    - es:ESHttpHead
                                    - es:ESHttpPost
                                    - es:ESHttpPut
                                    - es:ESHttpDelete
                                Resource:
                                    - !Sub arn:aws:es:${AWS::Region}:${AWS::AccountId}:domain/${OpenSearchDomainName}
                                    - !Sub arn:aws:es:${AWS::Region}:${AWS::AccountId}:domain/${OpenSearchDomainName}/*
                            # SNS
                            -
                                Effect: Allow
                                Action: sns:Publish
                                Resource: !Ref SNSTopic

    IngestMainLambda:
        Type: AWS::Lambda::Function
        Metadata:
            cfn_nag:
                rules_to_suppress:
                    -
                        id: W89
                        reason: Workflow not using VPC
                    -
                        id: W92
                        reason: Workflow not limiting simultaneous executions
        Properties:
            FunctionName: !Sub
                - ${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Workflow
                    - IngestMain
                    - Name
            Description: !Sub (${SolutionLowerCaseId}) ingest main state machine lambda (512MB)
            Runtime: !FindInMap
                - Node
                - Runtime
                - Version
            MemorySize: 512
            Timeout: 900
            Handler: index.handler
            Role: !GetAtt IngestMainRole.Arn
            Code:
                S3Bucket: !Ref S3Bucket
                S3Key: !Sub
                    - ${KeyPrefix}/${package}
                    - package: !FindInMap
                        - Workflow
                        - IngestMain
                        - Package
            Layers:
                - !Ref AwsSdkLayer
                - !Ref CoreLibLayer
                - !Ref MediaInfoLayer
            TracingConfig:
                Mode: Active
            Environment:
                Variables:
                    ENV_EXPECTED_BUCKET_OWNER: !Ref AWS::AccountId
                    ENV_CUSTOM_USER_AGENT: !Ref CustomUserAgent
                    ENV_ANONYMOUS_USAGE: !Ref AnonymousUsage
                    ENV_SOLUTION_UUID: !Ref SolutionUuid
                    ENV_SOLUTION_ID: !Ref SolutionId
                    ENV_RESOURCE_PREFIX: !Ref ResourcePrefix
                    ENV_IOT_HOST: !Ref IotHost
                    ENV_IOT_TOPIC: !Ref IotTopic
                    ENV_INGEST_BUCKET: !Ref IngestBucket
                    ENV_PROXY_BUCKET: !Ref ProxyBucket
                    ENV_SNS_TOPIC_ARN: !Ref SNSTopic
                    ENV_ES_DOMAIN_ENDPOINT: !Ref OpenSearchDomainEndpoint

    IngestMainStateMachine:
        Type: AWS::StepFunctions::StateMachine
        Properties:
            StateMachineName: !Sub
                - ${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Workflow
                    - IngestMain
                    - Name
            RoleArn: !GetAtt IngestStateMachineServiceRole.Arn
            DefinitionString:
                !Sub
                    - |-
                        {
                            "Comment": "ingest state machine to create record, to extract media technical metadata, and to index results to elasticsearch",
                            "StartAt": "Create record",
                            "States": {
                                "Create record": {
                                    "Type": "Task",
                                    "Resource": "${x0}",
                                    "Parameters": {
                                        "operation": "create-record",
                                        "executionArn.$": "$$.Execution.Id",
                                        "input.$": "$.input"
                                    },
                                    "Next": "Start fixity (nested)",
                                    "Retry": [
                                        {
                                            "ErrorEquals": [
                                                "States.ALL"
                                            ],
                                            "IntervalSeconds": 1,
                                            "MaxAttempts": 2,
                                            "BackoffRate": 1.2
                                        }
                                    ]
                                },
                                "Start fixity (nested)": {
                                    "Type": "Task",
                                    "Resource": "arn:aws:states:::states:startExecution.sync",
                                    "Parameters": {
                                        "Input": {
                                            "operation": "check-restore-status",
                                            "uuid.$": "$.uuid",
                                            "status.$": "$.status",
                                            "progress.$": "$.progress",
                                            "input.$": "$.input",
                                            "data.$": "$.data"
                                        },
                                        "StateMachineArn": "${IngestFixityStateMachine}"
                                    },
                                    "Next": "Fixity completed"
                                },
                                "Fixity completed": {
                                    "Type": "Task",
                                    "Resource": "${x0}",
                                    "Parameters": {
                                        "operation": "fixity-completed",
                                        "nestedStateOutput.$": "$"
                                    },
                                    "Retry": [
                                        {
                                            "ErrorEquals": [
                                                "States.ALL"
                                            ],
                                            "IntervalSeconds": 1,
                                            "MaxAttempts": 2,
                                            "BackoffRate": 1.1
                                        }
                                    ],
                                    "Next": "Choose by media type"
                                },
                                "Choose by media type": {
                                    "Type": "Choice",
                                    "Choices": [
                                        {
                                            "Variable":  "$.input.type",
                                            "StringEquals": "image",
                                            "Next": "Start image ingest (nested)"
                                        },
                                        {
                                            "Variable":  "$.input.type",
                                            "StringEquals": "video",
                                            "Next": "Start video ingest (nested)"
                                        },
                                        {
                                            "Variable":  "$.input.type",
                                            "StringEquals": "audio",
                                            "Next": "Start audio ingest (nested)"
                                        },
                                        {
                                            "Variable":  "$.input.type",
                                            "StringEquals": "document",
                                            "Next": "Start document ingest (nested)"
                                        }
                                    ],
                                    "Default": "Media type not supported"
                                },
                                "Start image ingest (nested)": {
                                    "Type": "Task",
                                    "Resource": "arn:aws:states:::states:startExecution.sync",
                                    "Parameters": {
                                        "Input": {
                                            "operation": "run-imageinfo",
                                            "uuid.$": "$.uuid",
                                            "status": "NOT_STARTED",
                                            "progress": 0,
                                            "input.$": "$.input",
                                            "data.$": "$.data"
                                        },
                                        "StateMachineArn": "${IngestImageStateMachine}"
                                    },
                                    "Next": "Update record"
                                },
                                "Start video ingest (nested)": {
                                    "Type": "Task",
                                    "Resource": "arn:aws:states:::states:startExecution.sync",
                                    "Parameters": {
                                        "Input": {
                                            "operation": "run-mediainfo",
                                            "uuid.$": "$.uuid",
                                            "status": "NOT_STARTED",
                                            "progress": 0,
                                            "input.$": "$.input",
                                            "data.$": "$.data"
                                        },
                                        "StateMachineArn": "${IngestVideoStateMachine}"
                                    },
                                    "Next": "Update record"
                                },
                                "Start audio ingest (nested)": {
                                    "Type": "Task",
                                    "Resource": "arn:aws:states:::states:startExecution.sync",
                                    "Parameters": {
                                        "Input": {
                                            "operation": "run-mediainfo",
                                            "uuid.$": "$.uuid",
                                            "status": "NOT_STARTED",
                                            "progress": 0,
                                            "input.$": "$.input",
                                            "data.$": "$.data"
                                        },
                                        "StateMachineArn": "${IngestAudioStateMachine}"
                                    },
                                    "Next": "Update record"
                                },
                                "Start document ingest (nested)": {
                                    "Type": "Task",
                                    "Resource": "arn:aws:states:::states:startExecution.sync",
                                    "Parameters": {
                                        "Input": {
                                            "operation": "run-docinfo",
                                            "uuid.$": "$.uuid",
                                            "status": "NOT_STARTED",
                                            "progress": 0,
                                            "input.$": "$.input",
                                            "data.$": "$.data"
                                        },
                                        "StateMachineArn": "${IngestDocumentStateMachine}"
                                    },
                                    "Next": "Update record"
                                },
                                "Media type not supported": {
                                    "Type": "Fail"
                                },
                                "Update record": {
                                    "Type": "Task",
                                    "Resource": "${x0}",
                                    "Parameters": {
                                        "operation": "update-record",
                                        "nestedStateOutput.$": "$"
                                    },
                                    "Retry": [
                                        {
                                            "ErrorEquals": [
                                                "States.ALL"
                                            ],
                                            "IntervalSeconds": 1,
                                            "MaxAttempts": 4,
                                            "BackoffRate": 1.2
                                        }
                                    ],
                                    "Next": "Index ingest results"
                                },
                                "Index ingest results": {
                                    "Type": "Task",
                                    "Resource": "${x0}",
                                    "Parameters": {
                                        "operation": "index-ingest-results",
                                        "uuid.$": "$.uuid",
                                        "status": "NOT_STARTED",
                                        "progress": 0,
                                        "input.$": "$.input",
                                        "data.$": "$.data"
                                    },
                                    "Retry": [
                                        {
                                            "ErrorEquals": [
                                                "States.ALL"
                                            ],
                                            "IntervalSeconds": 1,
                                            "MaxAttempts": 4,
                                            "BackoffRate": 1.2
                                        }
                                    ],
                                    "Next": "Completed"
                                },
                                "Completed": {
                                    "Type": "Task",
                                    "Resource": "${x0}",
                                    "Parameters": {
                                        "operation": "job-completed",
                                        "uuid.$": "$.uuid",
                                        "status.$": "$.status",
                                        "progress.$": "$.progress",
                                        "input.$": "$.input",
                                        "data.$": "$.data"
                                    },
                                    "End": true
                                }
                            }
                        }
                    -
                        {
                            x0: !GetAtt IngestMainLambda.Arn
                        }

    ################################################################################
    #
    # Ingest Automation
    # * Ingest status updater
    # * Ingest Table DDB Stream Event
    # * Backlog Status Change Event (Ingest) supports AWS Elemental MediaConvert
    #
    ################################################################################
    IngestStatusUpdaterLogGroup:
        Type: AWS::Logs::LogGroup
        Metadata:
            cfn_nag:
                rules_to_suppress:
                    -
                        id: W84
                        reason: Use default encryption. Disable additional KMS encryption requirement. https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html
        Properties:
            LogGroupName: !Sub
                - /aws/lambda/${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Workflow
                    - IngestStatusUpdater
                    - Name
            RetentionInDays: 7

    IngestStatusUpdaterRole:
        Type: AWS::IAM::Role
        Properties:
            AssumeRolePolicyDocument:
                Version: "2012-10-17"
                Statement:
                    -
                        Effect: Allow
                        Action: sts:AssumeRole
                        Principal:
                            Service: lambda.amazonaws.com
            Path: !Sub /${ResourcePrefix}/
            ManagedPolicyArns:
                - arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess
            Policies:
                -
                    PolicyName: !Sub ${ResourcePrefix}-BacklogEventBridgeEvent
                    PolicyDocument:
                        Version: "2012-10-17"
                        Statement:
                            # CloudWatch Logs
                            -
                                Effect: Allow
                                Action:
                                    - logs:CreateLogGroup
                                    - logs:CreateLogStream
                                    - logs:PutLogEvents
                                Resource: !GetAtt IngestStatusUpdaterLogGroup.Arn
                            # Step Functions - send task results on MediaConvert Job Status Change Event
                            -
                                Effect: Allow
                                Action:
                                    - states:SendTaskSuccess
                                    - states:SendTaskFailure
                                Resource:
                                    - !Ref IngestVideoStateMachine
                                    - !Ref IngestAudioStateMachine
                            # DynamoDB - get and delete token from ServiceToken
                            -
                                Effect: Allow
                                Action:
                                    - dynamodb:Scan
                                    - dynamodb:Query
                                    - dynamodb:UpdateItem
                                    - dynamodb:DeleteItem
                                Resource: !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${ResourcePrefix}-*
                            # IoT (TO BE REMOVED)
                            -
                                Effect: Allow
                                Action: iot:Publish
                                Resource: !Sub arn:aws:iot:${AWS::Region}:${AWS::AccountId}:topic/${IotTopic}
                            # SNS
                            -
                                Effect: Allow
                                Action: sns:Publish
                                Resource: !Ref SNSTopic
                -
                    PolicyName: !Sub ${ResourcePrefix}-DDBStreamEvent
                    PolicyDocument:
                        Version: "2012-10-17"
                        Statement:
                            # DynamoDB stream permission
                            -
                                Effect: Allow
                                Action:
                                    - dynamodb:DescribeStream
                                    - dynamodb:GetRecords
                                    - dynamodb:GetShardIterator
                                    - dynamodb:ListStreams
                                Resource: !GetAtt IngestTable.StreamArn
                            # S3 - allow to list objects on REMOVE event
                            -
                                Effect: Allow
                                Action: s3:ListBucket
                                Resource: !Sub arn:aws:s3:::${ProxyBucket}
                            # S3 - allow to delete objects on REMOVE event
                            -
                                Effect: Allow
                                Action:
                                    - s3:GetObject
                                    - s3:DeleteObject
                                Resource: !Sub arn:aws:s3:::${ProxyBucket}/*
                            # OpenSearch - allow delete document on REMOVE event
                            -
                                Effect: Allow
                                Action:
                                    - es:ESHttpGet
                                    - es:ESHttpHead
                                    - es:ESHttpPost
                                    - es:ESHttpPut
                                    - es:ESHttpDelete
                                Resource:
                                    - !Sub arn:aws:es:${AWS::Region}:${AWS::AccountId}:domain/${OpenSearchDomainName}
                                    - !Sub arn:aws:es:${AWS::Region}:${AWS::AccountId}:domain/${OpenSearchDomainName}/*

    IngestStatusUpdaterLambda:
        Type: AWS::Lambda::Function
        Metadata:
            cfn_nag:
                rules_to_suppress:
                    -
                        id: W89
                        reason: Workflow not using VPC
                    -
                        id: W92
                        reason: Workflow not limiting simultaneous executions
        Properties:
            FunctionName: !Sub
                - ${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Workflow
                    - IngestStatusUpdater
                    - Name
            Description: !Sub (${SolutionLowerCaseId}) ingest status updater lambda (128MB)
            Runtime: !FindInMap
                - Node
                - Runtime
                - Version
            MemorySize: 128
            Timeout: 900
            Handler: index.handler
            Role: !GetAtt IngestStatusUpdaterRole.Arn
            Code:
                S3Bucket: !Ref S3Bucket
                S3Key: !Sub
                    - ${KeyPrefix}/${package}
                    - package: !FindInMap
                        - Workflow
                        - IngestStatusUpdater
                        - Package
            Layers:
                - !Ref AwsSdkLayer
                - !Ref CoreLibLayer
            TracingConfig:
                Mode: Active
            Environment:
                Variables:
                    ENV_EXPECTED_BUCKET_OWNER: !Ref AWS::AccountId
                    ENV_CUSTOM_USER_AGENT: !Ref CustomUserAgent
                    ENV_ANONYMOUS_USAGE: !Ref AnonymousUsage
                    ENV_SOLUTION_UUID: !Ref SolutionUuid
                    ENV_SOLUTION_ID: !Ref SolutionId
                    ENV_RESOURCE_PREFIX: !Ref ResourcePrefix
                    ENV_IOT_HOST: !Ref IotHost
                    ENV_IOT_TOPIC: !Ref IotTopic
                    ENV_INGEST_BUCKET: !Ref IngestBucket
                    ENV_PROXY_BUCKET: !Ref ProxyBucket
                    ENV_SNS_TOPIC_ARN: !Ref SNSTopic
                    ENV_ES_DOMAIN_ENDPOINT: !Ref OpenSearchDomainEndpoint

    ################################################################################
    # DynamoDB Ingest Table DDB Stream Event
    IngestTableStreamEventSource:
        Type: AWS::Lambda::EventSourceMapping
        Properties:
            Enabled: true
            EventSourceArn: !GetAtt IngestTable.StreamArn
            FunctionName: !Ref IngestStatusUpdaterLambda
            BatchSize: 1
            MaximumRetryAttempts: 10
            StartingPosition: LATEST

    ################################################################################
    # Backlog Ingest Status Change Event
    # * events fired by our Service Backlog EventBus
    IngestBacklogStatusChangeEvent:
        Type: AWS::Events::Rule
        Properties:
            Name: !Sub ${ResourcePrefix}-IngestBacklogStatusChangeEvent
            Description: !Sub "(${SolutionId}) Backlog Ingest Status Change Event"
            EventBusName: !GetAtt EventBridgeBus.Name
            EventPattern:
                source:
                    - !FindInMap
                        - EventBridge
                        - Rule
                        - Source
                region:
                    - !Ref AWS::Region
                detail-type:
                    - !FindInMap
                        - EventBridge
                        - Rule
                        - Detail
                detail:
                    status:
                        # MediaConvert
                        - COMPLETE
                        - CANCELED
                        - ERROR
                    serviceApi:
                        - mediaconvert:createjob
            State: ENABLED
            Targets:
                -
                    Id: !Sub Id-${IngestStatusUpdaterLambda}
                    Arn: !GetAtt IngestStatusUpdaterLambda.Arn

    IngestBacklogStatusChangePermission:
        Type: AWS::Lambda::Permission
        Properties:
            FunctionName: !Ref IngestStatusUpdaterLambda
            Action: lambda:InvokeFunction
            Principal: events.amazonaws.com
            SourceArn: !GetAtt IngestBacklogStatusChangeEvent.Arn

    ################################################################################
    #
    # Analysis Workflow
    # * State Machine Service Role
    # * Audio (Nested) State Machine
    # * Video (Nested) State Machine
    # * Image (Nested) State Machine
    # * Document (Nested) State Machine
    # * Analysis (Main) State Machine
    #
    ################################################################################
    AnalysisStateMachineServiceRole:
        Type: AWS::IAM::Role
        Properties:
            AssumeRolePolicyDocument:
                Version: "2012-10-17"
                Statement:
                    -
                        Effect: Allow
                        Action: sts:AssumeRole
                        Principal:
                            Service: !Sub states.${AWS::Region}.amazonaws.com
            Path: !Sub /${ResourcePrefix}/
            Policies:
                -
                    PolicyName: !Sub ${ResourcePrefix}-analysis-statemachine-role
                    PolicyDocument:
                        Version: "2012-10-17"
                        Statement:
                            -
                                Effect: Allow
                                Action: lambda:InvokeFunction
                                Resource:
                                    - !GetAtt AnalysisVideoLambda.Arn
                                    - !GetAtt AnalysisAudioLambda.Arn
                                    - !GetAtt AnalysisImageLambda.Arn
                                    - !GetAtt AnalysisDocumentLambda.Arn
                                    - !GetAtt AnalysisMainLambda.Arn
                            # Below polices are needed for Service Integration of nested workflows
                            # https://docs.aws.amazon.com/step-functions/latest/dg/stepfunctions-iam.html
                            -
                                Effect: Allow
                                Action: states:StartExecution
                                Resource:
                                    - !Sub
                                        - arn:aws:states:${AWS::Region}:${AWS::AccountId}:stateMachine:${ResourcePrefix}-${name}
                                        - name: !FindInMap
                                            - Workflow
                                            - AnalysisVideo
                                            - Name
                                    - !Sub
                                        - arn:aws:states:${AWS::Region}:${AWS::AccountId}:stateMachine:${ResourcePrefix}-${name}
                                        - name: !FindInMap
                                            - Workflow
                                            - AnalysisAudio
                                            - Name
                                    - !Sub
                                        - arn:aws:states:${AWS::Region}:${AWS::AccountId}:stateMachine:${ResourcePrefix}-${name}
                                        - name: !FindInMap
                                            - Workflow
                                            - AnalysisImage
                                            - Name
                                    - !Sub
                                        - arn:aws:states:${AWS::Region}:${AWS::AccountId}:stateMachine:${ResourcePrefix}-${name}
                                        - name: !FindInMap
                                            - Workflow
                                            - AnalysisDocument
                                            - Name
                            -
                                Effect: Allow
                                Action:
                                    - states:DescribeExecution
                                    - states:StopExecution
                                Resource:
                                    - !Sub
                                        - arn:aws:states:${AWS::Region}:${AWS::AccountId}:execution:${ResourcePrefix}-${name}:*
                                        - name: !FindInMap
                                            - Workflow
                                            - AnalysisVideo
                                            - Name
                                    - !Sub
                                        - arn:aws:states:${AWS::Region}:${AWS::AccountId}:execution:${ResourcePrefix}-${name}:*
                                        - name: !FindInMap
                                            - Workflow
                                            - AnalysisAudio
                                            - Name
                                    - !Sub
                                        - arn:aws:states:${AWS::Region}:${AWS::AccountId}:execution:${ResourcePrefix}-${name}:*
                                        - name: !FindInMap
                                            - Workflow
                                            - AnalysisImage
                                            - Name
                                    - !Sub
                                        - arn:aws:states:${AWS::Region}:${AWS::AccountId}:execution:${ResourcePrefix}-${name}:*
                                        - name: !FindInMap
                                            - Workflow
                                            - AnalysisDocument
                                            - Name
                            -
                                Effect: Allow
                                Action:
                                    - events:PutTargets
                                    - events:PutRule
                                    - events:DescribeRule
                                Resource: !Sub arn:aws:events:${AWS::Region}:${AWS::AccountId}:rule/StepFunctionsGetEventsForStepFunctionsExecutionRule

    ################################################################################
    # Analysis Video State Machine resources
    AnalysisVideoLogGroup:
        Type: AWS::Logs::LogGroup
        Metadata:
            cfn_nag:
                rules_to_suppress:
                    -
                        id: W84
                        reason: Use default encryption. Disable additional KMS encryption requirement. https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html
        Properties:
            LogGroupName: !Sub
                - /aws/lambda/${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Workflow
                    - AnalysisVideo
                    - Name
            RetentionInDays: 7

    AnalysisVideoRole:
        Type: AWS::IAM::Role
        Metadata:
            cfn_nag:
                rules_to_suppress:
                    -
                        id: W11
                        reason: Wildcard character is prefixed and scoped with the ResourcePrefix
        Properties:
            AssumeRolePolicyDocument:
                Version: "2012-10-17"
                Statement:
                    -
                        Effect: Allow
                        Action: sts:AssumeRole
                        Principal:
                            Service: lambda.amazonaws.com
            Path: !Sub /${ResourcePrefix}/
            ManagedPolicyArns:
                - arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess
            Policies:
                -
                    PolicyName: !Sub ${ResourcePrefix}-analysis-video
                    PolicyDocument:
                        Version: "2012-10-17"
                        Statement:
                            # CloudWatch Logs
                            -
                                Effect: Allow
                                Action:
                                    - logs:CreateLogGroup
                                    - logs:CreateLogStream
                                    - logs:PutLogEvents
                                Resource: !GetAtt AnalysisVideoLogGroup.Arn
                            # S3 - proxy bucket
                            -
                                Effect: Allow
                                Action: s3:ListBucket
                                Resource: !Sub arn:aws:s3:::${ProxyBucket}
                            -
                                Effect: Allow
                                Action:
                                    - s3:GetObject
                                    - s3:PutObject
                                Resource: !Sub arn:aws:s3:::${ProxyBucket}/*
                            # DynamoDB
                            -
                                Effect: Allow
                                Action:
                                    - dynamodb:Scan
                                    - dynamodb:Query
                                    - dynamodb:PutItem
                                    - dynamodb:UpdateItem
                                    - dynamodb:DeleteItem
                                Resource: !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${ResourcePrefix}-*
                            # Rekognition
                            -
                                Effect: Allow
                                Action:
                                    - rekognition:DescribeCollection
                                    - rekognition:StartContentModeration
                                    - rekognition:StartCelebrityRecognition
                                    - rekognition:StartFaceDetection
                                    - rekognition:StartFaceSearch
                                    - rekognition:StartLabelDetection
                                    - rekognition:StartPersonTracking
                                    - rekognition:StartSegmentDetection
                                    - rekognition:StartTextDetection
                                    - rekognition:GetContentModeration
                                    - rekognition:GetCelebrityRecognition
                                    - rekognition:GetFaceDetection
                                    - rekognition:GetFaceSearch
                                    - rekognition:GetLabelDetection
                                    - rekognition:GetPersonTracking
                                    - rekognition:GetSegmentDetection
                                    - rekognition:GetTextDetection
                                    - rekognition:DetectFaces
                                    - rekognition:DetectLabels
                                    - rekognition:DetectModerationLabels
                                    - rekognition:DetectText
                                    - rekognition:RecognizeCelebrities
                                    - rekognition:SearchFacesByImage
                                Resource: "*"
                            # IAM - Rekognition/Textract pass SNS topic role for notification
                            -
                                Effect: Allow
                                Action: iam:PassRole
                                Resource: !GetAtt BacklogTopicRole.Arn
                            # EventBridge
                            -
                                Effect: Allow
                                Action: events:PutEvents
                                Resource: !GetAtt EventBridgeBus.Arn
                            # Backlog Custom Labels state machine
                            -
                                Effect: Allow
                                Action: states:StartExecution
                                Resource: !Ref BacklogCustomLabelsStateMachine
                            -
                                Effect: Allow
                                Action: states:DescribeExecution
                                Resource: !Sub arn:aws:states:${AWS::Region}:${AWS::AccountId}:execution:${BacklogCustomLabelsStateMachine.Name}:*
                            # Rekognition Custom Labels
                            -
                                Effect: Allow
                                Action: rekognition:DescribeProjectVersions
                                Resource: !Sub arn:aws:rekognition:${AWS::Region}:${AWS::AccountId}:project/*/*
                            # OpenSearch
                            -
                                Effect: Allow
                                Action:
                                    - es:ESHttpGet
                                    - es:ESHttpHead
                                    - es:ESHttpPost
                                    - es:ESHttpPut
                                    - es:ESHttpDelete
                                Resource:
                                    - !Sub arn:aws:es:${AWS::Region}:${AWS::AccountId}:domain/${OpenSearchDomainName}
                                    - !Sub arn:aws:es:${AWS::Region}:${AWS::AccountId}:domain/${OpenSearchDomainName}/*

    AnalysisVideoLambda:
        Type: AWS::Lambda::Function
        Metadata:
            cfn_nag:
                rules_to_suppress:
                    -
                        id: W89
                        reason: Workflow not using VPC
                    -
                        id: W92
                        reason: Workflow not limiting simultaneous executions
        Properties:
            FunctionName: !Sub
                - ${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Workflow
                    - AnalysisVideo
                    - Name
            Description: !Sub (${SolutionLowerCaseId}) analysis video state machine lambda (2048MB)
            Runtime: !FindInMap
                - Node
                - Runtime
                - Version
            MemorySize: 2048
            Timeout: 900
            Handler: index.handler
            Role: !GetAtt AnalysisVideoRole.Arn
            Code:
                S3Bucket: !Ref S3Bucket
                S3Key: !Sub
                    - ${KeyPrefix}/${package}
                    - package: !FindInMap
                        - Workflow
                        - AnalysisVideo
                        - Package
            Layers:
                - !Ref AwsSdkLayer
                - !Ref CoreLibLayer
                - !Ref CanvasLibLayer
                - !Ref BacklogLibLayer
            TracingConfig:
                Mode: Active
            Environment:
                Variables:
                    ENV_EXPECTED_BUCKET_OWNER: !Ref AWS::AccountId
                    ENV_CUSTOM_USER_AGENT: !Ref CustomUserAgent
                    ENV_ANONYMOUS_USAGE: !Ref AnonymousUsage
                    ENV_SOLUTION_UUID: !Ref SolutionUuid
                    ENV_SOLUTION_ID: !Ref SolutionId
                    ENV_RESOURCE_PREFIX: !Ref ResourcePrefix
                    ENV_IOT_HOST: !Ref IotHost
                    ENV_IOT_TOPIC: !Ref IotTopic
                    ENV_PROXY_BUCKET: !Ref ProxyBucket
                    ENV_ES_DOMAIN_ENDPOINT: !Ref OpenSearchDomainEndpoint
                    ## Service Backlog ##
                    ENV_BACKLOG_EB_BUS: !GetAtt EventBridgeBus.Name
                    ENV_BACKLOG_TABLE: !Ref BacklogTable
                    ENV_BACKLOG_TOPIC_ARN: !Ref BacklogTopic
                    ENV_BACKLOG_TOPIC_ROLE_ARN: !GetAtt BacklogTopicRole.Arn
                    ENV_ATOMICLOCK_TABLE: !Ref AtomicLockTable

    AnalysisVideoStateMachine:
        Type: AWS::StepFunctions::StateMachine
        Properties:
            StateMachineName: !Sub
                - ${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Workflow
                    - AnalysisVideo
                    - Name
            RoleArn: !GetAtt AnalysisStateMachineServiceRole.Arn
            DefinitionString:
                !Sub
                    - |-
                        {
                            "StartAt": "Start video analysis",
                            "States": {
                                "Start video analysis": {
                                    "Type": "Parallel",
                                    "Branches": [
                                        {
                                            "StartAt": "Frame-based detection iterators",
                                            "States": {
                                                "Frame-based detection iterators": {
                                                    "Type": "Task",
                                                    "Resource": "${x0}",
                                                    "Parameters": {
                                                        "operation": "prepare-frame-detection-iterators",
                                                        "uuid.$": "$.uuid",
                                                        "input.$": "$.input",
                                                        "data.$": "$.data",
                                                        "status": "NOT_STARTED",
                                                        "progress": 0
                                                    },
                                                    "Next": "Frame based analysis",
                                                    "Retry": [
                                                        {
                                                            "ErrorEquals": [
                                                                "States.ALL"
                                                            ],
                                                            "IntervalSeconds": 1,
                                                            "MaxAttempts": 2,
                                                            "BackoffRate": 1.1
                                                        }
                                                    ]
                                                },
                                                "Frame based analysis": {
                                                    "Type": "Map",
                                                    "ItemsPath": "$.data.iterators",
                                                    "MaxConcurrency": 10,
                                                    "Iterator": {
                                                        "StartAt": "Detect frame (Iterator)",
                                                        "States": {
                                                            "Detect frame (Iterator)": {
                                                                "Type": "Task",
                                                                "Resource": "${x0}",
                                                                "Parameters": {
                                                                    "operation": "detect-frame-iterator",
                                                                    "uuid.$": "$.uuid",
                                                                    "status.$": "$.status",
                                                                    "progress.$": "$.progress",
                                                                    "data.$": "$.data"
                                                                },
                                                                "Next": "More frames (Iterator)?",
                                                                "Retry": [
                                                                    {
                                                                        "ErrorEquals": [
                                                                            "States.ALL"
                                                                        ],
                                                                        "IntervalSeconds": 1,
                                                                        "MaxAttempts": 2,
                                                                        "BackoffRate": 1.1
                                                                    }
                                                                ]
                                                            },
                                                            "More frames (Iterator)?": {
                                                                "Type": "Choice",
                                                                "Choices": [
                                                                    {
                                                                        "Variable": "$.status",
                                                                        "StringEquals": "COMPLETED",
                                                                        "Next": "Detect frame completed"
                                                                    }
                                                                ],
                                                                "Default": "Detect frame (Iterator)"
                                                            },
                                                            "Detect frame completed": {
                                                                "Type": "Succeed"
                                                            }
                                                        }
                                                    },
                                                    "ResultPath": "$.data.iterators",
                                                    "Next": "Frame-based track iterators"
                                                },
                                                "Frame-based track iterators": {
                                                    "Type": "Task",
                                                    "Resource": "${x0}",
                                                    "Parameters": {
                                                        "operation": "prepare-frame-track-iterators",
                                                        "uuid.$": "$.uuid",
                                                        "data.$": "$.data",
                                                        "status": "NOT_STARTED",
                                                        "progress": 0
                                                    },
                                                    "Next": "Frame based track iterators",
                                                    "Retry": [
                                                        {
                                                            "ErrorEquals": [
                                                                "States.ALL"
                                                            ],
                                                            "IntervalSeconds": 1,
                                                            "MaxAttempts": 2,
                                                            "BackoffRate": 1.1
                                                        }
                                                    ]
                                                },
                                                "Frame based track iterators": {
                                                    "Type": "Map",
                                                    "ItemsPath": "$.data.iterators",
                                                    "MaxConcurrency": 10,
                                                    "Iterator": {
                                                        "StartAt": "Create frame-based track (Iterator)",
                                                        "States": {
                                                            "Create frame-based track (Iterator)": {
                                                                "Type": "Task",
                                                                "Resource": "${x0}",
                                                                "Parameters": {
                                                                    "operation": "create-track-iterator",
                                                                    "uuid.$": "$.uuid",
                                                                    "status.$": "$.status",
                                                                    "progress.$": "$.progress",
                                                                    "data.$": "$.data"
                                                                },
                                                                "Next": "More frame-based tracks (Iterator)?",
                                                                "Retry": [
                                                                    {
                                                                        "ErrorEquals": [
                                                                            "States.ALL"
                                                                        ],
                                                                        "IntervalSeconds": 1,
                                                                        "MaxAttempts": 2,
                                                                        "BackoffRate": 1.1
                                                                    }
                                                                ]
                                                            },
                                                            "More frame-based tracks (Iterator)?": {
                                                                "Type": "Choice",
                                                                "Choices": [
                                                                    {
                                                                        "Variable": "$.status",
                                                                        "StringEquals": "COMPLETED",
                                                                        "Next": "Index frame-based analysis (Iterator)"
                                                                    }
                                                                ],
                                                                "Default": "Create frame-based track (Iterator)"
                                                            },
                                                            "Index frame-based analysis (Iterator)": {
                                                                "Type": "Task",
                                                                "Resource": "${x0}",
                                                                "Parameters": {
                                                                    "operation": "index-analysis-results",
                                                                    "uuid.$": "$.uuid",
                                                                    "status.$": "$.status",
                                                                    "progress.$": "$.progress",
                                                                    "data.$": "$.data"
                                                                },
                                                                "End": true,
                                                                "Retry": [
                                                                    {
                                                                        "ErrorEquals": [
                                                                            "States.ALL"
                                                                        ],
                                                                        "IntervalSeconds": 1,
                                                                        "MaxAttempts": 2,
                                                                        "BackoffRate": 1.1
                                                                    }
                                                                ]
                                                            }
                                                        }
                                                    },
                                                    "ResultPath": "$.data.iterators",
                                                    "End": true
                                                }
                                            }
                                        },
                                        {
                                            "StartAt": "Video-based detection iterators",
                                            "States": {
                                                "Video-based detection iterators": {
                                                    "Type": "Task",
                                                    "Resource": "${x0}",
                                                    "Parameters": {
                                                        "operation": "prepare-video-detection-iterators",
                                                        "uuid.$": "$.uuid",
                                                        "input.$": "$.input",
                                                        "data.$": "$.data",
                                                        "status": "NOT_STARTED",
                                                        "progress": 0
                                                    },
                                                    "Next": "Video based analysis",
                                                    "Retry": [
                                                        {
                                                            "ErrorEquals": [
                                                                "States.ALL"
                                                            ],
                                                            "IntervalSeconds": 1,
                                                            "MaxAttempts": 2,
                                                            "BackoffRate": 1.1
                                                        }
                                                    ]
                                                },
                                                "Video based analysis": {
                                                    "Type": "Map",
                                                    "ItemsPath": "$.data.iterators",
                                                    "MaxConcurrency": 10,
                                                    "Iterator": {
                                                        "StartAt": "Start detection and wait (Iterator)",
                                                        "States": {
                                                            "Start detection and wait (Iterator)": {
                                                                "Type": "Task",
                                                                "Resource":"arn:aws:states:::lambda:invoke.waitForTaskToken",
                                                                "Parameters": {
                                                                    "FunctionName": "${a0}",
                                                                    "Payload": {
                                                                        "token.$":"$$.Task.Token",
                                                                        "operation": "start-detection-iterator",
                                                                        "uuid.$": "$.uuid",
                                                                        "status": "NOT_STARTED",
                                                                        "progress": 0,
                                                                        "data.$": "$.data"
                                                                    }
                                                                },
                                                                "TimeoutSeconds": 86400,
                                                                "Next": "Collect detection results (Iterator)"
                                                            },
                                                            "Collect detection results (Iterator)": {
                                                                "Type": "Task",
                                                                "Resource": "${x0}",
                                                                "Parameters": {
                                                                    "uuid.$": "$.uuid",
                                                                    "status.$": "$.status",
                                                                    "progress.$": "$.progress",
                                                                    "operation": "collect-results-iterator",
                                                                    "data.$": "$.data"
                                                                },
                                                                "Next": "Create video-based track (Iterator)",
                                                                "Retry": [
                                                                    {
                                                                        "ErrorEquals": [
                                                                            "States.ALL"
                                                                        ],
                                                                        "IntervalSeconds": 1,
                                                                        "MaxAttempts": 2,
                                                                        "BackoffRate": 1.1
                                                                    }
                                                                ]
                                                            },
                                                            "Create video-based track (Iterator)": {
                                                                "Type": "Task",
                                                                "Resource": "${x0}",
                                                                "Parameters": {
                                                                    "operation": "create-track-iterator",
                                                                    "uuid.$": "$.uuid",
                                                                    "status.$": "$.status",
                                                                    "progress.$": "$.progress",
                                                                    "data.$": "$.data"
                                                                },
                                                                "Next": "More video-based tracks (Iterator)?",
                                                                "Retry": [
                                                                    {
                                                                        "ErrorEquals": [
                                                                            "States.ALL"
                                                                        ],
                                                                        "IntervalSeconds": 1,
                                                                        "MaxAttempts": 2,
                                                                        "BackoffRate": 1.1
                                                                    }
                                                                ]
                                                            },
                                                            "More video-based tracks (Iterator)?": {
                                                                "Type": "Choice",
                                                                "Choices": [
                                                                    {
                                                                        "Variable": "$.status",
                                                                        "StringEquals": "COMPLETED",
                                                                        "Next": "Index video-based analysis (Iterator)"
                                                                    }
                                                                ],
                                                                "Default": "Create video-based track (Iterator)"
                                                            },
                                                            "Index video-based analysis (Iterator)": {
                                                                "Type": "Task",
                                                                "Resource": "${x0}",
                                                                "Parameters": {
                                                                    "operation": "index-analysis-results",
                                                                    "uuid.$": "$.uuid",
                                                                    "status.$": "$.status",
                                                                    "progress.$": "$.progress",
                                                                    "data.$": "$.data"
                                                                },
                                                                "End": true,
                                                                "Retry": [
                                                                    {
                                                                        "ErrorEquals": [
                                                                            "States.ALL"
                                                                        ],
                                                                        "IntervalSeconds": 1,
                                                                        "MaxAttempts": 2,
                                                                        "BackoffRate": 1.1
                                                                    }
                                                                ]
                                                            }
                                                        }
                                                    },
                                                    "ResultPath": "$.data.iterators",
                                                    "End": true
                                                }
                                            }
                                        },
                                        {
                                            "StartAt": "Custom detection iterators",
                                            "States": {
                                                "Custom detection iterators": {
                                                    "Type": "Task",
                                                    "Resource": "${x0}",
                                                    "Parameters": {
                                                        "operation": "prepare-custom-detection-iterators",
                                                        "uuid.$": "$.uuid",
                                                        "input.$": "$.input",
                                                        "data.$": "$.data",
                                                        "status": "NOT_STARTED",
                                                        "progress": 0
                                                    },
                                                    "Next": "Custom analysis",
                                                    "Retry": [
                                                        {
                                                            "ErrorEquals": [
                                                                "States.ALL"
                                                            ],
                                                            "IntervalSeconds": 1,
                                                            "MaxAttempts": 2,
                                                            "BackoffRate": 1.1
                                                        }
                                                    ]
                                                },
                                                "Custom analysis": {
                                                    "Type": "Map",
                                                    "ItemsPath": "$.data.iterators",
                                                    "MaxConcurrency": 10,
                                                    "Iterator": {
                                                        "StartAt": "Start custom and wait (Iterator)",
                                                        "States": {
                                                            "Start custom and wait (Iterator)": {
                                                                "Type": "Task",
                                                                "Resource":"arn:aws:states:::lambda:invoke.waitForTaskToken",
                                                                "Parameters": {
                                                                    "FunctionName": "${a0}",
                                                                    "Payload": {
                                                                        "token.$":"$$.Task.Token",
                                                                        "operation": "start-detection-iterator",
                                                                        "uuid.$": "$.uuid",
                                                                        "status": "NOT_STARTED",
                                                                        "progress": 0,
                                                                        "data.$": "$.data"
                                                                    }
                                                                },
                                                                "TimeoutSeconds": 86400,
                                                                "Next": "Collect custom results (Iterator)"
                                                            },
                                                            "Collect custom results (Iterator)": {
                                                                "Type": "Task",
                                                                "Resource": "${x0}",
                                                                "Parameters": {
                                                                    "uuid.$": "$.uuid",
                                                                    "status.$": "$.status",
                                                                    "progress.$": "$.progress",
                                                                    "operation": "collect-results-iterator",
                                                                    "data.$": "$.data"
                                                                },
                                                                "Next": "Create custom track (Iterator)",
                                                                "Retry": [
                                                                    {
                                                                        "ErrorEquals": [
                                                                            "States.ALL"
                                                                        ],
                                                                        "IntervalSeconds": 1,
                                                                        "MaxAttempts": 2,
                                                                        "BackoffRate": 1.1
                                                                    }
                                                                ]
                                                            },
                                                            "Create custom track (Iterator)": {
                                                                "Type": "Task",
                                                                "Resource": "${x0}",
                                                                "Parameters": {
                                                                    "operation": "create-track-iterator",
                                                                    "uuid.$": "$.uuid",
                                                                    "status.$": "$.status",
                                                                    "progress.$": "$.progress",
                                                                    "data.$": "$.data"
                                                                },
                                                                "Next": "More custom tracks (Iterator)?",
                                                                "Retry": [
                                                                    {
                                                                        "ErrorEquals": [
                                                                            "States.ALL"
                                                                        ],
                                                                        "IntervalSeconds": 1,
                                                                        "MaxAttempts": 2,
                                                                        "BackoffRate": 1.1
                                                                    }
                                                                ]
                                                            },
                                                            "More custom tracks (Iterator)?": {
                                                                "Type": "Choice",
                                                                "Choices": [
                                                                    {
                                                                        "Variable": "$.status",
                                                                        "StringEquals": "COMPLETED",
                                                                        "Next": "Index custom analysis (Iterator)"
                                                                    }
                                                                ],
                                                                "Default": "Create custom track (Iterator)"
                                                            },
                                                            "Index custom analysis (Iterator)": {
                                                                "Type": "Task",
                                                                "Resource": "${x0}",
                                                                "Parameters": {
                                                                    "operation": "index-analysis-results",
                                                                    "uuid.$": "$.uuid",
                                                                    "status.$": "$.status",
                                                                    "progress.$": "$.progress",
                                                                    "data.$": "$.data"
                                                                },
                                                                "End": true,
                                                                "Retry": [
                                                                    {
                                                                        "ErrorEquals": [
                                                                            "States.ALL"
                                                                        ],
                                                                        "IntervalSeconds": 1,
                                                                        "MaxAttempts": 2,
                                                                        "BackoffRate": 1.1
                                                                    }
                                                                ]
                                                            }
                                                        }
                                                    },
                                                    "ResultPath": "$.data.iterators",
                                                    "End": true
                                                }
                                            }
                                        }
                                    ],
                                    "Next": "Video analysis completed"
                                },
                                "Video analysis completed": {
                                    "Type": "Task",
                                    "Resource": "${x0}",
                                    "Parameters": {
                                        "operation": "job-completed",
                                        "stateExecution.$": "$$.Execution",
                                        "parallelStateOutputs.$": "$"
                                    },
                                    "End": true,
                                    "Retry": [
                                        {
                                            "ErrorEquals": [
                                                "States.ALL"
                                            ],
                                            "IntervalSeconds": 1,
                                            "MaxAttempts": 2,
                                            "BackoffRate": 1.1
                                        }
                                    ]
                                }
                            }
                        }
                    -
                        {
                            a0: !Ref AnalysisVideoLambda,
                            x0: !GetAtt AnalysisVideoLambda.Arn
                        }

    ################################################################################
    # Analysis Audio State Machine resources
    AnalysisAudioLogGroup:
        Type: AWS::Logs::LogGroup
        Metadata:
            cfn_nag:
                rules_to_suppress:
                    -
                        id: W84
                        reason: Use default encryption. Disable additional KMS encryption requirement. https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html
        Properties:
            LogGroupName: !Sub
                - /aws/lambda/${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Workflow
                    - AnalysisAudio
                    - Name
            RetentionInDays: 7

    AnalysisAudioRole:
        Type: AWS::IAM::Role
        Metadata:
            cfn_nag:
                rules_to_suppress:
                    -
                        id: W11
                        reason: Wildcard character is prefixed and scoped with the ResourcePrefix
        Properties:
            AssumeRolePolicyDocument:
                Version: "2012-10-17"
                Statement:
                    -
                        Effect: Allow
                        Action: sts:AssumeRole
                        Principal:
                            Service: lambda.amazonaws.com
            Path: !Sub /${ResourcePrefix}/
            ManagedPolicyArns:
                - arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess
            Policies:
                -
                    PolicyName: !Sub ${ResourcePrefix}-analysis-audio
                    PolicyDocument:
                        Version: "2012-10-17"
                        Statement:
                            # CloudWatch Logs
                            -
                                Effect: Allow
                                Action:
                                    - logs:CreateLogGroup
                                    - logs:CreateLogStream
                                    - logs:PutLogEvents
                                Resource: !GetAtt AnalysisAudioLogGroup.Arn
                            # S3 - proxy bucket
                            -
                                Effect: Allow
                                Action: s3:ListBucket
                                Resource: !Sub arn:aws:s3:::${ProxyBucket}
                            -
                                Effect: Allow
                                Action:
                                    - s3:GetObject
                                    - s3:PutObject
                                Resource: !Sub arn:aws:s3:::${ProxyBucket}/*
                            # DynamoDB
                            -
                                Effect: Allow
                                Action:
                                    - dynamodb:Scan
                                    - dynamodb:Query
                                    - dynamodb:PutItem
                                    - dynamodb:UpdateItem
                                    - dynamodb:DeleteItem
                                Resource: !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${ResourcePrefix}-*
                            # Comprehend
                            -
                                Effect: Allow
                                Action:
                                    - comprehend:BatchDetectDominantLanguage
                                    - comprehend:BatchDetectEntities
                                    - comprehend:BatchDetectKeyPhrases
                                    - comprehend:BatchDetectSentiment
                                    - comprehend:BatchDetectSyntax
                                    - comprehend:StartEntitiesDetectionJob
                                    - comprehend:StartKeyPhrasesDetectionJob
                                    - comprehend:StartDominantLanguageDetectionJob
                                    - comprehend:StartSentimentDetectionJob
                                    - comprehend:StartTopicsDetectionJob
                                    - comprehend:StopDominantLanguageDetectionJob
                                    - comprehend:StopEntitiesDetectionJob
                                    - comprehend:StopKeyPhrasesDetectionJob
                                    - comprehend:StopSentimentDetectionJob
                                    - comprehend:DescribeDominantLanguageDetectionJob
                                    - comprehend:DescribeEntitiesDetectionJob
                                    - comprehend:DescribeKeyPhrasesDetectionJob
                                    - comprehend:DescribeSentimentDetectionJob
                                    - comprehend:DescribeTopicsDetectionJob
                                Resource: "*"
                            -
                                Effect: Allow
                                Action: comprehend:DescribeEntityRecognizer
                                Resource: !Sub arn:aws:comprehend:${AWS::Region}:${AWS::AccountId}:entity-recognizer/*
                            # Transcribe
                            -
                                Effect: Allow
                                Action:
                                    - transcribe:GetTranscriptionJob
                                    - transcribe:StartTranscriptionJob
                                    - transcribe:GetVocabulary
                                    - transcribe:DescribeLanguageModel
                                Resource: "*"
                            # IAM - Comprehend/Transcribe pass data access role
                            -
                                Effect: Allow
                                Action: iam:PassRole
                                Resource: !GetAtt ServiceDataAccessRole.Arn
                            # EventBridge
                            -
                                Effect: Allow
                                Action: events:PutEvents
                                Resource: !GetAtt EventBridgeBus.Arn
                            # OpenSearch
                            -
                                Effect: Allow
                                Action:
                                    - es:ESHttpGet
                                    - es:ESHttpHead
                                    - es:ESHttpPost
                                    - es:ESHttpPut
                                    - es:ESHttpDelete
                                Resource:
                                    - !Sub arn:aws:es:${AWS::Region}:${AWS::AccountId}:domain/${OpenSearchDomainName}
                                    - !Sub arn:aws:es:${AWS::Region}:${AWS::AccountId}:domain/${OpenSearchDomainName}/*

    AnalysisAudioLambda:
        Type: AWS::Lambda::Function
        Metadata:
            cfn_nag:
                rules_to_suppress:
                    -
                        id: W89
                        reason: Workflow not using VPC
                    -
                        id: W92
                        reason: Workflow not limiting simultaneous executions
        Properties:
            FunctionName: !Sub
                - ${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Workflow
                    - AnalysisAudio
                    - Name
            Description: !Sub (${SolutionLowerCaseId}) analysis audio state machine lambda (512MB)
            Runtime: !FindInMap
                - Node
                - Runtime
                - Version
            MemorySize: 512
            Timeout: 900
            Handler: index.handler
            Role: !GetAtt AnalysisAudioRole.Arn
            Code:
                S3Bucket: !Ref S3Bucket
                S3Key: !Sub
                    - ${KeyPrefix}/${package}
                    - package: !FindInMap
                        - Workflow
                        - AnalysisAudio
                        - Package
            Layers:
                - !Ref AwsSdkLayer
                - !Ref CoreLibLayer
                - !Ref BacklogLibLayer
            TracingConfig:
                Mode: Active
            Environment:
                Variables:
                    ENV_EXPECTED_BUCKET_OWNER: !Ref AWS::AccountId
                    ENV_CUSTOM_USER_AGENT: !Ref CustomUserAgent
                    ENV_ANONYMOUS_USAGE: !Ref AnonymousUsage
                    ENV_SOLUTION_UUID: !Ref SolutionUuid
                    ENV_SOLUTION_ID: !Ref SolutionId
                    ENV_RESOURCE_PREFIX: !Ref ResourcePrefix
                    ENV_IOT_HOST: !Ref IotHost
                    ENV_IOT_TOPIC: !Ref IotTopic
                    ENV_PROXY_BUCKET: !Ref ProxyBucket
                    ENV_DATA_ACCESS_ROLE: !GetAtt ServiceDataAccessRole.Arn
                    ENV_ES_DOMAIN_ENDPOINT: !Ref OpenSearchDomainEndpoint
                    # Service Backlog ##
                    ENV_BACKLOG_EB_BUS: !GetAtt EventBridgeBus.Name
                    ENV_BACKLOG_TABLE: !Ref BacklogTable
                    ENV_BACKLOG_TOPIC_ARN: !Ref BacklogTopic
                    ENV_BACKLOG_TOPIC_ROLE_ARN: !GetAtt BacklogTopicRole.Arn

    AnalysisAudioStateMachine:
        Type: AWS::StepFunctions::StateMachine
        Properties:
            StateMachineName: !Sub
                - ${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Workflow
                    - AnalysisAudio
                    - Name
            RoleArn: !GetAtt AnalysisStateMachineServiceRole.Arn
            DefinitionString:
                !Sub
                    - |-
                        {
                            "StartAt": "Start transcribe and wait",
                            "States": {
                                "Start transcribe and wait": {
                                    "Type": "Task",
                                    "Resource":"arn:aws:states:::lambda:invoke.waitForTaskToken",
                                    "Parameters": {
                                        "FunctionName": "${a0}",
                                        "Payload": {
                                            "token.$":"$$.Task.Token",
                                            "operation": "start-transcribe",
                                            "uuid.$": "$.uuid",
                                            "status": "NOT_STARTED",
                                            "progress": 0,
                                            "input.$": "$.input"
                                        }
                                    },
                                    "TimeoutSeconds": 86400,
                                    "Retry": [
                                        {
                                            "ErrorEquals": [
                                                "States.ALL"
                                            ],
                                            "IntervalSeconds": 1,
                                            "MaxAttempts": 1,
                                            "BackoffRate": 1.2
                                        }
                                    ],
                                    "Next": "Collect transcribe results"
                                },
                                "Collect transcribe results": {
                                    "Type": "Task",
                                    "Resource": "${x0}",
                                    "Parameters": {
                                        "operation": "collect-transcribe-results",
                                        "uuid.$": "$.uuid",
                                        "status": "NOT_STARTED",
                                        "progress": 0,
                                        "input.$": "$.input",
                                        "data.$": "$.data"
                                    },
                                    "Next": "Got transcription data?",
                                    "Retry": [
                                        {
                                            "ErrorEquals": [
                                                "States.ALL"
                                            ],
                                            "IntervalSeconds": 1,
                                            "MaxAttempts": 4,
                                            "BackoffRate": 1.1
                                        }
                                    ]
                                },
                                "Got transcription data?": {
                                    "Type": "Choice",
                                    "Choices": [
                                        {
                                            "And": [
                                                {
                                                    "Variable": "$.data.transcribe",
                                                    "IsPresent": true
                                                },
                                                {
                                                    "Variable": "$.data.transcribe.languageCode",
                                                    "IsPresent": true
                                                },
                                                {
                                                    "Variable": "$.data.transcribe.output",
                                                    "IsPresent": true
                                                },
                                                {
                                                    "Variable": "$.data.transcribe.vtt",
                                                    "IsPresent": true
                                                }
                                            ],
                                            "Next": "Index transcribe results"
                                        }
                                    ],
                                    "Default": "Analysis completed"
                                },
                                "Index transcribe results": {
                                    "Type": "Task",
                                    "Resource": "${x0}",
                                    "Parameters": {
                                        "operation": "index-transcribe-results",
                                        "uuid.$": "$.uuid",
                                        "status": "NOT_STARTED",
                                        "progress": 0,
                                        "input.$": "$.input",
                                        "data.$": "$.data"
                                    },
                                    "Next": "Start comprehend analysis",
                                    "Retry": [
                                        {
                                            "ErrorEquals": [
                                                "States.ALL"
                                            ],
                                            "IntervalSeconds": 1,
                                            "MaxAttempts": 4,
                                            "BackoffRate": 1.1
                                        }
                                    ]
                                },
                                "Start comprehend analysis": {
                                    "Type": "Parallel",
                                    "Branches": [
                                        {
                                            "StartAt": "Batch detect entities",
                                            "States": {
                                                "Batch detect entities": {
                                                    "Type": "Task",
                                                    "Resource": "${x0}",
                                                    "Parameters": {
                                                        "uuid.$": "$.uuid",
                                                        "status": "NOT_STARTED",
                                                        "progress.$": "$.progress",
                                                        "operation": "start-entity",
                                                        "input.$": "$.input",
                                                        "data.$": "$.data"
                                                    },
                                                    "Next": "Index entity results",
                                                    "Retry": [
                                                        {
                                                            "ErrorEquals": [
                                                                "States.ALL"
                                                            ],
                                                            "IntervalSeconds": 1,
                                                            "MaxAttempts": 8,
                                                            "BackoffRate": 1.2
                                                        }
                                                    ]
                                                },
                                                "Index entity results": {
                                                    "Type": "Task",
                                                    "Resource": "${x0}",
                                                    "Parameters": {
                                                        "uuid.$": "$.uuid",
                                                        "status": "NOT_STARTED",
                                                        "progress": 0,
                                                        "operation": "index-entity-results",
                                                        "input.$": "$.input",
                                                        "data.$": "$.data"
                                                    },
                                                    "End": true,
                                                    "Retry": [
                                                        {
                                                            "ErrorEquals": [
                                                                "States.ALL"
                                                            ],
                                                            "IntervalSeconds": 1,
                                                            "MaxAttempts": 6,
                                                            "BackoffRate": 1.1
                                                        }
                                                    ]
                                                }
                                            }
                                        },
                                        {
                                            "StartAt": "Batch detect keyphrases",
                                            "States": {
                                                "Batch detect keyphrases": {
                                                    "Type": "Task",
                                                    "Resource": "${x0}",
                                                    "Parameters": {
                                                        "uuid.$": "$.uuid",
                                                        "status": "NOT_STARTED",
                                                        "progress.$": "$.progress",
                                                        "operation": "start-keyphrase",
                                                        "input.$": "$.input",
                                                        "data.$": "$.data"
                                                    },
                                                    "Next": "Index keyphrase results",
                                                    "Retry": [
                                                        {
                                                            "ErrorEquals": [
                                                                "States.ALL"
                                                            ],
                                                            "IntervalSeconds": 1,
                                                            "MaxAttempts": 8,
                                                            "BackoffRate": 1.2
                                                        }
                                                    ]
                                                },
                                                "Index keyphrase results": {
                                                    "Type": "Task",
                                                    "Resource": "${x0}",
                                                    "Parameters": {
                                                        "uuid.$": "$.uuid",
                                                        "status": "NOT_STARTED",
                                                        "progress": 0,
                                                        "operation": "index-keyphrase-results",
                                                        "input.$": "$.input",
                                                        "data.$": "$.data"
                                                    },
                                                    "End": true,
                                                    "Retry": [
                                                        {
                                                            "ErrorEquals": [
                                                                "States.ALL"
                                                            ],
                                                            "IntervalSeconds": 1,
                                                            "MaxAttempts": 6,
                                                            "BackoffRate": 1.1
                                                        }
                                                    ]
                                                }
                                            }
                                        },
                                        {
                                            "StartAt": "Batch detect sentiments",
                                            "States": {
                                                "Batch detect sentiments": {
                                                    "Type": "Task",
                                                    "Resource": "${x0}",
                                                    "Parameters": {
                                                        "uuid.$": "$.uuid",
                                                        "status": "NOT_STARTED",
                                                        "progress.$": "$.progress",
                                                        "operation": "start-sentiment",
                                                        "input.$": "$.input",
                                                        "data.$": "$.data"
                                                    },
                                                    "Next": "Index sentiment results",
                                                    "Retry": [
                                                        {
                                                            "ErrorEquals": [
                                                                "States.ALL"
                                                            ],
                                                            "IntervalSeconds": 1,
                                                            "MaxAttempts": 8,
                                                            "BackoffRate": 1.2
                                                        }
                                                    ]
                                                },
                                                "Index sentiment results": {
                                                    "Type": "Task",
                                                    "Resource": "${x0}",
                                                    "Parameters": {
                                                        "uuid.$": "$.uuid",
                                                        "status": "NOT_STARTED",
                                                        "progress": 0,
                                                        "operation": "index-sentiment-results",
                                                        "input.$": "$.input",
                                                        "data.$": "$.data"
                                                    },
                                                    "End": true,
                                                    "Retry": [
                                                        {
                                                            "ErrorEquals": [
                                                                "States.ALL"
                                                            ],
                                                            "IntervalSeconds": 1,
                                                            "MaxAttempts": 6,
                                                            "BackoffRate": 1.1
                                                        }
                                                    ]
                                                }
                                            }
                                        },
                                        {
                                            "StartAt": "Check custom entity criteria",
                                            "States": {
                                                "Check custom entity criteria": {
                                                    "Type": "Task",
                                                    "Resource": "${x0}",
                                                    "Parameters": {
                                                        "uuid.$": "$.uuid",
                                                        "status": "NOT_STARTED",
                                                        "progress.$": "$.progress",
                                                        "operation": "check-custom-entity-criteria",
                                                        "input.$": "$.input",
                                                        "data.$": "$.data"
                                                    },
                                                    "Next": "Can start custom entity?",
                                                    "Retry": [
                                                        {
                                                            "ErrorEquals": [
                                                                "States.ALL"
                                                            ],
                                                            "IntervalSeconds": 1,
                                                            "MaxAttempts": 1,
                                                            "BackoffRate": 1.2
                                                        }
                                                    ]
                                                },
                                                "Can start custom entity?" : {
                                                    "Type": "Choice",
                                                    "Choices": [
                                                        {
                                                            "And": [
                                                                {
                                                                    "Variable": "$.data.comprehend",
                                                                    "IsPresent": true
                                                                },
                                                                {
                                                                    "Variable": "$.data.comprehend.customentity",
                                                                    "IsPresent": true
                                                                },
                                                                {
                                                                    "Variable": "$.data.comprehend.customentity.prefix",
                                                                    "IsPresent": true
                                                                }
                                                            ],
                                                            "Next": "Start and wait custom entity"
                                                        }
                                                    ],
                                                    "Default": "Custom entity skipped"
                                                },
                                                "Start and wait custom entity": {
                                                    "Type": "Task",
                                                    "Resource":"arn:aws:states:::lambda:invoke.waitForTaskToken",
                                                    "Parameters": {
                                                        "FunctionName": "${a0}",
                                                        "Payload": {
                                                            "token.$":"$$.Task.Token",
                                                            "uuid.$": "$.uuid",
                                                            "status": "NOT_STARTED",
                                                            "progress.$": "$.progress",
                                                            "operation": "start-custom-entity",
                                                            "input.$": "$.input",
                                                            "data.$": "$.data"
                                                        }
                                                    },
                                                    "TimeoutSeconds": 86400,
                                                    "Next": "Wait for custom entity status (3mins)",
                                                    "Retry": [
                                                        {
                                                            "ErrorEquals": [
                                                                "States.ALL"
                                                            ],
                                                            "IntervalSeconds": 1,
                                                            "MaxAttempts": 6,
                                                            "BackoffRate": 1.1
                                                        }
                                                    ]
                                                },
                                                "Wait for custom entity status (3mins)": {
                                                    "Type": "Wait",
                                                    "Seconds": 180,
                                                    "Next": "Check custom entity status"
                                                },
                                                "Check custom entity status": {
                                                    "Type": "Task",
                                                    "Resource": "${x0}",
                                                    "Parameters": {
                                                        "uuid.$": "$.uuid",
                                                        "status": "NOT_STARTED",
                                                        "progress": 0,
                                                        "operation": "check-custom-entity-status",
                                                        "input.$": "$.input",
                                                        "data.$": "$.data"
                                                    },
                                                    "Next": "Custom entity completed?",
                                                    "Retry": [
                                                        {
                                                            "ErrorEquals": [
                                                                "States.ALL"
                                                            ],
                                                            "IntervalSeconds": 1,
                                                            "MaxAttempts": 1,
                                                            "BackoffRate": 1.1
                                                        }
                                                    ]
                                                },
                                                "Custom entity completed?": {
                                                    "Type": "Choice",
                                                    "Choices": [
                                                        {
                                                            "Or": [
                                                                {
                                                                    "Variable": "$.status",
                                                                    "StringEquals": "NO_DATA"
                                                                },
                                                                {
                                                                    "Variable": "$.status",
                                                                    "StringEquals": "ERROR"
                                                                }
                                                            ],
                                                            "Next": "Custom entity skipped"
                                                        },
                                                        {
                                                            "Variable": "$.status",
                                                            "StringEquals": "COMPLETED",
                                                            "Next": "Create custom entity track"
                                                        }
                                                    ],
                                                    "Default": "Wait for custom entity status (3mins)"
                                                },
                                                "Create custom entity track": {
                                                    "Type": "Task",
                                                    "Resource": "${x0}",
                                                    "Parameters": {
                                                        "uuid.$": "$.uuid",
                                                        "status": "NOT_STARTED",
                                                        "progress": 0,
                                                        "operation": "create-custom-entity-track",
                                                        "input.$": "$.input",
                                                        "data.$": "$.data"
                                                    },
                                                    "Next": "Index custom entity results",
                                                    "Retry": [
                                                        {
                                                            "ErrorEquals": [
                                                                "States.ALL"
                                                            ],
                                                            "IntervalSeconds": 1,
                                                            "MaxAttempts": 2,
                                                            "BackoffRate": 1.1
                                                        }
                                                    ]
                                                },
                                                "Index custom entity results": {
                                                    "Type": "Task",
                                                    "Resource": "${x0}",
                                                    "Parameters": {
                                                        "uuid.$": "$.uuid",
                                                        "status": "NOT_STARTED",
                                                        "progress": 0,
                                                        "operation": "index-custom-entity-results",
                                                        "input.$": "$.input",
                                                        "data.$": "$.data"
                                                    },
                                                    "End": true,
                                                    "Retry": [
                                                        {
                                                            "ErrorEquals": [
                                                                "States.ALL"
                                                            ],
                                                            "IntervalSeconds": 1,
                                                            "MaxAttempts": 6,
                                                            "BackoffRate": 1.1
                                                        }
                                                    ]
                                                },
                                                "Custom entity skipped": {
                                                    "Type": "Succeed"
                                                }
                                            }
                                        }
                                    ],
                                    "Next": "Analysis completed"
                                },
                                "Analysis completed": {
                                    "Type": "Task",
                                    "Resource": "${x0}",
                                    "Parameters": {
                                        "operation": "job-completed",
                                        "stateExecution.$": "$$.Execution",
                                        "parallelStateOutputs.$": "$"
                                    },
                                    "End": true,
                                    "Retry": [
                                        {
                                            "ErrorEquals": [
                                                "States.ALL"
                                            ],
                                            "IntervalSeconds": 1,
                                            "MaxAttempts": 4,
                                            "BackoffRate": 1.1
                                        }
                                    ]
                                }
                            }
                        }
                    -
                        {
                            a0: !Ref AnalysisAudioLambda,
                            x0: !GetAtt AnalysisAudioLambda.Arn
                        }

    ################################################################################
    # Analysis Image State Machine resources
    AnalysisImageLogGroup:
        Type: AWS::Logs::LogGroup
        Metadata:
            cfn_nag:
                rules_to_suppress:
                    -
                        id: W84
                        reason: Use default encryption. Disable additional KMS encryption requirement. https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html
        Properties:
            LogGroupName: !Sub
                - /aws/lambda/${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Workflow
                    - AnalysisImage
                    - Name
            RetentionInDays: 7

    AnalysisImageRole:
        Type: AWS::IAM::Role
        Metadata:
            cfn_nag:
                rules_to_suppress:
                    -
                        id: W11
                        reason: Wildcard character is prefixed and scoped with the ResourcePrefix
        Properties:
            AssumeRolePolicyDocument:
                Version: "2012-10-17"
                Statement:
                    -
                        Effect: Allow
                        Action: sts:AssumeRole
                        Principal:
                            Service: lambda.amazonaws.com
            Path: !Sub /${ResourcePrefix}/
            ManagedPolicyArns:
                - arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess
            Policies:
                -
                    PolicyName: !Sub ${ResourcePrefix}-analysis-image
                    PolicyDocument:
                        Version: "2012-10-17"
                        Statement:
                            # CloudWatch Logs
                            -
                                Effect: Allow
                                Action:
                                    - logs:CreateLogGroup
                                    - logs:CreateLogStream
                                    - logs:PutLogEvents
                                Resource: !GetAtt AnalysisImageLogGroup.Arn
                            # S3 - proxy bucket
                            -
                                Effect: Allow
                                Action: s3:ListBucket
                                Resource: !Sub arn:aws:s3:::${ProxyBucket}
                            -
                                Effect: Allow
                                Action:
                                    - s3:GetObject
                                    - s3:PutObject
                                Resource: !Sub arn:aws:s3:::${ProxyBucket}/*
                            # DynamoDB
                            -
                                Effect: Allow
                                Action:
                                    - dynamodb:Scan
                                    - dynamodb:Query
                                    - dynamodb:UpdateItem
                                    - dynamodb:DeleteItem
                                Resource: !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${ResourcePrefix}-*
                            # Rekognition
                            -
                                Effect: Allow
                                Action:
                                    - rekognition:DetectFaces
                                    - rekognition:DetectLabels
                                    - rekognition:DetectModerationLabels
                                    - rekognition:DetectText
                                    - rekognition:RecognizeCelebrities
                                    - rekognition:DescribeCollection
                                    - rekognition:SearchFacesByImage
                                Resource: "*"
                            # OpenSearch
                            -
                                Effect: Allow
                                Action:
                                    - es:ESHttpGet
                                    - es:ESHttpHead
                                    - es:ESHttpPost
                                    - es:ESHttpPut
                                    - es:ESHttpDelete
                                Resource:
                                    - !Sub arn:aws:es:${AWS::Region}:${AWS::AccountId}:domain/${OpenSearchDomainName}
                                    - !Sub arn:aws:es:${AWS::Region}:${AWS::AccountId}:domain/${OpenSearchDomainName}/*

    AnalysisImageLambda:
        Type: AWS::Lambda::Function
        Metadata:
            cfn_nag:
                rules_to_suppress:
                    -
                        id: W89
                        reason: Workflow not using VPC
                    -
                        id: W92
                        reason: Workflow not limiting simultaneous executions
        Properties:
            FunctionName: !Sub
                - ${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Workflow
                    - AnalysisImage
                    - Name
            Description: !Sub (${SolutionLowerCaseId}) analysis image state machine lambda (384MB)
            Runtime: !FindInMap
                - Node
                - Runtime
                - Version
            MemorySize: 384
            Timeout: 900
            Handler: index.handler
            Role: !GetAtt AnalysisImageRole.Arn
            Code:
                S3Bucket: !Ref S3Bucket
                S3Key: !Sub
                    - ${KeyPrefix}/${package}
                    - package: !FindInMap
                        - Workflow
                        - AnalysisImage
                        - Package
            Layers:
                - !Ref AwsSdkLayer
                - !Ref CoreLibLayer
            TracingConfig:
                Mode: Active
            Environment:
                Variables:
                    ENV_EXPECTED_BUCKET_OWNER: !Ref AWS::AccountId
                    ENV_CUSTOM_USER_AGENT: !Ref CustomUserAgent
                    ENV_ANONYMOUS_USAGE: !Ref AnonymousUsage
                    ENV_SOLUTION_UUID: !Ref SolutionUuid
                    ENV_SOLUTION_ID: !Ref SolutionId
                    ENV_RESOURCE_PREFIX: !Ref ResourcePrefix
                    ENV_IOT_HOST: !Ref IotHost
                    ENV_IOT_TOPIC: !Ref IotTopic
                    ENV_PROXY_BUCKET: !Ref ProxyBucket
                    ENV_ES_DOMAIN_ENDPOINT: !Ref OpenSearchDomainEndpoint

    # BLIP model lambda
    BlipModelLogGroup:
        Type: AWS::Logs::LogGroup
        Metadata:
            cfn_nag:
                rules_to_suppress:
                    -
                        id: W84
                        reason: Use default encryption. Disable additional KMS encryption requirement. https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html
        Properties:
            LogGroupName: !Sub
                - /aws/lambda/${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Workflow
                    - BlipModel
                    - Name
            RetentionInDays: 7

    BlipModelRole:
        Type: AWS::IAM::Role
        Metadata:
            cfn_nag:
                rules_to_suppress:
                    -
                        id: W11
                        reason: Wildcard character is prefixed and scoped with the ResourcePrefix
        Properties:
            AssumeRolePolicyDocument:
                Version: "2012-10-17"
                Statement:
                    -
                        Effect: Allow
                        Action: sts:AssumeRole
                        Principal:
                            Service: lambda.amazonaws.com
            Path: !Sub /${ResourcePrefix}/
            ManagedPolicyArns:
                - arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess
            Policies:
                -
                    PolicyName: !Sub ${ResourcePrefix}-analysis-image
                    PolicyDocument:
                        Version: "2012-10-17"
                        Statement:
                            # CloudWatch Logs
                            -
                                Effect: Allow
                                Action:
                                    - logs:CreateLogGroup
                                    - logs:CreateLogStream
                                    - logs:PutLogEvents
                                Resource: !GetAtt BlipModelLogGroup.Arn
                            # S3 - proxy bucket
                            -
                                Effect: Allow
                                Action: s3:ListBucket
                                Resource: !Sub arn:aws:s3:::${ProxyBucket}
                            -
                                Effect: Allow
                                Action: s3:GetObject
                                Resource: !Sub arn:aws:s3:::${ProxyBucket}/*

    BlipModelLambda:
        Condition: bBlipImageArn
        Type: AWS::Lambda::Function
        Metadata:
            cfn_nag:
                rules_to_suppress:
                    -
                        id: W89
                        reason: Workflow not using VPC
                    -
                        id: W92
                        reason: Workflow not limiting simultaneous executions
        Properties:
            FunctionName: !Sub
                - ${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Workflow
                    - BlipModel
                    - Name
            Description: !Sub (${SolutionLowerCaseId}) BLIP model lambda (5308MB)
            MemorySize: 5308
            Timeout: 900
            PackageType: Image
            Role: !GetAtt BlipModelRole.Arn
            Code:
                ImageUri: !Ref BlipImageArn
            Architectures:
                - x86_64
            TracingConfig:
                Mode: Active

    # Placeholder if BlipImageArn not defined
    BlipModelPlaceholderLogGroup:
        Type: AWS::Logs::LogGroup
        Metadata:
            cfn_nag:
                rules_to_suppress:
                    -
                        id: W84
                        reason: Use default encryption. Disable additional KMS encryption requirement. https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html
        Properties:
            LogGroupName: !Sub
                - /aws/lambda/${ResourcePrefix}-${name}-placeholder
                - name: !FindInMap
                    - Workflow
                    - BlipModel
                    - Name
            RetentionInDays: 7

    BlipModelPlaceholderLambda:
        Type: AWS::Lambda::Function
        Metadata:
            cfn_nag:
                rules_to_suppress:
                    -
                        id: W89
                        reason: Workflow not using VPC
                    -
                        id: W92
                        reason: Workflow not limiting simultaneous executions
        Properties:
            FunctionName: !Sub
                - ${ResourcePrefix}-${name}-placeholder
                - name: !FindInMap
                    - Workflow
                    - BlipModel
                    - Name
            Description: !Sub (${SolutionLowerCaseId}) BLIP model (placeholder) lambda (128MB)
            Runtime: !FindInMap
                - Node
                - Runtime
                - Version
            MemorySize: 128
            Timeout: 3
            Handler: index.handler
            Role: !GetAtt BlipModelRole.Arn
            Code:
                ZipFile: |
                    exports.handler = async (event, context) => {
                        return {
                            caption: undefined,
                        };
                    };

    # Allow AnalysisStateMachine to run Blip model
    BlipModelAnalysisPolicy:
        Type: AWS::IAM::Policy
        Properties:
            Roles:
                - !Ref AnalysisStateMachineServiceRole
            PolicyName: AllowAnalysisStateMachineInvokeBlipModel
            PolicyDocument:
                Version: "2012-10-17"
                Statement:
                    -
                        Effect: Allow
                        Action: lambda:InvokeFunction
                        Resource: !If
                            - bBlipImageArn
                            - !GetAtt BlipModelLambda.Arn
                            - !GetAtt BlipModelPlaceholderLambda.Arn

    AnalysisImageStateMachine:
        Type: AWS::StepFunctions::StateMachine
        Properties:
            StateMachineName: !Sub
                - ${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Workflow
                    - AnalysisImage
                    - Name
            RoleArn: !GetAtt AnalysisStateMachineServiceRole.Arn
            DefinitionString:
                !Sub
                    - |-
                        {
                            "StartAt": "Run parallel states",
                            "States": {
                                "Run parallel states": {
                                    "Type": "Parallel",
                                    "Branches": [
                                        {
                                            "StartAt": "Start image analysis",
                                            "States": {
                                                "Start image analysis": {
                                                    "Type": "Task",
                                                    "Resource": "${AnalysisImageLambda.Arn}",
                                                    "Parameters": {
                                                        "operation": "start-image-analysis",
                                                        "uuid.$": "$.uuid",
                                                        "status": "NOT_STARTED",
                                                        "progress": 0,
                                                        "input.$": "$.input",
                                                        "data.$": "$.data"
                                                    },
                                                    "End": true,
                                                    "Retry": [
                                                        {
                                                            "ErrorEquals": [
                                                                "States.ALL"
                                                            ],
                                                            "IntervalSeconds": 1,
                                                            "MaxAttempts": 6,
                                                            "BackoffRate": 1.1
                                                        }
                                                    ]
                                                }
                                            }
                                        },
                                        {
                                            "StartAt": "Run BLIP model",
                                            "States": {
                                                "Run BLIP model": {
                                                    "Type": "Task",
                                                    "Resource": "${blipLambda}",
                                                    "Parameters": {
                                                        "bucket.$": "$.input.destination.bucket",
                                                        "key.$": "$.input.image.key"
                                                    },
                                                    "ResultPath": "$.data.image",
                                                    "End": true,
                                                    "Retry": [
                                                        {
                                                            "ErrorEquals": [
                                                                "States.ALL"
                                                            ],
                                                            "IntervalSeconds": 1,
                                                            "MaxAttempts": 2,
                                                            "BackoffRate": 1.1
                                                        }
                                                    ]
                                                }
                                            }
                                        }
                                    ],
                                    "Next": "Index analysis results"
                                },
                                "Index analysis results": {
                                    "Type": "Task",
                                    "Resource": "${AnalysisImageLambda.Arn}",
                                    "Parameters": {
                                        "operation": "index-analysis-results",
                                        "parallelStateOutputs.$": "$",
                                        "stateExecution.$": "$$.Execution"
                                    },
                                    "End": true,
                                    "Retry": [
                                        {
                                            "ErrorEquals": [
                                                "States.ALL"
                                            ],
                                            "IntervalSeconds": 1,
                                            "MaxAttempts": 6,
                                            "BackoffRate": 1.1
                                        }
                                    ]
                                }
                            }
                        }
                    -
                        {
                            blipLambda: !If [
                                bBlipImageArn,
                                !GetAtt BlipModelLambda.Arn,
                                !GetAtt BlipModelPlaceholderLambda.Arn
                            ]
                        }

    ################################################################################
    # Analysis Document State Machine resources
    AnalysisDocumentLogGroup:
        Type: AWS::Logs::LogGroup
        Metadata:
            cfn_nag:
                rules_to_suppress:
                    -
                        id: W84
                        reason: Use default encryption. Disable additional KMS encryption requirement. https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html
        Properties:
            LogGroupName: !Sub
                - /aws/lambda/${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Workflow
                    - AnalysisDocument
                    - Name
            RetentionInDays: 7

    AnalysisDocumentRole:
        Type: AWS::IAM::Role
        Metadata:
            cfn_nag:
                rules_to_suppress:
                    -
                        id: W11
                        reason: Wildcard character is prefixed and scoped with the ResourcePrefix
        Properties:
            AssumeRolePolicyDocument:
                Version: "2012-10-17"
                Statement:
                    -
                        Effect: Allow
                        Action: sts:AssumeRole
                        Principal:
                            Service: lambda.amazonaws.com
            Path: !Sub /${ResourcePrefix}/
            ManagedPolicyArns:
                - arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess
            Policies:
                -
                    PolicyName: !Sub ${ResourcePrefix}-analysis-document
                    PolicyDocument:
                        Version: "2012-10-17"
                        Statement:
                            # CloudWatch Logs
                            -
                                Effect: Allow
                                Action:
                                    - logs:CreateLogGroup
                                    - logs:CreateLogStream
                                    - logs:PutLogEvents
                                Resource: !GetAtt AnalysisDocumentLogGroup.Arn
                            # S3 - proxy bucket
                            -
                                Effect: Allow
                                Action: s3:ListBucket
                                Resource: !Sub arn:aws:s3:::${ProxyBucket}
                            -
                                Effect: Allow
                                Action:
                                    - s3:GetObject
                                    - s3:PutObject
                                Resource: !Sub arn:aws:s3:::${ProxyBucket}/*
                            # DynamoDB
                            -
                                Effect: Allow
                                Action:
                                    - dynamodb:Scan
                                    - dynamodb:Query
                                    - dynamodb:UpdateItem
                                    - dynamodb:DeleteItem
                                Resource: !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${ResourcePrefix}-*
                            # Textract
                            -
                                Effect: Allow
                                Action: textract:AnalyzeDocument
                                Resource: "*"
                            # OpenSearch
                            -
                                Effect: Allow
                                Action:
                                    - es:ESHttpGet
                                    - es:ESHttpHead
                                    - es:ESHttpPost
                                    - es:ESHttpPut
                                    - es:ESHttpDelete
                                Resource:
                                    - !Sub arn:aws:es:${AWS::Region}:${AWS::AccountId}:domain/${OpenSearchDomainName}
                                    - !Sub arn:aws:es:${AWS::Region}:${AWS::AccountId}:domain/${OpenSearchDomainName}/*

    AnalysisDocumentLambda:
        Type: AWS::Lambda::Function
        Metadata:
            cfn_nag:
                rules_to_suppress:
                    -
                        id: W89
                        reason: Workflow not using VPC
                    -
                        id: W92
                        reason: Workflow not limiting simultaneous executions
        Properties:
            FunctionName: !Sub
                - ${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Workflow
                    - AnalysisDocument
                    - Name
            Description: !Sub (${SolutionLowerCaseId}) analysis document state machine lambda (384MB)
            Runtime: !FindInMap
                - Node
                - Runtime
                - Version
            MemorySize: 384
            Timeout: 900
            Handler: index.handler
            Role: !GetAtt AnalysisDocumentRole.Arn
            Code:
                S3Bucket: !Ref S3Bucket
                S3Key: !Sub
                    - ${KeyPrefix}/${package}
                    - package: !FindInMap
                        - Workflow
                        - AnalysisDocument
                        - Package
            Layers:
                - !Ref AwsSdkLayer
                - !Ref CoreLibLayer
            TracingConfig:
                Mode: Active
            Environment:
                Variables:
                    ENV_EXPECTED_BUCKET_OWNER: !Ref AWS::AccountId
                    ENV_CUSTOM_USER_AGENT: !Ref CustomUserAgent
                    ENV_ANONYMOUS_USAGE: !Ref AnonymousUsage
                    ENV_SOLUTION_UUID: !Ref SolutionUuid
                    ENV_SOLUTION_ID: !Ref SolutionId
                    ENV_RESOURCE_PREFIX: !Ref ResourcePrefix
                    ENV_IOT_HOST: !Ref IotHost
                    ENV_IOT_TOPIC: !Ref IotTopic
                    ENV_PROXY_BUCKET: !Ref ProxyBucket
                    ENV_ES_DOMAIN_ENDPOINT: !Ref OpenSearchDomainEndpoint

    AnalysisDocumentStateMachine:
        Type: AWS::StepFunctions::StateMachine
        Properties:
            StateMachineName: !Sub
                - ${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Workflow
                    - AnalysisDocument
                    - Name
            RoleArn: !GetAtt AnalysisStateMachineServiceRole.Arn
            DefinitionString:
                !Sub
                    - |-
                        {
                            "StartAt": "Analyze document",
                            "States": {
                                "Analyze document": {
                                    "Type": "Task",
                                    "Resource": "${x0}",
                                    "Parameters": {
                                        "operation": "start-document-analysis",
                                        "uuid.$": "$.uuid",
                                        "status.$": "$.status",
                                        "progress.$": "$.progress",
                                        "input.$": "$.input",
                                        "data.$": "$.data",
                                        "stateExecution.$": "$$.Execution"
                                    },
                                    "Next": "More pages?",
                                    "Retry": [
                                        {
                                            "ErrorEquals": [
                                                "States.ALL"
                                            ],
                                            "IntervalSeconds": 1,
                                            "MaxAttempts": 4,
                                            "BackoffRate": 1.2
                                        }
                                    ]
                                },
                                "More pages?": {
                                    "Type": "Choice",
                                    "Choices": [
                                        {
                                            "Variable": "$.status",
                                            "StringEquals": "COMPLETED",
                                            "Next": "Index analysis results"
                                        }
                                    ],
                                    "Default": "Analyze document"
                                },
                                "Index analysis results": {
                                    "Type": "Task",
                                    "Resource": "${x0}",
                                    "Parameters": {
                                        "operation": "index-analysis-results",
                                        "uuid.$": "$.uuid",
                                        "status.$": "$.status",
                                        "progress.$": "$.progress",
                                        "input.$": "$.input",
                                        "data.$": "$.data"
                                    },
                                    "End": true,
                                    "Retry": [
                                        {
                                            "ErrorEquals": [
                                                "States.ALL"
                                            ],
                                            "IntervalSeconds": 1,
                                            "MaxAttempts": 4,
                                            "BackoffRate": 1.2
                                        }
                                    ]
                                }
                            }
                        }
                    -
                        {
                            x0: !GetAtt AnalysisDocumentLambda.Arn
                        }

    ################################################################################
    # Analysis Main State Machine resources
    AnalysisMainLogGroup:
        Type: AWS::Logs::LogGroup
        Metadata:
            cfn_nag:
                rules_to_suppress:
                    -
                        id: W84
                        reason: Use default encryption. Disable additional KMS encryption requirement. https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html
        Properties:
            LogGroupName: !Sub
                - /aws/lambda/${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Workflow
                    - AnalysisMain
                    - Name
            RetentionInDays: 7

    AnalysisMainRole:
        Type: AWS::IAM::Role
        Metadata:
            cfn_nag:
                rules_to_suppress:
                    -
                        id: W11
                        reason: Wildcard character is prefixed and scoped with the ResourcePrefix
        Properties:
            AssumeRolePolicyDocument:
                Version: "2012-10-17"
                Statement:
                    -
                        Effect: Allow
                        Action: sts:AssumeRole
                        Principal:
                            Service: lambda.amazonaws.com
            Path: !Sub /${ResourcePrefix}/
            ManagedPolicyArns:
                - arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess
            Policies:
                -
                    PolicyName: !Sub ${ResourcePrefix}-analysis-main
                    PolicyDocument:
                        Version: "2012-10-17"
                        Statement:
                            # CloudWatch Logs
                            -
                                Effect: Allow
                                Action:
                                    - logs:CreateLogGroup
                                    - logs:CreateLogStream
                                    - logs:PutLogEvents
                                Resource: !GetAtt AnalysisMainLogGroup.Arn
                            # S3 - proxy bucket
                            -
                                Effect: Allow
                                Action: s3:ListBucket
                                Resource: !Sub arn:aws:s3:::${ProxyBucket}
                            -
                                Effect: Allow
                                Action: s3:GetObject
                                Resource: !Sub arn:aws:s3:::${ProxyBucket}/*
                            # DynamoDB
                            -
                                Effect: Allow
                                Action:
                                    - dynamodb:Scan
                                    - dynamodb:Query
                                    - dynamodb:UpdateItem
                                    - dynamodb:DeleteItem
                                Resource: !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${ResourcePrefix}-*
                            # OpenSearch
                            -
                                Effect: Allow
                                Action:
                                    - es:ESHttpGet
                                    - es:ESHttpHead
                                    - es:ESHttpPost
                                    - es:ESHttpPut
                                    - es:ESHttpDelete
                                Resource:
                                    - !Sub arn:aws:es:${AWS::Region}:${AWS::AccountId}:domain/${OpenSearchDomainName}
                                    - !Sub arn:aws:es:${AWS::Region}:${AWS::AccountId}:domain/${OpenSearchDomainName}/*
                            # IoT
                            -
                                Effect: Allow
                                Action: iot:Publish
                                Resource: !Sub arn:aws:iot:${AWS::Region}:${AWS::AccountId}:topic/${IotTopic}
                            # SNS
                            -
                                Effect: Allow
                                Action: sns:Publish
                                Resource: !Ref SNSTopic
                            # Rekognition
                            -
                                Effect: Allow
                                Action: rekognition:ListFaces
                                Resource: !Sub arn:aws:rekognition:${AWS::Region}:${AWS::AccountId}:collection/*
                            -
                                Effect: Allow
                                Action: rekognition:DescribeProjectVersions
                                Resource: !Sub arn:aws:rekognition:${AWS::Region}:${AWS::AccountId}:project/*/*
                            # Comprehend
                            -
                                Effect: Allow
                                Action: comprehend:DescribeEntityRecognizer
                                Resource: !Sub arn:aws:comprehend:${AWS::Region}:${AWS::AccountId}:entity-recognizer/*
                            # Transcribe
                            -
                                Effect: Allow
                                Action:
                                    - transcribe:GetVocabulary
                                    - transcribe:DescribeLanguageModel
                                Resource: "*"

    AnalysisMainLambda:
        Type: AWS::Lambda::Function
        Metadata:
            cfn_nag:
                rules_to_suppress:
                    -
                        id: W89
                        reason: Workflow not using VPC
                    -
                        id: W92
                        reason: Workflow not limiting simultaneous executions
        Properties:
            FunctionName: !Sub
                - ${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Workflow
                    - AnalysisMain
                    - Name
            Description: !Sub (${SolutionLowerCaseId}) analysis main state machine lambda (128MB)
            Runtime: !FindInMap
                - Node
                - Runtime
                - Version
            MemorySize: 128
            Timeout: 900
            Handler: index.handler
            Role: !GetAtt AnalysisMainRole.Arn
            Code:
                S3Bucket: !Ref S3Bucket
                S3Key: !Sub
                    - ${KeyPrefix}/${package}
                    - package: !FindInMap
                        - Workflow
                        - AnalysisMain
                        - Package
            Layers:
                - !Ref AwsSdkLayer
                - !Ref CoreLibLayer
            TracingConfig:
                Mode: Active
            Environment:
                Variables:
                    ENV_EXPECTED_BUCKET_OWNER: !Ref AWS::AccountId
                    ENV_CUSTOM_USER_AGENT: !Ref CustomUserAgent
                    ENV_ANONYMOUS_USAGE: !Ref AnonymousUsage
                    ENV_SOLUTION_UUID: !Ref SolutionUuid
                    ENV_SOLUTION_ID: !Ref SolutionId
                    ENV_RESOURCE_PREFIX: !Ref ResourcePrefix
                    ENV_IOT_HOST: !Ref IotHost
                    ENV_IOT_TOPIC: !Ref IotTopic
                    ENV_PROXY_BUCKET: !Ref ProxyBucket
                    ENV_ES_DOMAIN_ENDPOINT: !Ref OpenSearchDomainEndpoint
                    ENV_SNS_TOPIC_ARN: !Ref SNSTopic
                    ## Default AI/ML options ##
                    ENV_DEFAULT_AI_OPTIONS: !Ref DefaultAIOptions
                    ENV_DEFAULT_MINCONFIDENCE: !Ref DefaultMinConfidence
                    ENV_AI_OPTIONS_S3KEY: !Ref AIOptionsS3Key

    AnalysisMainStateMachine:
        Type: AWS::StepFunctions::StateMachine
        Properties:
            StateMachineName: !Sub
                - ${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Workflow
                    - AnalysisMain
                    - Name
            RoleArn: !GetAtt AnalysisStateMachineServiceRole.Arn
            DefinitionString:
                !Sub
                    - |-
                        {
                            "StartAt": "Prepare analysis",
                            "States": {
                                "Prepare analysis": {
                                    "Type": "Task",
                                    "Resource": "${x0}",
                                    "Parameters": {
                                        "operation": "prepare-analysis",
                                        "executionArn.$": "$$.Execution.Id",
                                        "status": "NOT_STARTED",
                                        "progress": 0,
                                        "input.$": "$.input"
                                    },
                                    "Next": "Run parallel states",
                                    "Retry": [
                                        {
                                            "ErrorEquals": [
                                                "States.ALL"
                                            ],
                                            "IntervalSeconds": 1,
                                            "MaxAttempts": 2,
                                            "BackoffRate": 1.2
                                        }
                                    ]
                                },
                                "Run parallel states": {
                                    "Type": "Parallel",
                                    "Branches": [
                                        {
                                            "StartAt": "Video analysis enabled?",
                                            "States": {
                                                "Video analysis enabled?": {
                                                    "Type": "Choice",
                                                    "Choices": [
                                                        {
                                                            "Variable": "$.input.video.enabled",
                                                            "BooleanEquals": false,
                                                            "Next": "Skip video analysis"
                                                        }
                                                    ],
                                                    "Default": "Start video analysis and wait"
                                                },
                                                "Start video analysis and wait": {
                                                    "Type": "Task",
                                                    "Resource": "arn:aws:states:::states:startExecution.sync",
                                                    "Parameters": {
                                                        "Input": {
                                                            "uuid.$": "$.uuid",
                                                            "status": "NOT_STARTED",
                                                            "progress": 0,
                                                            "input.$": "$.input",
                                                            "data.$": "$.data"
                                                        },
                                                        "StateMachineArn": "${AnalysisVideoStateMachine}"
                                                    },
                                                    "End": true
                                                },
                                                "Skip video analysis": {
                                                    "Type": "Succeed"
                                                }
                                            }
                                        },
                                        {
                                            "StartAt": "Audio analysis enabled?",
                                            "States": {
                                                "Audio analysis enabled?": {
                                                    "Type": "Choice",
                                                    "Choices": [
                                                        {
                                                            "Variable": "$.input.audio.enabled",
                                                            "BooleanEquals": false,
                                                            "Next": "Skip audio analysis"
                                                        }
                                                    ],
                                                    "Default": "Start audio analysis and wait"
                                                },
                                                "Start audio analysis and wait": {
                                                    "Type": "Task",
                                                    "Resource": "arn:aws:states:::states:startExecution.sync",
                                                    "Parameters": {
                                                        "Input": {
                                                            "uuid.$": "$.uuid",
                                                            "status": "NOT_STARTED",
                                                            "progress": 0,
                                                            "input.$": "$.input",
                                                            "data.$": "$.data"
                                                        },
                                                        "StateMachineArn": "${AnalysisAudioStateMachine}"
                                                    },
                                                    "End": true
                                                },
                                                "Skip audio analysis": {
                                                    "Type": "Succeed"
                                                }
                                            }
                                        },
                                        {
                                            "StartAt": "Image analysis enabled?",
                                            "States": {
                                                "Image analysis enabled?": {
                                                    "Type": "Choice",
                                                    "Choices": [
                                                        {
                                                            "Variable": "$.input.image.enabled",
                                                            "BooleanEquals": false,
                                                            "Next": "Skip image analysis"
                                                        }
                                                    ],
                                                    "Default": "Start image analysis and wait"
                                                },
                                                "Start image analysis and wait": {
                                                    "Type": "Task",
                                                    "Resource": "arn:aws:states:::states:startExecution.sync",
                                                    "Parameters": {
                                                        "Input": {
                                                            "uuid.$": "$.uuid",
                                                            "status": "NOT_STARTED",
                                                            "progress": 0,
                                                            "input.$": "$.input",
                                                            "data.$": "$.data"
                                                        },
                                                        "StateMachineArn": "${AnalysisImageStateMachine}"
                                                    },
                                                    "End": true
                                                },
                                                "Skip image analysis": {
                                                    "Type": "Succeed"
                                                }
                                            }
                                        },
                                        {
                                            "StartAt": "Document analysis enabled?",
                                            "States": {
                                                "Document analysis enabled?": {
                                                    "Type": "Choice",
                                                    "Choices": [
                                                        {
                                                            "Variable": "$.input.document.enabled",
                                                            "BooleanEquals": false,
                                                            "Next": "Skip document analysis"
                                                        }
                                                    ],
                                                    "Default": "Start document analysis and wait"
                                                },
                                                "Start document analysis and wait": {
                                                    "Type": "Task",
                                                    "Resource": "arn:aws:states:::states:startExecution.sync",
                                                    "Parameters": {
                                                        "Input": {
                                                            "uuid.$": "$.uuid",
                                                            "status": "NOT_STARTED",
                                                            "progress": 0,
                                                            "input.$": "$.input",
                                                            "data.$": "$.data"
                                                        },
                                                        "StateMachineArn": "${AnalysisDocumentStateMachine}"
                                                    },
                                                    "End": true
                                                },
                                                "Skip document analysis": {
                                                    "Type": "Succeed"
                                                }
                                            }
                                        }
                                    ],
                                    "Next": "Collect analysis results"
                                },
                                "Collect analysis results": {
                                    "Type": "Task",
                                    "Resource": "${x0}",
                                    "Parameters": {
                                        "operation": "collect-analysis-results",
                                        "status": "NOT_STARTED",
                                        "progress": 0,
                                        "parallelStateOutputs.$": "$"
                                    },
                                    "Next": "Analysis completed",
                                    "Retry": [
                                        {
                                            "ErrorEquals": [
                                                "States.ALL"
                                            ],
                                            "IntervalSeconds": 1,
                                            "MaxAttempts": 2,
                                            "BackoffRate": 1.2
                                        }
                                    ]
                                },
                                "Analysis completed": {
                                    "Type": "Task",
                                    "Resource": "${x0}",
                                    "Parameters": {
                                        "operation": "job-completed",
                                        "uuid.$": "$.uuid",
                                        "status": "NOT_STARTED",
                                        "progress": 0,
                                        "input.$": "$.input",
                                        "data.$": "$.data"
                                    },
                                    "End": true
                                }
                            }
                        }
                    -
                        {
                            x0: !GetAtt AnalysisMainLambda.Arn
                        }

    ################################################################################
    #
    # Analysis Automation
    # * Analysis status updater
    # * Backlog Status Change Event (Analysis) includes
    #   Amazon Rekognition, Amazon Transcribe,
    #   Amazon Comprehend, & Custom Labels state machine
    #
    ################################################################################
    AnalysisStatusUpdaterLogGroup:
        Type: AWS::Logs::LogGroup
        Metadata:
            cfn_nag:
                rules_to_suppress:
                    -
                        id: W84
                        reason: Use default encryption. Disable additional KMS encryption requirement. https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html
        Properties:
            LogGroupName: !Sub
                - /aws/lambda/${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Workflow
                    - AnalysisStatusUpdater
                    - Name
            RetentionInDays: 7

    AnalysisStatusUpdaterRole:
        Type: AWS::IAM::Role
        Metadata:
            cfn_nag:
                rules_to_suppress:
                    -
                        id: W11
                        reason: Wildcard character is prefixed and scoped with the ResourcePrefix
        Properties:
            AssumeRolePolicyDocument:
                Version: "2012-10-17"
                Statement:
                    -
                        Effect: Allow
                        Action: sts:AssumeRole
                        Principal:
                            Service: lambda.amazonaws.com
            Path: !Sub /${ResourcePrefix}/
            ManagedPolicyArns:
                - arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess
            Policies:
                -
                    PolicyName: !Sub ${ResourcePrefix}-analysis-status-updater
                    PolicyDocument:
                        Version: "2012-10-17"
                        Statement:
                            # CloudWatch Logs
                            -
                                Effect: Allow
                                Action:
                                    - logs:CreateLogGroup
                                    - logs:CreateLogStream
                                    - logs:PutLogEvents
                                Resource: !GetAtt AnalysisStatusUpdaterLogGroup.Arn
                            # Step Functions - send task results on MediaConvert Job Status Change Event
                            -
                                Effect: Allow
                                Action:
                                    - states:SendTaskSuccess
                                    - states:SendTaskFailure
                                Resource:
                                    - !Ref AnalysisVideoStateMachine
                                    - !Ref AnalysisAudioStateMachine
                            # DynamoDB - get token from ServiceToken / delete items from AIML table on REMOVE event
                            -
                                Effect: Allow
                                Action:
                                    - dynamodb:Scan
                                    - dynamodb:Query
                                    - dynamodb:UpdateItem
                                    - dynamodb:DeleteItem
                                Resource: !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${ResourcePrefix}-*
                            # SNS
                            -
                                Effect: Allow
                                Action: sns:Publish
                                Resource: !Ref SNSTopic
                            # Transcribe
                            -
                                Effect: Allow
                                Action: transcribe:GetTranscriptionJob
                                Resource: "*"

    AnalysisStatusUpdaterLambda:
        Type: AWS::Lambda::Function
        Metadata:
            cfn_nag:
                rules_to_suppress:
                    -
                        id: W89
                        reason: Workflow not using VPC
                    -
                        id: W92
                        reason: Workflow not limiting simultaneous executions
        Properties:
            FunctionName: !Sub
                - ${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Workflow
                    - AnalysisStatusUpdater
                    - Name
            Description: !Sub (${SolutionLowerCaseId}) analysis status updater lambda (128MB)
            Runtime: !FindInMap
                - Node
                - Runtime
                - Version
            MemorySize: 128
            Timeout: 900
            Handler: index.handler
            Role: !GetAtt AnalysisStatusUpdaterRole.Arn
            Code:
                S3Bucket: !Ref S3Bucket
                S3Key: !Sub
                    - ${KeyPrefix}/${package}
                    - package: !FindInMap
                        - Workflow
                        - AnalysisStatusUpdater
                        - Package
            Layers:
                - !Ref AwsSdkLayer
                - !Ref CoreLibLayer
            TracingConfig:
                Mode: Active
            Environment:
                Variables:
                    ENV_EXPECTED_BUCKET_OWNER: !Ref AWS::AccountId
                    ENV_CUSTOM_USER_AGENT: !Ref CustomUserAgent
                    ENV_ANONYMOUS_USAGE: !Ref AnonymousUsage
                    ENV_SOLUTION_UUID: !Ref SolutionUuid
                    ENV_SOLUTION_ID: !Ref SolutionId
                    ENV_RESOURCE_PREFIX: !Ref ResourcePrefix
                    ENV_INGEST_BUCKET: !Ref IngestBucket
                    ENV_PROXY_BUCKET: !Ref ProxyBucket
                    ENV_SNS_TOPIC_ARN: !Ref SNSTopic

    ################################################################################
    # Analysis Backlog Status Change Event
    # * events fired by our Service Backlog EventBus
    AnalysisBacklogStatusChangeEvent:
        Type: AWS::Events::Rule
        Properties:
            Name: !Sub ${ResourcePrefix}-AnalysisBacklogStatusChangeEvent
            Description: !Sub "(${SolutionId}) Backlog Analysis Status Change Event"
            EventBusName: !GetAtt EventBridgeBus.Name
            EventPattern:
                source:
                    - !FindInMap
                        - EventBridge
                        - Rule
                        - Source
                region:
                    - !Ref AWS::Region
                detail-type:
                    - !FindInMap
                        - EventBridge
                        - Rule
                        - Detail
                detail:
                    status:
                        # Rekognition (SUCCEEDED, FAILED)
                        - SUCCEEDED
                        - FAILED
                        # Transcribe (COMPLETED, FAILED)
                        - COMPLETED
                        # Comprehend (PROCESSING)
                        - PROCESSING
                        # Custom Labels state machine (SUCCEEDED, FAILED, ABORTED, TIMED_OUT)
                        - ABORTED
                        - TIMED_OUT
                    serviceApi:
                        # Rekognition APIs supported by backlog management
                        - rekognition:startcontentmoderation
                        - rekognition:startcelebrityrecognition
                        - rekognition:startfacedetection
                        - rekognition:startfacesearch
                        - rekognition:startlabeldetection
                        - rekognition:startpersontracking
                        - rekognition:startsegmentdetection
                        - rekognition:starttextdetection
                        # Transcribe APIs supported by backlog management
                        - transcribe:startmedicaltranscriptionjob
                        - transcribe:starttranscriptionjob
                        # Comprehend APIs supported by backlog management
                        - comprehend:startdocumentclassificationjob
                        - comprehend:startdominantlanguagedetectionjob
                        - comprehend:startentitiesdetectionjob
                        - comprehend:startkeyphrasesdetectionjob
                        - comprehend:startsentimentdetectionjob
                        - comprehend:starttopicsdetectionjob
                        # Custom Custom Labels API supported by backlog management
                        - custom:startcustomlabelsdetection
            State: ENABLED
            Targets:
                -
                    Id: !Sub Id-${AnalysisStatusUpdaterLambda}
                    Arn: !GetAtt AnalysisStatusUpdaterLambda.Arn

    AnalysisBacklogStatusChangePermission:
        Type: AWS::Lambda::Permission
        Properties:
            FunctionName: !Ref AnalysisStatusUpdaterLambda
            Action: lambda:InvokeFunction
            Principal: events.amazonaws.com
            SourceArn: !GetAtt AnalysisBacklogStatusChangeEvent.Arn

    ################################################################################
    #
    # Main (Grand) Workflow - chain Ingest and Analysis workflows
    #
    ################################################################################
    MainStateMachineServiceRole:
        Type: AWS::IAM::Role
        Properties:
            AssumeRolePolicyDocument:
                Version: "2012-10-17"
                Statement:
                    -
                        Effect: Allow
                        Action: sts:AssumeRole
                        Principal:
                            Service: !Sub states.${AWS::Region}.amazonaws.com
            Path: !Sub /${ResourcePrefix}/
            Policies:
                -
                    PolicyName: !Sub ${ResourcePrefix}-main-statemachine-role
                    PolicyDocument:
                        Version: "2012-10-17"
                        Statement:
                            # Below polices are needed for Service Integration of nested workflows
                            # https://docs.aws.amazon.com/step-functions/latest/dg/stepfunctions-iam.html
                            -
                                Effect: Allow
                                Action: states:StartExecution
                                Resource:
                                    - !Ref IngestMainStateMachine
                                    - !Ref AnalysisMainStateMachine
                            -
                                Effect: Allow
                                Action:
                                    - states:DescribeExecution
                                    - states:StopExecution
                                Resource:
                                    - !Sub arn:aws:states:${AWS::Region}:${AWS::AccountId}:execution:${IngestMainStateMachine.Name}:*
                                    - !Sub arn:aws:states:${AWS::Region}:${AWS::AccountId}:execution:${AnalysisMainStateMachine.Name}:*
                            -
                                Effect: Allow
                                Action:
                                    - events:PutTargets
                                    - events:PutRule
                                    - events:DescribeRule
                                Resource: !Sub arn:aws:events:${AWS::Region}:${AWS::AccountId}:rule/StepFunctionsGetEventsForStepFunctionsExecutionRule

    MainStateMachine:
        Type: AWS::StepFunctions::StateMachine
        Properties:
            StateMachineName: !Sub
                - ${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Workflow
                    - Main
                    - Name
            RoleArn: !GetAtt MainStateMachineServiceRole.Arn
            DefinitionString:
                !Sub
                    - |-
                        {
                            "Comment": "main state machine to run ingest and anlysis sub state machines",
                            "StartAt": "Start ingest state machine",
                            "States": {
                                "Start ingest state machine": {
                                    "Type": "Task",
                                    "Resource": "arn:aws:states:::states:startExecution.sync",
                                    "Parameters": {
                                        "Input": {
                                            "input.$": "$.input"
                                        },
                                        "StateMachineArn": "${IngestStateMachine}"
                                    },
                                    "ResultSelector": {
                                        "ingestOutput.$": "States.StringToJson($.Input)"
                                    },
                                    "Next": "Start analysis state machine"
                                },
                                "Start analysis state machine": {
                                    "Type": "Task",
                                    "Resource": "arn:aws:states:::states:startExecution.sync",
                                    "Parameters": {
                                        "Input": {
                                            "input.$": "$.ingestOutput.input"
                                        },
                                        "StateMachineArn": "${AnalysisStateMachine}"
                                    },
                                    "End": true
                                }
                            }
                        }
                    -
                        {
                            IngestStateMachine: !Ref IngestMainStateMachine,
                            AnalysisStateMachine: !Ref AnalysisMainStateMachine
                        }

    ################################################################################
    #
    # State Machine Error Handling
    # * Handle errors from AnalysisMain and IngestMain state machines
    #
    ################################################################################
    StateMachineErrorHandlerLogGroup:
        Type: AWS::Logs::LogGroup
        Metadata:
            cfn_nag:
                rules_to_suppress:
                    -
                        id: W84
                        reason: Use default encryption. Disable additional KMS encryption requirement. https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html
        Properties:
            LogGroupName: !Sub
                - /aws/lambda/${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Workflow
                    - StateMachineErrorHandler
                    - Name
            RetentionInDays: 7

    StateMachineErrorHandlerRole:
        Type: AWS::IAM::Role
        Properties:
            AssumeRolePolicyDocument:
                Version: "2012-10-17"
                Statement:
                    -
                        Effect: Allow
                        Action: sts:AssumeRole
                        Principal:
                            Service: lambda.amazonaws.com
            Path: !Sub /${ResourcePrefix}/
            ManagedPolicyArns:
                - arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess
            Policies:
                -
                    PolicyName: !Sub ${ResourcePrefix}-state-machine-error-handler
                    PolicyDocument:
                        Version: "2012-10-17"
                        Statement:
                            # CloudWatch Logs
                            -
                                Effect: Allow
                                Action:
                                    - logs:CreateLogGroup
                                    - logs:CreateLogStream
                                    - logs:PutLogEvents
                                Resource: !GetAtt StateMachineErrorHandlerLogGroup.Arn
                            # Step Functions
                            -
                                Effect: Allow
                                Action: states:DescribeStateMachine
                                Resource:
                                    - !Ref IngestMainStateMachine
                                    - !Ref AnalysisMainStateMachine
                            -
                                Effect: Allow
                                Action:
                                    - states:DescribeExecution
                                    - states:GetExecutionHistory
                                Resource:
                                    - !Sub arn:aws:states:${AWS::Region}:${AWS::AccountId}:execution:${IngestMainStateMachine.Name}:*
                                    - !Sub arn:aws:states:${AWS::Region}:${AWS::AccountId}:execution:${AnalysisMainStateMachine.Name}:*
                            # DynamoDB - allow to update executions column on ingest table
                            -
                                Effect: Allow
                                Action:
                                    - dynamodb:Scan
                                    - dynamodb:Query
                                    - dynamodb:UpdateItem
                                    - dynamodb:DeleteItem
                                Resource: !GetAtt IngestTable.Arn
                            # IoT
                            -
                                Effect: Allow
                                Action: iot:Publish
                                Resource: !Sub arn:aws:iot:${AWS::Region}:${AWS::AccountId}:topic/${IotTopic}
                            # SNS
                            -
                                Effect: Allow
                                Action: sns:Publish
                                Resource: !Ref SNSTopic

    StateMachineErrorHandlerLambda:
        Type: AWS::Lambda::Function
        Metadata:
            cfn_nag:
                rules_to_suppress:
                    -
                        id: W89
                        reason: Workflow not using VPC
                    -
                        id: W92
                        reason: Workflow not limiting simultaneous executions
        Properties:
            FunctionName: !Sub
                - ${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Workflow
                    - StateMachineErrorHandler
                    - Name
            Description: !Sub (${SolutionLowerCaseId}) state machine error handler lambda (128MB)
            Runtime: !FindInMap
                - Node
                - Runtime
                - Version
            MemorySize: 128
            Timeout: 900
            Handler: index.handler
            Role: !GetAtt StateMachineErrorHandlerRole.Arn
            Code:
                S3Bucket: !Ref S3Bucket
                S3Key: !Sub
                    - ${KeyPrefix}/${package}
                    - package: !FindInMap
                        - Workflow
                        - StateMachineErrorHandler
                        - Package
            Layers:
                - !Ref AwsSdkLayer
                - !Ref CoreLibLayer
            TracingConfig:
                Mode: Active
            Environment:
                Variables:
                    ENV_EXPECTED_BUCKET_OWNER: !Ref AWS::AccountId
                    ENV_CUSTOM_USER_AGENT: !Ref CustomUserAgent
                    ENV_ANONYMOUS_USAGE: !Ref AnonymousUsage
                    ENV_SOLUTION_UUID: !Ref SolutionUuid
                    ENV_SOLUTION_ID: !Ref SolutionId
                    ENV_RESOURCE_PREFIX: !Ref ResourcePrefix
                    ENV_IOT_HOST: !Ref IotHost
                    ENV_IOT_TOPIC: !Ref IotTopic
                    ENV_INGEST_BUCKET: !Ref IngestBucket
                    ENV_PROXY_BUCKET: !Ref ProxyBucket
                    ENV_SNS_TOPIC_ARN: !Ref SNSTopic

    StateMachineStatusChangeEvent:
        Type: AWS::Events::Rule
        Properties:
            Name: !Sub ${ResourcePrefix}-StateMachineStatusChange
            Description: !Sub (${SolutionLowerCaseId}) State Machine Status Change Event
            EventPattern:
                source:
                    - aws.states
                region:
                    - !Ref AWS::Region
                detail-type:
                    - Step Functions Execution Status Change
                detail:
                    status:
                        - FAILED
                        - ABORTED
                        - TIMED_OUT
                    stateMachineArn:
                        - !Ref IngestMainStateMachine
                        - !Ref AnalysisMainStateMachine
            State: ENABLED
            Targets:
                -
                    Id: !Sub Id-${StateMachineErrorHandlerLambda}
                    Arn: !GetAtt StateMachineErrorHandlerLambda.Arn

    StateMachineStatusChangePermission:
        Type: AWS::Lambda::Permission
        Properties:
            FunctionName: !Ref StateMachineErrorHandlerLambda
            Action: lambda:InvokeFunction
            Principal: events.amazonaws.com
            SourceArn: !GetAtt StateMachineStatusChangeEvent.Arn

    ################################################################################
    #
    # (OPTIONAL) Ingest Automation
    # * Ingest S3 Event - trigger main workflow on s3:objectcreated event
    #
    ################################################################################
    IngestS3EventLogGroup:
        Type: AWS::Logs::LogGroup
        Metadata:
            cfn_nag:
                rules_to_suppress:
                    -
                        id: W84
                        reason: Use default encryption. Disable additional KMS encryption requirement. https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html
        Properties:
            LogGroupName: !Sub
                - /aws/lambda/${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Workflow
                    - IngestS3Event
                    - Name
            RetentionInDays: 7

    IngestS3EventRole:
        Type: AWS::IAM::Role
        Properties:
            AssumeRolePolicyDocument:
                Version: "2012-10-17"
                Statement:
                    -
                        Effect: Allow
                        Action: sts:AssumeRole
                        Principal:
                            Service: lambda.amazonaws.com
            Path: !Sub /${ResourcePrefix}/
            ManagedPolicyArns:
                - arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess
            Policies:
                -
                    PolicyName: !Sub ${ResourcePrefix}-ingest-s3event
                    PolicyDocument:
                        Version: "2012-10-17"
                        Statement:
                            # CloudWatch Logs
                            -
                                Effect: Allow
                                Action:
                                    - logs:CreateLogGroup
                                    - logs:CreateLogStream
                                    - logs:PutLogEvents
                                Resource: !GetAtt IngestS3EventLogGroup.Arn
                            # S3 - read object on ingest bucket
                            -
                                Effect: Allow
                                Action: s3:GetObject
                                Resource: !Sub arn:aws:s3:::${IngestBucket}/*
                            # Step Functions - start ingest main state machine
                            -
                                Effect: Allow
                                Action:
                                    - states:DescribeStateMachine
                                    - states:StartExecution
                                Resource: !Ref MainStateMachine

    IngestS3EventLambda:
        Type: AWS::Lambda::Function
        Metadata:
            cfn_nag:
                rules_to_suppress:
                    -
                        id: W89
                        reason: Workflow not using VPC
                    -
                        id: W92
                        reason: Workflow not limiting simultaneous executions
        Properties:
            FunctionName: !Sub
                - ${ResourcePrefix}-${name}
                - name: !FindInMap
                    - Workflow
                    - IngestS3Event
                    - Name
            Description: !Sub (${SolutionLowerCaseId}) ingest s3 event lambda (128MB)
            Runtime: !FindInMap
                - Node
                - Runtime
                - Version
            MemorySize: 128
            Timeout: 900
            Handler: index.handler
            Role: !GetAtt IngestS3EventRole.Arn
            Code:
                S3Bucket: !Ref S3Bucket
                S3Key: !Sub
                    - ${KeyPrefix}/${package}
                    - package: !FindInMap
                        - Workflow
                        - IngestS3Event
                        - Package
            Layers:
                - !Ref AwsSdkLayer
                - !Ref CoreLibLayer
            TracingConfig:
                Mode: Active
            Environment:
                Variables:
                    ENV_EXPECTED_BUCKET_OWNER: !Ref AWS::AccountId
                    ENV_CUSTOM_USER_AGENT: !Ref CustomUserAgent
                    ENV_ANONYMOUS_USAGE: !Ref AnonymousUsage
                    ENV_SOLUTION_UUID: !Ref SolutionUuid
                    ENV_SOLUTION_ID: !Ref SolutionId
                    ENV_RESOURCE_PREFIX: !Ref ResourcePrefix
                    ENV_IOT_HOST: !Ref IotHost
                    ENV_IOT_TOPIC: !Ref IotTopic
                    ENV_INGEST_BUCKET: !Ref IngestBucket
                    ENV_PROXY_BUCKET: !Ref ProxyBucket

    IngestObjectCreatedEvent:
        Type: AWS::Events::Rule
        Properties:
            Name: !Sub ${ResourcePrefix}-IngestObjectCreated
            Description: !Sub (${SolutionLowerCaseId}) Ingest Bucket Object Created Event
            EventPattern:
                source:
                    - aws.s3
                region:
                    - !Ref AWS::Region
                detail-type:
                    - Object Created
                detail:
                    bucket:
                        name:
                            - !Ref IngestBucket
            State: !If
                - bStartOnObjectCreation
                - ENABLED
                - DISABLED
            Targets:
                -
                    Id: !Sub Id-${IngestS3EventLambda}
                    Arn: !GetAtt IngestS3EventLambda.Arn

    IngestObjectCreatedPermission:
        Type: AWS::Lambda::Permission
        Properties:
            FunctionName: !Ref IngestS3EventLambda
            Action: lambda:InvokeFunction
            Principal: events.amazonaws.com
            SourceArn: !GetAtt IngestObjectCreatedEvent.Arn

    CustomResourcesPolicyBackend:
        Type: AWS::IAM::Policy
        Properties:
            Roles:
                - !Ref CustomResourcesRoleName
            PolicyName: !Sub ${ResourcePrefix}-custom-resources-backend-stack
            PolicyDocument:
                Version: "2012-10-17"
                Statement:
                    # S3 - put bucket notification
                    -
                        Effect: Allow
                        Action: s3:PutBucketNotification
                        Resource: !Sub arn:aws:s3:::${IngestBucket}
                    # SageMaker - describe model endpoint
                    -
                        Effect: Allow
                        Action: sagemaker:DescribeEndpoint
                        Resource: !Sub arn:aws:sagemaker:${AWS::Region}:${AWS::AccountId}:endpoint/*

    ConfigureBucketNotification:
        Condition: bStartOnObjectCreation
        DependsOn: CustomResourcesPolicyBackend
        Type: Custom::ConfigureBucketNotification
        Properties:
            ServiceToken: !Ref CustomResourcesLambdaArn
            Data:
                Bucket: !Ref IngestBucket
                NotificationConfiguration:
                    EventBridgeConfiguration:
                        EventBridgeEnabled: !Ref AWS::NoValue

Outputs:
    # SNS Topic
    SNSTopicArn:
        Value: !Ref SNSTopic
        Description: SNSTopicArn
    # DyanmoDB
    BacklogTable:
        Value: !Ref BacklogTable
        Description: BacklogTable
    BacklogTableArn:
        Value: !GetAtt BacklogTable.Arn
        Description: BacklogTableArn
    AtomicLockTable:
        Value: !Ref AtomicLockTable
        Description: AtomicLockTable
    AtomicLockTableArn:
        Value: !GetAtt AtomicLockTable.Arn
        Description: AtomicLockTableArn
    ServiceTokenTable:
        Value: !Ref ServiceTokenTable
        Description: ServiceTokenTable
    ServiceTokenTableArn:
        Value: !GetAtt ServiceTokenTable.Arn
        Description: ServiceTokenTableArn
    IngestTable:
        Value: !Ref IngestTable
        Description: IngestTable
    IngestTableArn:
        Value: !GetAtt IngestTable.Arn
        Description: IngestTableArn
    AnalysisTable:
        Value: !Ref AnalysisTable
        Description: AnalysisTable
    AnalysisTableArn:
        Value: !GetAtt AnalysisTable.Arn
        Description: AnalysisTableArn
    # State Machines
    BacklogCustomLabelsStateMachine:
        Value: !Ref BacklogCustomLabelsStateMachine
        Description: BacklogCustomLabelsStateMachine
    BacklogCustomLabelsStateMachineName:
        Value: !GetAtt BacklogCustomLabelsStateMachine.Name
        Description: BacklogCustomLabelsStateMachineName
    IngestMainStateMachine:
        Value: !Ref IngestMainStateMachine
        Description: IngestMainStateMachine
    IngestMainStateMachineName:
        Value: !GetAtt IngestMainStateMachine.Name
        Description: IngestMainStateMachine
    AnalysisMainStateMachine:
        Value: !Ref AnalysisMainStateMachine
        Description: AnalysisMainStateMachine
    AnalysisMainStateMachineName:
        Value: !GetAtt AnalysisMainStateMachine.Name
        Description: AnalysisMainStateMachine
    MainStateMachine:
        Value: !Ref MainStateMachine
        Description: MainStateMachine
    MainStateMachineName:
        Value: !GetAtt MainStateMachine.Name
        Description: MainStateMachine
    # Misc.
    DefaultAIOptions:
        Value: !Ref DefaultAIOptions
        Description: DefaultAIOptions
    DefaultMinConfidence:
        Value: !Ref DefaultMinConfidence
        Description: DefaultMinConfidence