AWSTemplateFormatVersion: '2010-09-09'
Description: Reporting for mobile notifications
Mappings:
 Constants:
    App:
      Value: report
    Stack:
      Value: mobile-notifications

 StageVariables:
    CODE:
      NotificationAlarmPeriod: 1200
      ScalingUpPeriod: 300
      ScalingUpThreshold: 20
      ScalingDownPeriod: 300
      ScalingDownThreshold: 15
      InstanceType: "t4g.micro"
    PROD:
      NotificationAlarmPeriod: 1200
      ScalingUpPeriod: 300
      ScalingUpThreshold: 20
      ScalingDownPeriod: 300
      ScalingDownThreshold: 15
      InstanceType: "t4g.micro"
Outputs:
  LoadBalancerUrl:
    Value:
      !GetAtt LoadBalancerToPrivateASG.DNSName
Parameters:
  AMI:
    Description: AMI used by the instances,
    Type: AWS::EC2::Image::Id
  Stage:
    Type: String
    AllowedValues:
    - CODE
    - PROD
    Description: Environment name
  VPCSecurityGroup:
    Type: AWS::EC2::SecurityGroup::Id
    Description: The default security group of the VPC
  VpcId:
    Type: AWS::EC2::VPC::Id
    Description: The VPC
  PublicSubnets:
    Type: List<AWS::EC2::Subnet::Id>
    Description: The public subnets of the VPC for the loadbalancer
  PrivateSubnets:
    Type: List<AWS::EC2::Subnet::Id>
    Description: The private subnets of the VPC for the autoscaling group
  ASGMinSize:
    Type: Number
    Description: Minimum size of the autoscaling group
  ASGMaxSize:
    Type: Number
    Description: Maximum size of the autoscaling group
  DistBucket:
    Type: String
    Description: The name of the s3 bucket containing the server artifact
  AppCertArn:
    Type: String
    Description: ACM Certificate for app use
  HostedZone:
    Type: String
    Description:  The HostedZone, should contain the trailing dot zone.example.com.
  DomainName:
    Type: String
    Description: The domain name of the ELB, should contain the trailing dot stuff.zone.example.com.
