---
AWSTemplateFormatVersion: '2010-09-09'
Description:  Elastic Beanstalk

# App stack creation prerequisites:  first create a VPC stack, then a DB stack.

Parameters:

  ApplicationName:
    Description: Name of your application
    Type: String
    MinLength: 1
    MaxLength: 255
    AllowedPattern: "^[a-zA-Z][-a-zA-Z0-9]*$"

  StackType:
    Description: node, rails, python, python3 or spring
    Type: String
    MinLength: 1
    MaxLength: 255
    AllowedValues:
      - node
      - rails
      - spring
      - python
      - python3
    ConstraintDescription: Specify node, rails, python, python3 or spring

  EnvironmentName:
    Description: Environment name, either dev or prod.
    Type: String
    MinLength: 1
    MaxLength: 255
    AllowedValues:
      - dev
      - prod
    ConstraintDescription: Specify either dev or prod

  NetworkStackName:
    Description: Name of an active CloudFormation stack of networking resources
    Type: String
    MinLength: 1
    MaxLength: 255
    AllowedPattern: "^[a-zA-Z][-a-zA-Z0-9]*$"

  DatabaseStackName:
    Description: Name of an active CloudFormation stack of database resources
    Type: String
    MinLength: 1
    MaxLength: 255
    AllowedPattern: "^[a-zA-Z][-a-zA-Z0-9]*$"

  DatabaseName:
    Description: Database name (schema).
    Type: String
    MinLength: 1
    MaxLength: 255
    AllowedPattern: "^[a-zA-Z][-a-zA-Z0-9]*$"

  DatabasePassword:
    NoEcho: true
    Type: String
    Description: Database admin account password
    MinLength: 6
    MaxLength: 41
    AllowedPattern: "[a-zA-Z0-9]*"
    ConstraintDescription: Password must contain only alphanumeric characters

  AppS3Bucket:
    Description: S3 Bucket containing your application package.
    Type: String
    MinLength: 1
    MaxLength: 255

  AppS3Key:
    Description: S3 Bucket key for your application package
    Type: String
    MinLength: 1
    MaxLength: 255

  EC2KeyPairName:
    Description: EC2 key pair name for SSH access
    Type: AWS::EC2::KeyPair::KeyName

  DevInstanceType:
    Description: The instance type for the dev environment
    Type: String
    MinLength: 1
    MaxLength: 255
    Default: t2.micro

  ProdInstanceType:
    Description: The instance type for the prod environment
    Type: String
    MinLength: 1
    MaxLength: 255
    Default: t2.large

  SSLCertificateArn:
    Description: The SSL/TLS certificate ARN
    Type: String
    MinLength: 0
    MaxLength: 2048
    Default: ""

  AutoScalingMinInstanceCount:
    Description: Minimum number of EC2 instances for Auto Scaling
    Type: Number
    MinValue: 1
    MaxValue: 20
    Default: 2
    ConstraintDescription: Specify a number between 1 - 20

  AutoScalingMaxInstanceCount:
    Description: Maximum number of EC2 instances for Auto Scaling
    Type: Number
    MinValue: 1
    MaxValue: 20
    Default: 6
    ConstraintDescription: Specify a number between 1 - 20

Conditions:

  CreateProdEnv: !Equals [ !Ref EnvironmentName, prod ]

  TlsEnabled: !Not [ !Equals [ !Ref SSLCertificateArn, "" ] ]

Mappings:
  # Maps stack type parameter to solution stack name string
  StackMap:
    node:
      stackName: 64bit Amazon Linux 2018.03 v4.5.3 running Node.js
    rails:
      stackName: 64bit Amazon Linux 2018.03 v2.8.3 running Ruby 2.4 (Puma)
    spring:
      stackName: 64bit Amazon Linux 2018.03 v3.0.3 running Tomcat 8 Java 8
    python:
      stackName: 64bit Amazon Linux 2018.03 v2.7.3 running Python 2.7
    python3:
      stackName: 64bit Amazon Linux 2018.03 v2.7.3 running Python 3.6

