Description: >
  WebViewer Server Infrastructure 3.0.0

Parameters:
  WebViewerServerVersion:
    Type: String
    Description: "The version of webviewer server to pull from docker hub. This is appended to the end of the pull."

    Default: "3.0.0"
    ConstraintDescription: "Must be a valid version tag or else the server build will fail."
    
  BalancerVersion:
    Type: String
    Description: "The version of webviewer server load balancer to pull from docker hub. This is appended to the end of the pull."
    Default: "3.0.0"
    ConstraintDescription: "Must be a valid version tag or else the server build will fail."

  PDFClusterMaxSize:
    Description: The maximum number of PDF instance servers the ECS cluster can scale up to.
    MinValue: 1
    MaxValue: 16
    Default: 2
    ConstraintDescription: Must be a value between 1 and 16. Msut be greater than or equal to PDFClusterMinSize.
    Type: Number

  PDFClusterMinSize:
    Description: The minimum number of PDF instance servers the ECS cluster can scale down to.
    MinValue: 1
    MaxValue: 16
    Default: 2
    ConstraintDescription: Must be a value between 1 and 16. Must be less than or equal to PDFClusterMaxSize.
    Type: Number

  BalancerClusterMaxSize:
    Type: Number
    Description: The maximum number of HaProxy instance servers the ECS cluster can scale up to.
    MinValue: 1
    MaxValue: 2
    Default: 1

  AvailabilityZone1:
    Type: AWS::EC2::AvailabilityZone::Name
    Description: 'The first availability zone.'
    ConstraintDescription: 'Must be a different availability zone than zone 2.'

  AvailabilityZone2:
    Type: AWS::EC2::AvailabilityZone::Name
    Description: 'The second availability zone.'
    ConstraintDescription: 'Must be a different availability zone than zone 1.'

  HealthCheckCall:
    Description: The call to make for health checks. Change to \'node /usr/local/apache-tomcat/bin/healthcheck.js\' if using a version lower than 2.1.6.
    Type: String
    Default: 'python3 /usr/local/apache-tomcat/bin/healthcheck.py'
    ConstraintDescription: Must be a valid SNS topic ARN.

  IncludeDemo:
    Description: Decides whether the demo application is installed on the server.
    Type: String
    AllowedValues:
        - 'true'
        - 'false'
    Default: 'true'
    ConstraintDescription: Either true or false.

  EnableDebugMode:
    Description: Decides whether the demo application is installed on the server.
    Type: String
    AllowedValues:
        - 'true'
        - 'false'
    Default: 'false'
    ConstraintDescription: Either true or false.

  ECSNotificationTopic:
    Description: A SNS topic ARN to send container state change events to.
    Type: String
    Default: ''
    ConstraintDescription: Must be a valid SNS topic ARN.

  HTTPEnabled:
    Description: Decides whether HTTP is enabled.
    Type: String
    AllowedValues:
      - 'true'
      - 'false'
    Default: 'true'
    ConstraintDescription: Either true or false.

  HAProxyCPU:
    Description: >
      The amount of CPU to provide to HAProxy, must correspond to one of these set values with the specified in HAProxyMemory
      [256 (.25 vCPU), 512 (0.5 GB), 1024 (1GB), 2048 (2GB)]
      [512 (.5 vCPU), 1024 (1GB), 2048 (2GB)]
      [1024 (1 vCPU), 2048 (2GB GB), 3072 (3GB), 4096 (4GB)]
      [2048 (2 vCPU), 4096 (4GB)]
    Type: Number
    AllowedValues:
      - 256
      - 512
      - 1024
      - 2048
    Default: 512
  HAProxyMemory:
    Description: >
      The amount of memory to provide to HAProxy, must correspond to one of these set values with the one specified in HAProxyCPU:
      [256 (.25 vCPU), 512 (0.5 GB), 1024 (1GB), 2048 (2GB)]
      [512 (.5 vCPU), 1024 (1GB), 2048 (2GB)]
      [1024 (1 vCPU), 2048 (2GB GB), 3072 (3GB), 4096 (4GB)]
      [2048 (2 vCPU), 4096 (4GB)]
    Type: Number
    AllowedValues:
      - 512
      - 1024
      - 2048
      - 3072
      - 4096
    Default: 1024

  WebviewerServerCPU:
    Description: >
      The amount of CPU to provide to WebViewer Server, must correspond to one of these set values with the one specified in WebViewerServerMemory:
        [4096 (4 vCPU), 8192 (8GB), 30720 (30GB)],
        [8192 (8 vCPU), 16384 (16GB), 30720 (32GB)]
        [16384 (16vCPU), 30720GB]
    Type: Number
    AllowedValues:
      - 4096
      - 8192
      - 16384
    Default: 4096

  WebviewerServerMemory:
    Description: >
      The amount of memory to provide to WebViewer Server, must correspond to one of these set values with the one specified in WebViewerServerCPU:
        [4096 (4 vCPU), 8192 (8GB), 30720 (30GB)],
        [8192 (8 vCPU), 16384 (16GB), 30720 (32GB)]
        [16384 (16vCPU), 30720GB]
    Type: Number
    AllowedValues:
      - 8192
      - 16384
      - 30720
    Default: 8192

  HTTPPort:
    Description: The public port for HTTP connections to the main load balancer.
    Type: Number
    Default: 80
    MinValue: 80
    MaxValue: 65535
    ConstraintDescription: Must be a value between 80 and 65535.
    
  SSLEnabled:
    Description: Decides whether SSL is enabled.
    Type: String
    AllowedValues:
      - 'true'
      - 'false'
    Default: 'false'
    ConstraintDescription: Either true or false.

  SSLPort:
    Description: The public port for SSL connections to the main load balancer.
    Type: Number
    Default: 443
    MinValue: 443
    MaxValue: 65535
    ConstraintDescription: Must be a valid port between 443 and 65535.

  SSLServerCertificateArn:
    Description: The name of a server certificate uploaded to AWS IAM server-certificates or a acm certificate private cert.
    Type: String
    Default: ""
    MinLength: 0
    MaxLength: 200
    ConstraintDescription: Must be a valid server certificate, uploaded to your IAM or ACM.
 
  PDFNetKey:
    Description: Your PDFNet license key.
    Type: String
    MaxLength: 4096
    ConstraintDescription: Must be a valid key with permission to use PDFTron Server.

  ForceURLRecheck:
    Description: https://www.pdftron.com/documentation/web/guides/wv-server-config/#force-url-rechecking
    Type: String
    AllowedValues:
      - 'true'
      - 'false'
    Default: 'false'
    ConstraintDescription: Either true or false.

  EnableSessionAuth:
    Description: https://www.pdftron.com/documentation/web/guides/wv-server-config/#access-control
    Type: String
    AllowedValues:
      - 'true'
      - 'false'
    Default: 'false'
    ConstraintDescription: Either true or false.

  EnablePerSessionCaching:
    Description: https://www.pdftron.com/documentation/web/guides/wv-server-config/#session-caching
    Type: String
    AllowedValues:
      - 'true'
      - 'false'
    Default: 'false'
    ConstraintDescription: Either true or false.

  RequireFetchRoots:
    Description: https://www.pdftron.com/documentation/web/guides/wv-server-config/#url-root-restriction
    Default: ""
    Type: String

  DefaultBaseFetchAddress:
    Description: https://www.pdftron.com/documentation/web/guides/wv-server-config/#relative-url
    Default: ""
    Type: String

  ForceLosslessImages:
    Description: https://www.pdftron.com/documentation/web/guides/wv-server-config/#force-lossless-images
    Type: String
    AllowedValues:
      - 'true'
      - 'false'
    Default: 'false'
    ConstraintDescription: Either true or false.

  ServerFetchTimeout:
    Description: https://www.pdftron.com/documentation/web/guides/wv-server-config/#fetch-timeout
    Type: Number 
    MinValue: 1000
    Default: 20000
    ConstraintDescription: A number in milliseconds greater than 1000.

  DisableClientSideRendering:
    Description: https://www.pdftron.com/documentation/web/guides/wv-server-config/#disable-client-side-rendering
    Type: String
    AllowedValues:
      - 'true'
      - 'false'
    Default: 'false'
    ConstraintDescription: Either true or false.

  DisableFetchValidation:
    Description: https://www.pdftron.com/documentation/web/guides/wv-server-config/#disable-fetch-validation
    Type: String
    AllowedValues:
      - 'true'
      - 'false'
    Default: 'false'
    ConstraintDescription: Either true or false.

  AllowedClientOrigins:
    Description: https://www.pdftron.com/documentation/web/guides/wv-server-config/#restrict-client-origins
    Type: String
    Default: ""

  MaxExcelCellCount:
    Description: https://www.pdftron.com/documentation/web/guides/wv-server-config/#max-excel-cell-count
    Type: String
    Default: ""

  EnableAWSErrorReporting:
    Description: If set to true, container will dump crash data to the S3 bucket specified by ErrorReportingS3. Requires an access key and ID specified in AWSAccessKeyId and AWSSecretKey. The access key provided must be able to upload to the S3 bucket specified in ErrorReportingS3.
    Type: String
    AllowedValues:
      - 'true'
      - 'false'
    Default: 'false'
    ConstraintDescription: Either true or false.

  ErrorReportingS3:
    Description: The name the s3 bucket to send crash dumps to if EnableAWSErrorReporting is set to true.
    Type: String
    Default: ""

  AWSAccessKeyId:
    Description: The access key ID for your access ID. Used with AWSErrorReporting.
    Type: String
    Default: ""

  AWSSecretKey:
    Description: The secret access key for your access ID. Used with AWSErrorReporting.
    Type: String
    Default: ""

  BlockedKeys:
    Description: Blocks requests with keys containing the specified keywords. Each entry is separated by a comma.
    Type: String
    Default: ""

  DefaultWebViewerServerInstanceCount:
    Description: The number of WebViewer Server instances to start with by default. We recommend 2 servers for redundancy at least.
    Type: Number
    MinValue: 1
    MaxValue: 16
    Default: 2

  DefaultHAProxyInstanceCount:
    Description: The number of HAProxy instances to start with by default. One should work in most situations.
    Type: Number
    MinValue: 1
    MaxValue: 3
    Default: 1

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      -
        Label:
          default: General Configuration
        Parameters:
          - AvailabilityZone1
          - AvailabilityZone2
      -
        Label:
          default: HTTP Configuration
        Parameters:
          - HTTPEnabled
          - HTTPPort
      -
        Label:
          default: SSL Configuration
        Parameters:
          - SSLEnabled
          - SSLPort
          - SSLServerCertificateArn
      -
        Label:
          default: Load Balancer Cluster Configuration
        Parameters:
          - BalancerVersion
          - BalancerClusterMaxSize
          - HealthCheckCall
          - DefaultHAProxyInstanceCount
          - HAProxyCPU
          - HAProxyMemory
      -
        Label:
          default: PDFTron Server Cluster Configuration
        Parameters:
          - WebViewerServerVersion
          - PDFClusterMaxSize
          - PDFClusterMinSize
          - DefaultWebViewerServerInstanceCount
          - WebviewerServerCPU
          - WebviewerServerMemory
      -
        Label:
          default: Deployment Options
        Parameters:
          - ECSNotificationTopic
      -
        Label:
          default: Container Configuration
        Parameters:
          - PDFNetKey
          - IncludeDemo
          - ForceLosslessImages
          - ServerFetchTimeout
          - DisableClientSideRendering
          - MaxExcelCellCount
      -
        Label:
          default: Security Options
        Parameters:
          - EnableSessionAuth
          - EnablePerSessionCaching
          - RequireFetchRoots
          - DefaultBaseFetchAddress
          - ForceURLRecheck
          - DisableFetchValidation
          - AllowedClientOrigins
          - BlockedKeys
      -
        Label:
          default: Debug Configuration
        Parameters:
          - EnableAWSErrorReporting
          - ErrorReportingS3
          - AWSAccessKeyId
          - AWSSecretKey
          - EnableDebugMode

