AWSTemplateFormatVersion: '2010-09-09'
Description: "Perforce Swarm"

Parameters: 
  InstanceType: 
    Type: String
    Default: t3.small
    AllowedValues:
      - t3.nano
      - t3.micro
      - t3.small
      - t3.medium
      - m5.large
      - m5.xlarge
      - m5.2xlarge
      - m5.4xlarge
      - m5.8xlarge
      - r5.large
      - r5.xlarge
      - r5.2xlarge
      - r5.4xlarge
      - r5.8xlarge
    Description: "Select Swarm server EC2 instance type. Default is t3.small."
  KeyPairName:
    Description: "Public/private key pairs allow you to securely connect to your instance after it launches"
    Type: AWS::EC2::KeyPair::KeyName
  AvailabilityZone:
    Type: AWS::EC2::AvailabilityZone::Name
    Description: "Select AvailabilityZone according to Subnet"
  Prefix:
    Default: "esp-"
    MaxLength: '20'
    Description: "Prefix for naming, e.g. CustomerID.  Max 20"
    Type: String
  Owner:
    Type: "String"
    Description: "Owner of resources - e.g. for billing purposes"
    Default: ""
  Environment:
    Type: "String"
    Description: "Environment to deploy to, e.g prod, test, staging"
    Default: "prod"
  RootVolumeSize:
    Type: String
    Default: '20'
    Description: "The size (GB) of the root EBS volume."
  EC2Profile:
    Type: "String"
    Description: "EC2 profile setting for IAM role (optional)"
  PerforceMasterIP:
    Type: "String"
    Description: "Privarte IP Address of commit server Swarm should connect to (Helix core Private IP)"
  PreUserData:
    Type: "String"
    Default: ""
    Description: "Customer userdata to run before default userdata (optional)"
  PostUserData:
    Type: "String"
    Default: ""
    Description: "Customer userdata to run after default userdata (optional)"
  HelixCoreInstanceID:
    Default: ""
    Description: "Instance ID of Helix Core server"
    Type: String
  IAMTagKey:
    Type: "String"
    Default: ""
    Description: "Tag key for use in controlling IAM access - used by HRA stack (optional)"
  IAMTagValue:
    Type: "String"
    Default: ""
    Description: "Value of tag specified by IAMTagKey if that is set (optional)"
  PrivateIpAddress:
    Type: "String"
    Description: "If not set will be auto-assigned"
    Default: ""
  SubnetId:
    Type: AWS::EC2::Subnet::Id
    Description: "Subnet selected should be in same AvailabilityZone, also Auto-assign public IPv4 address should be enabled in the subnet"
  SecurityGroup:
    Type: AWS::EC2::SecurityGroup::Id
    Description: "It should allow connecting to Helix core server on port 1666"

Conditions: 
  # Used for tagging e.g. by HRA
  HasIAMTagKey: !Not # this is true when not empty
    - !Equals
      - !Ref IAMTagKey
      - ''
  RequestedPrivateIP: !Not 
    - !Equals
      - !Ref PrivateIpAddress
      - ''

Mappings:
  AMIRegion:
    ap-northeast-1:
      AMI: ami-03e734487e72fa720
    ap-northeast-2:
      AMI: ami-06497dc215fd37ee0
    ap-northeast-3:
      AMI: ami-0738619beafb75396
    ap-south-1:
      AMI: ami-01d1815917a4afa0b
    ap-southeast-1:
      AMI: ami-0f4a982f2ad35baf3
    ap-southeast-2:
      AMI: ami-098dd89e242be5d86
    ca-central-1:
      AMI: ami-02e9a68b8ca3573d1
    eu-central-1:
      AMI: ami-09251d0a251c0aa5e
    eu-north-1:
      AMI: ami-0f2a4245c59b88a19
    eu-west-1:
      AMI: ami-04b636ead665db59f
    eu-west-2:
      AMI: ami-0aa33d09b0a52afbe
    eu-west-3:
      AMI: ami-0b2eefd09f9711ec2
    sa-east-1:
      AMI: ami-006f79f958af87c95
    us-east-1:
      AMI: ami-00f9a6a63240641d2
    us-east-2:
      AMI: ami-028fb759dc7b039fd
    us-west-1:
      AMI: ami-057357dffb3298de4
    us-west-2:
      AMI: ami-01026da5b8b796de3
      
