# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. 
# A copy of the License is located at
#    http://aws.amazon.com/apache2.0/
# or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 
# either express or implied. See the License for the specific language governing permissions and limitations under the License.

AWSTemplateFormatVersion: 2010-09-09

Description: Stack to deploy a highly available, elastic, scalable REDCap environment. This master stack launches multiple nested stacks for different tiers.



Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
    - Label:
        default: General AWS
      Parameters:
        - EC2KeyName
        - AccessCidr
    - Label:
          default: DNS and SSL
      Parameters:
        - EBEndpoint
        - UseRoute53Boolean
        - UseACMBoolean
        - HostedZoneIda
        - HostedZoneName
        - DomainName
    - Label:
          default: 'Email config - using AWS Simple Email Service (SES)'
      Parameters:
        - SESUsername
        - SESPassword
        - SESRegion
    - Label:
        default: Database Tier
      Parameters:
        - MultiAZDatabase
        - DatabaseInstanceType
        - DatabaseMasterPassword
    - Label:
        default: Web Tier
      Parameters:
        - WebInstanceType
        - WebAsgMax
        - WebAsgMin
        - PHPVersion
    - Label:
        default: 'REDCap Application Source'
      Parameters:
        - S3orAPI
        - RedcapUname
        - RedcapPword
        - Redcapver        
        - RedcapS3Bucket
        - RedcapS3Key
        - RedcapS3BucketRegion

    - Label:
        default: VPC Networking (changing this is optional)
      Parameters:
        - VPCcidr
        - p1cidr
        - p2cidr
        - a1cidr
        - a2cidr
        - d1cidr
        - d2cidr
    
    ParameterLabels:
      EC2KeyName:
        default: EC2 Key Pair
      AccessCidr:
        default: Limit access to IP address range?

      EBEndpoint:
        default: Elastic Beanstalk Endpoint Name
      UseRoute53Boolean:
        default: Use Route 53?
      UseACMBoolean:
        default: Use AWS Certificate Manager?
      HostedZoneIda:
        default: Route53 Hosted Zone ID
      HostedZoneName:
        default: Hosted Zone
      DomainName:
        default: Site Domain

      SESUsername:
        default: SES Username
      SESPassword:
        default: SES Password
      SESRegion:
        default: SES Region

      MultiAZDatabase:
        default: Use Primary and Standby Database Instances?
      DatabaseInstanceType:
        default: DB Instance Class
      DatabaseMasterPassword:
        default: 'DB Master Password & initial password for REDCap user "redcap_admin"'


      WebAsgMax:
        default: Maximum REDCap Instances
      WebAsgMin:
        default: Minimum REDCap Instances
      WebInstanceType:
        default: Web Tier Instance Type
      PHPVersion:
        default: PHP Version

      S3orAPI:
        default: Download REDCap automatically?
      RedcapUname:
        default: REDCap Community Username
      RedcapPword:
        default: REDCap Community Password
      Redcapver:
        default: REDCap version      
      RedcapS3Bucket:
        default: S3 Bucket
      RedcapS3Key:
        default: S3 Key
      RedcapS3BucketRegion:
        default: S3 Bucket Region

      VPCcidr:
        default: VPC CIDR Range
      p1cidr:
        default: Public Subnet A CIDR Range
      p2cidr:
        default: Public Subnet B CIDR Range
      a1cidr:
        default: Application Subnet A CIDR Range
      a2cidr:
        default: Application Subnet B CIDR Range
      d1cidr:
        default: Database Subnet A CIDR Range
      d2cidr:
        default: Database Subnet B CIDR Range



