---
AWSTemplateFormatVersion: '2010-09-09'
Description: Autoscaling EC2 Running Mediawiki on Nginx, RDS Backed, Behind Existing Load Balancer
Parameters:
  # Ownership
  Owner:
    Type: String
    Default: FirstName LastName
  DeleteAfter:
    Type: String
    Default: 00/00/201x

  # Network
  VPC:
    Type: AWS::EC2::VPC::Id
  SubnetIdList:
    Description: Private Subnet ID List.
    Type: List<AWS::EC2::Subnet::Id>
  InternalAccessSecurityGroup:
    Type: AWS::EC2::SecurityGroup::Id
  RemoteSecurityGroup:
    Type: AWS::EC2::SecurityGroup::Id
  RdsSecurityGroup:
    Type: AWS::EC2::SecurityGroup::Id
  EfsSecurityGroup:
    Type: AWS::EC2::SecurityGroup::Id
  EfsFileSystemId:
    Type: String
    Default: fs-00000000

  # Instance
  KeyPairName:
    Type: AWS::EC2::KeyPair::KeyName
  InstanceType:
    Type: String
    AllowedValues:
    - t2.nano
    - t2.micro
    - t2.small
    - t2.medium
    - m3.medium
    - m4.large
    - c3.medium
    - c4.medium
    Default: t2.small

  # Autoscaling
  MinAutoScaleCount:
    Type: Number
    Default: 1
  MaxAutoScaleCount:
    Type: Number
    Default: 1
  AlarmSnsTopicArn:
    Type: String
    Default: arn:aws:sns:us-west-2:000000000000:alerts

  # Cloudwatch Alerts

  # Deployment
  CookbookName:
    Type: String
    Default: bonusbits_mediawiki_nginx
  CookbookVersion:
    Type: String
    Default: latest
  ChefClientVersion:
    Type: String
    Default: 12.18.31
  DataBagItem:
    Type: String
  DataBagSecret:
    Type: String
    NoEcho: true
    MinLength: 684
    Default: "CYEh1mnfH+TrzeVa8cyDAHWicV8hKni7P40xVPUn2Cm8638QvFS24iK7DRtP5VlDpogSaxDm050riKcAZ++rwUsJdUcQyx6KdXIMKEVml+ZjYW9BC3c4fMWPYbBkdWHBTpUP6OpMl9eNdPoa36vKRuxBrYGAKzLLDlTTqvoRlKEpAKpdSdgp3zZnGeymEeYYyG2Lv9p6ZbmlcDqRdB2hPu+grNkDWNrwDfV/UxF3QOtLQQB7c5edDHa1rmegNbP1Gb/h2Gu+HFf2sQnCFnjG20rxwJaL5lSdUs2AOdR+B+fDL+y7o6LxIN3n3EQWfdzzDsJXFjKhF0b+mAAmgJy9q2R1Sqr0f0X078icvu9zlUMjgJxSAShsXW00y9swcItQXFpJTFXnaswbd7YtJBsrNvfcOuSF9GhO6y1vCTbeOUMet+G6KbDIO0YxKF+fi07VPSTkAoXznC0t9o8s7Kqdd1pgx3xxVr80r9v3uKZcM+wMBk7TrpswKr6A4sIT0kBaJTgr37uDH8uIoiqEpxHfHEge6vVWfJyRaXjdyG7aJ3VwuJvSrIkuVJCam3ie2Dr09KBUWNYHXOmPb3eM0ByudcXNS0IXNAWqQJ5leNTINcqq4OZmAJHHOUo7qJhtqLVtr+Zwcady+WQ0ta1kmhQuWJ8RAhCCBUCUTISKEtXuF8E="

  # MediaWiki
  MediawikiMajorRelease:
    Type: String
    Default: 1
  MediawikiMinorRelease:
    Type: String
    Default: 28
  SiteFolderName:
    Type: String
    Default: mediawiki
  UploadFolderName:
    Type: String
    Default: uploads

  # Nginx
  RootSitePath:
    Type: String
    Default: "/var/www/html"
  NginxXforwardedTraffic:
    Type: String
    Default: false
    AllowedValues:
      - true
      - false
  NginxRewriteWikiAlias:
    Type: String
    Default: false
    AllowedValues:
      - true
      - false

  # DNS
  UpdateDNS:
    Type: String
    Default: true
    AllowedValues:
      - true
      - false
  HostedZoneId:
    Type: String
    Description: Route53 Hosted Zone ID (Z1234567890).
    Default: Z1234567890
  RecordName:
    Type: String
    Description: DNS Alias Record Name (web01.example.com)
    Default: web01.example.com