Conditions:
  HTTP: !Equals [!Ref HTTPEnabled, "true"]
  SSL: !Equals [!Ref SSLEnabled, "true"]
  EnableNotification: !Not [!Equals [!Ref ECSNotificationTopic, ""]]

Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.6.0.0/16
      EnableDnsSupport: 'true'
      EnableDnsHostnames: 'true'
      InstanceTenancy: default

  InternetGateway:
    Type: AWS::EC2::InternetGateway
    DependsOn: VPC

  AttachVpcGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref InternetGateway

  RouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC

  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC

  PublicRouteTable2:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC

  IGRoute:
    Type: AWS::EC2::Route
    DependsOn:
      - AttachVpcGateway
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

  IGRoute2:
    Type: AWS::EC2::Route
    DependsOn:
      - AttachVpcGateway
      - VPC
    Properties:
      RouteTableId: !Ref PublicRouteTable2
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

  Subnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.6.1.0/24
      AvailabilityZone: !Ref AvailabilityZone1

  Subnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.6.2.0/24
      AvailabilityZone: !Ref AvailabilityZone2

  PublicSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.6.4.0/24
      AvailabilityZone: !Ref AvailabilityZone2

  PublicSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.6.3.0/24
      AvailabilityZone: !Ref AvailabilityZone1

  Subnet1Association:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref Subnet1
      RouteTableId: !Ref RouteTable

  Subnet2Association:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref Subnet2
      RouteTableId: !Ref RouteTable

  PublicSubnetAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet
      RouteTableId: !Ref PublicRouteTable

  PublicSubnetAssociation2:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet2
      RouteTableId: !Ref PublicRouteTable2

  NatElasticIp:
    Type: AWS::EC2::EIP
    DependsOn:
      - AttachVpcGateway
      - VPC
    Properties:
      Domain: vpc

  NatGateway:
    Type: AWS::EC2::NatGateway
    DependsOn:
      - AttachVpcGateway
    Properties:
      SubnetId: !Ref PublicSubnet
      AllocationId: !GetAtt NatElasticIp.AllocationId

  NatRoute:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref RouteTable
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NatGateway

  ApryseCloudWatchLogs:
    Type: AWS::Logs::LogGroup
    Properties:
      RetentionInDays: 30
    
  ## IAM Stuff
  EcsHostRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument: |
        {
          "Version": "2008-10-17",
          "Statement": [
            {
              "Action": "sts:AssumeRole",
              "Principal": {
                "Service": [
                  "ecs-tasks.amazonaws.com"
                ]
              },
              "Effect": "Allow"
            }
          ]
        }
      ManagedPolicyArns:
        - 'arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy'

  LambdaRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument: | 
        {

        "Version": "2012-10-17",
          "Statement": [
            {
              "Action": "sts:AssumeRole",
              "Principal": {
                "Service": [
                  "lambda.amazonaws.com"
                ]
              },
              "Effect": "Allow"
            }
          ]
        }
      Policies:
        - PolicyName: pdftron-lambda-policy
          PolicyDocument: |
            {
              "Version": "2012-10-17",
              "Statement": [
                {
                  "Effect": "Allow",
                  "Action": [
                    "elasticloadbalancing:DescribeLoadBalancers",
                    "ecs:ListContainerInstances",
                    "ecs:ListTasks",
                    "ecs:DescribeTasks",
                    "ecs:UpdateService",
                    "ec2:*Vpc*",
                    "ecs:DescribeContainerInstances",
                    "ec2:DescribeNetworkInterfaces",
                    "ec2:CreateNetworkInterface",
                    "ec2:DeleteNetworkInterface",
                    "autoscaling:DescribeAutoScalingGroups",
                    "ec2:Describe*",
                    "cloudwatch:PutMetricData",
                    "cloudwatch:GetMetricStatistics",
                    "logs:CreateLogGroup",
                    "logs:CreateLogStream",
                    "logs:DescribeLogStreams",
                    "logs:PutLogEvents",
                    "sns:Publish"
                  ],
                  "Resource": "*"
                }
              ]
            }

  ### Security Groups
  LambdaSG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId: !Ref VPC
      GroupDescription: Gives lambda access to specific ec2 ports.
      SecurityGroupEgress:
        - IpProtocol: -1
          CidrIp: 0.0.0.0/0

  AWSBalancerSG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId: !Ref VPC
      GroupDescription: Security group for the pdftron aws balancer.
      SecurityGroupIngress:
          # http port
        - IpProtocol: tcp
          FromPort: !Ref HTTPPort
          ToPort: 8090
          CidrIp: 0.0.0.0/0
          # https port
        - IpProtocol: tcp
          FromPort: !Ref SSLPort
          ToPort: 8443
          CidrIp: 0.0.0.0/0
        - IpProtocol: tcp
          FromPort: 60001
          ToPort: 60001
          CidrIp: 0.0.0.0/0
          # https port
      SecurityGroupEgress:
        - IpProtocol: -1
          CidrIp: 0.0.0.0/0

  HAProxySG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId: !Ref VPC
      GroupDescription: Security group for the pdftron aws balancer.
      SecurityGroupIngress:
        # health checks
        - IpProtocol: tcp
          FromPort: 60001
          ToPort: 60001
          SourceSecurityGroupId: !Ref AWSBalancerSG
          # lambda discovery port
        - IpProtocol: tcp
          FromPort: 4893
          ToPort: 4893
          SourceSecurityGroupId: !Ref LambdaSG
          # http port
        - IpProtocol: tcp
          FromPort: 8050
          ToPort: 8050
          SourceSecurityGroupId: !Ref AWSBalancerSG
          # https port (an extra port internal http traffic that was https gets routed to)
      SecurityGroupEgress:
        - IpProtocol: -1
          CidrIp: 0.0.0.0/0

  PDFDSG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId: !Ref VPC
      GroupDescription: Security group for the pdftron annot instances.
      SecurityGroupIngress:
          # http port
        - IpProtocol: tcp
          FromPort: 8090
          ToPort: 8090
          SourceSecurityGroupId: !GetAtt HAProxySG.GroupId
        - IpProtocol: tcp
          FromPort: 8090
          ToPort: 8090
          SourceSecurityGroupId: !GetAtt LambdaSG.GroupId
          # https port
        - IpProtocol: tcp
          FromPort: 8443
          ToPort: 8443
          SourceSecurityGroupId: !GetAtt HAProxySG.GroupId
      SecurityGroupEgress:
        - IpProtocol: -1
          CidrIp: 0.0.0.0/0

  ## Balancer ECS Cluster
  MainLoadBalancer:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    DependsOn: AttachVpcGateway
    Properties:
      Name: !Sub "${AWS::StackName}-WVS-ELB" 
      Subnets:
        - !Ref PublicSubnet
        - !Ref PublicSubnet2
      SecurityGroups:
        - !Ref AWSBalancerSG
      LoadBalancerAttributes:
        -
          Key: idle_timeout.timeout_seconds
          Value: 360 

  HTTPListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      LoadBalancerArn: !Ref MainLoadBalancer
      Port: !Ref HTTPPort
      DefaultActions:
        !If
        - HTTP
        -
          -
            Type: forward
            TargetGroupArn: !Ref HAProxyTarget
        -
          -
            Type: fixed-response
            FixedResponseConfig:
              ContentType: "text/plain"
              MessageBody: "HTTP disabled"
              StatusCode: "404"
      Protocol: HTTP

  HTTPSListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Condition: SSL
    Properties:
      LoadBalancerArn: !Ref MainLoadBalancer
      Port: !Ref SSLPort
      Certificates:
        - !Ref SSLServerCertificateArn
      DefaultActions:
        -
          Type: forward
          TargetGroupArn: !Ref HAProxyTarget
      Protocol: HTTPS

  HAProxyTarget:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    DependsOn: MainLoadBalancer
    Properties:
      HealthCheckIntervalSeconds: 30
      HealthCheckPath: /http_health_check
      HealthCheckProtocol: HTTP
      HealthCheckPort: 60001
      HealthyThresholdCount: 3
      UnhealthyThresholdCount: 3
      Port: 8050
      Protocol: HTTP
      TargetType: ip
      VpcId: !Ref VPC
  
  BalancerCluster:
    Type: AWS::ECS::Cluster
    Properties:
      ClusterName: !Sub "${AWS::StackName}-LB-Cluster"
  
  HAProxyTaskDef:
    Type: AWS::ECS::TaskDefinition
    Properties:
      NetworkMode: awsvpc
      RequiresCompatibilities:
        - FARGATE
      #256 (.25 vCPU) - Available memory values: 512 (0.5 GB), 1024 (1 GB), 2048 (2 GB)
      #512 (.5 vCPU) - Available memory values: 1024 (1 GB), 2048 (2 GB), 3072 (3 GB), 4096 (4 GB)
      #1024 (1 vCPU) - Available memory values: 2048 (2 GB), 3072 (3 GB), 4096 (4 GB), 5120 (5 GB), 6144 (6 GB), 7168 (7 GB), 8192 (8 GB)
      #2048 (2 vCPU) - Available memory values: 4096 (4 GB) and 16384 (16 GB) in increments of 1024 (1 GB)
      #4096 (4 vCPU) - Available memory values: 8192 (8 GB) and 30720 (30 GB) in increments of 1024 (1 GB)
      #8192 (8 vCPU) - Available memory values: 16 GB and 60 GB in 4 GB increments
      #16384 (16vCPU) - Available memory values: 32GB and 120 GB in 8 GB increments
      Cpu: !Ref HAProxyCPU
      Memory: !Ref HAProxyMemory
      ExecutionRoleArn: !Ref EcsHostRole
      ContainerDefinitions:
        -
          Name: pdftron-awsm-haproxy
          Cpu: !Ref HAProxyCPU
          Memory: !Ref HAProxyMemory
          Image: !Join
            - ""
            - - 'pdftron/wv-loadbalancer:'
              - !Ref BalancerVersion
          Essential: true
          HealthCheck:
            Command: [ 'CMD-SHELL', 'curl --fail http://localhost:60001/http_health_check || exit 1' ]
            Retries: 5
            Interval: 60
          LogConfiguration:

            LogDriver: awslogs
            Options:
              awslogs-group: !Ref ApryseCloudWatchLogs
              awslogs-region: !Ref AWS::Region
              awslogs-stream-prefix: pdft-internal-balancer
          PortMappings:
            -
              ContainerPort: 60001
              HostPort: 60001
            -
              ContainerPort: 8050
              HostPort: 8050
            -
              ContainerPort: 9001
              HostPort: 9001
            -
              ContainerPort: 4893
              HostPort: 4893
          Environment:
            -
              Name: ENABLE_TCP_MANAGEMENT
              Value: 'true'
            -
              Name: SET_DEFAULT_ENTRIES
              Value: 'true'
            -
              Name: DISABLE_SSL
              Value: 'true'
  
  HAProxyECSService:
    Type: AWS::ECS::Service
    DependsOn:
      - HTTPListener
    Properties:
      Cluster: !Ref BalancerCluster
      TaskDefinition: !Ref HAProxyTaskDef
      LaunchType: FARGATE
      NetworkConfiguration:
        AwsvpcConfiguration:
          # change to DISABLED if you're using private subnets that have access to a NAT gateway
          AssignPublicIp: ENABLED
          Subnets:
            - !Ref Subnet1
            - !Ref Subnet2
          SecurityGroups:
            - !Ref HAProxySG
      DeploymentConfiguration:
        MinimumHealthyPercent: 0
      DesiredCount: !Ref DefaultHAProxyInstanceCount
      LoadBalancers:
        - ContainerName: pdftron-awsm-haproxy
          ContainerPort: 8050
          TargetGroupArn: !Ref HAProxyTarget

  BalancerASTarget:
    Type: AWS::ApplicationAutoScaling::ScalableTarget
    Properties:
      MinCapacity: 1
      MaxCapacity: !Ref BalancerClusterMaxSize
      RoleARN: !GetAtt EcsHostRole.Arn
      ResourceId: !Join
        - "/"
        - - service
          - !Ref BalancerCluster
          - !GetAtt HAProxyECSService.Name
      ScalableDimension: ecs:service:DesiredCount
      ServiceNamespace: ecs
  
  BalancerASPolicy:
    Type: AWS::ApplicationAutoScaling::ScalingPolicy
    DependsOn:
      - BalancerASTarget
    Properties:
      PolicyName: pdftron-balancer-scale
      PolicyType: TargetTrackingScaling
      ScalableDimension: ecs:service:DesiredCount
      ServiceNamespace: ecs
      ResourceId: !Join
        - "/"
        - - service
          - !Ref BalancerCluster
          - !GetAtt HAProxyECSService.Name
      TargetTrackingScalingPolicyConfiguration:
        PredefinedMetricSpecification:
          PredefinedMetricType: ECSServiceAverageCPUUtilization
        TargetValue: 80
        ScaleInCooldown: 200

  ### PDF Cluster Config
  
  PDFCluster:
    Type: AWS::ECS::Cluster
    Properties:
      ClusterName: !Sub "${AWS::StackName}-WVCluster"

  PDFDTaskDef:
    Type: AWS::ECS::TaskDefinition
    Properties:
      NetworkMode: awsvpc
      RequiresCompatibilities:
        - FARGATE
      #256 (.25 vCPU) - Available memory values: 512 (0.5 GB), 1024 (1 GB), 2048 (2 GB)
      #512 (.5 vCPU) - Available memory values: 1024 (1 GB), 2048 (2 GB), 3072 (3 GB), 4096 (4 GB)
      #1024 (1 vCPU) - Available memory values: 2048 (2 GB), 3072 (3 GB), 4096 (4 GB), 5120 (5 GB), 6144 (6 GB), 7168 (7 GB), 8192 (8 GB)
      #2048 (2 vCPU) - Available memory values: 4096 (4 GB) and 16384 (16 GB) in increments of 1024 (1 GB)
      #4096 (4 vCPU) - Available memory values: 8192 (8 GB) and 30720 (30 GB) in increments of 1024 (1 GB)
      #8192 (8 vCPU) - Available memory values: 16 GB and 60 GB in 4 GB increments
      #16384 (16vCPU) - Available memory values: 32GB and 120 GB in 8 GB increments
      Cpu: !Ref WebviewerServerCPU
      Memory: !Ref WebviewerServerMemory
      ExecutionRoleArn: !Ref EcsHostRole
      EphemeralStorage:
        SizeInGiB: 100
      ContainerDefinitions:
        -
          Name: pdf-server
          Cpu: !Ref WebviewerServerCPU
          Memory: !Ref WebviewerServerCPU
          Image: !Join
            - ""
            - - 'pdftron/webviewer-server:'
              - !Ref WebViewerServerVersion
          Essential: true
          HealthCheck:
            Command: [ 'CMD-SHELL', !Ref HealthCheckCall ]
            Retries: 5
            Interval: 60
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Ref ApryseCloudWatchLogs
              awslogs-region: !Ref AWS::Region
              awslogs-stream-prefix: pdft-pdf-server
          PortMappings:
            -
              ContainerPort: 8090
              HostPort: 8090
          Environment:
            -
              Name: TRN_PDFNET_KEY
              Value: !Ref PDFNetKey
            -
              Name: INCLUDE_DEMO
              Value: !Ref IncludeDemo
            -
              Name: TRN_BALANCER_COOKIE_NAME
              Value: HAPROXID
            -
              Name: TRN_DEBUG_MODE
              Value: !Ref EnableDebugMode
            -
              Name: TRN_MAX_CACHE_AGE_MINUTES
              Value: 10
            -
              Name: TRN_MAX_CACHED_MB # this should not be changed unless you also resize the volume
              Value: 30000
            -
              Name: TRN_FORCE_URL_RECHECK
              Value: !Ref ForceURLRecheck
            -
              Name: TRN_ENABLE_SESSION_AUTH
              Value: !Ref EnableSessionAuth
            -
              Name: TRN_ENABLE_PER_SESSION_CACHING
              Value: !Ref EnablePerSessionCaching
            -
              Name: TRN_FETCH_REQUIRED_URL_ROOTS
              Value: !Ref RequireFetchRoots
            -
              Name: TRN_FETCH_DEFAULT_BASE_ADDR
              Value: !Ref DefaultBaseFetchAddress
            -
              Name: TRN_FORCE_LOSSLESS_IMAGES
              Value: !Ref ForceLosslessImages
            -
              Name: TRN_DISABLE_CLIENT_SIDE_RENDERING
              Value: !Ref DisableClientSideRendering
            -
              Name: TRN_ALLOWED_ORIGINS
              Value: !Ref AllowedClientOrigins
            -
              Name: TRN_DISABLE_VALIDATION
              Value: !Ref DisableFetchValidation
            -
              Name: TRN_FETCH_TIMEOUT_MS
              Value: !Ref ServerFetchTimeout
            -
              Name: AWS_ERROR_REPORTING
              Value: !Ref EnableAWSErrorReporting
            -
              Name: ERROR_REPORTING_S3
              Value: !Ref ErrorReportingS3
            -
              Name: TRN_MAX_EXCEL_CELL_COUNT
              Value: !Ref MaxExcelCellCount
            -
              Name: TRN_BLOCKED_DOMAINS
              Value: !Ref BlockedKeys
            -
              Name: AWS_REGION
              Value: !Ref AWS::Region
            -
              Name: AWS_ACCESS_KEY_ID
              Value: !Ref AWSAccessKeyId
            -
              Name: AWS_SECRET_ACCESS_KEY
              Value: !Ref AWSSecretKey

  PDFDECSService:
    Type: AWS::ECS::Service
    DependsOn:
      - HTTPListener
    Properties:
      Cluster: !Ref PDFCluster
      TaskDefinition: !Ref PDFDTaskDef
      LaunchType: FARGATE
      DeploymentConfiguration:
        MinimumHealthyPercent: 50
        MaximumPercent: 200
      DesiredCount: !Ref DefaultWebViewerServerInstanceCount
      NetworkConfiguration:
        AwsvpcConfiguration:
          # change to DISABLED if you're using private subnets that have access to a NAT gateway
          AssignPublicIp: ENABLED
          Subnets:
            - !Ref Subnet1
            - !Ref Subnet2
          SecurityGroups:
            - !Ref PDFDSG

  PDFDASTarget:
    Type: AWS::ApplicationAutoScaling::ScalableTarget
    Properties:
      MinCapacity: !Ref PDFClusterMinSize
      MaxCapacity: !Ref PDFClusterMaxSize
      RoleARN: !GetAtt EcsHostRole.Arn
      ResourceId: !Join
        - "/"
        - - service
          - !Ref PDFCluster
          - !GetAtt PDFDECSService.Name
      ServiceNamespace: ecs
      ScalableDimension: ecs:service:DesiredCount
  
  PDFDASPolicyUp:
    Type: AWS::ApplicationAutoScaling::ScalingPolicy
    DependsOn:
      - PDFDASTarget
    Properties:
      PolicyName: !Sub "${AWS::StackName}-pdfd-scale-up" 
      PolicyType: StepScaling
      ResourceId: !Join
        - "/"
        - - service
          - !Ref PDFCluster
          - !GetAtt PDFDECSService.Name
      ServiceNamespace: ecs
      ScalableDimension: ecs:service:DesiredCount
      StepScalingPolicyConfiguration:
        AdjustmentType: ChangeInCapacity
        Cooldown: 300
        MetricAggregationType: Minimum
        StepAdjustments:
          - MetricIntervalLowerBound: 0
            ScalingAdjustment: 1
  
  PDFDASPolicyDown:
    Type: AWS::ApplicationAutoScaling::ScalingPolicy
    DependsOn:
      - PDFDASTarget
    Properties:
      PolicyName: !Sub "${AWS::StackName}-pdfd-scale-down" 
      PolicyType: StepScaling
      ResourceId: !Join
        - "/"
        - - service
          - !Ref PDFCluster
          - !GetAtt PDFDECSService.Name
      ServiceNamespace: ecs
      ScalableDimension: ecs:service:DesiredCount
      StepScalingPolicyConfiguration:
        AdjustmentType: ChangeInCapacity
        Cooldown: 300
        MetricAggregationType: Minimum
        StepAdjustments:
          - MetricIntervalUpperBound: 0
            ScalingAdjustment: -1

  AlarmScaleUpQueue:
    Type: AWS::CloudWatch::Alarm
    Properties:
      ComparisonOperator: GreaterThanThreshold
      EvaluationPeriods: 3
      MetricName: InstanceScaleCount
      Namespace: !Sub "${AWS::StackName}-pdfd"
      Period: 60
      Statistic: Average
      Threshold: 0.75
      AlarmDescription: PDFD Server queue scale up alarm.
      AlarmActions:
        - !Ref PDFDASPolicyUp

  AlarmScaleDownQueue:
    Type: AWS::CloudWatch::Alarm
    Properties:
      ComparisonOperator: LessThanThreshold
      EvaluationPeriods: 3
      MetricName: InstanceScaleCount
      Namespace: !Sub "${AWS::StackName}-pdfd"
      Period: 60
      Statistic: Average
      Threshold: 0.25
      AlarmDescription: PDFD Server queue scale down alarm.
      AlarmActions:
        - !Ref PDFDASPolicyDown

  # Lambda
  EveryMinuteCloudwatchEvent:
    Type: AWS::Events::Rule
    Properties:
      ScheduleExpression: rate(1 minute)
      Targets:
        -
          Id: MonitorScheduler
          Arn: !GetAtt MonitorLambda.Arn
        -
          Id: HADiscoveryScheduler
          Arn: !GetAtt HAControllerLambda.Arn

  HAControllerLambda:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        ZipFile: |
            const{AutoScalingClient,DescribeAutoScalingGroupsCommand}=require("@aws-sdk/client-auto-scaling"),{EC2Client,DescribeInstancesCommand,DescribeNetworkInterfacesCommand}=require("@aws-sdk/client-ec2"),{ECSClient,DescribeTasksCommand,ListTasksCommand}=require("@aws-sdk/client-ecs"),net=require("net"),http=require("http"),PDF_TYPE="PDF",region=process.env.Region,BalancerCluster=process.env.BalancerClusterName,WVSCluster=process.env.WVSClusterName,StackName=process.env.StackName,FORCE_RELOAD=process.env.FORCE_RELOAD,port=4893,autoscaling=new AutoScalingClient({region:region}),ec2=new EC2Client({region:region}),ecs=new ECSClient({region:region}),getGroupInstanceIPs=async a=>{let b=new ListTasksCommand({cluster:`${a}`,query:"taskArns"}),c=await ecs.send(b);var d=[];if(0<c.taskArns.length){networkIds=[],b=new DescribeTasksCommand({cluster:`${a}`,tasks:c.taskArns}),c=await ecs.send(b);for(let a of c.tasks)for(let b of a.attachments[0].details)"networkInterfaceId"===b.name&&networkIds.push(b.value);b=new DescribeNetworkInterfacesCommand({NetworkInterfaceIds:networkIds}),c=await ec2.send(b);for(let a of c.NetworkInterfaces)d.push(a.PrivateIpAddress)}return console.log(`Found instances [ ${d} ] for cluster ${a}`),d};function makeHealthCheck(a){return new Promise((b,c)=>{var d=http.get("http://"+a+":8090/blackbox/health",a=>200===a.statusCode?void b():void(console.log("Health check error: "+a.statusCode),c()));d.setTimeout(5e3,()=>{console.log("No Server, connection timed out."),c()}),d.on("error",a=>{console.log("No Server: "+a),c()})})}const processInstanceData=async(a,b)=>{const c={},d={};if(1>a.length)return Promise.resolve([]);for(let e of a.Reservations)for(let a of e.Instances){const e=a.PrivateIpAddress;if(b&&!c[e]&&0!==b.length)try{await makeHealthCheck(e),d[b[0]]=e,c[e]=!0,b.shift()}catch(a){console.log("Skipping instance server.")}else d[e]=e}return d},getAvailableInstances=async()=>{const a=await getGroupInstanceIPs(BalancerCluster),b=await getGroupInstanceIPs(WVSCluster),c={},d=["webA","webB","webC","webD","webE","webF","webG","webH","webI","webJ","webK","webL","webM","webN","webO","webP"];let e=0;for(let a of b){if(e>d.length){console.error("More servers found than available names! Ignoring remaining servers.");break}c[d[e++]]=a}return console.log(`Found available instances`,c),{balancerIps:a,pdfdInstances:c}},createCommands=(a,b)=>{const c=a===PDF_TYPE?"remapped_nodes":"remapped_annotation_nodes",d=a===PDF_TYPE?"nodes":"annotation_nodes";let e="set server "+d+"/"+b.name+" addr "+b.ip+" 8090\n",f="set server "+d+"_haproxid/"+b.name+" addr "+b.ip+" 8090\n",g="set server "+c+"/"+b.name+" addr "+b.ip+" 8090\n",h="set server "+c+"_haproxid/"+b.name+" addr "+b.ip+" 8090\n";return e+f+g+h},connectSocket=a=>new Promise((b,c)=>{var d=new net.createConnection(port,a);d.on("connect",()=>{b(d)}),d.on("error",()=>{c(a+": Balancer not online.")})}),getCurrentMap=a=>writeSocket("show servers state nodes\n",a,!1).then(a=>{var b={};if(a)for(var c,d=a.toString("utf8").split("\n"),e=2;e<d.length;++e)c=d[e].split(" "),c[3]&&c[3].startsWith("web")&&(b[c[3]]={ip:c[4],internal:c[17],port:c[18]});return b}),writeSocket=(a,b)=>new Promise((c,d)=>{b.write(a,a=>{a&&(console.log(a),d())}),b.on("data",a=>{console.log("balancer response:"+a),b.end(),c(a)})}),shouldUpdate=(a,b)=>{if(!0===FORCE_RELOAD)return!0;for(node in a)if(a[node].ip===b.ip)return!1;return!0},configureBalancer=async(a,b)=>{var c=await connectSocket(a),d=await getCurrentMap(c),e="";for(k in b)inst={name:k,ip:b[k]},!0===shouldUpdate(d,inst)&&(e+=createCommands(PDF_TYPE,inst));return 0===e.length?Promise.reject(a+": No changes to write."):void(c=await connectSocket(a),await writeSocket(e,c,!0))};exports.handler=async(a,b)=>{b.callbackWaitsForEmptyEventLoop=!1;var c=await getAvailableInstances();for(var d in c.balancerIps){var e=c.balancerIps[d];await configureBalancer(e,c.pdfdInstances).catch(a=>{console.log(a)})}};
      Role: !GetAtt LambdaRole.Arn
      Handler: index.handler
      Runtime: nodejs24.x
      Timeout: 20
      VpcConfig:
        SecurityGroupIds:
          - !Ref LambdaSG
        SubnetIds:
          - !Ref Subnet1
          - !Ref Subnet2
      Environment:
        Variables:
          StackName: !Ref AWS::StackName
          BalancerClusterName: !Ref BalancerCluster
          WVSClusterName: !Ref PDFCluster
          Region: !Ref AWS::Region

  HACLambdaPerm:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:InvokeFunction
      FunctionName: !GetAtt HAControllerLambda.Arn
      Principal: events.amazonaws.com
      SourceArn: !GetAtt EveryMinuteCloudwatchEvent.Arn
   
  MonitorLambda:
    Type: AWS::Lambda::Function
    Properties:
      Code: 
        ZipFile: |
            const{AutoScalingClient,DescribeAutoScalingGroupsCommand}=require("@aws-sdk/client-auto-scaling"),{EC2Client,DescribeInstancesCommand,DescribeNetworkInterfacesCommand}=require("@aws-sdk/client-ec2"),{ECSClient,DescribeTasksCommand,ListTasksCommand}=require("@aws-sdk/client-ecs"),{CloudWatchClient,PutMetricDataCommand}=require("@aws-sdk/client-cloudwatch"),http=require("http"),PDFDCluster=process.env.PDFDCluster,region=process.env.Region,scalingNamespace=process.env.ScalingNamespace,autoscaling=new AutoScalingClient({region:region}),ec2=new EC2Client({region:region}),ecs=new ECSClient({region:region}),cw=new CloudWatchClient({region:region}),getPDFDServerIPs=async a=>{let b=new ListTasksCommand({cluster:`${a}`,query:"taskArns"}),c=await ecs.send(b);var d=[];if(0<c.taskArns.length){networkIds=[],b=new DescribeTasksCommand({cluster:`${a}`,tasks:c.taskArns}),c=await ecs.send(b);for(let a of c.tasks)for(let b of a.attachments[0].details)"networkInterfaceId"===b.name&&networkIds.push(b.value);b=new DescribeNetworkInterfacesCommand({NetworkInterfaceIds:networkIds}),c=await ec2.send(b);for(let a of c.NetworkInterfaces)d.push(a.PrivateIpAddress)}return console.log(`Found instances [ ${d} ] for cluster ${a}`),d};function makeHealthCheck(a){return new Promise((b,c)=>{http.get("http://"+a+":8090/blackbox/GetPerfInfo",a=>{let c="";a.on("data",a=>{c+=a}),a.on("end",()=>{parsedData=JSON.parse(c),b(parsedData)})}).on("error",a=>{console.log("Error: "+a.message),c()})})}exports.handler=async(a,b)=>{b.callbackWaitsForEmptyEventLoop=!1,addresses=await getPDFDServerIPs(PDFDCluster);const c={},d=[];for(let e of addresses)c[e]=!0,d.push(makeHealthCheck(e).then(async a=>{const b=a.queueTime;console.log("Status of server "+e+" is ",b);const c=500<b[2]||200<b[2]&&b[1]>b[2]&&b[0]>b[1],d=c?1:0,f=new PutMetricDataCommand({Namespace:scalingNamespace,MetricData:[{MetricName:"InstanceScaleCount",Value:d}]});await cw.send(f)}).catch(a=>{console.log("Data fetch failed: "+a)}));return Promise.all(d).then(()=>{})};
      Role: !GetAtt LambdaRole.Arn
      Handler: index.handler
      Runtime: nodejs24.x
      Timeout: 20
      VpcConfig:
        SecurityGroupIds:
          - !Ref LambdaSG
        SubnetIds:
          - !Ref Subnet1
          - !Ref Subnet2
      Environment:
        Variables:
          ScalingNamespace: !Sub "${AWS::StackName}-pdfd"
          PDFDCluster: !Ref PDFCluster
          Region: !Ref AWS::Region
  
  MonitorLambdaPerm:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:InvokeFunction
      FunctionName: !GetAtt MonitorLambda.Arn
      Principal: events.amazonaws.com
      SourceArn: !GetAtt EveryMinuteCloudwatchEvent.Arn

  ECSTaskEventRule:
    Type: AWS::Events::Rule
    Properties:
      Description: "Triggered when a WVS task's state changes on ECS."
      Name: !Join
        - "-"
        - - !Ref AWS::StackName
          - "ECSTaskState"
      EventPattern:
        source:
          - aws.ecs
        detail-type:
          - ECS Task State Change
        detail:
          lastStatus:
            - PENDING
            - RUNNING
          clusterArn:
            - !GetAtt BalancerCluster.Arn
            - !GetAtt PDFCluster.Arn
      State: ENABLED
      Targets:
        -
          Arn: !GetAtt HAControllerLambda.Arn
          Id: "HAControllerTarget"

  NotificationLambda:
    Type: AWS::Lambda::Function
    Condition: EnableNotification
    Properties:
      Code:
        ZipFile: |
          const{SNSClient,PublishCommand}=require('@aws-sdk/client-sns'),region=process.env.REGION;process.env.PATH=process.env.PATH+':'+process.env.LAMBDA_TASK_ROOT,exports.handler=async a=>{const b=new SNSClient({region:region});if(console.log(a),'DAILY RESTART TASK'==a.detail.stoppedReason)return void console.log('Daily restart event detected, ignoring.');let c,d;'ECS Task State Change'===a['detail-type']&&null!=a.detail.stoppingAt?(c=`ECS task ${a.detail.taskArn} for cluster ${a.detail.clusterArn} in ${a.region} was stopped because [${a.detail.stoppedReason}]`,d=`WVS - Task Stop Detected`):'ECS Task State Change'===a['detail-type']&&(c=`ECS task ${a.detail.taskArn} for cluster ${a.detail.clusterArn} in ${a.region} is now being started`,d=`WVS - Task Start Detected`);var e={Message:c,Subject:d,TopicArn:process.env.NotifyTopic};await b.send(new PublishCommand(e))};
      Role: !GetAtt LambdaRole.Arn
      Handler: index.handler
      Runtime: nodejs24.x
      Timeout: 20
      VpcConfig:
        SecurityGroupIds:
          - !Ref LambdaSG
        SubnetIds:
          - !Ref Subnet1
          - !Ref Subnet2
      Environment:
        Variables:
          NotifyTopic: !Ref ECSNotificationTopic


  PermissionForEventsToInvokeLambda:
    Type: AWS::Lambda::Permission
    Properties: 
      FunctionName: !Ref HAControllerLambda
      Action: "lambda:InvokeFunction"
      Principal: "events.amazonaws.com"
      SourceArn: !GetAtt ECSTaskEventRule.Arn

  EventBridgeLambdaPermissionNotify:
    Type: AWS::Lambda::Permission
    Condition: EnableNotification
    Properties:
      FunctionName: !GetAtt NotificationLambda.Arn
      Action: lambda:InvokeFunction
      Principal: events.amazonaws.com
      SourceArn: !GetAtt ECSTaskStartedEventRule.Arn

  ECSTaskStartedEventRule:
    Type: AWS::Events::Rule
    Condition: EnableNotification
    Properties:
      Description: "Triggered when a WVS task is started on ECS."
      Name: !Join
        - "-"
        - - !Ref AWS::StackName
          - "ECSTaskStarted"
      EventPattern:
        source:
          - aws.ecs
        detail-type:
          - ECS Task State Change
        detail:
          lastStatus:
            - PENDING
          clusterArn:
            - !GetAtt BalancerCluster.Arn
            - !GetAtt PDFCluster.Arn
      State: ENABLED
      Targets:
        -
          Arn: !GetAtt NotificationLambda.Arn
          Id: !Ref NotificationLambda

  EventBridgeLambdaPermission:
    Type: AWS::Lambda::Permission
    Condition: EnableNotification
    Properties:
      FunctionName: !GetAtt NotificationLambda.Arn
      Action: lambda:InvokeFunction
      Principal: events.amazonaws.com
      SourceArn: !GetAtt ECSTaskStoppedEventRule.Arn

  ECSTaskStoppedEventRule:
    Type: AWS::Events::Rule
    Condition: EnableNotification
    Properties:
      Description: "Triggered when a WVS task is stopped on ECS."
      Name: !Join
        - "-"
        - - !Ref AWS::StackName
          - "ECSTaskStopped"
      EventPattern:
        source:
          - aws.ecs
        detail-type:
          - ECS Task State Change
        detail:
          lastStatus:
            - RUNNING
          clusterArn:
            - !GetAtt BalancerCluster.Arn
            - !GetAtt PDFCluster.Arn
      State: ENABLED
      Targets:
        -
          Arn: !GetAtt NotificationLambda.Arn
          Id: !Ref NotificationLambda

