AWSTemplateFormatVersion: '2010-09-09'
Description: Resources to setup Linkerd for routing traffic in cluster
Parameters:
  keyPairName:
    Description:  Key pair name for ec2.
    Type: String
  ami:
    Description:  Amazon image ID.
    Type: String
  publicSubnet1a:
    Description:  subnet to launch virtual server in.
    Type: AWS::EC2::Subnet::Id
  publicSubnet1b:
    Description:  subnet to launch virtual server in.
    Type: AWS::EC2::Subnet::Id
  instanceType:
    Description:  instance tyep for ec2.
    Type: String
    Default: m5.xlarge
  # volSize:
  #   Description:  The size of root volume for ec2.
  #   Type: Number
  #   Default: 16
  ecsCluster:
    Description:  ECS Cluster for application running.
    Type: String
  nodeSecurityGroupId:
    Description:  Security group id for ECS nodes.
    Type: String
  nodesRole:
    Description: Role for nodes in ECS cluster.
    Type: String
  s3Dns:
    Description: DNS of S3 endpoint.
    Type: String


Resources:
  Ec2InstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Path: "/"
      Roles:
        - !Ref nodesRole

  # Consul instance (Note that this is a single standalone instance but
  # should be HA for production, and potentially deployed in the ECS
  # cluster as well)
  ConsulInstance:
    Type: AWS::EC2::Instance
    CreationPolicy:
      ResourceSignal:
        Timeout: PT15M
    Properties:
      SubnetId: !Ref publicSubnet1a
      ImageId: !Ref ami
      IamInstanceProfile: !Ref Ec2InstanceProfile
      InstanceType: !Ref instanceType
      KeyName: !Ref keyPairName
      UserData:
        Fn::Base64: !Sub |
          #!/bin/bash -x
          sudo yum update -y
          sudo yum install -y aws-cfn-bootstrap aws-cli jq wget

          #docker & ecs-agent (removed due to use ecs-optimiezed AMI)
          #sudo amazon-linux-extras disable docker
          #sudo amazon-linux-extras install -y ecs
          #sudo systemctl restart docker
          #usermod -a -G docker ec2-user

          # Install ssm-agent (added due to use ecs-optimiezed AMI)
          sudo yum install -y https://${s3Dns}/amazon-ssm-${AWS::Region}/latest/linux_amd64/amazon-ssm-agent.rpm
          sudo systemctl enable amazon-ssm-agent
          sudo systemctl start amazon-ssm-agent
        
          EC2_INSTANCE_IP_ADDRESS=$(curl -s http://169.254.169.254/latest/meta-data/local-ipv4)
          EC2_INSTANCE_ID=$(curl -s http://169.254.169.254/latest/meta-data/instance-id)
          mkdir -p /opt/consul/data
          mkdir -p /opt/consul/config
          docker run -d --net=host -p 8300:8300 -p 8301:8301 -p 8301:8301/udp -p 8302:8302 \
            -p 8302:8302/udp -p 8400:8400 -p 8500:8500 -p 53:53/udp \
            -v /opt/consul/data:/consul/data -v /opt/consul/config:/consul/config \
            -v /var/run/docker.sock:/var/run/docker.sock \
            -h $EC2_INSTANCE_ID --name consul-server -e CONSUL_ALLOW_PRIVILEGED_PORTS=1 \
            -l service_name=consul-server consul:1.2.3 agent -server \
            -bootstrap-expect 1 -advertise $EC2_INSTANCE_IP_ADDRESS -client 0.0.0.0 -ui
        
          /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource ConsulInstance --region ${AWS::Region}

      SecurityGroupIds: 
        - !Ref nodeSecurityGroupId
      Tags:
        - Key: Name
          Value: !Sub '${AWS::StackName}-consul-server'

  # A log group for storing the std logs
  LinkerdLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub '${AWS::StackName}-daemon-linkerd'

  # Define the linkerd task
  LinkerdTaskDefinition:
    Type: AWS::ECS::TaskDefinition
    Properties:
      Family: linkerd
      Volumes:
        - Host:
            SourcePath: '/etc/linkerd'
          Name: linkerd-config
      ContainerDefinitions:
        - Name: linkerd
          Image: docker.io/buoyantio/linkerd:1.4.6
          Command:
            - "-log.level=DEBUG"
            - "/etc/linkerd/linkerd.yaml"
          Memory: 1024
          PortMappings:
            - HostPort: 4140
              ContainerPort: 4140
              Protocol: tcp
            - HostPort: 4141
              ContainerPort: 4141
              Protocol: tcp
            - HostPort: 9990
              ContainerPort: 9990
              Protocol: tcp
          Essential: true
          MountPoints:
            - ContainerPath: '/etc/linkerd'
              SourceVolume: 'linkerd-config'
              ReadOnly: true
          Environment:
            - Name: SERVICE_9990_NAME
              Value: linkerd
          LogConfiguration:
            LogDriver: 'awslogs'
            Options:
              awslogs-group: !Sub '${AWS::StackName}-daemon-linkerd'
              awslogs-region: !Ref 'AWS::Region'
              awslogs-stream-prefix: 'linkerd'

  # Linkerd daemon
  LinkerdDaemon:
    Type: AWS::ECS::Service
    Properties:
      ServiceName: 'linkerd'
      Cluster: !Ref ecsCluster
      TaskDefinition: !Ref LinkerdTaskDefinition
      SchedulingStrategy: 'DAEMON'

  # A log group for storing the std logs
  ConsulAgentLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub '${AWS::StackName}-daemon-consul-agent'

  # Consul agent role. This role authorizes the Consul daemon to query the list of EC2 instances
  # by tag in order to locate the Consul server.
  ConsulAgentRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
        - Effect: Allow
          Principal:
            Service: "ecs-tasks.amazonaws.com"
          Action: ['sts:AssumeRole']
      Path: /
      Policies:
      - PolicyName: query-ec2-instances
        PolicyDocument:
          Statement:
          - Effect: Allow
            Action:
              - 'ec2:DescribeInstances'
            Resource: '*'

  # Define the consul agent task
  ConsulAgentTaskDefinition:
    Type: AWS::ECS::TaskDefinition
    Properties:
      Family: consul-agent
      TaskRoleArn: !GetAtt 'ConsulAgentRole.Arn'
      Volumes:
        - Host:
            SourcePath: /opt/consul/config
          Name: consul-config
        - Host:
            SourcePath: /opt/consul/data
          Name: consul-data
        - Host:
            SourcePath: /var/run/docker.sock
          Name: consul-docker
      ContainerDefinitions:
        - Name: consul-agent
          Image: docker.io/consul:1.2.3
          Command: [
            "consul", "agent", "-ui",
            "-config-file", "/etc/consul/consul-agent.json",
            "-data-dir", "/consul/data"
          ]
          Memory: 128
          PortMappings:
            - HostPort: 8301
              ContainerPort: 8301
              Protocol: tcp
            - HostPort: 8301
              ContainerPort: 8301
              Protocol: udp
            - HostPort: 8400
              ContainerPort: 8400
              Protocol: tcp
            - HostPort: 8500
              ContainerPort: 8500
              Protocol: tcp
            - HostPort: 53
              ContainerPort: 53
              Protocol: udp
          Essential: true
          MountPoints:
            - ContainerPath: /etc/consul
              SourceVolume: consul-config
              ReadOnly: true
            - ContainerPath: /consul/data
              SourceVolume: consul-data
              ReadOnly: false
            - ContainerPath: /var/run/docker.sock
              SourceVolume: consul-docker
              ReadOnly: true
          LogConfiguration:
            LogDriver: 'awslogs'
            Options:
              awslogs-group: !Sub '${AWS::StackName}-daemon-consul-agent'
              awslogs-region: !Ref 'AWS::Region'
              awslogs-stream-prefix: 'consul-agent'

  # Consul agent daemon
  ConsulAgentDaemon:
    Type: AWS::ECS::Service
    Properties:
      ServiceName: 'consul-agent'
      Cluster: !Ref ecsCluster
      TaskDefinition: !Ref ConsulAgentTaskDefinition
      SchedulingStrategy: 'DAEMON'

  # A log group for storing the std logs
  ConsulRegistratorLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub '${AWS::StackName}-daemon-consul-registrator'

  # Define the consul registrator task
  ConsulRegistratorTaskDefinition:
    Type: AWS::ECS::TaskDefinition
    Properties:
      Family: consul-registrator
      Volumes:
        - Host:
            SourcePath: /opt/consul-registrator/bin
          Name: consul-registrator-bin
        - Host:
            SourcePath: /var/run/docker.sock
          Name: consul-registrator-docker
      NetworkMode: host
      ContainerDefinitions:
        - Name: consul-registrator
          Image: "gliderlabs/registrator:v7"
          EntryPoint:
            - /bin/consul-registrator/start.sh
          Memory: 128
          Essential: true
          MountPoints:
            - ContainerPath: /bin/consul-registrator
              SourceVolume: consul-registrator-bin
              ReadOnly: true
            - ContainerPath: /tmp/docker.sock
              SourceVolume: consul-registrator-docker
              ReadOnly: true
          LogConfiguration:
            LogDriver: 'awslogs'
            Options:
              awslogs-group: !Sub '${AWS::StackName}-daemon-consul-registrator'
              awslogs-region: !Ref 'AWS::Region'
              awslogs-stream-prefix: 'consul-registrator'

  # Consul registrator daemon
  ConsulRegistratorDaemon:
    Type: AWS::ECS::Service
    Properties:
      ServiceName: 'consul-registrator'
      Cluster: !Ref ecsCluster
      TaskDefinition: !Ref ConsulRegistratorTaskDefinition
      SchedulingStrategy: 'DAEMON'

Outputs:
  ConsulDashboard:
    Description: The address of the Consul admin dashboard
    Value: !Join [ '', [ 'http://', !GetAtt 'ConsulInstance.PublicDnsName', ':8500' ]]