---
AWSTemplateFormatVersion: '2010-09-09'
Description: Handel-created ECS Cluster

Resources:
  #
  # Service role used by ECS
  #
  ServiceRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: {{serviceRoleName}}
      Path: "/services/"
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement: 
          - Effect: "Allow"
            Principal: 
              Service: 
              - "ecs.amazonaws.com"
            Action:
            - "sts:AssumeRole"
      Policies:
      - PolicyName: {{serviceRoleName}}
        PolicyDocument:
          Version: "2012-10-17"
          Statement:
          - Effect: 'Allow'
            Action:
            - "ec2:AuthorizeSecurityGroupIngress"
            - "ec2:Describe*"
            - "elasticloadbalancing:DeregisterInstancesFromLoadBalancer"
            - "elasticloadbalancing:DeregisterTargets"
            - "elasticloadbalancing:Describe*"
            - "elasticloadbalancing:RegisterInstancesWithLoadBalancer"
            - "elasticloadbalancing:RegisterTargets"
            Resource:
            - "*"
      {{#if permissionsBoundary}}
      PermissionsBoundary: {{permissionsBoundary}}
      {{/if}}

  #
  # Configure IAM resources for ECS resources
  #
  TaskRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: {{stackName}}
      Path: "/services/"
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement: 
          - Effect: "Allow"
            Principal: 
              Service: 
              - "ecs-tasks.amazonaws.com"
            Action:
            - "sts:AssumeRole"
      {{#if permissionsBoundary}}
      PermissionsBoundary: {{permissionsBoundary}}
      {{/if}}
  TaskRolePolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: {{stackName}}
      Roles:
      - !Ref TaskRole
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
        {{#each policyStatements}}
        - Effect: {{Effect}}
          Action:
          {{#each Action}}
          - '{{{this}}}'
          {{/each}}
          Resource:
          {{#each Resource}}
          - '{{{this}}}'
          {{/each}}
        {{/each}}
  EcsIamRole:
    Type: AWS::IAM::Role
    Properties:
      Path: "/services/"
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal:
            Service:
            - ec2.amazonaws.com
          Action:
          - sts:AssumeRole
      ManagedPolicyArns:
      - arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role
      {{#if permissionsBoundary}}
      PermissionsBoundary: {{permissionsBoundary}}
      {{/if}}
  EcsInstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Path: "/"
      Roles:
      - Ref: EcsIamRole
  
  #
  # Configure ECS Cluster
  #
  EcsLaunchConfiguration:
    Type: AWS::AutoScaling::LaunchConfiguration
    Properties:
      BlockDeviceMappings:
      - DeviceName: "/dev/xvda"
        Ebs:
          VolumeSize: 8
          VolumeType: gp2
          DeleteOnTermination: true
      - DeviceName: "/dev/xvdcz"
        Ebs:
          VolumeSize: 22
          VolumeType: gp2
          DeleteOnTermination: true
      IamInstanceProfile:
        Ref: EcsInstanceProfile
      ImageId: {{amiImageId}}
      InstanceMonitoring: false
      InstanceType: {{instanceType}}
      SecurityGroups:
      - {{ecsSecurityGroupId}}
      UserData: {{{userData}}}
      {{#if sshKeyName}}
      KeyName: {{sshKeyName}}
      {{/if}}
  EcsAutoScalingGroup:
    Type: AWS::AutoScaling::AutoScalingGroup
    Properties:
      Cooldown: {{asgCooldown}}
      HealthCheckGracePeriod: 300
      HealthCheckType: EC2
      LaunchConfigurationName:
        Ref: EcsLaunchConfiguration
      MaxSize: {{maxInstances}}
      MinSize: {{minInstances}}
      Tags:
      {{#if tags}}
      {{#each tags}}
      - Key: {{@key}}
        Value: {{this}}
        PropagateAtLaunch: true
      {{/each}}
      {{/if}}
      - Key: Name
        Value: {{clusterName}}
        PropagateAtLaunch: true
      VPCZoneIdentifier: 
      {{#each privateSubnetIds}}
      - {{this}}
      {{/each}}
  EcsAutoScalingPolicyUp:
    Type: AWS::AutoScaling::ScalingPolicy
    Properties:
      AdjustmentType: ChangeInCapacity
      AutoScalingGroupName:
        Ref: EcsAutoScalingGroup
      ScalingAdjustment: 1
  EcsClusterNeedsScalingUp:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmActions:
      - Ref: EcsAutoScalingPolicyUp
      MetricName: ClusterNeedsScalingUp
      ComparisonOperator: GreaterThanThreshold
      Statistic: Minimum
      AlarmDescription: ECS cluster has been calculated to need scaling up
      Period: '60'
      Dimensions:
      - Name: ClusterName 
        Value: {{clusterName}}
      EvaluationPeriods: '1'
      Namespace: Handel/ECS
      Threshold: '0'
  EcsAutoScalingPolicyDown:
    Type: AWS::AutoScaling::ScalingPolicy
    Properties:
      AdjustmentType: ChangeInCapacity
      AutoScalingGroupName:
        Ref: EcsAutoScalingGroup
      ScalingAdjustment: -1
  EcsClusterNeedsScalingDown:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmActions:
      - Ref: EcsAutoScalingPolicyDown
      MetricName: ClusterNeedsScalingDown
      ComparisonOperator: GreaterThanThreshold
      Statistic: Minimum
      AlarmDescription: ECS cluster has been calculated to need scaling down
      Period: '60'
      Dimensions:
      - Name: ClusterName 
        Value: {{clusterName}}
      EvaluationPeriods: '1'
      Namespace: Handel/ECS
      Threshold: '0'
  EcsAutoScalingLifecycle:
    Type: AWS::AutoScaling::LifecycleHook
    Properties:
      AutoScalingGroupName:
        Ref: EcsAutoScalingGroup
      HeartbeatTimeout: 300
      LifecycleTransition: autoscaling:EC2_INSTANCE_TERMINATING
  EcsCluster:
    Type: AWS::ECS::Cluster
    Properties:
      ClusterName: {{clusterName}}

  # 
  # Configure ECS Service that runs on the cluster
  #
  EcsService:
    Type: AWS::ECS::Service
    DependsOn:
    {{#if oneOrMoreTasksHasRouting}}
    - AlbListener
    {{/if}}
    - EcsCluster
    Properties:
      Cluster:
        Ref: EcsCluster
      DeploymentConfiguration:
        MaximumPercent: 200  #TODO - Does this need to be dynamically configured, or is just a blanket 200 ok?
        MinimumHealthyPercent: {{minimumHealthyPercentDeployment}}
      DesiredCount: {{autoScaling.minTasks}}
      {{#if oneOrMoreTasksHasRouting}}
      HealthCheckGracePeriodSeconds: {{loadBalancer.healthCheckGracePeriod}}
      LoadBalancers:
      {{#each containerConfigs}}
      {{#if routingInfo}}      
      - ContainerName: {{name}}
        ContainerPort: {{routingInfo.containerPort}}
        TargetGroupArn:
          Ref: AlbTargetGroup{{name}}
      {{/if}}
      {{/each}}
      Role: !GetAtt ServiceRole.Arn
      {{/if}}
      TaskDefinition:
        Ref: TaskDefinition{{deploymentSuffix}}
      PlacementStrategies:
        - Type: spread
          Field: instanceId
  TaskDefinition{{deploymentSuffix}}:
    Type: AWS::ECS::TaskDefinition
    Properties:
      Family: {{clusterName}}
      TaskRoleArn: !GetAtt TaskRole.Arn
      NetworkMode: bridge
      {{#if volumes}}
      Volumes:
      {{#each volumes}} 
      - Name: {{name}}
        Host:
          SourcePath: {{sourcePath}}
      {{/each}}
      {{/if}}
      ContainerDefinitions:
      {{#each containerConfigs}}
      - Name: {{name}}
        Image: {{imageName}}
        Memory: {{maxMb}}
        Cpu: {{cpuUnits}}
        Essential: true
        Privileged: false
        DisableNetworking: false
        {{#if links}}
        Links:
        {{#each links}}
        - {{this}}
        {{/each}} 
        {{/if}}
        {{#if portMappings}}
        PortMappings:
        {{#each portMappings}}
        - ContainerPort: {{this}}
          Protocol: tcp
        {{/each}}
        {{/if}}
        {{#if ../logging}}
        LogConfiguration:
          LogDriver: awslogs
          Options:
            awslogs-group: !Ref ContainerLogGroup
            awslogs-region: !Ref "AWS::Region"
            awslogs-stream-prefix: {{name}}
        {{/if}}
        Environment:
        {{#each environmentVariables}}
        - Name: {{@key}}
          Value: {{this}}
        {{/each}}
        {{#if mountPoints}}
        MountPoints:
        {{#each mountPoints}}
        - SourceVolume: {{sourceVolume}}
          ContainerPath: {{containerPath}}
        {{/each}}
        {{/if}}
      {{/each}}

  {{#if logging}}
  ContainerLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: ecs/{{logGroupName}}
      {{#if logRetentionInDays}}
      RetentionInDays: {{logRetentionInDays}}
      {{/if}}
  {{/if}}

  {{#if autoScaling.scalingEnabled}}
  #
  # Configure Service Auto Scaling
  #
  ServiceAutoScalingRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: {{clusterName}}-service-autoscaling
      Path: "/services/"
      AssumeRolePolicyDocument:
        Statement:
        - Effect: Allow
          Principal:
            Service: 
            - application-autoscaling.amazonaws.com
          Action: 
          - 'sts:AssumeRole'
      {{#if permissionsBoundary}}
      PermissionsBoundary: {{permissionsBoundary}}
      {{/if}}
  ServiceAutoScalingPolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: {{clusterName}}-service-autoscaling
      Roles:
      - !Ref ServiceAutoScalingRole
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
        - Effect: Allow
          Action: 
          - 'application-autoscaling:*'
          - 'cloudwatch:DescribeAlarms'
          - 'cloudwatch:PutMetricAlarm'
          - 'ecs:DescribeServices'
          - 'ecs:UpdateService'
          Resource: 
          - '*'
  ScalableTarget:
    Type: AWS::ApplicationAutoScaling::ScalableTarget
    Properties:
      MaxCapacity: {{autoScaling.maxTasks}}
      MinCapacity: {{autoScaling.minTasks}}
      ResourceId: !Join ["", [ "service/{{clusterName}}/", !GetAtt EcsService.Name]]
      RoleARN: !GetAtt ServiceAutoScalingRole.Arn
      ScalableDimension: ecs:service:DesiredCount
      ServiceNamespace: ecs
  {{#each autoScaling.scalingPolicies}}
  ServiceScalingPolicy{{@index}}:
    Type: AWS::ApplicationAutoScaling::ScalingPolicy
    Properties:
      PolicyName: {{../clusterName}}-{{@index}}
      PolicyType: StepScaling
      ScalingTargetId: !Ref ScalableTarget
      StepScalingPolicyConfiguration:
        AdjustmentType: {{adjustmentType}}
        Cooldown: {{cooldown}}
        MetricAggregationType: {{metricAggregationType}}
        StepAdjustments:
        - ScalingAdjustment: {{adjustmentValue}}
          {{#if scaleUp}}
          MetricIntervalLowerBound: 0
          {{/if}}
          {{#if scaleDown}}
          MetricIntervalUpperBound: 0
          {{/if}}
  ServiceScalingAlarm{{@index}}:
    Type: AWS::CloudWatch::Alarm
    Properties:
      ActionsEnabled: true
      AlarmActions:
      - !Ref ServiceScalingPolicy{{@index}}
      AlarmDescription: Handel-created alarm for ECS cluster '{{../clusterName}}'' application auto-scaling
      ComparisonOperator: {{comparisonOperator}}
      Dimensions:
      {{#if dimensions}}
      {{#each dimensions}}
      - Name: {{name}}
        Value: {{value}}
      {{/each}}
      {{else}}
      - Name: ClusterName
        Value: {{../clusterName}}
      - Name: ServiceName
        Value: !GetAtt EcsService.Name
      {{/if}}
      EvaluationPeriods: {{evaluationPeriods}}
      {{#if scaleDown}}
      InsufficientDataActions:
      - !Ref ServiceScalingPolicy{{@index}}
      {{/if}}
      MetricName: {{metricName}}
      Namespace: {{namespace}}
      Period: {{period}}
      Statistic: {{metricAggregationType}}
      Threshold: {{threshold}}
  {{/each}}
  {{/if}}

  {{#if oneOrMoreTasksHasRouting}}
  # 
  # Configure Load Balancer
  # 
  Alb:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    DependsOn: AlbSecurityGroup
    Properties:
      Name: {{loadBalancer.albName}}
      Scheme: internet-facing
      LoadBalancerAttributes:
      - Key: idle_timeout.timeout_seconds
        Value: {{loadBalancer.timeout}} #should take the timeout parameter from loadBalancer, it is added to the handlebars params...
      Subnets:
      {{#each publicSubnetIds}}
      - {{this}}
      {{/each}}
      SecurityGroups:
      - Ref: AlbSecurityGroup
      Tags:
      {{#if tags}}
      {{#each tags}}
      - Key: {{@key}}
        Value: {{this}}
      {{/each}}
      {{/if}}
      - Key: Name
        Value: {{loadBalancer.albName}}
  AlbSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security group for ALB
      VpcId: {{vpcId}}
      SecurityGroupIngress:
      {{#if loadBalancer.httpsCertificate}}
      - IpProtocol: tcp
        FromPort: '443'
        ToPort: '443'
        CidrIp: 0.0.0.0/0
      {{/if}}
      - IpProtocol: tcp
        FromPort: '80'
        ToPort: '80'
        CidrIp: 0.0.0.0/0
      Tags:
      {{#if tags}}
      {{#each tags}}
      - Key: {{@key}}
        Value: {{this}}
      {{/each}}
      {{/if}}
      - Key: Name
        Value: {{loadBalancer.albName}}-alb
  EcsIngressFromAlb:
    Type: AWS::EC2::SecurityGroupIngress
    DependsOn: AlbSecurityGroup
    Properties:
      GroupId: {{ecsSecurityGroupId}}
      IpProtocol: tcp
      FromPort: '0'
      ToPort: '65535'
      SourceSecurityGroupId:
        Ref: AlbSecurityGroup
  AlbListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    DependsOn:
    {{#each containerConfigs}}
    {{#if routingInfo}}
    - AlbTargetGroup{{name}}
    {{/if}}
    {{/each}}
    - Alb
    Properties:
      DefaultActions:
      - Type: forward
        TargetGroupArn:
          Ref: AlbTargetGroup{{loadBalancer.defaultRouteContainer.name}}
      LoadBalancerArn:
        Ref: Alb
      {{#if loadBalancer.httpsCertificate}}
      Port: '443'
      Protocol: HTTPS
      Certificates:
      - CertificateArn: {{loadBalancer.httpsCertificate}}
      {{else}}
      Port: '80'
      Protocol: HTTP
      {{/if}}
  {{#if loadBalancer.httpsCertificate}}
  RedirectToHttpsListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    DependsOn:
      - Alb
    Properties:
      DefaultActions:
        - Type: redirect
          RedirectConfig:
            Protocol: HTTPS
            StatusCode: HTTP_301
            Port: 443
      Port: '80'
      Protocol: HTTP
      LoadBalancerArn:
        Ref: Alb
  {{/if}}
  {{#each containerConfigs}}
  {{#if routingInfo}}
  AlbListenerRule{{name}}:
    Type: AWS::ElasticLoadBalancingV2::ListenerRule
    DependsOn:
    - AlbListener
    - AlbTargetGroup{{name}}
    Properties:
      Actions:
      - Type: forward
        TargetGroupArn:
          Ref: AlbTargetGroup{{name}}
      Conditions:
      - Field: path-pattern
        Values:
        - "{{routingInfo.basePath}}"
      ListenerArn:
        Ref: AlbListener
      Priority: {{routingInfo.albPriority}}
  AlbTargetGroup{{name}}:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      HealthCheckIntervalSeconds: 10
      HealthCheckPath: {{routingInfo.healthCheckPath}}
      HealthCheckProtocol: HTTP
      HealthCheckTimeoutSeconds: 5
      HealthyThresholdCount: 2
      Name: {{routingInfo.targetGroupName}}
      Port: 80
      Protocol: HTTP
      Tags:
      {{#if ../tags}}
      {{#each ../tags}}
      - Key: {{@key}}
        Value: {{this}}
      {{/each}}
      {{/if}}
      - Key: Name
        Value: {{routingInfo.targetGroupName}}
      UnhealthyThresholdCount: 2
      VpcId: {{../vpcId}}
      TargetGroupAttributes:
      - Key: deregistration_delay.timeout_seconds
        Value: 60
  {{/if}}
  {{/each}}
  {{#each loadBalancer.dnsNames}}
  DnsName{{@index}}:
    Type: "AWS::Route53::RecordSetGroup"
    Properties:
      Comment: Handel-created DNS Records for {{name}}
      HostedZoneId: {{zoneId}}
      RecordSets:
        - Name: {{name}}
          Type: A
          AliasTarget:
            DNSName: !GetAtt Alb.DNSName
            HostedZoneId: !GetAtt Alb.CanonicalHostedZoneID
        - Name: {{name}}
          Type: AAAA
          AliasTarget:
            DNSName: !GetAtt Alb.DNSName
            HostedZoneId: !GetAtt Alb.CanonicalHostedZoneID
  {{/each}}
  {{/if}}
Outputs:
  EcsClusterId:
    Description: The EcsClusterId
    Value:
      Ref: EcsCluster
