AWSTemplateFormatVersion: "2010-09-09"
Description: "Deploys the EC2 Autoscaling, LaunchConfig and Instance for Artifactory (qs-1qpmmjh5o)"
Parameters:
  PrivateSubnetIds:
    Type: List<AWS::EC2::Subnet::Id>
  MinScalingNodes:
    Type: Number
  MaxScalingNodes:
    Type: Number
  DeploymentTag:
    Type: String
  HostRole:
    Type: String
  ArtifactoryProduct:
    Description: JFrog Artifactory product you want to install into an AMI.
    AllowedValues:
      - JFrog-Artifactory-Pro
      - JFrog-Artifactory-Enterprise
      - JFrog-Container-Registry
    Default: JFrog-Artifactory-Enterprise
    Type: String
  QsS3BucketName:
    Type: String
  QsS3KeyPrefix:
    Type: String
  QsS3Uri:
    Type: String
  ArtifactoryLicensesSecretName:
    Type: String
  ArtifactoryServerName:
    Type: String
  Certificate:
    Type: String
  CertificateKey:
    Type: String
    NoEcho: 'true'
  CertificateDomain:
    Type: String
  EnableSSL:
    Type: String
  ArtifactoryS3Bucket:
    Type: String
  DatabaseUrl:
    Type: String
  DatabaseDriver:
    Type: String
  DatabasePluginUrl:
    Type: String
  DatabasePlugin:
    Type: String
  DatabaseType:
    Type: String
  DatabaseUser:
    Type: String
  DatabasePassword:
    Type: String
    NoEcho: 'true'
  ArtifactoryPrimary:
    Type: String
  MasterKey:
    Type: String
    NoEcho: 'true'
  ExtraJavaOptions:
    Type: String
  ArtifactoryVersion:
    Type: String
  KeyPairName:
    Type: AWS::EC2::KeyPair::KeyName
  TargetGroupARN:
    Type: String
  SSLTargetGroupARN:
    Type: String
  InternalTargetGroupARN:
    Type: String
  HostProfile:
    Type: String
  SecurityGroups:
    Type: String
  InstanceType:
    Type: String
  PrimaryVolume:
    Type: String
  VolumeSize:
    Type: Number
  UserDataDirectory:
    Description: Directory to store Artifactory data. Can be used to store data (via symlink) in detachable volume
    Type: String
    Default: '/artifactory-user-data'

# To populate additional mappings use the following with the desired --region
# aws --region us-west-2 ec2 describe-images --owners amazon --filters 'Name=name,Values=amzn-ami-hvm-2018.03.0.20181129-x86_64-gp2' 'Name=state,Values=available' --output json | jq -r '.Images | sort_by(.CreationDate) | last(.[]).ImageId'
Mappings:
  AWSAMIRegionMap:
    ap-northeast-1:
      CentOS7HVM: "ami-00a5245b4816c38e6"
    ap-northeast-2:
      CentOS7HVM: "ami-00dc207f8ba6dc919"
    ap-south-1:
      CentOS7HVM: "ami-0ad42f4f66f6c1cc9"
    ap-southeast-1:
      CentOS7HVM: "ami-05b3bcf7f311194b3"
    ap-southeast-2:
      CentOS7HVM: "ami-02fd0b06f06d93dfc"
    ca-central-1:
      CentOS7HVM: "ami-07423fb63ea0a0930"
    eu-central-1:
      CentOS7HVM: "ami-0cfbf4f6db41068ac"
    eu-west-1:
      CentOS7HVM: "ami-08935252a36e25f85"
    sa-east-1:
      CentOS7HVM: "ami-05145e0b28ad8e0b2"
    us-east-1:
      CentOS7HVM: "ami-0affd4508a5d2481b"
    us-east-2:
      CentOS7HVM: "ami-01e36b7901e884a10"
    us-west-1:
      CentOS7HVM: "ami-098f55b4287a885ba"
    us-west-2:
      CentOS7HVM: "ami-0bc06212a56393ee1"
  ArtifactoryProductMap:
    JFrog-Container-Registry:
      "7153": "Jcr7153"
      flavor: "jcr"
      haEabled: false
      product: "jcr"
    JFrog-Artifactory-Enterprise:
      "7153": "Artifactory7153"
      flavor: "pro"
      haEabled: true
      product: "artifactory"
    JFrog-Artifactory-Pro:
      "7153": "Artifactory7153"
      flavor: "pro"
      haEabled: false
      product: "artifactory"