Metadata:
  TemplateAuthor: Levon Becker
  AWS::CloudFormation::Interface:
    ParameterGroups:
      # Ownership
      - Label:
          default: Ownership
        Parameters:
          - Owner
          - DeleteAfter

      # Network
      - Label:
          default: Network
        Parameters:
          - VPC
          - SubnetIdList
          - InternalAccessSecurityGroup
          - RemoteSecurityGroup
          - RdsSecurityGroup
          - EfsSecurityGroup
          - EfsFileSystemId

      # Instance
      - Label:
          default: Instance
        Parameters:
          - KeyPairName
          - InstanceType

      # Autoscaling
      - Label:
          default: Autoscaling
        Parameters:
          - MinAutoScaleCount
          - MaxAutoScaleCount
          - AlarmSnsTopicArn

      # Deployment
      - Label:
          default: Deployment
        Parameters:
          - CookbookName
          - CookbookVersion
          - ChefClientVersion
          - DataBagItem
          - DataBagSecret

      # Mediawiki
      - Label:
          default: Mediawiki
        Parameters:
          - MediawikiMajorRelease
          - MediawikiMinorRelease
          - SiteFolderName
          - UploadFolderName

      # Nginx
      - Label:
          default: Nginx
        Parameters:
          - RootSitePath
          - NginxXforwardedTraffic
          - NginxRewriteWikiAlias

      # DNS
      - Label:
          default: DNS
        Parameters:
          - UpdateDNS
          - HostedZoneId
          - RecordName

    ParameterLabels:
      # Ownership
      Owner:
        default: Team or Individual Owner
      DeleteAfter:
        default: Delete After Date

      # Network
      VPC:
        default: VPC ID
      SubnetIdList:
        default: Subnet ID List
      InternalAccessSecurityGroup:
        default: Internal Access Security Group
      RemoteSecurityGroup:
        default: Remote Security Group
      RdsSecurityGroup:
        default: RDS Security Group
      EfsSecurityGroup:
        default: EFS Security Group
      EfsFileSystemId:
        default: EFS File System ID

      # Instance
      KeyPairName:
        default: EC2 Key Pair Name
      InstanceType:
        default: Instance Type

      # Autoscaling
      MinAutoScaleCount:
        default: Min AutoScale Count
      MaxAutoScaleCount:
        default: Max AutoScale Count
      AlarmSnsTopicArn:
        default: Alarm SNS Topic ARN

      # Deployment
      CookbookName:
        default: Cookbook Name
      CookbookVersion:
        default: Cookbook Version
      ChefClientVersion:
        default: Chef Client Version
      DataBagItem:
        default: Data Bag Item
      DataBagSecret:
        default: Data Bag Secret

      # MediaWiki
      MediawikiMajorRelease:
        default: Mediawiki Major Release
      MediawikiMinorRelease:
        default: Mediawiki Minor Release
      SiteFolderName:
        default: Site Folder Name
      UploadFolderName:
        default: UploadFolderName

      # Nginx
      RootSitePath:
        default: Root Site Path
      NginxXforwardedTraffic:
        default: X-Forwarded Trafic
      NginxRewriteWikiAlias:
        default: Rewrite wiki Alias

      # DNS
      UpdateDNS:
        default: Update DNS
      RecordName:
        default: Record Name
      HostedZoneId:
        default: Hosted Zone ID

