AWSTemplateFormatVersion: 2010-09-09
Description: >-
  AWS CloudFormation Sample Template: Create an LB connected to an EC2 machine with a nginx server on it, with logs sent to CloudWatch
  This template assumes that you have a correctly setup VPC with 1 or more private subnets and 2 or more public subnets.
  If you plan to SSH directly into the EC2 machine, you might also need to manually create a Bastion host.
  Depending on your VPC setup.

  This template also assigns an EC2 role which enables SSM access should you want to directly connect without SSH.

  NOTE: This Template will Create Resources on AWS that will incurr cost!

Parameters:
  VpcId:
    Type: 'AWS::EC2::VPC::Id'
    Description: VpcId of your existing Virtual Private Cloud (VPC)
    ConstraintDescription: must be the VPC Id of an existing Virtual Private Cloud.
  LoadBalancerSubnets:
    Type: 'List<AWS::EC2::Subnet::Id>'
    Description: The list of public SubnetIds to be used by the Load Balancer
    ConstraintDescription: >-
      must be a list of at least two existing public subnets associated with at least
      two different availability zones. They should be residing in the selected
      Virtual Private Cloud.
  WebAppSubnets:
    Type: AWS::EC2::Subnet::Id
    Description: The subnet ID for the webapp server
    ConstraintDescription: >-
      Must be the ID of a Private subnet in your VPC
  InstanceType:
    Description: WebServer EC2 instance type
    Type: String
    Default: t2.small
  KeyName:
    Description: The EC2 Key Pair to allow SSH access to the instances
    Type: 'AWS::EC2::KeyPair::KeyName'
    ConstraintDescription: must be the name of an existing EC2 KeyPair.
  IngressLocation:
    Description: The IP address range that can be used to access EC2 instances
    Type: String
    MinLength: '9'
    MaxLength: '18'
    Default: 0.0.0.0/0
    AllowedPattern: '(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})'
    ConstraintDescription: must be a valid IP CIDR range of the form x.x.x.x/x.
Mappings:
  Region2Examples:
    us-east-1:
      Examples: 'https://s3.amazonaws.com/cloudformation-examples-us-east-1'
    us-west-2:
      Examples: 'https://s3-us-west-2.amazonaws.com/cloudformation-examples-us-west-2'
  AWSInstanceType2Arch:
    t2.nano:
      Arch: HVM64
    t2.micro:
      Arch: HVM64
    t2.small:
      Arch: HVM64
    t2.medium:
      Arch: HVM64
    t2.large:
      Arch: HVM64
  AWSInstanceType2NATArch:
    t2.nano:
      Arch: NATHVM64
    t2.micro:
      Arch: NATHVM64
    t2.small:
      Arch: NATHVM64
    t2.medium:
      Arch: NATHVM64
    t2.large:
      Arch: NATHVM64
  AWSRegionArch2AMI:
    us-east-1:
      HVM64: ami-0c2b8ca1dad447f8a
    us-west-2:
      HVM64: ami-083ac7c7ecf9bb9b0