Conditions:
  IsSecondary: !Equals [!Ref ArtifactoryPrimary, 'false']

Resources:
  ArtifactoryScalingGroup:
    Type: AWS::AutoScaling::AutoScalingGroup
    Properties:
      LaunchConfigurationName: !Ref ArtifactoryLaunchConfiguration
      VPCZoneIdentifier: !Ref PrivateSubnetIds
      MinSize: !Ref MinScalingNodes
      MaxSize: !Ref MaxScalingNodes
      Cooldown: '300'
      DesiredCapacity: !Ref MinScalingNodes
      TargetGroupARNs:
        - !Ref TargetGroupARN
        - !Ref SSLTargetGroupARN
        - !Ref InternalTargetGroupARN
      HealthCheckType: ELB
      HealthCheckGracePeriod: 1800
      Tags:
        - Key: Name
          Value: !Ref DeploymentTag
          PropagateAtLaunch: true
        - Key: ArtifactoryVersion
          Value: !Ref ArtifactoryVersion
          PropagateAtLaunch: true
      TerminationPolicies:
        - OldestInstance
        - Default
    CreationPolicy:
      ResourceSignal:
        Count: !Ref MinScalingNodes
        Timeout: PT60M

  ArtifactoryLaunchConfiguration:
    Type: AWS::AutoScaling::LaunchConfiguration
    Metadata:
      AWS::CloudFormation::Authentication:
        S3AccessCreds:
          type: S3
          roleName:
            - !Ref HostRole  # !Ref ArtifactoryHostRole
          buckets:
            - !Ref QsS3BucketName
      AWS::CloudFormation::Init:
        configSets:
          jfrog_ami_setup:
            - "config-cloudwatch"
            - "config-ansible-art-ami"
            - "config-artifactory-primary"
            - "secure-artifactory"
          artifactory_install:
            - "config-cloudwatch"
            - "config-artifactory-primary"
            - "secure-artifactory"
        config-cloudwatch:
          files:
            /root/cloudwatch.conf:
              content: |
                [general]
                state_file = /var/awslogs/state/agent-state

                [/var/log/messages]
                file = /var/log/messages
                log_group_name = /artifactory/instances/{instance_id}
                log_stream_name = /var/log/messages/
                datetime_format = %b %d %H:%M:%S

                [/var/log/jfrog-ami-setup.log]
                file = /var/log/messages
                log_group_name = /artifactory/instances/{instance_id}
                log_stream_name = /var/log/jfrog-ami-setup.log
                datetime_format = %b %d %H:%M:%S

                [/var/log/jfrog-ami-artifactory.log]
                file = /var/log/messages
                log_group_name = /artifactory/instances/{instance_id}
                log_stream_name = /var/log/jfrog-ami-artifactory.log
                datetime_format = %b %d %H:%M:%S
              mode: "0400"
        config-ansible-art-ami:
          files:
            /root/.jfrog_ami/jfrog-ami-setup.yml:
              content: !Sub |
                  # Base install for JFrogAMIInstance
                  - import_playbook: artifactory-ami.yml
                    vars:
                      ami_creation: false
                      artifactory_flavour: "pro"
                      artifactory_ha_enabled: false
                      artifactory_tar: "https://releases.jfrog.io/artifactory/artifactory-pro/org/artifactory/pro/jfrog-artifactory-pro/${ArtifactoryVersion}/jfrog-artifactory-pro-${ArtifactoryVersion}-linux.tar.gz"
                      artifactory_version: ${ArtifactoryVersion}
                      db_download_url: "https://jdbc.postgresql.org/download/postgresql-42.2.12.jar"
                      db_type: "postgresql"
                      db_driver: "org.postgresql.Driver"
              mode: "0400"
        config-artifactory-primary:
          files:
            /root/attach_volume.sh:
              content: !Sub |
                #!/usr/bin/env bash
                IS_PRIMARY="${ArtifactoryPrimary}"

                if [[ $IS_PRIMARY != "true" ]]; then
                  echo 'Not primary node. Skipping EBS volume attachment.'
                  lsblk # debug
                  exit 0
                fi

                echo "Using primary volume ID ${PrimaryVolume}"
                VOLUME_ID="${PrimaryVolume}"
                echo "VOLUME_ID: $VOLUME_ID"
                if [[ -z "$VOLUME_ID" ]]; then
                  echo 'Invalid $VOLUME_ID'
                  exit 1
                fi

                # Get instance id from AWS
                INSTANCE_ID=$(curl -s http://169.254.169.254/latest/meta-data/instance-id)

                # Attach the volume created by another CFT
                # the device name should become /dev/nvme1n1
                # See: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/nvme-ebs-volumes.html
                echo "Attaching volume $VOLUME_ID to instance $INSTANCE_ID"
                /var/awslogs/bin/aws ec2 attach-volume --volume-id $VOLUME_ID --instance-id $INSTANCE_ID --device /dev/xvdf --region ${AWS::Region}

                echo "Wait for volume $VOLUME_ID to attach"
                sleep 30 # Give volume time to attach
                lsblk # debug
              mode: "0770"
            /root/.jfrog_ami/artifactory.yml:
              content: !Sub
                - |
                  # Base install for Artifactory
                  - import_playbook: site-artifactory.yml
                    vars:
                      artifactory_product: ${product}
                      artifactory_flavour: ${flavor}
                      artifactory_ha_enabled: ${ha_enabled}
                      artifactory_is_primary: ${ArtifactoryPrimary}
                      artifactory_server_name: ${ArtifactoryServerName}
                      server_name: ${ArtifactoryServerName}.${CertificateDomain}
                      use_custom_data_directory: true
                      custom_data_directory: "${UserDataDirectory}"
                      s3_region: ${AWS::Region}
                      s3_bucket: ${ArtifactoryS3Bucket}
                      certificate: ${Certificate}
                      certificate_key: ${CertificateKey}
                      certificate_domain: ${CertificateDomain}
                      enable_ssl: ${EnableSSL}
                      ssl_dir: /etc/pki/tls/certs
                      db_type: ${DatabaseType}
                      db_driver: ${DatabaseDriver}
                      db_url: ${DatabaseUrl}
                      db_user: ${DatabaseUser}
                      db_password: ${DatabasePassword}
                      master_key: ${MasterKey}
                      join_key: ${MasterKey}
                      extra_java_opts: ${ExtraJavaOptions}
                      artifactory_version: ${ArtifactoryVersion}
                      artifactory_keystore:
                        path: /opt/jfrog/artifactory/app/third-party/java/lib/security/cacerts
                        default_password: changeit
                        new_keystore_pass: ${DatabasePassword}
                      artifactory_java_db_drivers:
                        - name: ${DatabasePlugin}
                          url: ${DatabasePluginUrl}
                          owner: artifactory
                          group: artifactory
                      product_id: 'CloudFormation_SP_EC2/1.0.0'
                - flavor: !FindInMap [ArtifactoryProductMap, !Ref ArtifactoryProduct, flavor]
                  ha_enabled: !FindInMap [ArtifactoryProductMap, !Ref ArtifactoryProduct, haEabled]
                  product: !FindInMap [ArtifactoryProductMap, !Ref ArtifactoryProduct, product]
              mode: "0400"
            /root/.vault_pass.txt:
              content: !Sub |
                ${DatabasePassword}
              mode: "0400"
            /root/.secureit.sh:
              content:
                ansible-vault encrypt /root/.jfrog_ami/artifactory.yml --vault-id /root/.vault_pass.txt
              mode: "0770"
        secure-artifactory:
          commands:
            'secure ansible playbook':
              command: '/root/.secureit.sh'
              ignoreErrors: 'false'
    Properties:
      KeyName: !Ref KeyPairName
      IamInstanceProfile: !Ref HostProfile
      ImageId: !FindInMap
        - AWSAMIRegionMap
        - !Ref AWS::Region
        - 'CentOS7HVM'
      SecurityGroups:
        - !Ref SecurityGroups
      BlockDeviceMappings:
        !If
          - IsSecondary
          - - DeviceName: /dev/xvda
              Ebs:
                VolumeSize: !Ref VolumeSize
                VolumeType: gp2
                DeleteOnTermination: true
                Encrypted: true
          - !Ref AWS::NoValue
      InstanceType: !Ref InstanceType
      UserData:
        Fn::Base64:
          !Sub |
            #!/bin/bash -x

            #CFN Functions

            function cfn_fail

            {

            cfn-signal -e 1 --stack ${AWS::StackName} --region ${AWS::Region} --resource ArtifactoryScalingGroup

            exit 1

            }

            function cfn_success

            {

            cfn-signal -e 0 --stack ${AWS::StackName} --region ${AWS::Region} --resource ArtifactoryScalingGroup

            exit 0

            }

            S3URI=${QsS3Uri}

            # Update OS
            yum update -y

            # Install git
            yum install -y epel-release git policycoreutils-python

            yum update --security -y 2>&1 | tee /var/log/userdata.yum_security_update.log

            yum install -y jq python3 libselinux-python3

            echo $PATH

            PATH=/opt/aws/bin:$PATH

            echo $PATH

            # Create virtual env and activate
            python3 -m venv ~/venv --system-site-packages
            source ~/venv/bin/activate

            pip install --upgrade pip
            pip install wheel

            # Install Cloudformation helper scripts
            pip install https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-py3-latest.tar.gz 2>&1 | tee /var/log/userdata.aws_cfn_bootstrap_install.log

            pip install awscli 2>&1 | tee /var/log/userdata.awscli_install.log

            pip install ansible 2>&1 | tee /var/log/userdata.ansible_install.log

            mkdir ~/.jfrog_ami

            aws s3 --region ${AWS::Region} sync s3://${QsS3BucketName}/${QsS3KeyPrefix}cloudInstallerScripts/ ~/.jfrog_ami/ || cfn_fail

            setsebool httpd_can_network_connect 1 -P

            # CentOS cloned virtual machines do not create a new machine id
            # https://www.thegeekdiary.com/centos-rhel-7-how-to-change-the-machine-id/
            rm -f /etc/machine-id
            systemd-machine-id-setup

            cfn-init -v --stack ${AWS::StackName} --resource ArtifactoryLaunchConfiguration --configsets jfrog_ami_setup --region ${AWS::Region} || cfn_fail

            # Setup CloudWatch Agent
            curl https://s3.amazonaws.com/aws-cloudwatch/downloads/latest/awslogs-agent-setup.py -O
            chmod +x ./awslogs-agent-setup.py
            ./awslogs-agent-setup.py -n -r ${AWS::Region} -c /root/cloudwatch.conf 2>&1 | tee /var/log/userdata.cloudwatch_agent_install.log

            /root/attach_volume.sh || cfn_fail

            ansible-galaxy collection install community.general ansible.posix

            aws secretsmanager get-secret-value --secret-id ${ArtifactoryLicensesSecretName} --region ${AWS::Region} | jq -r '{"artifactory_licenses":(.SecretString | fromjson )}' > ~/.jfrog_ami/licenses.json || cfn_fail

            ansible-playbook /root/.jfrog_ami/jfrog-ami-setup.yml --vault-id /root/.vault_pass.txt 2>&1 | tee /var/log/jfrog-ami-setup.log || cfn_fail
            ansible-playbook /root/.jfrog_ami/artifactory.yml -e "@~/.jfrog_ami/licenses.json" --vault-id /root/.vault_pass.txt 2>&1 | tee /var/log/jfrog-ami-artifactory.log || cfn_fail

            rm -rf /root/.secureit.sh

            cfn_success &> /var/log/cfn_success.log
            cfn_success || cfn_fail