Resources:

  ElasticBeanstalkServiceRole:
    Type: AWS::IAM::Role
    Properties:
      Path: /
      AssumeRolePolicyDocument: |
        {
          "Statement": [{
            "Effect": "Allow",
            "Principal": { "Service": [ "elasticbeanstalk.amazonaws.com" ]},
            "Action": [ "sts:AssumeRole" ]
          }]
        }
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSElasticBeanstalkEnhancedHealth
        - arn:aws:iam::aws:policy/service-role/AWSElasticBeanstalkService

  Application:
    Type: AWS::ElasticBeanstalk::Application
    Properties:
      ApplicationName: !Ref ApplicationName

  ApplicationVersion:
    Type: AWS::ElasticBeanstalk::ApplicationVersion
    Properties:
      ApplicationName: !Ref Application
      SourceBundle:
        S3Bucket: !Ref AppS3Bucket
        S3Key: !Ref AppS3Key

  Environment:
    Type: AWS::ElasticBeanstalk::Environment
    Properties:
      EnvironmentName: !Sub "${ApplicationName}-${EnvironmentName}"
      ApplicationName: !Ref Application
      TemplateName: !Ref ConfigurationTemplate
      VersionLabel: !Ref ApplicationVersion
    DependsOn:
      - ConfigurationTemplate
      - ApplicationVersion

  # The configuration template contains environment parameters such as those
  # that relate to the autoscaling group (e.g. size, triggers), placement of
  # resources in the VPC, load balancer setup, and environment variables
  ConfigurationTemplate:
    Type: AWS::ElasticBeanstalk::ConfigurationTemplate
    Properties:
      ApplicationName: !Ref Application
      SolutionStackName: !FindInMap [ StackMap, !Ref StackType, stackName ]
      OptionSettings:

        - Namespace: aws:elasticbeanstalk:environment
          OptionName: EnvironmentType
          Value: LoadBalanced

        - Namespace: aws:elasticbeanstalk:environment
          OptionName: LoadBalancerType
          Value: application

        - Namespace: aws:elasticbeanstalk:environment
          OptionName: ServiceRole
          Value: !Ref ElasticBeanstalkServiceRole

          # AUTOSCALING OPTIONS
        - Namespace: aws:autoscaling:asg
          OptionName: MinSize
          Value: !Ref AutoScalingMinInstanceCount

        - Namespace: aws:autoscaling:asg
          OptionName: MaxSize
          Value: !Ref AutoScalingMaxInstanceCount

        - Namespace: aws:autoscaling:launchconfiguration
          OptionName: SecurityGroups
          Value:
            Fn::ImportValue: !Sub "${NetworkStackName}-AppSecurityGroupID"

        - Namespace: aws:autoscaling:launchconfiguration
          OptionName: SSHSourceRestriction
          Value:
            "Fn::Join":
              - ','
              - - 'tcp, 22, 22'
                - !ImportValue
                  "Fn::Sub": "${NetworkStackName}-BastionGroupID"

        - Namespace: aws:autoscaling:launchconfiguration
          OptionName: InstanceType
          Value: !If [ CreateProdEnv, !Ref ProdInstanceType, !Ref DevInstanceType ]

        - Namespace: aws:autoscaling:launchconfiguration
          OptionName: IamInstanceProfile
          Value: !Ref AppInstanceProfile

        - Namespace: aws:autoscaling:launchconfiguration
          OptionName: EC2KeyName
          Value: !Ref EC2KeyPairName

        - Namespace: aws:autoscaling:updatepolicy:rollingupdate
          OptionName: RollingUpdateEnabled
          Value: true

        - Namespace: aws:autoscaling:updatepolicy:rollingupdate
          OptionName: RollingUpdateType
          Value: Health

        - Namespace: aws:autoscaling:trigger
          OptionName: MeasureName
          Value: CPUUtilization

        - Namespace: aws:autoscaling:trigger
          OptionName: Unit
          Value: Percent

        - Namespace: aws:autoscaling:trigger
          OptionName: UpperThreshold
          Value: 80

        - Namespace: aws:autoscaling:trigger
          OptionName: LowerThreshold
          Value: 40

          # VPC OPTIONS (PLACEMENT OF RESOURCES IN SUBNETS)
        - Namespace: aws:ec2:vpc
          OptionName: VPCId
          Value:
            Fn::ImportValue: !Sub "${NetworkStackName}-VpcID"

        - Namespace: aws:ec2:vpc
          OptionName: Subnets
          Value:
            "Fn::Join":
              - ','
              - - !ImportValue
                  "Fn::Sub": "${NetworkStackName}-PrivateSubnet1ID"
                - !ImportValue
                  "Fn::Sub": "${NetworkStackName}-PrivateSubnet2ID"

        - Namespace: aws:ec2:vpc
          OptionName: ELBSubnets
          Value:
            "Fn::Join":
              - ','
              - - !ImportValue
                  "Fn::Sub": "${NetworkStackName}-PublicSubnet1ID"
                - !ImportValue
                  "Fn::Sub": "${NetworkStackName}-PublicSubnet2ID"

        - Namespace: aws:elbv2:listener:default
          OptionName: ListenerEnabled
          Value: !If [ TlsEnabled, false, true ]

        - Namespace: aws:elbv2:loadbalancer
          OptionName: SecurityGroups
          Value:
            Fn::ImportValue: !Sub "${NetworkStackName}-ELBSecurityGroupID"

        - Namespace: aws:elbv2:loadbalancer
          OptionName: ManagedSecurityGroup
          Value:
            Fn::ImportValue: !Sub "${NetworkStackName}-ELBSecurityGroupID"

        - Namespace: aws:elbv2:listenerrule:default
          OptionName: PathPatterns
          Value: "/*"

        - Namespace: !Sub
            - "aws:elbv2:listener:${ListenPort}"
            - ListenPort:
                "Fn::ImportValue": !Sub "${NetworkStackName}-ELBIngressPort"
          OptionName: Protocol
          Value: !If [ TlsEnabled, HTTPS, HTTP ]

        - Namespace: !Sub
            - "aws:elbv2:listener:${ListenPort}"
            - ListenPort:
                "Fn::ImportValue": !Sub "${NetworkStackName}-ELBIngressPort"
          OptionName: Rules
          Value: default

        - Namespace: !Sub
            - "aws:elbv2:listener:${ListenPort}"
            - ListenPort:
                "Fn::ImportValue": !Sub "${NetworkStackName}-ELBIngressPort"
          OptionName: SSLCertificateArns
          Value: !Ref SSLCertificateArn

          # CLOUDWATCH LOGS
        - Namespace: aws:elasticbeanstalk:cloudwatch:logs
          OptionName: StreamLogs
          Value: true

        - Namespace: aws:elasticbeanstalk:cloudwatch:logs
          OptionName: DeleteOnTerminate
          Value: true

        # ENVIRONMENT VARIABLES - COMMON TO ALL STACKS
        - Namespace: aws:elasticbeanstalk:application:environment
          OptionName: AWS_REGION
          Value: !Ref AWS::Region

          # ENVIRONMENT VARIABLES - NODE, RAILS - Move to parameter store
        - Namespace: aws:elasticbeanstalk:application:environment
          OptionName: DB_PASSWORD
          Value: !Ref DatabasePassword

        - Namespace: aws:elasticbeanstalk:application:environment
          OptionName: DB_USER
          Value:
            Fn::ImportValue: !Sub ${DatabaseStackName}-DatabaseUser

        - Namespace: aws:elasticbeanstalk:application:environment
          OptionName: DB_NAME
          Value: !Ref DatabaseName

        - Namespace: aws:elasticbeanstalk:application:environment
          OptionName: DB_HOST
          Value:
            Fn::ImportValue: !Sub "${DatabaseStackName}-DatabaseURL"

        # ENVIRONMENT VARIABLES - SPRING
        - Namespace: aws:elasticbeanstalk:application:environment
          OptionName: spring.datasource.password
          Value: !Ref DatabasePassword

        - Namespace: aws:elasticbeanstalk:application:environment
          OptionName: spring.datasource.username
          Value:
            Fn::ImportValue: !Sub "${DatabaseStackName}-DatabaseUser"

        - Namespace: aws:elasticbeanstalk:application:environment
          OptionName: spring.datasource.url
          Value:
            "Fn::Join":
              - ''
              - - 'jdbc:mysql://'
                - !ImportValue
                  "Fn::Sub": "${DatabaseStackName}-DatabaseURL"
                - ':3306/'
                - Ref: DatabaseName

  # IAM resources
  AppRole:
    Type: AWS::IAM::Role
    Properties:
      Path: /
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          -
            Effect: Allow
            Principal:
              Service:
                - ec2.amazonaws.com
            Action:
              - sts:AssumeRole

  AppPolicies:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: App
      Roles:
        - !Ref AppRole
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          -
            Effect: Allow
            Action: "*"
            Resource: "*"

  AppInstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Path: /
      Roles:
        - !Ref AppRole

Outputs:

  Name:
    Description: Elastic Beanstalk Stack Name
    Value: !Ref AWS::StackName
    Export:
      Name: !Sub ${AWS::StackName}-Name

  EnvironmentURL:
    Description: Environment URL
    Value: !GetAtt Environment.EndpointURL
    Export:
      Name: !Sub "${AWS::StackName}-EnvironmentURL"

  EnvironmentName:
    Description: Environment Name
    Value: !Sub "${ApplicationName}-${EnvironmentName}"
    Export:
      Name: !Sub "${AWS::StackName}-EnvironmentName"

  TypeOfStack:
    Description: Stack type
    Value: !Ref StackType
    Export:
      Name: !Sub "${AWS::StackName}-TypeOfStack"