Outputs:
  WVSECSClusterName:
    Description: Cluster name for the WVS ECS cluster.
    Value: !Sub "${AWS::StackName}-WVCluster"
    Export:
      Name: WVS-ClusterName

  WVSECSServiceName:
    Description: Service name for the WVS ECS service.
    Value: !GetAtt PDFDECSService.Name
    Export:
      Name: WVS-ServiceName

  HAProxyECSClusterName:
    Description: Cluster name for the WVS ECS cluster.
    Value: !Sub "${AWS::StackName}-LB-Cluster"
    Export:
      Name: Balancer-ClusterName

  HAProxyECSServiceName:
    Description: Service name for the WVS ECS service.
    Value: !GetAtt HAProxyECSService.Name
    Export:
      Name: Balancer-ServiceName

  LoadBalancer:
    Description: Reference to the Classic Load Balancer.
    Value: !Sub "https://console.aws.amazon.com/ec2/v2/home?region=${AWS::Region}#LoadBalancers:search=${AWS::StackName}"

  VPC:
    Description: Reference to the VPC.
    Value: !Sub "https://console.aws.amazon.com/vpc/home?region=${AWS::Region}#vpcs:filter=${AWS::StackName}"

  BalancerCluster:
    Description: Reference to the Balancer ECS Cluster.
    Value: !Sub "https://console.aws.amazon.com/ecs/home?region=${AWS::Region}#/clusters/${BalancerCluster}/services"

  PDFCluster:
    Description: Reference to the PDF ECS cluster.
    Value: !Sub "https://console.aws.amazon.com/ecs/home?region=${AWS::Region}#/clusters/${PDFCluster}/services"

  ApryseCloudWatchLogs:
    Description: Reference to logs for the PDF servers.
    Value: !Sub "https://console.aws.amazon.com/cloudwatch/home?region=${AWS::Region}#logStream:group=${ApryseCloudWatchLogs}"