Resources:
  SwarmEIP:
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc
      Tags:
        - Key: Name
          Value: !Sub ${Prefix}${Environment}-eip-p4
        - Key: Service
          Value: "P4"
        - Key: Owner
          Value: !Ref Owner
        - Key: Environment
          Value: !Ref Environment
        - Fn::If:
          - HasIAMTagKey
          - Key: !Ref IAMTagKey
            Value: !Ref IAMTagValue
          - Ref: AWS::NoValue

  SwarmServerInstance:
    Type: AWS::EC2::Instance
    CreationPolicy:
      ResourceSignal:    
        Count: 1
        Timeout: "PT10M"
    Properties:
      AvailabilityZone: !Ref AvailabilityZone
      DisableApiTermination: false
      InstanceInitiatedShutdownBehavior: stop
      EbsOptimized: true
      ImageId: !FindInMap
        - AMIRegion
        - !Ref AWS::Region
        - AMI
      InstanceType: !Ref InstanceType
      KeyName: !Ref KeyPairName
      Monitoring: true
      IamInstanceProfile: !Ref EC2Profile
      Tags:
      - Key: Name
        Value: !Sub ${Prefix}helix-swarm-${Environment}
      - Key: Service
        Value: "P4"
      - Key: Owner
        Value: !Ref Owner
      - Key: Environment
        Value: !Ref Environment
      - Fn::If:
        - HasIAMTagKey
        - Key: !Ref IAMTagKey
          Value: !Ref IAMTagValue
        - Ref: AWS::NoValue
      BlockDeviceMappings:
        - DeviceName: /dev/sda1
          Ebs:
            VolumeType: gp2
            VolumeSize: !Ref RootVolumeSize
            DeleteOnTermination: true
            Encrypted: false
      NetworkInterfaces:
      - DeleteOnTermination: true
        Description: Primary network interface
        DeviceIndex: "0"
        SubnetId: !Ref SubnetId
        PrivateIpAddress: 
          Fn::If:
          - RequestedPrivateIP
          - !Ref PrivateIpAddress
          - Ref: AWS::NoValue
        GroupSet:
        - !Ref SecurityGroup
      UserData: !Base64
        Fn::Sub: 
          - |
            #!/bin/bash

            set -eux -o pipefail

            trap 'catch $? $LINENO' ERR

            catch() {
              echo ""
              echo "ERROR CAUGHT!"
              echo ""
              echo "Error code $1 occurred on line $2"
              echo ""
              cfn-signal --stack ${AWS::StackName} --resource SwarmServerInstance --region ${AWS::Region} --exit-code $1
              exit $1
            }

            export HOST=$(echo "${Prefix}${Environment}-swarm-p4" | tr _ -)
            hostname "$HOST"

            export PATH=$PATH:/opt/perforce/bin/

            # During Swarm installation this file gets populated with this servers hostname
            # /opt/perforce/etc/swarm-cron-hosts.conf
            # To work around this issue I am adding the ServerID into /etc/hosts
            echo "127.0.0.1 $HOST" >> /etc/hosts

            SWARM_PRIVATE_IP_ADDRESS="$(ifconfig | grep -Eo 'inet (addr:)?([0-9]*\.){3}[0-9]*' | grep -Eo '([0-9]*\.){3}[0-9]*' | grep -v '127.0.0.1')"
            SWARM_PUBLIC_IP_ADDRESS="${SWARM_PUBLIC_IP_ADDRESS}"

            # by the time swarm comes up we know P4D is up.  Generate a P4D ticket so that pre/post userdata can run p4 commands
            export P4PORT="ssl:${PerforceMasterIP}:1666"
            export P4USER="perforce"
            export P4TRUST=/root/.p4trust
            export P4TICKETS=/root/.p4tickets
            /opt/perforce/bin/p4 trust -y -f

            export SWARM_INSTANCE_ID=$(curl http://169.254.169.254/latest/meta-data/instance-id)       
            export SWARM_USER="swarm"
            export SWARM_GROUP="swarm"
            export SWARM_PORT="80"
            export P4D_SUPER_USER="perforce"
            export P4D_HOST="ssl:${PerforceMasterIP}"
            export P4D_PORT="1666"
            export SWARM_EMAIL_HOST="localhost"
            export SWARM_ADDITIONAL_ARGS=""
            export SWARM_ADMIN_PASSWORD="$SWARM_INSTANCE_ID"

            # the default is to expect the perforce password in helix core to be the instance ID
            # We must set this environment variable before PreUserData so a user has the opportunity to overwrite it before we use it
            export P4D_SUPER_USER_PASSWORD="${HelixCoreInstanceID}"          

            ${PreUserData}

            export P4PASSWD=$(echo "$P4D_SUPER_USER_PASSWORD" | /opt/perforce/bin/p4 login -a -p | grep -v "Enter")

            _USERS=$(p4 users -a)

            if [[ $_USERS == *"Swarm Admin"* ]]; then
              echo "Swarm user exists...this is a restore from backup"
              /opt/perforce/swarm/sbin/configure-swarm.sh --extensions --non-interactive --swarm-host $SWARM_PRIVATE_IP_ADDRESS --swarm-port $SWARM_PORT --create-group $SWARM_GROUP --swarm-user $SWARM_USER --swarm-passwd $SWARM_ADMIN_PASSWORD --super-user $P4D_SUPER_USER --super-passwd $P4D_SUPER_USER_PASSWORD --p4port $P4D_HOST:$P4D_PORT --email-host $SWARM_EMAIL_HOST $SWARM_ADDITIONAL_ARGS
            else
              echo "Swarm user does not exist, creating Swarm user during Swarm installation..."
              /opt/perforce/swarm/sbin/configure-swarm.sh --extensions --non-interactive --swarm-host $SWARM_PRIVATE_IP_ADDRESS --swarm-port $SWARM_PORT --create swarm --create-group $SWARM_GROUP --swarm-user $SWARM_USER --swarm-passwd $SWARM_ADMIN_PASSWORD --super-user $P4D_SUPER_USER --super-passwd $P4D_SUPER_USER_PASSWORD --p4port $P4D_HOST:$P4D_PORT --email-host $SWARM_EMAIL_HOST $SWARM_ADDITIONAL_ARGS
            fi
            
            # The extension installation defaults to secure mode which we must diable because we do not have a valid certificate  
            p4 extension --configure Perforce::helix-swarm -o | sed "s/Swarm-Secure:.*/Swarm-Secure: false/g" | p4 extension --configure Perforce::helix-swarm -i

            ${PostUserData}

            /opt/perforce/bin/p4 -p "ssl:${PerforceMasterIP}:1666" property -a -n P4.Swarm.URL -v "http://$SWARM_PUBLIC_IP_ADDRESS"

            cfn-signal --stack ${AWS::StackName} --resource SwarmServerInstance --region ${AWS::Region}

          - SWARM_PUBLIC_IP_ADDRESS: !Ref SwarmEIP

  assocEIP:
    Type: AWS::EC2::EIPAssociation
    Properties:
      AllocationId:
        Fn::GetAtt:
        - SwarmEIP
        - AllocationId
      InstanceId: !Ref SwarmServerInstance
              
Outputs:
  EC2InstanceId:
    Description: InstanceId of the newly created EC2 instance
    Value: !Ref SwarmServerInstance
  SwarmEIP:
    Description: Elastic IP Adress attached to Hansoft server
    Value: !Ref SwarmEIP
  SwarmPrivateIP:
    Description: Private IP address of the Swarm Server
    Value: !GetAtt SwarmServerInstance.PrivateIp