Mappings:
  RegionMap:
    # Amazon Linux AMI 2016.09.0 was released on 2016-09-27
    us-east-1:
      EBS: ami-b73b63a0
    us-west-2:
      EBS: ami-5ec1673e
#      EBS: ami-7a139f1a # Baked AMI
    us-west-1:
      EBS: ami-23e8a343
    eu-west-1:
      EBS: ami-9398d3e0
    eu-central-1:
      EBS: ami-f9619996
    ap-southeast-1:
      EBS: ami-b953f2da
    ap-northeast-1:
      EBS: ami-0c11b26d
    ap-northeast-2:
      EBS: ami-983ce8f6
    ap-southeast-2:
      EBS: ami-db704cb8
    sa-east-1:
      EBS: ami-97831ffb
    us-gov-west-1:
      EBS: ami-7cb1091d
    cn-north-1:
      EBS: ami-7c15c111

Conditions:
  HasKeyPair: !Not [!Equals [ !Ref KeyPairName, '' ]]
  RunDnsScript: !Equals [ !Ref UpdateDNS, 'true' ]

Resources:
  # CloudWatch
  CloudWatchLogsGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Ref "AWS::StackName"
      RetentionInDays: '30'

  # IAM
  IamRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Ref AWS::StackName
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/ReadOnlyAccess
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - ec2.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: "/"

  IamPolicies:
    Type: AWS::IAM::Policy
    DependsOn: IamRole
    Properties:
      PolicyName: !Join [ '', [ !Ref "AWS::StackName", -instance-policy ] ]
      Roles: [!Ref IamRole]
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
        # Allow Full S3 Access
        - Effect: Allow
          Action:
          - s3:*
          Resource: "*"
        # Allow Read/Write Route53 for Userdata Script DNS A Record Set
        - Effect: Allow
          Action:
          - route53:ChangeResourceRecordSets
          - route53:Get*
          - route53:List*
          Resource: "*"
        # Allow Write CloudWatch Logs
        - Effect: Allow
          Action:
          - cloudwatch:PutMetricData
          Resource: "*"
        - Effect: Allow
          Action:
          - logs:PutLogEvents
          - logs:CreateLogStream
          - logs:DescribeLogStreams
          - logs:CreateLogGroup
          Resource:
            Fn::Join:
            - ''
            - - 'arn:aws:logs:'
              - !Ref AWS::Region
              - ":"
              - !Ref AWS::AccountId
              - ":log-group:"
              - !Ref CloudWatchLogsGroup
              - ":log-stream:*"

  IamInstanceProfile:
    Type: AWS::IAM::InstanceProfile
    DependsOn: IamRole
    Properties:
      Path: "/"
      Roles: [!Ref IamRole]

  # Security Groups
  SecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Allow Web Traffic to Instance
      VpcId: !Ref VPC
      SecurityGroupIngress:
      - IpProtocol: tcp
        FromPort: 80
        ToPort: 80
        CidrIp: 0.0.0.0/0
      - IpProtocol: tcp
        FromPort: 443
        ToPort: 443
        CidrIp: 0.0.0.0/0
      SecurityGroupEgress:
      - IpProtocol: "-1"
        FromPort: "-1"
        ToPort: "-1"
        CidrIp: 0.0.0.0/0
      Tags:
        - Key: Name
          Value: !Ref "AWS::StackName"
        - Key: Owner
          Value: !Ref Owner
        - Key: DeleteAfter
          Value: !Ref DeleteAfter

  AutoScalingGroup:
    Type: AWS::AutoScaling::AutoScalingGroup
    DependsOn:
      - LaunchConfiguration
    Properties:
      VPCZoneIdentifier: !Ref SubnetIdList
      LaunchConfigurationName: !Ref LaunchConfiguration
      MinSize: !Ref MinAutoScaleCount
      MaxSize: !Ref MaxAutoScaleCount
      DesiredCapacity: !Ref MinAutoScaleCount
      MetricsCollection:
        - Granularity: 1Minute
          Metrics:
            - GroupMinSize
            - GroupMaxSize
            - GroupDesiredCapacity
            - GroupInServiceInstances
            - GroupPendingInstances
            - GroupStandbyInstances
            - GroupTerminatingInstances
            - GroupTotalInstances
      NotificationConfigurations:
        - TopicARN: !Ref AlarmSnsTopicArn
          NotificationTypes:
            - autoscaling:EC2_INSTANCE_LAUNCH
            - autoscaling:EC2_INSTANCE_LAUNCH_ERROR
            - autoscaling:EC2_INSTANCE_TERMINATE
            - autoscaling:EC2_INSTANCE_TERMINATE_ERROR
      Tags:
        - Key: Name
          Value: !Ref "AWS::StackName"
          PropagateAtLaunch: true
        - Key: Owner
          Value: !Ref Owner
          PropagateAtLaunch: true
        - Key: DeleteAfter
          Value: !Ref DeleteAfter
          PropagateAtLaunch: true
    CreationPolicy:
      ResourceSignal:
        Count: !Ref MinAutoScaleCount
        Timeout: PT20M
    UpdatePolicy:
      AutoScalingRollingUpdate:
        MinInstancesInService: !Ref MinAutoScaleCount
        MaxBatchSize: 1

  CPUAlarmHigh:
    Type: AWS::CloudWatch::Alarm
    DependsOn:
      - AutoScalingGroup
    Properties:
      AlarmName: !Join [ '', [ !Ref "AWS::StackName", '-cpu' ]]
      AlarmDescription: Alert if CPU > 50% for 10 minutes
      MetricName: CPUUtilization
      Namespace: AWS/EC2
      Statistic: Average
      Period: '300'
      EvaluationPeriods: '2'
      Threshold: '50'
      AlarmActions:
        - !Ref AlarmSnsTopicArn
      OKActions:
        - !Ref AlarmSnsTopicArn
      InsufficientDataActions:
        - !Ref AlarmSnsTopicArn
      Dimensions:
        - Name: AutoScalingGroupName
          Value: !Ref AutoScalingGroup
      ComparisonOperator: GreaterThanThreshold

  LaunchConfiguration:
    Type: AWS::AutoScaling::LaunchConfiguration
    DependsOn:
      - SecurityGroup
      - IamInstanceProfile
    Properties:
      AssociatePublicIpAddress: false
      ImageId: !FindInMap [ RegionMap, !Ref "AWS::Region", EBS ]
      SecurityGroups:
        - !Ref SecurityGroup
        - !Ref InternalAccessSecurityGroup
        - !Ref RemoteSecurityGroup
        - !Ref RdsSecurityGroup
        - !Ref EfsSecurityGroup
      InstanceType: !Ref InstanceType
      IamInstanceProfile: !Ref IamInstanceProfile
      KeyName: !If [ HasKeyPair, !Ref KeyPairName, !Ref "AWS::NoValue"]
      InstanceMonitoring: true
      BlockDeviceMappings:
        - DeviceName: /dev/xvda
          Ebs:
            DeleteOnTermination: true
            VolumeType: gp2
            VolumeSize: 8
      UserData:
        Fn::Base64:
          !Sub |
            #!/bin/bash -xe
            yum update -y
            yum install -y aws-cfn-bootstrap aws-cli cloud-init git
            /opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource LaunchConfiguration --region ${AWS::Region} --configsets ec2_setup
            /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource AutoScalingGroup --region ${AWS::Region}
    Metadata:
      AWS::CloudFormation::Init:
        configSets:
          ec2_setup:
            - config_cfn
            - install_chef_client
            - setup_chef_client
            - run_chef_client
            - update_dns
            - warm_ebs
        config_cfn:
          files:
            "/etc/cfn/cfn-hup.conf":
              content: !Sub |
                [main]
                stack=${AWS::StackId}
                region=${AWS::Region}
              mode: '000400'
              owner: root
              group: root
            "/etc/cfn/hooks.d/cfn-auto-reloader.conf":
              content: !Sub |
                [cfn-auto-reloader-hook]
                triggers=post.update
                path=Resources.LaunchConfiguration.Metadata.AWS::CloudFormation::Init
                action=/opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource LaunchConfiguration --region ${AWS::Region} --configsets ec2_setup
                runas=root
          services:
            sysvinit:
              cfn-hup:
                enabled: true
                ensureRunning: true
                files:
                  - /etc/cfn/cfn-hup.conf
                  - /etc/cfn/hooks.d/cfn-auto-reloader.conf
        install_chef_client:
          commands:
            01_install_chef_client:
              command: !Sub "curl -L https://omnitruck.chef.io/install.sh | bash -s -- -v ${ChefClientVersion}"
        setup_chef_client:
          commands:
            01_mkdir_chef_repo:
              command: "mkdir -p /opt/chef-repo"
            02_mkdir_chef_repo_subdirs:
              cwd: /opt/chef-repo
              command: "mkdir -p cookbooks checksums environments cache backup data_bags roles downloads"
            03_download_cookbook:
              cwd: /opt/chef-repo/cookbooks
              command: !Sub "git clone --branch ${CookbookVersion} https://github.com/bonusbits/${CookbookName}.git"
            04_deploy_databags:
              cwd: /opt/chef-repo
              command: !Sub "cp -R cookbooks/${CookbookName}/test/data_bags/${CookbookName} data_bags/"
          files:
            "/opt/chef-repo/encrypted_data_bag_secret":
              content: !Sub ${DataBagSecret}
            "/opt/chef-repo/client.rb":
              content: !Sub |
                node_name 'ec2_node'
                checksum_path '/opt/chef-repo/checksums'
                file_cache_path '/opt/chef-repo/cache'
                file_backup_path '/opt/chef-repo/backup'
                cookbook_path '/opt/chef-repo/cookbooks'
                data_bag_path '/opt/chef-repo/data_bags'
                environment_path '/opt/chef-repo/environments'
                role_path '/opt/chef-repo/roles'
                chef_server_url 'http://127.0.0.1:8889'
                encrypted_data_bag_secret '/opt/chef-repo/encrypted_data_bag_secret'
            "/opt/chef-repo/environments/ec2_environment.json":
              content: !Sub |
               {
                   "name": "ec2_environment",
                   "description": "Environment",
                   "cookbook_versions": {},
                   "json_class": "Chef::Environment",
                   "chef_type": "environment",
                   "default_attributes": {
                   },
                   "override_attributes": {
                       "${CookbookName}": {
                           "data_bag_item": "${DataBagItem}",
                           "aws": {
                               "stack_name": "${AWS::StackName}",
                               "vpc_id": "${VPC}",
                               "logs_group_name": "${CloudWatchLogsGroup}",
                               "efs_filesystem_id": "${EfsFileSystemId}"
                           },
                           "mediawiki": {
                               "version_major": "${MediawikiMajorRelease}",
                               "version_minor": "${MediawikiMinorRelease}",
                               "site_folder_name": "${SiteFolderName}",
                               "uploads_folder_name": "${UploadFolderName}"
                           },
                           "nginx": {
                               "root_site_path": "${RootSitePath}",
                               "x_forwarded_traffic": ${NginxXforwardedTraffic},
                               "rewrite_wiki_alias": ${NginxRewriteWikiAlias}
                           },
                           "dns": {
                               "configure": ${UpdateDNS},
                               "hosted_zone_id": "${HostedZoneId}",
                               "record_name": "${RecordName}"
                           }
                       }
                   }
               }
            "/opt/chef-repo/roles/ec2_role.json":
              content: !Sub |
                {
                  "name": "ec2_role",
                  "description": "Role",
                  "json_class": "Chef::Role",
                  "default_attributes": {},
                  "override_attributes": {
                  },
                  "chef_type": "role",
                  "run_list": [
                    "recipe[${CookbookName}]"
                  ],
                  "env_run_lists": {
                  }
                }
        run_chef_client:
          commands:
            01_run_chef:
              command: "/opt/chef/bin/chef-client -z -o role[ec2_role] --environment ec2_environment --config /opt/chef-repo/client.rb --log_level info --force-formatter --chef-zero-port 8889"
        update_dns:
          Condition: RunDnsScript
          commands:
            01_update_dns:
              command: "/usr/sbin/update-dns"
        warm_ebs:
          commands:
            01_warm_ebs:
              command: "echo 'ACTION: Performing EBS Warming' && dd if=/dev/xvda of=/dev/xvda conv=notrunc bs=1M status=progress"

