Description: >
  This template deploys an empty ECS Cluster and a mesh(AppMesh), 
  Public ALB, Security Groups, CloudMap Namespace and Static entries for Virtual Services.

Parameters:

  EnvironmentName:
    Description: An environment name that will be prefixed to resource names
    Type: String

  EnvoyImage:
    Description: App Mesh Envoy container image. See https://docs.aws.amazon.com/app-mesh/latest/userguide/envoy.html.
    Type: String

  Namespace:
    Type: String
    Default: yelb.local

  ContainerPort:
    Type: Number
    Default: 80

  LoadBalancerPort:
    Type: Number
    Default: 80

  CountOfGatewayTasks:
    Description: The number of tasks to be instantiated for the Gateway service
    Type: Number
    Default: 1

  LoadBalancerPath:
    Type: String
    Default: "*"
    Description: A path on the public load balancer that this service
                 should be connected to. Use * to send all load balancer
                 traffic to this service.

Resources:

  #------ ECS Resources ------#
  #ECS Cluster
  Cluster:
    Type: AWS::ECS::Cluster
    Properties:
      ClusterName: !Ref EnvironmentName
      ClusterSettings:
        - Name: containerInsights
          Value: enabled

  #ECS Task Execution Role
  ExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Join ['-', [!Ref EnvironmentName, ExecutionRole]]
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service: ecs-tasks.amazonaws.com
            Action: 'sts:AssumeRole'
      ManagedPolicyArns:
        - 'arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy'

  #ECS Task Role
  TaskRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Join ['-', [!Ref EnvironmentName, TaskRole]]
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service: ecs-tasks.amazonaws.com
            Action: 'sts:AssumeRole'
      ManagedPolicyArns:
        - 'arn:aws:iam::aws:policy/CloudWatchFullAccess'
        - 'arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess'
        - 'arn:aws:iam::aws:policy/AWSAppMeshEnvoyAccess'

  #ECS Container SecurityGroup
  ContainerSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: !Join ['-', [!Ref EnvironmentName, ContainerSecurityGroup]]
      VpcId:
        'Fn::ImportValue': !Sub "${EnvironmentName}:VPC"
      SecurityGroupIngress:
        - IpProtocol: tcp
          CidrIp: '0.0.0.0/0'
          FromPort: 1025
          ToPort: 65535
        - IpProtocol: tcp
          FromPort: !Ref ContainerPort
          ToPort: !Ref ContainerPort
          SourceSecurityGroupId: !Ref LoadBalancerSecurityGroup

  #------ Load Balancing Resources ------#
  #Load Balancing Security Group
  LoadBalancerSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: !Join ['-', [!Ref EnvironmentName, LoadBalancerSecurityGroup]]
      VpcId:
        'Fn::ImportValue': !Sub "${EnvironmentName}:VPC"
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: !Ref LoadBalancerPort
          ToPort: !Ref LoadBalancerPort
          CidrIp: 0.0.0.0/0

  #Load Balancer
  PublicLoadBalancer:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Scheme: internet-facing
      Subnets:
        - { 'Fn::ImportValue': !Sub "${EnvironmentName}:PublicSubnet1" }  
        - { 'Fn::ImportValue': !Sub "${EnvironmentName}:PublicSubnet2" }  
      SecurityGroups: [!Ref 'LoadBalancerSecurityGroup']

  #Load Balancer Listener
  PublicLoadBalancerListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    DependsOn:
      - PublicLoadBalancer
    Properties:
      DefaultActions:
        - TargetGroupArn: !Ref TargetGroup
          Type: 'forward'
      LoadBalancerArn: !Ref 'PublicLoadBalancer'
      Port: 80
      Protocol: HTTP

  #Load Balancer TargetGroup
  TargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      HealthCheckIntervalSeconds: 6
      HealthCheckPath: '/'
      HealthCheckProtocol: HTTP
      HealthCheckTimeoutSeconds: 5
      HealthyThresholdCount: 2
      TargetType: ip
      Name: !Sub 'yelb-targetgroup'
      Port: 80
      Protocol: HTTP
      UnhealthyThresholdCount: 2
      TargetGroupAttributes:
      - Key: deregistration_delay.timeout_seconds
        Value: 120
      VpcId:
        Fn::ImportValue: !Sub '${EnvironmentName}:VPC'

  #Load Balancer Rule(s)
  LoadBalancerRule:
    Type: AWS::ElasticLoadBalancingV2::ListenerRule
    Properties:
      Actions:
        - TargetGroupArn: !Ref TargetGroup
          Type: 'forward'
      Conditions:
        - Field: path-pattern
          Values: [!Ref 'LoadBalancerPath']
      ListenerArn: !Ref PublicLoadBalancerListener
      Priority: 1

  #------ CloudMap Resources ------#
  #CloudMap Namespace
  CloudMapNamespace:
    Type: AWS::ServiceDiscovery::PrivateDnsNamespace
    Properties:
      Name: !Ref 'Namespace'
      Vpc: 
       Fn::ImportValue:
        !Sub "${EnvironmentName}:VPC"
      Description: !Sub "Service Discovery Namespace for Yelb"

  #Mesh Resource
  Mesh:
    Type: AWS::AppMesh::Mesh
    Properties:
      MeshName: !Ref EnvironmentName

  #To workaround the issue on AppMesh: https://github.com/aws/aws-app-mesh-roadmap/issues/65
  YelbUIStaticServiceDiscoveryEntry:
    Type: AWS::ServiceDiscovery::Service
    Properties:
        Name: yelb-ui
        DnsConfig:
            DnsRecords: 
                - Type: A
                  TTL: "10"
            NamespaceId: !Ref 'CloudMapNamespace'

  YelbUIIPStaticEntry:
    Type: AWS::ServiceDiscovery::Instance
    Properties:
      InstanceAttributes:
        AWS_INSTANCE_IPV4: 10.10.10.10
      ServiceId: !Ref YelbUIStaticServiceDiscoveryEntry
  

  YelbAppServerStaticServiceDiscoveryEntry:
    Type: AWS::ServiceDiscovery::Service
    Properties:
        Name: yelb-appserver
        DnsConfig:
            DnsRecords: 
                - Type: A
                  TTL: "10"
            NamespaceId: !Ref 'CloudMapNamespace'

  YelbAppServerIPStaticEntry:
    Type: AWS::ServiceDiscovery::Instance
    Properties:
      InstanceAttributes:
        AWS_INSTANCE_IPV4: 10.10.10.10
      ServiceId: !Ref YelbAppServerStaticServiceDiscoveryEntry
  
  YelbRedisStaticServiceDiscoveryEntry:
    Type: AWS::ServiceDiscovery::Service
    Properties:
        Name: yelb-redisserver
        DnsConfig:
            DnsRecords: 
                - Type: A
                  TTL: "10"
            NamespaceId: !Ref 'CloudMapNamespace'

  YelbRedisIPStaticEntry:
    Type: AWS::ServiceDiscovery::Instance
    Properties:
      InstanceAttributes:
        AWS_INSTANCE_IPV4: 10.10.10.10
      ServiceId: !Ref YelbRedisStaticServiceDiscoveryEntry

  YelbDBStaticServiceDiscoveryEntry:
    Type: AWS::ServiceDiscovery::Service
    Properties:
        Name: yelb-db
        DnsConfig:
            DnsRecords: 
                - Type: A
                  TTL: "10"
            NamespaceId: !Ref 'CloudMapNamespace'

  YelbDBStaticEntry:
    Type: AWS::ServiceDiscovery::Instance
    Properties:
      InstanceAttributes:
        AWS_INSTANCE_IPV4: 10.10.10.10
      ServiceId: !Ref YelbDBStaticServiceDiscoveryEntry