Resources:
  MyEC2Instance: 
    Type: AWS::EC2::Instance
    Properties: 
      ImageId: !FindInMap 
        - AWSRegionArch2AMI
        - !Ref 'AWS::Region'
        - !FindInMap 
          - AWSInstanceType2Arch
          - !Ref InstanceType
          - Arch
      KeyName: !Ref KeyName
      UserData:
        Fn::Base64: !Sub |
          #!/bin/bash
          yum update -y
          amazon-linux-extras install epel -y
          yum install nginx git gcc build-essential python3-pip python3-devel python3-setuptools -y
          mkdir /var/www
          git clone https://github.com/PagerDuty/sample-app-workshop.git /var/www
          cd /var/www
          python3 -m venv svcstopappenv
          source svcstopappenv/bin/activate
          pip3 install -r requirements.txt && deactivate
          cp /var/www/config/svcstopapp.service /etc/systemd/system
          usermod -a -G nginx ec2-user
          chmod -R 755 /var/www
          chown -R ec2-user:nginx /var/www
          systemctl start svcstopapp.service
          systemctl enable svcstopapp.service
          cp config/nginx.conf /etc/nginx/nginx.conf
          cp config/svcstopapp.conf /etc/nginx/conf.d/svcstopapp.conf
          systemctl start nginx
          systemctl enable nginx
          # Log Agent Setup
          cd ~
          mkdir -p /var/awslogs/state/
          touch /var/awslogs/state/agent-state
          chmod 775 /var/awslogs/state/agent-state
          curl https://gist.githubusercontent.com/giranm/7d80546ad76894b4a15eafad45c66ad6/raw/d92dbb673ad640a47f6c355bc08149158ef61c84/awslogs-agent-setup.py -O
          chmod +x ./awslogs-agent-setup.py
          ./awslogs-agent-setup.py -n -r us-east-1 -c https://gist.githubusercontent.com/giranm/9652d3f631d68b2e261ce39786ab8237/raw/2dbb3e29044bb254ca12c639838912a8d57e5e56/awslogs.conf
          systemctl start awslogs.service
          systemctl enable awslogs.service
      SecurityGroupIds:
            - !GetAtt "InstanceSecurityGroup.GroupId"
      InstanceType: !Ref InstanceType
      SubnetId: !Ref WebAppSubnets
      IamInstanceProfile: !Ref EC2InstanceProfile
# Attaching CloudWatch logs role needed
  EC2InstanceRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: ec2-instance-role
      ManagedPolicyArns:
        # Needed for SSM
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          -
            Effect: Allow
            Principal:
              Service:
                - ec2.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: /
  EC2InstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties: 
      InstanceProfileName: ec2-instance-profile
      Path: /
      Roles: 
       - !Ref EC2InstanceRole
  EC2InstanceCloudWatchLogsPolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: ec2-instance-cloudwatch-logs-policy
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          -
            Effect: Allow
            Action:
              - logs:*
            Resource:
              - "*"
      Roles:
        - !Ref EC2InstanceRole
# Existing resources
  ApplicationLoadBalancer:
    Type: 'AWS::ElasticLoadBalancingV2::LoadBalancer'
    Properties:
      SecurityGroups: 
        - !Ref LoadBalancerSecurityGroup
      Subnets: !Ref LoadBalancerSubnets
  ALBListener:
    Type: 'AWS::ElasticLoadBalancingV2::Listener'
    Properties:
      DefaultActions:
        - Type: forward
          TargetGroupArn: !Ref ALBTargetGroup
      LoadBalancerArn: !Ref ApplicationLoadBalancer
      Port: '80'
      Protocol: HTTP
  ALBTargetGroup:
    Type: 'AWS::ElasticLoadBalancingV2::TargetGroup'
    Properties:
      HealthCheckIntervalSeconds: 10
      HealthCheckTimeoutSeconds: 5
      HealthyThresholdCount: 2
      Port: 80
      Protocol: HTTP
      UnhealthyThresholdCount: 2
      VpcId: !Ref VpcId
      Targets:
      - Id:
          Ref: MyEC2Instance
  InstanceSecurityGroup:
    Type: 'AWS::EC2::SecurityGroup'
    Properties:
      GroupDescription: Enable SSH access and HTTP from the load balancer only
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: '22'
          ToPort: '22'
          CidrIp: !Ref IngressLocation
        - IpProtocol: tcp
          FromPort: '80'
          ToPort: '80'
          SourceSecurityGroupId: !Select 
            - 0
            - !GetAtt 
              - ApplicationLoadBalancer
              - SecurityGroups
      VpcId: !Ref VpcId
  LoadBalancerSecurityGroup:
    Type: 'AWS::EC2::SecurityGroup'
    Properties:
      GroupDescription: Enable Access to Loadbalancer from selected IP Range
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: '80'
          ToPort: '80'
          CidrIp: !Ref IngressLocation
      VpcId: !Ref VpcId
Outputs:
  SessionManagementListUrl:
    Description: The URL to the Session Management Console listing all instances it is aware of
    Value: !Sub https://${AWS::Region}.console.aws.amazon.com/systems-manager/session-manager/start-session?region=${AWS::Region}
  URL:
    Description: The URL of the website
    Value: !Join 
      - ''
      - - 'http://'
        - !GetAtt 
          - ApplicationLoadBalancer
          - DNSName