Parameters:
  EC2KeyName:
    ConstraintDescription: Must be letters (upper or lower), numbers, and special characters.
    Description: '[ REQUIRED ] Name of an EC2 KeyPair. Your bastion & Web instances will launch with this KeyPair. To create a KeyPair, see https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-key-pairs.html#having-ec2-create-your-key-pair'
    Type: AWS::EC2::KeyPair::KeyName
  AccessCidr:
    AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$
    Description: 'The CIDR IP range that is permitted to access your REDCap servers. Note: A value of 0.0.0.0/0 will allow access from ANY IP address.'
    Type: String
    Default: 0.0.0.0/0


  EBEndpoint:
    AllowedPattern: ^([a-zA-Z0-9-])*$
    Description: '[ REQUIRED ] The unique name to use for your Elastic Beanstalk URL (will be rendered http://(EBEndpoint).(region).elasticbeanstalk.com).  You can use the "nslookup" to see if an endpoint is in use.'
    ConstraintDescription: 'This name must be between 4 and 40 characters and contain only letters, numbers, and/or a hyphen'
    MaxLength: 40
    MinLength: 4    
    Type: String
  UseRoute53Boolean:
    AllowedValues:
      - true
      - false
    Default: false
    Description: Specifies whether a record set should be created in Route 53 for your REDCap domain name.  If not, you will recieve a default Elastic Beanstalk DNS name (e.g. redcap.us-east-1.elasticbeanstalk.com).
    Type: String
  UseACMBoolean:
    AllowedValues:
      - true
      - false
    Default: false
    Description: '[ Requires Route53 ] Specifies whether an SSL certificate should be generated for your domain name using AWS Certificate Manager (ACM).  If one is not generated, HTTP will be used and an SSL certificate can be applied after deployment.'
    Type: String
  HostedZoneIda:
    Description: '[ Optional, only if using Route53 ] The Route 53 hosted zone ID to create the domain in (e.g. Z2FDTNDATAQYW2).'
    Type: String
  HostedZoneName:
    AllowedPattern: ^$|(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$
    Description: '[ Optional, only if using Route53 ] The Route 53 hosted zone name to create the domain in (e.g. example.edu).'
    Type: String
  DomainName:
    AllowedPattern: ^([a-zA-Z0-9-])*$
    Description: '[ Optional, only if using Route53 ] The sub-domain name of the REDCap website.  This sub-domain will be prepended your specified Hosted Zone (e.g. redcap in redcap.example.edu).'
    Type: String



  SESUsername:
    Type: String
    Description: '[ Required ] Username to use for authentication to SES.'
  SESPassword:
    Type: String
    Description: '[ Required ] Password to use for authentication to SES.'
    NoEcho: true
  SESRegion:
    Type: String
    AllowedValues:
      - us-east-2
      - us-east-1
      - us-west-1
      - us-west-2
      - af-south-1
      - ap-south-1
      - ap-northeast-1
      - ap-northeast-2
      - ap-northeast-3
      - ap-southeast-1
      - ap-southeast-2
      - ca-central-1
      - eu-central-1
      - eu-west-1
      - eu-west-2
      - eu-west-3
      - eu-north-1
      - sa-east-1
      - us-gov-west-1 
    Description: Region where you configured your SES credentials.


  MultiAZDatabase:
    AllowedValues:
      - true
      - false
    Default: false
    Description: Specifies whether to deploy the AWS Aurora MySQL Database in a Multi-AZ configuration.
    Type: String
  DatabaseInstanceType:
    AllowedValues:
      - db.t3.small
      - db.t3.medium
      - db.t3.large
      - db.r5.large
      - db.r5.xlarge
      - db.r5.2xlarge
      - db.r5.4xlarge
      - db.r5.8xlarge
      - db.r5.16xlarge
      - db.r5.24xlarge
    ConstraintDescription: Must be a valid RDS instance class.
    Default: db.t3.small
    Description: The Amazon RDS database instance class (determines processing power and memory capacity of the database).
    Type: String
  DatabaseMasterPassword:
    AllowedPattern: ^([a-zA-Z0-9~#%^*_+,-])*$
    ConstraintDescription: Must have a length of 8-41 and be letters (upper or lower), numbers, and/or these special characters ~#%^*_+,-
    Description: '[ Required ] The Amazon RDS master password (this will also be used as the initial temp password for the REDCap user "redcap_admin"). Letters, numbers, and/or these special characters ~#%^*_+,-'
    MaxLength: 41
    MinLength: 8
    NoEcho: true
    Type: String


  WebInstanceType:
    AllowedValues:
      - t3.nano
      - t3.micro
      - t3.small
      - t3.medium
      - t3.large
      - t3.xlarge
      - t3.2xlarge
      - m5.large 
      - m5.xlarge 
      - m5.2xlarge 
      - m5.4xlarge 
      - m5.12xlarge 
      - m5.24xlarge 
      - c5.large 
      - c5.xlarge 
      - c5.2xlarge 
      - c5.4xlarge 
      - c5.9xlarge 
      - c5.18xlarge
      - r5.large
      - r5.xlarge
      - r5.2xlarge
      - r5.4xlarge
      - r5.8xlarge
      - r5.16xlarge
      - r5.24xlarge
    ConstraintDescription: Must be a valid Amazon EC2 instance type.
    Default: t3.micro
    Description: The Amazon EC2 instance type for your web instances.
    Type: String
  WebAsgMax:
    AllowedPattern: ^((?!0$)[1-2]?[0-9]|30)$
    ConstraintDescription: Must be a number between 1 and 30.
    Default: 2
    Description: Specifies the maximum number of EC2 instances in the Web Autoscaling Group.  Must be greater than or equal to the Minimum REDCap Instances.
    Type: String
  WebAsgMin:
    AllowedPattern: ^([0-0]?[0-9]|10)$
    ConstraintDescription: Must be a number between 0 and 10.
    Default: 2
    Description: Specifies the minimum number of EC2 instances in the Web Autoscaling Group.  A value of >1 will a highly available environment by placing instances in multiple availability zones.
    Type: String
  
  PHPVersion:
    AllowedValues:
      - 7.2
      - 7.3
      - 7.4
      - 8.0
      - 8.1
    Default: 8.1
    Description: The version of PHP to use with REDCap.  PHP 8.1 is recommended.
    Type: String


  S3orAPI:
    Description: "Download REDCap automatically or provide it in S3?"
    Type: String
    AllowedValues:
      - "Provide in S3"
      - "Download using REDCap API"
    Default: "Download using REDCap API"
  RedcapS3Bucket:
    Description: "The name of the S3 bucket that contains your REDCap source zip file (e.g. myredcapsourcefiles) (used if 'Provide in S3' is selected)"
    Type: String
  RedcapS3Key:
    Description: "The S3 key name - the file name of the REDCap source file (e.g. redcap8.6.0.zip) located inside the bucket provided above (used if 'Provide in S3' is selected)"
    Type: String
  RedcapS3BucketRegion:
    Description: "AWS Region of the S3 bucket that contains your REDCap source zip file (used if 'Provide in S3' is selected)"
    Type: String
    AllowedValues:
      - us-east-2
      - us-east-1
      - us-west-1
      - us-west-2
      - af-south-1
      - ap-south-1
      - ap-northeast-1
      - ap-northeast-2
      - ap-northeast-3
      - ap-southeast-1
      - ap-southeast-2
      - ca-central-1
      - eu-central-1
      - eu-west-1
      - eu-west-2
      - eu-west-3
      - eu-north-1
      - sa-east-1
      - us-gov-west-1
    Default: us-east-1
  RedcapUname:
    Description: Your REDCap Community Username (used if 'Download using REDCap API' is selected)
    Type: String
  RedcapPword:
    Description: Your REDCap Community Password (used if 'Download using REDCap API' is selected)
    Type: String
    NoEcho: true
  Redcapver:
    Description: The version of REDCap you want to install, 'latest' for latest version (used if 'Download using REDCap API' is selected)
    Type: String
    Default: latest

  VPCcidr:
    Description: (optional to change) CIDR IP Range for your REDCap VPC.
    AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$
    Type: String
    Default: 10.1.0.0/16
  p1cidr:
    Description: (optional to change) CIDR IP Range for the public subnet in AZ 'a' of your REDCap VPC.
    AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$
    Type: String
    Default: 10.1.0.0/24
  p2cidr:
    Description: (optional to change) CIDR IP Range for the public subnet in AZ 'b' of your REDCap VPC.
    AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$
    Type: String
    Default: 10.1.1.0/24
  a1cidr:
    Description: (optional to change) CIDR IP Range for the application subnet in AZ 'a' of your REDCap VPC.
    AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$
    Type: String
    Default: 10.1.2.0/24
  a2cidr:
    Description: (optional to change) CIDR IP Range for the application subnet in AZ 'b' of your REDCap VPC.
    AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$
    Type: String
    Default: 10.1.3.0/24
  d1cidr:
    Description: (optional to change) CIDR IP Range for the database subnet in AZ 'b' of your REDCap VPC.
    AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$
    Type: String
    Default: 10.1.4.0/24
  d2cidr:
    Description: (optional to change) CIDR IP Range for the database subnet in AZ 'b' of your REDCap VPC.
    AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$
    Type: String
    Default: 10.1.5.0/24



Conditions:
  DeployRoute53: 
    !Equals [ true, !Ref UseRoute53Boolean ]
  DeployACM: !And 
    - !Equals [ true, !Ref UseACMBoolean ]
    - !Condition DeployRoute53



Resources:
  certificate:
    Condition: DeployACM
    Type: AWS::CertificateManager::Certificate
    Properties: 
      DomainName: !Join ['', [!Ref DomainName, '.', !Ref HostedZoneName]]
      DomainValidationOptions: 
        - HostedZoneId: !Ref HostedZoneIda
          DomainName: !Join ['', [!Ref DomainName, '.', !Ref HostedZoneName]]
      ValidationMethod: DNS
  
  VPCStack:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: https://s3.amazonaws.com/redcap-aws-cloudformation/01-rc-vpc.yaml
      Parameters:
        AccessCidr: !Ref 'AccessCidr'
        RedcapS3Bucket: !Ref 'RedcapS3Bucket'
        UseACMBoolean: !Ref 'UseACMBoolean'
        UseRoute53Boolean: !Ref 'UseRoute53Boolean'
        VPCcidr: !Ref VPCcidr
        p1cidr: !Ref p1cidr
        p2cidr: !Ref p2cidr
        a1cidr: !Ref a1cidr
        a2cidr: !Ref a2cidr
        d1cidr: !Ref d1cidr
        d2cidr: !Ref d2cidr
  redcapelasticbeanstalk:
    DependsOn: [ VPCStack ]
    Type: AWS::CloudFormation::Stack
    Properties:
      Parameters:
        EBBucket: !Join [ "", [ !Ref "AWS::AccountId", '-', !Select [ 1, !Split [ '-', !GetAtt 'VPCStack.Outputs.SubnetPublicA'] ], '-redcapebapp' ] ]
        SESUsername:
          !Ref SESUsername
        SESPassword:
          !Ref SESPassword
        SESRegion:
          !Ref SESRegion
        DatabaseMasterPassword:
          !Ref DatabaseMasterPassword
        DatabaseInstanceType:
          !Ref DatabaseInstanceType
        MultiAZDatabase:
          !Ref MultiAZDatabase
        SslCertificate:
          !If [ DeployACM, !Ref certificate, '' ]
        WebAsgMax:
          !Ref WebAsgMax
        WebAsgMin:
          !Ref WebAsgMin
        WebInstanceType:
          !Ref WebInstanceType
        PHPVersion:
          !Ref PHPVersion
        S3AccessKey:
          !GetAtt 'VPCStack.Outputs.S3AccessKey'
        S3SecretKey:
          !GetAtt 'VPCStack.Outputs.S3SecretKey'
        S3FileRepositoryBucket: !Join [ "", [ !Ref "AWS::AccountId", '-', !Select [ 1, !Split [ '-', !GetAtt 'VPCStack.Outputs.SubnetPublicA'] ], '-redcaprepository' ] ]
        EBEndpoint:
          !Ref EBEndpoint
        UseRoute53Boolean:
          !Ref UseRoute53Boolean
        UseACMBoolean:
          !Ref UseACMBoolean
        HostedZoneName:
          !If [ DeployRoute53, !Ref HostedZoneName, '' ]
        DomainName:
          !If [ DeployRoute53, !Ref DomainName, '' ]
        S3orAPI:
          !Ref S3orAPI
        RedcapS3Bucket:
          !Ref RedcapS3Bucket
        RedcapS3Key:
          !Ref RedcapS3Key
        RedcapS3BucketRegion:
          !Ref RedcapS3BucketRegion
        RedcapUname:
          !Ref RedcapUname
        RedcapPword:
          !Ref RedcapPword
        Redcapver:
          !Ref Redcapver
        EC2KeyName:
          !Ref EC2KeyName
        VPCId:
          !GetAtt 'VPCStack.Outputs.VPCId'
        SubnetPublicA:
          !GetAtt 'VPCStack.Outputs.SubnetPublicA'
        SubnetPublicB:
          !GetAtt 'VPCStack.Outputs.SubnetPublicB'
        SubnetAppA:
          !GetAtt 'VPCStack.Outputs.SubnetAppA'
        SubnetAppB:
          !GetAtt 'VPCStack.Outputs.SubnetAppB'
        SubnetDataA:
          !GetAtt 'VPCStack.Outputs.SubnetDataA'
        SubnetDataB:
          !GetAtt 'VPCStack.Outputs.SubnetDataB'
        SGPublic:
          !GetAtt 'VPCStack.Outputs.SGPublic'
        SGApp:
          !GetAtt 'VPCStack.Outputs.SGApp'
        SGData:
          !GetAtt 'VPCStack.Outputs.SGData'
        EBServiceRole:
          !GetAtt 'VPCStack.Outputs.EBServiceRole'      
        EBInstanceProfile:
          !GetAtt 'VPCStack.Outputs.EBInstanceProfile'
        TempEC2InstanceProfile:
          !GetAtt 'VPCStack.Outputs.TempEC2InstanceProfile'
      TemplateURL: https://s3.amazonaws.com/redcap-aws-cloudformation/02-rc-elasticbeanstalk.yaml
  route53:
    Condition: DeployRoute53
    DependsOn: redcapelasticbeanstalk
    Type: AWS::CloudFormation::Stack
    Properties:
      Parameters:
        DnsEndpoint:
          !Join ['', [!Ref EBEndpoint, '.', !Ref 'AWS::Region', '.elasticbeanstalk.com']]
        HostedZoneId:
          !Ref HostedZoneIda
        HostedZoneName:
          !Ref HostedZoneName
        DomainName:
          !Ref DomainName
      TemplateURL: https://s3.amazonaws.com/redcap-aws-cloudformation/03-rc-route53.yaml

Outputs:
  REDCapURL:
    Value: !If [DeployRoute53, !If [DeployACM, !Join ['', ['https://', !Ref DomainName, '.', !Ref HostedZoneName, '/']], !Join ['', ['http://', !Ref DomainName, '.', !Ref HostedZoneName, '/']]], !Join ['', ['http://', !Ref EBEndpoint, '.', !Ref 'AWS::Region', '.elasticbeanstalk.com']]]