Resources:
  DnsRecord:
    Type: AWS::Route53::RecordSet
    Properties:
      HostedZoneName: !Ref HostedZone
      Name: !Ref DomainName
      ResourceRecords:
      - !GetAtt LoadBalancerToPrivateASG.DNSName
      TTL: 60
      Type: CNAME
  GuardianAccessSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: SSH and management server access from Guardian network
      SecurityGroupIngress:
      - SourceSecurityGroupId: !Ref VPCSecurityGroup
        FromPort: 22
        IpProtocol: tcp
        ToPort: 22
      VpcId: !Ref VpcId

  HighCpuAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmActions: [!Ref ScaleUpPolicy]
      AlarmDescription: !Sub
      - Scale-Up if cpu is greater than ${Threshold}% over last ${Period} seconds
      - Threshold: !FindInMap [ StageVariables, !Ref Stage, ScalingUpThreshold ]
        Period: !FindInMap [ StageVariables, !Ref Stage, ScalingUpPeriod ]
      ComparisonOperator: GreaterThanOrEqualToThreshold
      Dimensions:
      - Name: AutoScalingGroupName
        Value: !Ref PrivateReportAutoscalingGroup
      EvaluationPeriods: 1
      MetricName: CPUUtilization
      Namespace: AWS/EC2
      Period: !FindInMap [ StageVariables, !Ref Stage, ScalingUpPeriod ]
      Statistic: Average
      Threshold: !FindInMap [ StageVariables, !Ref Stage, ScalingUpThreshold ]

  LowCpuAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmActions: [!Ref ScaleDownPolicy]
      AlarmDescription: !Sub
      - Scale-Down if cpu is lower than ${Threshold}% over last ${Period} seconds
      - Threshold: !FindInMap [ StageVariables, !Ref Stage, ScalingDownThreshold ]
        Period: !FindInMap [ StageVariables, !Ref Stage, ScalingDownPeriod ]
      ComparisonOperator: LessThanThreshold
      Dimensions:
      - Name: AutoScalingGroupName
        Value: !Ref PrivateReportAutoscalingGroup
      EvaluationPeriods: 1
      MetricName: CPUUtilization
      Namespace: AWS/EC2
      Period: !FindInMap [ StageVariables, !Ref Stage, ScalingDownPeriod ]
      Statistic: Average
      Threshold: !FindInMap [ StageVariables, !Ref Stage, ScalingDownThreshold ]

  InstanceSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Open up HTTP access to load balancer
      SecurityGroupEgress:
      - CidrIp: 0.0.0.0/0
        FromPort: 80
        IpProtocol: tcp
        ToPort: 80
      - CidrIp: 0.0.0.0/0
        FromPort: 443
        IpProtocol: tcp
        ToPort: 443
      SecurityGroupIngress:
      - FromPort: 9000
        IpProtocol: tcp
        SourceSecurityGroupId: !Ref LoadBalancerSecurityGroup
        ToPort: 9000
      VpcId: !Ref VpcId

  LoadBalancerToPrivateASG:
    Type: AWS::ElasticLoadBalancing::LoadBalancer
    Properties:
      CrossZone: true
      HealthCheck:
        HealthyThreshold: 2
        Interval: 30
        Target: HTTP:9000/healthcheck
        Timeout: 10
        UnhealthyThreshold: 10
      Listeners:
      - InstancePort: 9000
        LoadBalancerPort: 443
        Protocol: HTTPS
        SSLCertificateId: !Ref AppCertArn
      SecurityGroups:
      - !Ref LoadBalancerSecurityGroup
      Subnets: !Ref PublicSubnets
  LoadBalancerSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Open up HTTP access to load balancer
      SecurityGroupEgress:
      - CidrIp: 0.0.0.0/0
        FromPort: 9000
        IpProtocol: tcp
        ToPort: 9000
      SecurityGroupIngress:
      - CidrIp: 0.0.0.0/0
        FromPort: 443
        IpProtocol: tcp
        ToPort: 443
      VpcId: !Ref VpcId

  NotificationsReportInstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Path: /
      Roles:
      - !Ref NotificationsReportRole
  NotificationsReportRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
        - Action:
            - sts:AssumeRole
          Effect: Allow
          Principal:
            Service:
              - ec2.amazonaws.com
      Path: /
      ManagedPolicyArns: [ !Sub 'arn:aws:iam::${AWS::AccountId}:policy/ssm-scala-v1' ]
      Policies:
      - PolicyName: root
        PolicyDocument:
          Statement:
          - Action: s3:GetObject
            Effect: Allow
            Resource: !Sub arn:aws:s3:::${DistBucket}/*
          - Action: ec2:DescribeTags
            Effect: Allow
            Resource: '*'
          - Action:
            - cloudwatch:*
            - logs:*
            Effect: Allow
            Resource: '*'
          - Action:
              - autoscaling:DescribeAutoScalingInstances
              - autoscaling:DescribeAutoScalingGroups
            Resource: '*'
            Effect: Allow
          - Effect: Allow
            Action:
            - kinesis:PutRecord
            - kinesis:PutRecords
            - kinesis:DescribeStream
            Resource: !Sub arn:aws:kinesis:${AWS::Region}:${AWS::AccountId}:stream/mobile-log-aggregation-${Stage}
      - PolicyName: dynamo
        PolicyDocument:
          Statement:
          - Action: dynamodb:*
            Effect: Allow
            Resource:
            - !Sub arn:aws:dynamodb:eu-west-1:${AWS::AccountId}:table/mobile-notifications-reports-${Stage}
            - !Sub arn:aws:dynamodb:eu-west-1:${AWS::AccountId}:table/mobile-notifications-reports-${Stage}/index/*
      - PolicyName: conf
        PolicyDocument:
          Statement:
          - Action: ssm:GetParametersByPath
            Effect: Allow
            Resource:
              !Sub
                - arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/notifications/${Stage}/${Stack}
                - Stack: !FindInMap [Constants, Stack, Value]

  PrivateReportAutoscalingGroup:
    Type: AWS::AutoScaling::AutoScalingGroup
    Properties:
      AvailabilityZones: !GetAZs
      HealthCheckGracePeriod: 400
      HealthCheckType: ELB
      LaunchConfigurationName: !Ref ReportLaunchConfig
      LoadBalancerNames:
      - !Ref LoadBalancerToPrivateASG
      MaxSize: !Ref ASGMaxSize
      MinSize: !Ref ASGMinSize
      NotificationConfiguration:
        NotificationTypes:
        - autoscaling:EC2_INSTANCE_LAUNCH_ERROR
        - autoscaling:EC2_INSTANCE_TERMINATE_ERROR
        TopicARN: !Sub arn:aws:sns:eu-west-1:${AWS::AccountId}:AutoscalingNotifications${Stage}
      Tags:
      - Key: Stage
        PropagateAtLaunch: true
        Value: !Ref Stage
      - Key: Stack
        PropagateAtLaunch: true
        Value: !FindInMap [Constants, Stack, Value]
      - Key: App
        PropagateAtLaunch: true
        Value: !FindInMap [Constants, App, Value]
      VPCZoneIdentifier: !Ref PrivateSubnets
  ReportLaunchConfig:
    Type: AWS::AutoScaling::LaunchConfiguration
    Properties:
      AssociatePublicIpAddress: false
      IamInstanceProfile: !Ref NotificationsReportInstanceProfile
      ImageId: !Ref AMI
      InstanceType: !FindInMap [StageVariables, !Ref Stage, InstanceType]
      SecurityGroups:
      - !Ref InstanceSecurityGroup
      - !Ref GuardianAccessSecurityGroup
      - !Ref VPCSecurityGroup
      MetadataOptions:
        HttpTokens: required
      UserData:
        Fn::Base64:
           !Sub
            - |
                #!/bin/bash -ev
                aws --region ${AWS::Region} s3 cp s3://${DistBucket}/${Stack}/${Stage}/${App}/${App}_1.0-latest_all.deb /tmp
                dpkg -i /tmp/${App}_1.0-latest_all.deb
                /opt/aws-kinesis-agent/configure-aws-kinesis-agent ${AWS::Region} mobile-log-aggregation-${Stage} /var/log/${App}/application.log
            - Stack: !FindInMap [Constants, Stack, Value]
              App: !FindInMap [Constants, App, Value]
  ScaleDownPolicy:
    Type: AWS::AutoScaling::ScalingPolicy
    Properties:
      AdjustmentType: ChangeInCapacity
      AutoScalingGroupName: !Ref PrivateReportAutoscalingGroup
      Cooldown: 3600
      ScalingAdjustment: -1
  ScaleUpPolicy:
    Type: AWS::AutoScaling::ScalingPolicy
    Properties:
      AdjustmentType: PercentChangeInCapacity
      AutoScalingGroupName: !Ref PrivateReportAutoscalingGroup
      Cooldown: 300
      ScalingAdjustment: 100

