Description: Creates Airflow Flower Service

Resources:
  FlowerExternalSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security group for Airflow Flower ALB
      GroupName: "{{ serviceName }}-{{ ENVIRONMENT }}-flower-external-security-group"
      SecurityGroupEgress:
      {% for ip in whitelistedIPs %}
        - CidrIp: "{{ ip }}"
          FromPort: 0
          IpProtocol: -1
          ToPort: 0
      {% endfor %}
      SecurityGroupIngress:
      {% for ip in whitelistedIPs %}
        - CidrIp: "{{ ip }}"
          FromPort: "{{ service.port }}"
          IpProtocol: tcp
          ToPort: "{{ service.port }}"
      {% endfor %}
      {% for ip in whitelistedIPs %}
        - CidrIp: "{{ ip }}"
          FromPort: "{{ service.portSSL }}"
          IpProtocol: tcp
          ToPort: "{{ service.portSSL }}"
      {% endfor %}
      VpcId: {{ externalServices.VPC.VpcId }}
      Tags:
        - Key: Name
          Value: "{{ serviceName }}-{{ ENVIRONMENT }}-flower-external-security-group"

  FlowerInternalSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security group for Airflow Flower
      GroupName: "{{ serviceName }}-{{ ENVIRONMENT }}-flower-internal-security-group"
      SecurityGroupEgress:
        - CidrIp: 0.0.0.0/0
          FromPort: 0
          IpProtocol: -1
          ToPort: 0
      SecurityGroupIngress:
        - SourceSecurityGroupId: !GetAtt FlowerExternalSecurityGroup.GroupId
          FromPort: "{{ service.flower.port }}"
          IpProtocol: tcp
          ToPort: "{{ service.flower.port }}"
      VpcId: {{ externalServices.VPC.VpcId }}
      Tags:
        - Key: Name
          Value: "{{ serviceName }}-{{ ENVIRONMENT }}-flower-internal-security-group"

  FlowerTaskDefinition:
    Type: AWS::ECS::TaskDefinition
    Properties:
      Cpu: "{{ service.flower.cpu }}"
      Memory: "{{ service.flower.memory }}"
      ExecutionRoleArn: !ImportValue airflow-{{ ENVIRONMENT }}-iam-ECSTaskRoleArn
      TaskRoleArn: !ImportValue airflow-{{ ENVIRONMENT }}-iam-ECSTaskRoleArn
      Family: "{{ serviceName }}-{{ ENVIRONMENT }}-flower-task-definition"
      NetworkMode: awsvpc
      RequiresCompatibilities:
        - FARGATE
      Volumes:
        - Name: "fansifter-efs"
          EFSVolumeConfiguration:
            FilesystemId: "{{ externalServices.EFS[ENVIRONMENT].FilesystemId }}"
            TransitEncryption: ENABLED
            AuthorizationConfig:
              AccessPointId: "{{ externalServices.EFS[ENVIRONMENT].AccessPointId }}"
              IAM: ENABLED
      ContainerDefinitions:
        - Name: "{{ serviceName }}-{{ ENVIRONMENT }}-flower"
          Image: !Sub
            - "${AWS::AccountId}.dkr.ecr.{{ AWS_REGION }}.amazonaws.com/${ecr_repository}:latest"
            - ecr_repository: "fansifter/airflow-{{ ENVIRONMENT }}"
          Essential: true
          PortMappings:
            - ContainerPort: "{{ service.flower.port }}"
              HostPort: "{{ service.flower.port }}"
          Command:
            - flower
          MountPoints:
            - ContainerPath: "/mnt/efs"
              SourceVolume: "fansifter-efs"
          Environment:
            - Name: REDIS_HOST
              Value: !ImportValue airflow-{{ ENVIRONMENT }}-redis-CeleryBackendHost
            - Name: REDIS_PORT
              Value: !ImportValue airflow-{{ ENVIRONMENT }}-redis-CeleryBackendPort
            - Name: POSTGRES_HOST
              Value: !Join ['', ['{{ "{{" }}resolve:secretsmanager:', {{ externalServices.Secrets.RDS[ENVIRONMENT].SecretARN }}, ':SecretString:host{{ "}}" }}' ]]
            - Name: POSTGRES_PORT
              Value: !Join ['', ['{{ "{{" }}resolve:secretsmanager:', {{ externalServices.Secrets.RDS[ENVIRONMENT].SecretARN }}, ':SecretString:port{{ "}}" }}' ]]
            - Name: POSTGRES_USER
              Value: !Join ['', ['{{ "{{" }}resolve:secretsmanager:', {{ externalServices.Secrets.RDS[ENVIRONMENT].SecretARN }}, ':SecretString:username{{ "}}" }}' ]]
            - Name: POSTGRES_PASSWORD
              Value: !Join ['', ['{{ "{{" }}resolve:secretsmanager:', {{ externalServices.Secrets.RDS[ENVIRONMENT].SecretARN }}, ':SecretString:password{{ "}}" }}' ]]
            - Name: POSTGRES_DB
              Value: "{{ metadataDb.dbName }}"
            - Name: AIRFLOW_BASE_URL
              Value: "{{ service.baseUrl }}"
            - Name: STAGE
              Value: "{{ ENVIRONMENT }}"
            - Name: FERNET_KEY
              Value: !Join ['', ['{{ "{{" }}resolve:secretsmanager:', !ImportValue airflow-{{ ENVIRONMENT }}-secrets-AirflowFernetKeySecretId, ':SecretString:fernet_key{{ "}}" }}' ]]
            # Overwriting values from airflow.cfg