Outputs:
  # Ownership
  Owner:
    Description: Team or Individual that Owns this Formation.
    Value: !Ref Owner
  DeleteAfter:
    Description: It is ok to delete the Formation after this date.
    Value: !Ref DeleteAfter

  # Network
  VPC:
    Value: !Ref VPC
  SubnetIdList:
    Value: !Join [',', !Ref SubnetIdList]
  InternalAccessSecurityGroup:
    Value: !Ref InternalAccessSecurityGroup
  RemoteSecurityGroup:
    Value: !Ref RemoteSecurityGroup
  RdsSecurityGroup:
    Value: !Ref RdsSecurityGroup
  EfsSecurityGroup:
    Value: !Ref EfsSecurityGroup
  EfsFileSystemId:
    Value: !Ref EfsFileSystemId

  # Instance
  KeyPairName:
    Description: Key Pair Name Used.
    Value: !Ref KeyPairName
  InstanceType:
    Description: Instance Type Used.
    Value: !Ref InstanceType

  # Autoscaling
  MinAutoScaleCount:
    Value: !Ref MinAutoScaleCount
  MaxAutoScaleCount:
    Value: !Ref MaxAutoScaleCount
  AlarmSnsTopicArn:
    Value: !Ref AlarmSnsTopicArn

  # Deployment
  CookbookName:
    Value: !Ref CookbookName
  CookbookVersion:
    Value: !Ref CookbookVersion
  ChefClientVersion:
    Value: !Ref ChefClientVersion
  DataBagItem:
    Value: !Ref DataBagItem

  # MediaWiki
  MediawikiMajorRelease:
    Value: !Ref MediawikiMajorRelease
  MediawikiMinorRelease:
    Value: !Ref MediawikiMinorRelease
  SiteFolderName:
    Value: !Ref SiteFolderName
  UploadFolderName:
    Value: !Ref UploadFolderName

  # Nginx
  RootSitePath:
    Value: !Ref RootSitePath
  NginxXforwardedTraffic:
    Value: !Ref NginxXforwardedTraffic
  NginxRewriteWikiAlias:
    Value: !Ref NginxRewriteWikiAlias

  # DNS
  UpdateDNS:
    Value: !Ref UpdateDNS
  HostedZoneId:
    Description: Hosted Zone ID.
    Value: !Ref HostedZoneId
  RecordName:
    Description: Record Name.
    Value: !Ref RecordName

  # Template Generated
#  InstanceURL:
#    Value: !Join [ "", [ "http://", !GetAtt LaunchConfiguration.PrivateDnsName ] ]
#  DnsURL:
#    Value: !Join [ "", [ "http://", !Ref RecordName ] ]
  SecurityGroup:
    Value: !Ref SecurityGroup
  AMI:
    Description: AMI Used.
    Value: !FindInMap [ RegionMap, !Ref "AWS::Region", EBS ]
  IAMRole:
    Description: IAM Instance Profile Role Created.
    Value: !Ref IamRole