Outputs:

  Mesh:
    Description: A reference to the AppMesh Mesh
    Value: !GetAtt Mesh.MeshName
    Export:
      Name: !Sub "${EnvironmentName}:Mesh"

  ExecutionRole:
    Description: A reference to the Task Execution Role
    Value: !GetAtt ExecutionRole.Arn
    Export:
      Name: !Sub "${EnvironmentName}:ExecutionRole"
  
  TaskRole:
    Description: A reference to the TaskRole
    Value: !GetAtt TaskRole.Arn
    Export:
      Name: !Sub "${EnvironmentName}:TaskRole"

  ContainerSecurityGroup:
    Description: A reference to the ContainerSecurityGroup
    Value: !Ref ContainerSecurityGroup
    Export:
      Name: !Sub "${EnvironmentName}:ContainerSecurityGroup"

  Namespace:
    Description: A reference to the Namespace
    Value: !GetAtt 'CloudMapNamespace.Id'
    Export:
      Name: !Sub "${EnvironmentName}:CloudMapNamespaceId"

  PublicLoadBalancer:
    Description: A reference to the LoadBalancer DNSName
    Value: !GetAtt PublicLoadBalancer.DNSName
    Export:
      Name: !Sub "${EnvironmentName}:PublicLoadBalancer"

  TargetGroupArn:
    Description: A reference to the TargetGroup
    Value: !Ref TargetGroup
    Export:
      Name: !Sub "${EnvironmentName}:TargetGroupArn"
