Description: "Master CloudFormation Template for the Bucket Rekognition Backfill application."
Parameters:
  ImagesBucket:
    Type: String
    Description: Enter the name of the S3 bucket where your images are stored
    MinLength: 1

  SourceCidr:
    Type: "String"
    Description: "Optional - CIDR/IP range for instance outside access - defaults to 0.0.0.0/0"
    Default: "0.0.0.0/0"

  JobDefinitionVersion:
    Type: String
    Description: The job defintion version number
    MinLength: 1
    MaxLength: 2
    Default: "1"

  VPC:
    Type: "AWS::EC2::VPC::Id"
    Description: The VPC ID to be used by the AWS Batch compute resources

  PrivateSubnets:
    Type: List<AWS::EC2::Subnet::Id>
    Description: The list of private subnets to be used by the AWS Batch compute resources

  ElasticsearchEndpoint:
    Type: String
    Description: The Elasticsearch domain endpoint where the image labels are saved

  ComputeEnvironmentName:
    Type: String
    Description: The AWS Batch Compute Environment name
    Default: ImageComputeEnvironment

  JobQueueName:
    Type: String
    Description: The AWS Batch Job Queue Name
    Default: image_labeler_queue

  JobDefinitionName:
    Type: String
    Description: The AWS Batch Job Definition name
    Default: image_labeler

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      -
        Label:
          default: "Mandatory Parameters"
        Parameters:
          - ImagesBucket
          - ElasticsearchEndpoint
          - VPC
          - PrivateSubnets
      -
        Label:
          default: "Optional parameters"
        Parameters:
          - ComputeEnvironmentName
          - JobQueueName
          - JobDefinitionName
          - JobDefinitionVersion
          - SourceCidr
    ParameterLabels:
      ImagesBucket:
        default: "Which S3 bucket contains your images?"

