AWSTemplateFormatVersion: '2010-09-09'
Description: 'stack: {{stackName}} | deployed by Kes'
Parameters:
  CmrPassword:
    Type: String
    Description: 'Password used to publish CMR records. This is encrypted by Custom::Cumulus'
    Default: ""
    NoEcho: true
  DockerPassword:
    Type: String
    Description: 'Password used to access a private docker repository (not required)'
    Default: ""
    NoEcho: true
  DockerEmail:
    Type: String
    Description: 'Email used to login to a private docker repository (not required)'
    Default: ""
    NoEcho: true
  LaunchpadPassphrase:
    Type: String
    Description: 'Passphrase of the Launchpad PIK certificate. This is encrypted by Custom::Cumulus'
    Default: ""
    NoEcho: true
{{#if es.name}}
  {{es.name}}DomainEndpoint:
    Type: String
    Description: 'ElasticSearch domain endpoint'
    NoEcho: true
{{/if}}
{{#each dynamos}}
  {{@key}}DynamoDBStreamArn:
    Type: String
    Description: 'DynamoDBStreamArn for {{@key}}'
    NoEcho: true
{{/each}}

Resources:

  #################################################
  #  BEGIN
  #################################################


{{#each nested_templates}}
{{#ifEquals @key "WorkflowLambdaVersions"}}
{{#ifEquals ../useWorkflowLambdaVersions true}}
  {{@key}}NestedStack:
    Type: "AWS::CloudFormation::Stack"
    Properties:
      Parameters:
      {{#each ../workflowLambdas}}
        {{@key}}LambdaFunction:
          Ref: {{@key}}LambdaFunction
      {{/each}}
      TemplateURL: {{this.url}}
{{/ifEquals}}
{{/ifEquals}}
{{#ifNotEquals @key "WorkflowLambdaVersions"}}
{{#ifDeployApi @key ../deployDistributionApi}}
  {{@key}}NestedStack:
    Type: "AWS::CloudFormation::Stack"
    Properties:
      Parameters:
        CmrPassword:
          Fn::GetAtt:
            - CumulusCustomResource
            - CmrPassword
        LaunchpadPassphrase:
          Fn::GetAtt:
            - CumulusCustomResource
            - LaunchpadPassphrase
      {{#if ../../es.name}}
        ElasticSearchDomain:
          Ref: {{../../es.name}}DomainEndpoint
      {{/if}}
        log2elasticsearchLambdaFunctionArn:
          Fn::GetAtt:
            - log2elasticsearchLambdaFunction
            - Arn
        {{#ifEquals @key "CumulusApiBackend"}}
        # Only the backend API lambdas need these references.
        EcsCluster:
          Ref: CumulusECSCluster
        AsyncOperationTaskDefinition:
          Ref: AsyncOperationTaskDefinition
        BulkDeleteLambdaFunctionArn:
          Fn::GetAtt:
            - BulkDeleteLambdaFunction
            - Arn
        CreateReconciliationReportLambdaFunctionArn:
          Fn::GetAtt:
            - CreateReconciliationReportLambdaFunction
            - Arn
        EmsIngestReportLambdaFunctionArn:
          Fn::GetAtt:
            - EmsIngestReportLambdaFunction
            - Arn
        EmsDistributionReportLambdaFunctionArn:
          Fn::GetAtt:
            - EmsDistributionReportLambdaFunction
            - Arn
        EmsProductMetadataReportLambdaFunctionArn:
          Fn::GetAtt:
            - EmsProductMetadataReportLambdaFunction
            - Arn
        messageConsumerLambdaFunctionArn:
          Fn::GetAtt:
            - messageConsumerLambdaFunction
            - Arn
        ScheduleSFLambdaFunctionArn:
          Fn::GetAtt:
            - ScheduleSFLambdaFunction
            - Arn
        KinesisInboundEventLoggerLambdaFunctionArn:
          Fn::GetAtt:
            - KinesisInboundEventLoggerLambdaFunction
            - Arn
        IndexFromDatabaseLambdaFunctionArn:
          Fn::GetAtt:
            - IndexFromDatabaseLambdaFunction
            - Arn
        BulkOperationLambdaFunctionArn:
          Fn::GetAtt:
            - BulkOperationLambdaFunction
            - Arn
        # The backend API needs a reference to the distributionRestApi so
        # it can construct values for the DISTRIBUTION_REDIRECT_ENDPOINT
        # environment variable. The /granules endpoint uses this variable.
        {{#ifEquals ../../deployDistributionApi true}}
        distributionRestApi:
          Fn::GetAtt:
            - CumulusApiDistributionNestedStack
            - Outputs.distributionRestApiResource
        {{/ifEquals}}
        {{/ifEquals}}
      {{# each ../../dynamos}}
        {{@key}}DynamoDB: {{../../../prefix}}-{{@key}}
      {{/each}}
      {{# if ../../vpc }}
        SecurityGroupId: {{../../vpc.securityGroup}}
      {{/if}}
      TemplateURL: {{../this.url}}
{{/ifDeployApi}}
{{/ifNotEquals}}
{{/each}}

  #################################################
  # Nested CloudFormation Templates config BEGIN
  #################################################

  #################################################
  # Cumulus Custom Resource BEGIN
  #################################################
  CumulusCustomResource:
    Type: Custom::Cumulus
    Properties:
      ServiceToken:
        Fn::GetAtt:
          - CustomBootstrapLambdaFunction
          - Arn
      Cmr:
        Password:
          Ref: CmrPassword
      Launchpad:
        Passphrase:
          Ref: LaunchpadPassphrase
    {{# if es.name}}
      ElasticSearch:
        host:
          Ref: {{es.name}}DomainEndpoint
        version: {{es.elasticSearchMapping}}
    {{/if}}
    {{# if dynamos}}
      DynamoDBTables:
      {{#each dynamos}}
        - name: {{../prefix}}-{{@key}}
          pointInTime: {{this.pointInTime}}
      {{/each}}
    {{/if}}
      Users:
        table: {{prefix}}-UsersTable
        records:
        {{# each users}}
          - username: {{username}}
            password: OAuth
        {{/each}}

  #################################################
  # Cumulus Custom Resource END
  #################################################

  #################################################

  # SNS config BEGIN
  #################################################
{{#each sns}}
  {{#if this.arn}}
  {{#each this.subscriptions}}
  # Subscriptions only
  {{@../key}}{{@key}}Subscription:
    Type: "AWS::SNS::Subscription"
    Properties:
      {{# if this.endpoint.function}}
      Endpoint:
        {{this.endpoint.function}}:
        {{#each this.endpoint.array}}
          - {{@this}}
        {{/each}}
      {{else}}
      Endpoint: {{this.endpoint}}
      {{/if}}
      Protocol: {{this.protocol}}
      TopicArn: {{../this.arn}}
  {{/each}}
  {{else}}
  {{@key}}Sns:
    Type: "AWS::SNS::Topic"
    Properties:
      DisplayName: {{../prefix}}-{{@key}}
      Subscription:
      {{#each this.subscriptions}}
        {{# if this.endpoint.function}}
        - Endpoint:
            {{this.endpoint.function}}:
            {{#each this.endpoint.array}}
              - {{@this}}
            {{/each}}
        {{else}}
        - Endpoint: {{this.endpoint}}
        {{/if}}
          Protocol: {{this.protocol}}
      {{/each}}
  {{/if}}

  {{# each this.subscriptions}}
  {{@../key}}{{@key}}SubscriptionPermission:
    Type: AWS::Lambda::Permission
    Properties:
    {{# if this.endpoint.function}}
      FunctionName:
        {{this.endpoint.function}}:
        {{#each this.endpoint.array}}
          - {{@this}}
        {{/each}}
    {{else}}
      FunctionName: {{this.endpoint}}
    {{/if}}
      Action: lambda:InvokeFunction
      Principal: sns.amazonaws.com
      SourceArn:
        {{#if ../this.arn}}
        {{../this.arn}}
        {{else}}
        Ref: {{@../key}}Sns
        {{/if}}
  {{/each}}

{{/each}}
  #################################################
  # SNS config END
  #################################################

  #################################################
  # SQS config BEGIN
  #################################################
{{#each sqs}}
  {{@key}}SQS:
    Type: AWS::SQS::Queue
    Properties:
      QueueName: {{../prefix}}-{{@key}}
      ReceiveMessageWaitTimeSeconds: 20
    {{#if this.retry}}
      RedrivePolicy:
        deadLetterTargetArn:
          Fn::GetAtt:
            - {{@key}}FailedSQS
            - Arn
        maxReceiveCount: {{this.retry}}
    {{/if}}
      VisibilityTimeout: {{this.visibilityTimeout}}

{{#if this.retry}}
  {{@key}}FailedSQS:
    Type: AWS::SQS::Queue
    Properties:
      QueueName: {{../prefix}}-{{@key}}-failed

{{/if}}

{{# each this.consumer }}
  {{@../key}}WatcherRule:
    Type: AWS::Events::Rule
    Properties:
      ScheduleExpression: {{this.schedule}}
      State: {{# if this.state}}{{this.state}}{{ else }}DISABLED{{/if}}
      Targets:
        - Id: {{@../key}}WatcherScheduler
          Input:
            Fn::Sub: '{"queueUrl": "${ {{@../key}}SQS}", "messageLimit": {{this.messageLimit}}, "timeLimit": 60 }'
          Arn:
            Fn::GetAtt:
            - {{this.lambda}}LambdaFunction
            - Arn

  {{@../key}}InvokeLambdaPermission:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName:
        Fn::GetAtt:
        - {{this.lambda}}LambdaFunction
        - Arn
      Action: lambda:InvokeFunction
      Principal: events.amazonaws.com
      SourceArn:
        Fn::GetAtt:
          - {{@../key}}WatcherRule
          - Arn

{{/each}}
{{/each}}
  #################################################
  # SQS config END
  #################################################

  #################################################
  # Step Functions config BEGIN
  #################################################
{{#each activities}}
  {{name}}Activity:
    Type: AWS::StepFunctions::Activity
    Properties:
      Name: {{../prefix}}-{{name}}-Activity
{{/each}}

{{#each stepFunctions}}
  {{../prefixNoDash}}{{@key}}StateMachine:
    Type: AWS::StepFunctions::StateMachine
    Properties:
      DefinitionString:
    {{#ifEquals ../useWorkflowLambdaVersions true}}
        Fn::Sub:
          - |
              {{{ToJson this}}}
          -
            {{#each ../workflowLambdas}}
            {{@key}}LambdaAliasOutput:
              Fn::GetAtt:
                - WorkflowLambdaVersionsNestedStack
                - Outputs.{{@key}}LambdaAliasOutput
            {{/each}}
            stackName: {{../stackName}}
    {{/ifEquals}}
    {{#ifNotEquals ../useWorkflowLambdaVersions true}}
        Fn::Sub: |
          {{{ToJson this}}}
    {{/ifNotEquals}}
      RoleArn: {{../iams.stepRoleArn}}

{{/each}}
  #################################################
  # Step Functions config END
  #################################################

  #################################################
  # CloudWatch RULE config BEGIN
  #################################################

{{# each rules }}
  {{@key}}Rule:
    Type: AWS::Events::Rule
    {{# if this.stateMachines}}
    DependsOn:
    {{#each this.stateMachines}}
      - {{this}}
    {{/each}}
    {{/if}}
    Properties:
    {{# if this.schedule}}
      ScheduleExpression: {{this.schedule}}
    {{/if}}
    {{# if this.eventPattern}}
      EventPattern:
        Fn::Sub: |
          {{{ToJson this.eventPattern}}}
    {{/if}}
      State: {{# if this.state}}{{this.state}}{{ else }}DISABLED{{/if}}
      Targets:
        {{# each this.targets}}
        - Id: {{@../key}}WatcherScheduler
          {{# if input}}
          Input: '{{{ToJson this.input}}}'
          {{/if}}
          Arn:
            Fn::GetAtt:
            - {{lambda}}LambdaFunction
            - Arn
        {{/each}}

  {{# each targets}}
  {{@../key}}RuleLambdaPermission:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName:
        Fn::GetAtt:
        - {{lambda}}LambdaFunction
        - Arn
      Action: lambda:InvokeFunction
      Principal: events.amazonaws.com
      SourceArn:
        Fn::GetAtt:
          - {{@../key}}Rule
          - Arn
  {{/each}}
{{/each}}

  #################################################
  # CloudWatch RULE config END
  #################################################

{{# if dynamo2ElasticSearch}}
  #################################################
  # DynamoDB Event Source Mapping BEGIN
  #################################################

  {{#each dynamo2ElasticSearch.tables}}
  {{this}}EventSourceMapping:
    Type: AWS::Lambda::EventSourceMapping
    Properties:
      EventSourceArn:
        Ref:
          {{this}}DynamoDBStreamArn
      FunctionName:
        Ref: {{../dynamo2ElasticSearch.lambda}}LambdaFunction
      BatchSize: {{../dynamo2ElasticSearch.batchSize}}
      StartingPosition: {{../dynamo2ElasticSearch.startingPosition}}
  {{/each}}

  #################################################
  # DynamoDB Event Source Mapping END
  #################################################
{{/if}}


  #################################################
  # Lambda config BEGIN
  #################################################
{{#each lambdas}}
  {{@key}}LambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
    {{# if this.layers}}
      Layers:
        {{#each this.layers}}
         - {{this}}
        {{/each}}
    {{/if}}
      Code:
        S3Bucket: {{this.bucket}}
        S3Key: {{this.remote}}
      FunctionName: {{../prefix}}-{{@key}}
      Environment:
        Variables:
          stackName: {{../stackName}}
          CMR_ENVIRONMENT: {{../cmr.cmrEnvironment}}
        {{#if this.useElasticSearch }}
        {{#if ../es.name}}
          ES_HOST:
            Ref: {{../es.name}}DomainEndpoint
        {{/if}}
        {{/if}}
        {{#ifNotEquals this.useMessageAdapter true}}
        {{#if this.envs.CUMULUS_MESSAGE_ADAPTER_DIR}}
        {{else}}
          CUMULUS_MESSAGE_ADAPTER_DIR: {{../cmaDir}}
        {{/if}}
        {{/ifNotEquals}}
        {{#each this.tables}}
          {{this}}: {{../../prefix}}-{{this}}
        {{/each}}
      {{# if this.useDistributionApi}}
        {{# if ../api_distribution_url}}
          DISTRIBUTION_ENDPOINT: {{../api_distribution_url}}
        {{/if}}
        {{#if ../deployDistributionApi}}
          DISTRIBUTION_ENDPOINT:
            Fn::GetAtt:
              - CumulusApiDistributionNestedStack
              - Outputs.distributionRestApiResourceUrl
        {{/if}}
      {{/if}}
    {{#each this.envs}}
      {{# if this.function}}
        {{#if this.array}}
          {{@key}}:
            {{this.function}}:
            {{#each this.array}}
              - {{this}}
            {{/each}}
        {{/if}}
        {{#if this.value}}
          {{@key}}:
            {{this.function}}: {{this.value}}
        {{/if}}
      {{else}}
          {{@key}}: {{{this}}}
      {{/if}}
    {{/each}}
      Handler: {{this.handler}}
      MemorySize: {{this.memory}}
      {{# if ../useXray}}
      TracingConfig:
        Mode: Active
      {{/if}}
{{# if this.apiRole }}
      Role: {{../iams.lambdaApiGatewayRoleArn}}
{{else if this.distributionRole}}
      Role: {{../iams.distributionRoleArn}}
{{else}}
{{#ifEquals @key "executeMigrations"}}
      Role: {{../iams.migrationRoleArn}}
{{/ifEquals}}
{{#ifNotEquals @key "executeMigrations"}}
      Role: {{../iams.lambdaProcessingRoleArn}}
{{/ifNotEquals}}
{{/if}}
      Runtime: {{# if this.runtime}}{{this.runtime}}{{else}}nodejs8.10{{/if}}
      Timeout: {{this.timeout}}
    {{# if this.deadletterqueue}}
      DeadLetterConfig:
        TargetArn:
          Fn::GetAtt:
            - {{this.deadletterqueue}}SQS
            - Arn
    {{/if}}
      Tags:
        - Key: Project
          Value: {{../prefix}}

  {{# if this.launchInVpc }}
    {{# if ../vpc }}
      VpcConfig:
        SecurityGroupIds:
          - {{../vpc.securityGroup}}
        SubnetIds:
        {{#each ../vpc.subnets}}
          - {{this}}
        {{/each}}
    {{/if}}
  {{/if}}

{{# if this.apiGateway }}
  {{@key}}LambdaPermissionApiGateway:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:InvokeFunction
      FunctionName:
        Fn::GetAtt:
        - {{@key}}LambdaFunction
        - Arn
      Principal: apigateway.amazonaws.com
{{/if}}

{{# if this.logToElasticSearch }}
  {{@key}}LogSubscription:
    Type: AWS::Logs::SubscriptionFilter
    DependsOn:
      - {{@key}}LogGroup
      - log2elasticsearchLambdaPermissionLog
    Properties:
      DestinationArn:
        Fn::GetAtt:
          - log2elasticsearchLambdaFunction
          - Arn
      LogGroupName: '/aws/lambda/{{../prefix}}-{{@key}}'
      FilterPattern: ""

  {{@key}}LogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: '/aws/lambda/{{../prefix}}-{{@key}}'
      RetentionInDays: 30
{{/if}}

{{#if this.logToSharedDestination }}
  # Configure Lambda log subscription to shareLogDestination
  {{@key}}LogSubscriptionToSharedDestination:
    Type: AWS::Logs::SubscriptionFilter
    DependsOn:
      - {{@key}}LogGroup
    Properties:
      DestinationArn: "{{this.logToSharedDestination}}"
      LogGroupName: '/aws/lambda/{{../stackName}}-{{@key}}'
      FilterPattern: ""

  {{@key}}LogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: '/aws/lambda/{{../stackName}}-{{@key}}'
      RetentionInDays: 30
{{/if}}


  {{#ifEquals @key "ScheduleSF"}}
  ## Generic lambda permission for custom rules
  ## created in the dashboard
  GenericLambdaPermission:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName:
        Fn::GetAtt:
        - ScheduleSFLambdaFunction
        - Arn
      Action: lambda:InvokeFunction
      Principal: events.amazonaws.com
  {{/ifEquals}}

{{/each}}

  log2elasticsearchLambdaPermissionLog:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:InvokeFunction
      FunctionName:
        Fn::GetAtt:
        - log2elasticsearchLambdaFunction
        - Arn
      Principal:
        Fn::Sub: 'logs.${AWS::Region}.amazonaws.com'

  #################################################
  # Lambda config END
  #################################################

  #################################################
  # ECS config BEGIN
  #################################################
{{# if iams.instanceProfile}}
  CumulusECSCluster:
    Type: AWS::ECS::Cluster

  CumulusContainerInstanceLaunch:
    Type: AWS::AutoScaling::LaunchConfiguration
    Properties:
    {{# if vpc.subnets }}
      AssociatePublicIpAddress: {{#if ecs.publicIp}}{{ecs.publicIp}}{{else}}false{{/if}}
    {{/if}}
    {{# if vpc.securityGroup}}
      SecurityGroups:
        - {{vpc.securityGroup}}
    {{/if}}
       {{# if ecs.efs.mount}}
        - Fn::ImportValue:
            "{{prefix}}-EFSSecurityGroup"
        {{/if}}

      ImageId: {{ecs.amiid}}

      InstanceType: {{ecs.instanceType}}
      IamInstanceProfile: {{iams.instanceProfile}}
      BlockDeviceMappings:
      - DeviceName: "/dev/xvdcz"
        Ebs:
          DeleteOnTermination: true
          VolumeSize: {{ecs.volumeSize}}
    {{# if ecs.keyPairName }}
      KeyName: {{ ecs.keyPairName }}
    {{/if}}
      UserData:
        Fn::Base64:
          Fn::Sub:
            - |
              Content-Type: multipart/mixed; boundary="==BOUNDARY=="
              MIME-Version: 1.0

              --==BOUNDARY==
              Content-Type: text/cloud-boothook; charset="us-ascii"

              sed -i '/^\s*DOCKER_STORAGE_OPTIONS=/d' /etc/sysconfig/docker-storage
              echo 'DOCKER_STORAGE_OPTIONS="--storage-driver {{ecs.docker.storageDriver}}"' >> /etc/sysconfig/docker-storage

              {{#ifEquals ecs.docker.storageDriver "devicemapper"}}
              sed -i '/^\s*OPTIONS=/d' /etc/sysconfig/docker
              echo 'OPTIONS="--default-ulimit nofile=1024:4096 --storage-opt dm.basesize={{ecs.volumeSize}}G"' >> /etc/sysconfig/docker
              {{/ifEquals}}

              --==BOUNDARY==
              Content-Type: text/x-shellscript; charset="us-ascii"

              {{# if ecs.efs.mount }}
              AZ=$(curl http://169.254.169.254/latest/meta-data/placement/availability-zone)

              if ! rpm -q nfs-utils >/dev/null 2>&1; then
                yum install -y nfs-utils
              fi

              mkdir -p {{ecs.efs.mount}}
              mount -t nfs4 -o nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2 ${!AZ}.${EFSFileSystemId}.efs.${AWS::Region}.amazonaws.com:/ {{ecs.efs.mount}}
              chmod 777 {{ecs.efs.mount}}

              service docker restart
              {{/if}}

              cat <<'EOF' >> /etc/ecs/ecs.config
              ECS_CLUSTER=${CumulusECSCluster}
              ECS_ENGINE_TASK_CLEANUP_WAIT_DURATION=1m
              ECS_CONTAINER_STOP_TIMEOUT={{ecs.container_stop_timeout}}
              EOF

              {{#ifEquals ecs.docker.registry "dockerhub"}}
              echo ECS_ENGINE_AUTH_TYPE=docker >> /etc/ecs/ecs.config
              echo 'ECS_ENGINE_AUTH_DATA={"https://index.docker.io/v1/":{"username":"{{ecs.docker.username}}","password": "${DockerPassword}","email":"${DockerEmail}"}}' >> /etc/ecs/ecs.config
              {{/ifEquals}}

              if ! which aws >/dev/null 2>&1; then
                yum install -y jq unzip
                curl "https://s3.amazonaws.com/aws-cli/awscli-bundle.zip" -o "awscli-bundle.zip"
                unzip awscli-bundle.zip
                ./awscli-bundle/install -i /usr/local/aws -b /usr/local/bin/aws
                rm -rf ./awscli-bundle awscli-bundle.zip
              fi

              aws s3 cp s3://{{bucket}}/{{stackName}}/deployment-staging/task-reaper.sh /usr/local/bin/task-reaper.sh
              chmod +x /usr/local/bin/task-reaper.sh

              cat <<'EOF' >> /etc/cron.d/task-reaper
              PATH=/bin:/usr/local/bin
              AWS_DEFAULT_REGION=${AWS::Region}
              LIFECYCLE_HOOK_NAME={{stackName}}-ecs-termination-hook
              * * * * * root /usr/local/bin/task-reaper.sh >> /var/log/task-reaper.log 2>&1
              EOF

              --==BOUNDARY==--
            {{# if ecs.efs.mount }}
            - EFSFileSystemId:
                Fn::ImportValue: "{{prefix}}-EFSFileSystemId"
            {{else}}
            # This is necessary because, if we are not setting a mount,
            # the Fn::Sub function still requires a variable map
            - Nothing: nothing
            {{/if}}

  CumulusECSAutoScalingGroup:
    Type: AWS::AutoScaling::AutoScalingGroup
    UpdatePolicy:
      AutoScalingRollingUpdate:
        MinInstancesInService: '{{ ecs.minInstances }}'
    Properties:
      AvailabilityZones:
{{# if ecs.availabilityZones }}
  {{#each ecs.availabilityZones}}
        - {{this}}
  {{/each}}
{{else if ecs.availabilityZone }}
        - {{ecs.availabilityZone}}
{{/if}}
    {{# if vpc.subnets }}
      VPCZoneIdentifier:
      {{#each vpc.subnets}}
        - {{this}}
      {{/each}}
    {{/if}}
      LaunchConfigurationName:
        Ref:
          CumulusContainerInstanceLaunch
      MinSize: '{{ ecs.minInstances }}'
      DesiredCapacity: '{{ ecs.desiredInstances }}'
      MaxSize: '{{ ecs.maxInstances }}'
      Tags:
      - Key: Name
        Value: "{{prefix}}-cumulus-ecs"
        PropagateAtLaunch: true

  CumulusECSAutoScalingLifeCycleHook:
    Type: AWS::AutoScaling::LifecycleHook
    Properties:
      LifecycleHookName: {{stackName}}-ecs-termination-hook
      AutoScalingGroupName:
        Ref:
          CumulusECSAutoScalingGroup
      DefaultResult: CONTINUE
      HeartbeatTimeout: 150
      LifecycleTransition: "autoscaling:EC2_INSTANCE_TERMINATING"

  CumulusECSScaleOutPolicy:
    Type: AWS::AutoScaling::ScalingPolicy
    Properties:
      AdjustmentType: PercentChangeInCapacity
      AutoScalingGroupName:
        Ref:
          CumulusECSAutoScalingGroup
      EstimatedInstanceWarmup: 180
      MetricAggregationType: Average
      PolicyType: StepScaling
      StepAdjustments:
        - MetricIntervalLowerBound: 0
          ScalingAdjustment: {{ecs.clusterAutoscaling.scaleOutAdjustmentPercent}}

  CumulusECSMemoryScaleOutAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      ActionsEnabled: true
      AlarmActions:
        - Ref: CumulusECSScaleOutPolicy
      ComparisonOperator: GreaterThanThreshold
      DatapointsToAlarm: 1
      Dimensions:
        - Name: ClusterName
          Value:
           Ref:
             CumulusECSCluster
      EvaluationPeriods: 1
      MetricName: MemoryReservation
      Namespace: AWS/ECS
      Period: 60
      Statistic: Average
      Threshold: {{ecs.clusterAutoscaling.scaleOutThresholdPercent}}
      Unit: Percent

  CumulusECSCPUScaleOutAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      ActionsEnabled: true
      AlarmActions:
        - Ref: CumulusECSScaleOutPolicy
      ComparisonOperator: GreaterThanThreshold
      DatapointsToAlarm: 1
      Dimensions:
        - Name: ClusterName
          Value:
            Ref:
              CumulusECSCluster
      EvaluationPeriods: 1
      MetricName: CPUReservation
      Namespace: AWS/ECS
      Period: 60
      Statistic: Average
      Threshold: {{ecs.clusterAutoscaling.scaleOutThresholdPercent}}
      Unit: Percent

  CumulusECSScaleInPolicy:
    Type: AWS::AutoScaling::ScalingPolicy
    Properties:
      AdjustmentType: PercentChangeInCapacity
      AutoScalingGroupName:
        Ref:
          CumulusECSAutoScalingGroup
      MetricAggregationType: Average
      PolicyType: StepScaling
      StepAdjustments:
        - MetricIntervalUpperBound: 0
          ScalingAdjustment: {{ecs.clusterAutoscaling.scaleInAdjustmentPercent}}

  CumulusECSMemoryScaleInAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      ActionsEnabled: true
      AlarmActions:
        - Ref: CumulusECSScaleInPolicy
      ComparisonOperator: LessThanThreshold
      DatapointsToAlarm: 1
      Dimensions:
        - Name: ClusterName
          Value:
            Ref:
              CumulusECSCluster
      EvaluationPeriods: 1
      MetricName: MemoryReservation
      Namespace: AWS/ECS
      Period: 60
      Statistic: Average
      Threshold: {{ecs.clusterAutoscaling.scaleInThresholdPercent}}
      Unit: Percent

  CumulusECSCPUScaleInAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      ActionsEnabled: true
      AlarmActions:
        - Ref: CumulusECSScaleInPolicy
      ComparisonOperator: LessThanThreshold
      DatapointsToAlarm: 1
      Dimensions:
        - Name: ClusterName
          Value:
            Ref:
              CumulusECSCluster
      EvaluationPeriods: 1
      MetricName: CPUReservation
      Namespace: AWS/ECS
      Period: 60
      Statistic: Average
      Threshold: {{ecs.clusterAutoscaling.scaleInThresholdPercent}}
      Unit: Percent

{{#each ecs.services}}
  # adding TaskDefinition for Lambda/ECS services
  {{@key}}TaskDefinition:
    Type: AWS::ECS::TaskDefinition
    Properties:
    {{# if this.volumes}}
      Volumes:
      {{# each this.volumes}}
        - Name: {{name}}
          Host:
            SourcePath: {{path}}
      {{/each}}
    {{/if}}
    {{# if this.networkMode}}
      NetworkMode: {{this.networkMode}}
    {{/if}}
      ContainerDefinitions:
      - Name: {{@key}}
        Cpu: {{#if this.cpu }}{{ this.cpu }}{{ else }}10{{/if}}
        Essential: true
      {{# if this.volumes}}
        MountPoints:
        {{# each this.volumes}}
          - SourceVolume: {{name}}
            ContainerPath: {{dst}}
        {{/each}}

      {{/if}}
      {{# if this.privileged }}
        Privileged: true
      {{/if}}

        Environment:
    {{#each this.envs}}
      {{# if this.function}}
        {{#if this.array}}
          - Name: {{@key}}
            Value:
              {{this.function}}:
              {{#each this.array}}
                - {{this}}
              {{/each}}
        {{/if}}
        {{#if this.value}}
          - Name: {{@key}}
            Value:
              {{this.function}}: {{this.value}}
        {{/if}}
      {{else}}
          - Name: {{@key}}
            Value: {{{this}}}
      {{/if}}
    {{/each}}
      {{# if ../ecs.docker}}
        Image: {{image}}
      {{else}}
        Image:
          Fn::Sub: ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/{{image}}
      {{/if}}
        MemoryReservation: {{#if this.memory }}{{ this.memory }}{{ else }}256{{/if}}
    {{# if this.commands }}
        Command:
      {{# each this.commands }}
        {{# if this.function}}
          - {{this.function}}: {{this.value}}
        {{else}}
          - {{{ @this }}}
        {{/if}}
      {{/each}}
    {{/if}}
        LogConfiguration:
          LogDriver: awslogs
          Options:
            awslogs-group:
              Ref: {{@key}}EcsLogs
            awslogs-region:
              Fn::Sub: ${AWS::Region}

  {{@key}}EcsLogs:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: {{../prefix}}-{{@key}}EcsLogs
      RetentionInDays: 30

  {{@key}}EcsLogSubscription:
    Type: AWS::Logs::SubscriptionFilter
    DependsOn:
      - {{@key}}EcsLogs
      - log2elasticsearchLambdaPermissionLog
    Properties:
      DestinationArn:
        Fn::GetAtt:
          - log2elasticsearchLambdaFunction
          - Arn
      LogGroupName: {{../prefix}}-{{@key}}EcsLogs
      FilterPattern: ""

  {{@key}}ECSService:
    Type: AWS::ECS::Service
    DependsOn:
    - CumulusECSAutoScalingGroup
    Properties:
      Cluster:
        Ref: CumulusECSCluster
      DesiredCount: {{# if this.count}}{{this.count}}{{ else }}0{{/if}}
      TaskDefinition:
        Ref: {{@key}}TaskDefinition
      DeploymentConfiguration:
        MaximumPercent: 100
        MinimumHealthyPercent: 0

  {{@key}}ECSServiceTaskCountLowAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmDescription: There are less tasks running than the desired
      AlarmName: {{../prefix}}-{{@key}}-TaskCountLowAlarm
      ComparisonOperator: LessThanThreshold
      EvaluationPeriods: 1
      MetricName: MemoryUtilization
      Statistic: SampleCount
      Threshold: {{# if this.count}}{{this.count}}{{ else }} 0 {{/if}}
      Period: 60
      Namespace: AWS/ECS
      Dimensions:
        - Name: ClusterName
          Value:
            Ref: CumulusECSCluster
        - Name: ServiceName
          Value:
            Fn::GetAtt:
              - {{@key}}ECSService
              - Name

  {{# if this.alarms}}
  # the service has cumstom alarms
  {{# each this.alarms}}
  {{@../key}}{{@key}}Alarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
    {{#if alarm_description}}
      AlarmDescription: {{ alarm_description }}
    {{/if}}
      AlarmName: {{../../prefix}}-{{@../key}}-{{@key}}Alarm
      ComparisonOperator: {{ comparison_operator }}
      EvaluationPeriods: {{#if evaluation_periods }}{{ evaluation_periods }}{{ else }}5{{/if}}
      MetricName: {{ metric }}
      Statistic: {{#if statistic }}{{ statistic }}{{ else }}Average{{/if}}
      Threshold: {{ threshold }}
      Period: {{#if period }}{{ period }}{{ else }}60{{/if}}
      Namespace: AWS/ECS
      Dimensions:
        - Name: ClusterName
          Value:
            Ref: CumulusECSCluster
        - Name: ServiceName
          Value:
            Fn::GetAtt:
              - {{@../key}}ECSService
              - Name
  {{/each}}
  {{/if}}

{{/each}}

{{#each ecs.tasks}}
  # adding TaskDefinition for Lambda/ECS tasks
  {{@key}}TaskDefinition:
    Type: AWS::ECS::TaskDefinition
    Properties:
    {{# if this.volumes}}
      Volumes:
      {{# each this.volumes}}
        - Name: {{name}}
          Host:
            SourcePath: {{path}}
      {{/each}}
    {{/if}}
    {{# if this.networkMode}}
      NetworkMode: {{this.networkMode}}
    {{/if}}
      ContainerDefinitions:
      - Name: {{@key}}
        Cpu: {{#if this.cpu }}{{ this.cpu }}{{ else }}10{{/if}}
        Essential: true
      {{# if this.volumes}}
        MountPoints:
        {{# each this.volumes}}
          - SourceVolume: {{name}}
            ContainerPath: {{dst}}
        {{/each}}

      {{/if}}
      {{# if this.privileged }}
        Privileged: true
      {{/if}}

        Environment:
    {{#each this.envs}}
      {{# if this.function}}
        {{#if this.array}}
          - Name: {{@key}}
            Value:
              {{this.function}}:
              {{#each this.array}}
                - {{this}}
              {{/each}}
        {{/if}}
        {{#if this.value}}
          - Name: {{@key}}
            Value:
              {{this.function}}: {{this.value}}
        {{/if}}
      {{else}}
          - Name: {{@key}}
            Value: {{{this}}}
      {{/if}}
    {{/each}}
      {{# if ../ecs.docker}}
        Image: {{image}}
      {{else}}
        Image:
          Fn::Sub: ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/{{image}}
      {{/if}}
        MemoryReservation: {{#if this.memory }}{{ this.memory }}{{ else }}256{{/if}}
    {{# if this.commands }}
        Command:
      {{# each this.commands }}
        {{# if this.function}}
          - {{this.function}}: {{this.value}}
        {{else}}
          - {{{ @this }}}
        {{/if}}
      {{/each}}
    {{/if}}
        LogConfiguration:
          LogDriver: awslogs
          Options:
            awslogs-group:
              Ref: {{@key}}EcsLogs
            awslogs-region:
              Fn::Sub: ${AWS::Region}

  {{@key}}EcsLogs:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: {{../prefix}}-{{@key}}EcsLogs
      RetentionInDays: 30

  {{@key}}EcsLogSubscription:
    Type: AWS::Logs::SubscriptionFilter
    DependsOn:
      - {{@key}}EcsLogs
      - log2elasticsearchLambdaPermissionLog
    Properties:
      DestinationArn:
        Fn::GetAtt:
          - log2elasticsearchLambdaFunction
          - Arn
      LogGroupName: {{../prefix}}-{{@key}}EcsLogs
      FilterPattern: ""
{{/each}}
{{/if}}
  #################################################
  # ECS config END
  #################################################

  #################################################
  # CloudWatch Dashboard BEGIN
  #################################################
  CumulusCloudWatchDashboard:
    Type: AWS::CloudWatch::Dashboard
    Properties:
      DashboardName: {{prefix}}-CloudWatch-Dashboard
      DashboardBody: '{{#buildCWDashboard dashboard ecs es prefix}}{{/buildCWDashboard}}'
  #################################################
  # CloudWatch Dashboard END
  #################################################

Outputs:

  Api:
  {{# if api_backend_url}}
    Value: {{api_backend_url}}
  {{else}}
    Value:
      Fn::GetAtt:
        - CumulusApiBackendNestedStack
        - Outputs.backendRestApiResourceUrl
  {{/if}}

  {{# if api_distribution_url}}
  Distribution:
    Value: {{api_distribution_url}}
  {{/if}}
  {{#if deployDistributionApi}}
  Distribution:
    Value:
      Fn::GetAtt:
        - CumulusApiDistributionNestedStack
        - Outputs.distributionRestApiResourceUrl
  {{/if}}

  ApiId:
    Value:
      Fn::GetAtt:
        - CumulusApiBackendNestedStack
        - Outputs.backendRestApiResource

  {{#if deployDistributionApi}}
  DistributionId:
    Value:
      Fn::GetAtt:
        - CumulusApiDistributionNestedStack
        - Outputs.distributionRestApiResource
  {{/if}}

  ApiStage:
    Value: {{apiStage}}

{{#each stepFunctions}}
  {{@key}}StateMachine:
    Value:
      Ref: {{../prefixNoDash}}{{@key}}StateMachine
{{/each}}

{{#each sqs}}
  {{@key}}SQSOutput:
    Value:
      Ref: {{@key}}SQS
{{/each}}

{{#each sns}}
  {{#if this.arn}}
  {{@key}}:
    Value: {{this.arn}}
  {{else}}
  {{@key}}SnsArn:
    Value:
      Ref: {{@key}}Sns
  {{/if}}
{{/each}}

  EncryptedCmrPassword:
    Value:
      Fn::GetAtt:
        - CumulusCustomResource
        - CmrPassword
  EncryptedLaunchpadPassphrase:
    Value:
      Fn::GetAtt:
        - CumulusCustomResource
        - LaunchpadPassphrase