#            - Name: AIRFLOW__LOGGING__REMOTE_LOGGING
#              Value: True
#            - Name: AIRFLOW__LOGGING__REMOTE_BASE_LOG_FOLDER
#              Value: !Join ['', ["cloudwatch://", !ImportValue airflow-{{ ENVIRONMENT }}-cloudwatch-AirflowExecutionLogGroupArn]]
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !ImportValue airflow-{{ ENVIRONMENT }}-cloudwatch-AirflowECSLogGroupName
              awslogs-region: "{{ AWS_REGION }}"
              awslogs-stream-prefix: flower
      Tags:
        - Key: Name
          Value: "{{ serviceName }}-{{ ENVIRONMENT }}-flower-task-definition"

  FlowerAlb:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Name: "{{ serviceName }}-{{ ENVIRONMENT }}-flower-alb"
      Type: application
      Scheme: internal
      SecurityGroups:
        - !Ref FlowerExternalSecurityGroup
      Subnets:
        - {{ externalServices.VPC.PublicSubnet1Id }}
        - {{ externalServices.VPC.PublicSubnet2Id }}
        - {{ externalServices.VPC.PublicSubnet3Id }}
      Tags:
        - Key: Name
          Value: "{{ serviceName }}-{{ ENVIRONMENT }}-flower-alb"

  FlowerAlbTargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      Name: "{{ serviceName }}-{{ ENVIRONMENT }}-flower-alb-tg"
      Port: "{{ service.flower.port }}"
      Protocol: HTTP
      VpcId: {{ externalServices.VPC.VpcId }}
      TargetType: ip
      HealthCheckEnabled: true
      HealthCheckIntervalSeconds: 60
      HealthCheckPath: /
      HealthCheckPort: "{{ service.flower.port }}"
      HealthCheckProtocol: HTTP
      HealthCheckTimeoutSeconds: 5
      HealthyThresholdCount: 2
      UnhealthyThresholdCount: 3
      Tags:
        - Key: Name
          Value: "{{ serviceName }}-{{ ENVIRONMENT }}-flower-alb-tg"

  FlowerAlbListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      DefaultActions:
        - TargetGroupArn: !Ref FlowerAlbTargetGroup
          Type: forward
      LoadBalancerArn: !Ref FlowerAlb
      Port: "{{ service.port }}"
      Protocol: HTTP

  FlowerAlbListenerSSL:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      DefaultActions:
        - TargetGroupArn: !Ref FlowerAlbTargetGroup
          Type: forward
      LoadBalancerArn: !Ref FlowerAlb
      Port: "{{ service.portSSL }}"
      Protocol: HTTPS
      Certificates:
        - CertificateArn: {{ externalServices.SSL.CertificateArn }}


  FlowerEcsService:
    Type: AWS::ECS::Service
    DependsOn: FlowerAlbListener
    Properties:
      ServiceName: "{{ serviceName }}-{{ ENVIRONMENT }}-flower"
      Cluster: !ImportValue airflow-{{ ENVIRONMENT }}-ecs-AirflowECSClusterArn
      TaskDefinition: !Ref FlowerTaskDefinition
      LaunchType: FARGATE
      DesiredCount: "{{ service.flower.desiredCount }}"
      NetworkConfiguration:
        AwsvpcConfiguration:
          AssignPublicIp: ENABLED
          SecurityGroups:
            - !Ref FlowerInternalSecurityGroup
          Subnets:
            - {{ externalServices.VPC.PublicSubnet1Id }}
            - {{ externalServices.VPC.PublicSubnet2Id }}
            - {{ externalServices.VPC.PublicSubnet3Id }}
      LoadBalancers:
        - ContainerName: "{{ serviceName }}-{{ ENVIRONMENT }}-flower"
          ContainerPort: "{{ service.flower.port }}"
          TargetGroupArn: !Ref FlowerAlbTargetGroup

  FlowerScalableTarget:
    Type: AWS::ApplicationAutoScaling::ScalableTarget
    Properties:
      MaxCapacity: "{{ service.flower.autoscaling.maxCapacity }}"
      MinCapacity: "{{ service.flower.autoscaling.minCapacity }}"
      RoleARN: !Sub 'arn:aws:iam::${AWS::AccountId}:role/aws-service-role/ecs.application-autoscaling.amazonaws.com/AWSServiceRoleForApplicationAutoScaling_ECSService'
      ServiceNamespace: ecs
      ScalableDimension: 'ecs:service:DesiredCount'
      ResourceId: !Join ["/", [service, !ImportValue airflow-{{ ENVIRONMENT }}-ecs-AirflowECSClusterId, !GetAtt FlowerEcsService.Name]]


  ServiceScalingPolicyCPU:
    Type: AWS::ApplicationAutoScaling::ScalingPolicy
    Properties:
      PolicyName: "{{ serviceName }}-{{ ENVIRONMENT }}-flower-tracking-cpu"
      PolicyType: TargetTrackingScaling
      ScalingTargetId: !Ref FlowerScalableTarget
      TargetTrackingScalingPolicyConfiguration:
        TargetValue: "{{ service.flower.autoscaling.cpu.target }}"
        ScaleInCooldown: "{{ service.flower.autoscaling.cpu.scaleInCooldown }}"
        ScaleOutCooldown: "{{ service.flower.autoscaling.cpu.scaleOutCooldown }}"
        PredefinedMetricSpecification:
          PredefinedMetricType: ECSServiceAverageCPUUtilization

  ServiceScalingPolicyMemory:
    Type: AWS::ApplicationAutoScaling::ScalingPolicy
    Properties:
      PolicyName: "{{ serviceName }}-{{ ENVIRONMENT }}-flower-tracking-memory"
      PolicyType: TargetTrackingScaling
      ScalingTargetId: !Ref FlowerScalableTarget
      TargetTrackingScalingPolicyConfiguration:
        TargetValue: "{{ service.flower.autoscaling.memory.target }}"
        ScaleInCooldown: "{{ service.flower.autoscaling.memory.scaleInCooldown }}"
        ScaleOutCooldown: "{{ service.flower.autoscaling.memory.scaleOutCooldown }}"
        PredefinedMetricSpecification:
          PredefinedMetricType: ECSServiceAverageMemoryUtilization

Outputs:
  AirflowFlowerEndpoint:
    Value: !GetAtt FlowerAlb.DNSName
    Export:
      Name: airflow-{{ ENVIRONMENT }}-flower-AlbDNSName
  AirflowFlowerAlbHostedZoneID:
    Value: !GetAtt FlowerAlb.CanonicalHostedZoneID
    Export:
      Name: airflow-{{ ENVIRONMENT }}-flower-CanonicalHostedZoneID