Resources:

  LambdaStack:
    Type: "AWS::CloudFormation::Stack"
    Properties:
      TemplateURL: https://s3.amazonaws.com/aws-bigdata-blog/artifacts/RekognitionBatch/cfn/lambda-functions.yaml
      TimeoutInMinutes: 30
      Parameters:
        JobDefinitionVersion: !Ref JobDefinitionVersion
        EsEndpoint: !Ref ElasticsearchEndpoint
        ImagesBucket: !Ref ImagesBucket

  SecurityGroup:
    Type: "AWS::EC2::SecurityGroup"
    Properties:
      GroupDescription: AWS Batch Beta Security Group
      VpcId: !Ref VPC
      SecurityGroupIngress:
        -
          IpProtocol: "tcp"
          FromPort: "22"
          ToPort: "22"
          CidrIp: !Ref SourceCidr

  AWSBatchServiceRole:
    Type: "AWS::IAM::Role"
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          -
            Effect: "Allow"
            Principal:
              Service: batch.amazonaws.com
            Action: sts:AssumeRole
      Path: "/"
      ManagedPolicyArns:
        - "arn:aws:iam::aws:policy/service-role/AWSBatchServiceRole"

  EC2InstanceRole:
    Type: "AWS::IAM::Role"
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          -
            Effect: Allow
            Principal:
              Service: ec2.amazonaws.com
            Action: sts:AssumeRole
      Path: "/"
      ManagedPolicyArns:
        - "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role"

  InstanceProfile:
    Type: "AWS::IAM::InstanceProfile"
    Properties:
      Path: "/"
      Roles:
        - !Ref EC2InstanceRole

  BatchJobRole:
    Type: "AWS::IAM::Role"
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          -
            Effect: Allow
            Principal:
              Service: ecs-tasks.amazonaws.com
            Action: sts:AssumeRole
      Path: "/"
      Policies:
        -
          PolicyName: batch-job-policy
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              -
                Effect: "Allow"
                Action: "rekognition:DetectLabels"
                Resource: "*"
              -
                Effect: Allow
                Action:
                    - es:ESHttp*
                Resource: "*"
              -
                Effect: Allow
                Action:
                    - "s3:GetObject"
                    - "s3:GetObjectVersion"
                Resource: !Sub arn:aws:s3:::${ImagesBucket}/*

  ImageLabelerRepository:
    Type: "AWS::ECR::Repository"
    DeletionPolicy: Retain
    Properties:
      RepositoryName: !Ref JobDefinitionName

  InventoryBucket:
    Type: AWS::S3::Bucket
    DeletionPolicy: Retain
    DependsOn: [ "LambdaStack" ]
    Properties:
      BucketName: !Sub rekdemo-inventory-${AWS::AccountId}-${AWS::Region}
      NotificationConfiguration:
        LambdaConfigurations:
          -
            Event: s3:ObjectCreated:*
            Function: !GetAtt [ LambdaStack, Outputs.S3InventoryProcessorFunction ]
            Filter:
              S3Key:
                Rules:
                  -
                    Name: prefix
                    Value: !Sub ${ImagesBucket}/images-inventory/data/
                  -
                    Name: suffix
                    Value: ".csv.gz"

  InventoryBucketPolicy:
    Type: "AWS::S3::BucketPolicy"
    Properties:
      Bucket: !Ref InventoryBucket
      PolicyDocument:
        Statement:
          -
            Action: "s3:PutObject"
            Effect: "Allow"
            Resource: !Sub arn:aws:s3:::${InventoryBucket}/*
            Principal:
              Service: "s3.amazonaws.com"
            Condition:
              ArnLike:
                aws:SourceArn: !Sub arn:aws:s3:::${ImagesBucket}
              StringEquals:
                aws:SourceAccount: !Ref AWS::AccountId
                s3:x-amz-acl: bucket-owner-full-control

  S3InventoryLambdaInvokePermission:
    Type: "AWS::Lambda::Permission"
    DependsOn: LambdaStack
    Properties:
      FunctionName: !GetAtt [ LambdaStack, Outputs.S3InventoryProcessorFunction ]
      Action: lambda:InvokeFunction
      Principal: s3.amazonaws.com
      SourceAccount: !Ref AWS::AccountId

  RekognitionLabelerLambdaInvokePermission:
    Type: "AWS::Lambda::Permission"
    DependsOn: LambdaStack
    Properties:
      FunctionName: !GetAtt [ LambdaStack, Outputs.RekognitionLabelerFunction ]
      Action: lambda:InvokeFunction
      Principal: s3.amazonaws.com
      SourceAccount: !Ref AWS::AccountId

  CodeBuildServiceRole:
    Type: "AWS::IAM::Role"
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          -
            Effect: Allow
            Principal:
              Service: codebuild.amazonaws.com
            Action: sts:AssumeRole
      Path: "/"
      Policies:
        -
          PolicyName: "CodeBuildServicePolicy"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              -
                Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                  - logs:DescribeLogStreams
                Resource: "arn:aws:logs:*:*:*"
              -
                Effect: Allow
                Action:
                  - ecr:BatchCheckLayerAvailability
                  - ecr:CompleteLayerUpload
                  - ecr:GetAuthorizationToken
                  - ecr:InitiateLayerUpload
                  - ecr:PutImage
                  - ecr:UploadLayerPart
                Resource: "*"
              -
                Effect: Allow
                Action:
                  - s3:GetObject
                  - s3:GetObjectVersion
                Resource: !Sub arn:aws:s3:::${InventoryBucket}/*

  RekognitionBatchImageBuild:
    Type: "AWS::CodeBuild::Project"
    Properties:
      Name: RekognitionBatchImageBuild
      Description: Builds the Rekognition Docker image used by the AWS Batch service
      ServiceRole: !GetAtt CodeBuildServiceRole.Arn
      Artifacts:
        Type: no_artifacts
      Source:
        Type: S3
        Location: !Sub ${InventoryBucket}/ecs/src/RekognitionBatchImage.zip
      Environment:
        ComputeType: BUILD_GENERAL1_SMALL
        Type: LINUX_CONTAINER
        Image: aws/codebuild/docker:1.12.1
        EnvironmentVariables:
          -
            Name: AWS_DEFAULT_REGION
            Value: !Ref AWS::Region
          -
            Name: AWS_ACCOUNT_ID
            Value: !Ref AWS::AccountId
          -
            Name: IMAGE_REPO_NAME
            Value: !Ref JobDefinitionName
          -
            Name: IMAGE_TAG
            Value: latest

  CodeBuildTrigger:
    Type: Custom::CodeBuildTrigger
    DependsOn: [ "RekognitionBatchImageBuild", "ImageLabelerRepository", "LambdaStack" ]
    Properties:
      ServiceToken: !GetAtt [LambdaStack, Outputs.CodeBuildTriggerFunction]
      ProjectName: !Ref RekognitionBatchImageBuild
      Bucket: !Ref InventoryBucket
      Key: ecs/src/RekognitionBatchImage.zip

  S3InventoryConfigResource:
    Type: Custom::S3InventoryConfigResource
    DependsOn: [ LambdaStack ]
    Properties:
      ServiceToken: !GetAtt [LambdaStack, Outputs.S3InventoryConfigFunction]
      Region: !Ref "AWS::Region"
      AccountId: !Ref "AWS::AccountId"
      ImagesBucket: !Ref ImagesBucket
      InventoryBucket: !Ref InventoryBucket

  S3NotificationConfigResource:
    Type: Custom::S3NotificationConfigResource
    DependsOn: [ LambdaStack ]
    Properties:
      ServiceToken: !GetAtt [LambdaStack, Outputs.S3NotificationConfigFunction]
      Bucket: !Ref ImagesBucket
      LambdaArn: !GetAtt [LambdaStack, Outputs.RekognitionLabelerFunction]

  BatchComputeEnvResource:
    Type: Custom::BatchComputeEnvResource
    DependsOn: [ LambdaStack ]
    Properties:
      ServiceToken: !GetAtt [LambdaStack, Outputs.AWSBatchComputeEnvFunction]
      Subnets: !Ref PrivateSubnets
      SecurityGroups: !Ref SecurityGroup
      ServiceRole: !GetAtt AWSBatchServiceRole.Arn
      InstanceProfile: !Ref InstanceProfile
      ComputeEnvName: !Ref ComputeEnvironmentName
      MaxvCpus: 32
      MinvCpus: 0
      DesiredvCpus: 0

  BatchJobQueueResource:
    Type: Custom::BatchJobQueueResource
    DependsOn: [ LambdaStack, BatchComputeEnvResource ]
    Properties:
      ServiceToken: !GetAtt [LambdaStack, Outputs.AWSBatchJobQueueFunction]
      ComputeEnvName: !Ref ComputeEnvironmentName
      JobQueueName: !Ref JobQueueName

  BatchJobDefinitionResource:
    Type: Custom::BatchJobDefinitionResource
    DependsOn: [ LambdaStack ]
    Properties:
      ServiceToken: !GetAtt [LambdaStack, Outputs.AWSBatchJobDefinitionFunction]
      EsEndpoint: !Ref ElasticsearchEndpoint
      JobRoleArn: !GetAtt BatchJobRole.Arn
      ImageName: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${JobDefinitionName}
      JobDefinitionName: !Ref JobDefinitionName
