svleeuwen
7/17/2017 - 9:38 AM

AWS Elastic Beanstalk Django template

AWS Elastic Beanstalk Django template

container_commands:
  01_mergedb:
    command: "python manage.py makemigrations --no-input --merge"
  02_syncdb:
    command: "python manage.py migrate --no-input"
  03_create_super_user:
    command: "python manage.py create_default_super_user"
  04_migrate_all:
    command: "python manage.py collectstatic --no-input"

option_settings:
  aws:elasticbeanstalk:container:python:staticfiles:
    "/static/": "staticfiles/"
  aws:elasticbeanstalk:container:python:
    WSGIPath: config/wsgi.py
AWSTemplateFormatVersion: '2010-09-09'
Description: "A Beanstalk web stack for hosting a Django application"

Parameters:
  AppEnvironment:
    Description: The business environment
    Default: "production"
    Type: String
    AllowedValues:
      - "production"
      - "staging"
  AppName:
    Description: "Optional ElasticBeanstalk application name (i.e. EB Physical ID). Leave blank to create a new AWS::ElasticBeanstalk::Application, or else enter an existing app's name."
    Type: String
    Default: ""

  # TODO: move connection information out of (leaky) environment and into S3 connection string file

  EnvDbDjangoHostname:
    Description: RDS database host URL
    Type: String
    Default: cms-db.mytomorrows.com
  EnvDbDjangoPort:
    Description: RDS port
    Type: String
    Default: "5432"
  EnvDbDjangoName:
    Description: RDS schema name
    Type: String
  EnvDbDjangoUsername:
    Description: RDS app user name
    Type: String
    MaxLength: '32'
    MinLength: '8'
    Default: oltproot
  EnvDbDjangoPassword:
    Description: RDS app user password
    Type: String
    MaxLength: '256'
    MinLength: '8'
    NoEcho: 'true'
  EnvDjangoDebug:
    Description: Django Debug flag
    Type: String
    Default: 'False'
  EnvDjangoSettings:
    Description: Django environment
    Type: String
    Default: 'config.settings.staging'
  EnvDjangoAWSID:
    Description: IAM access ID
    Type: String
  EnvDjangoAWSSecret:
    Description: IAM secret access key
    Type: String
    NoEcho: 'true'
  EnvDjangoS3Bucket:
    Description: S3 bucket address
    Type: String
    Default: 'storage.django.mytomorrows.com'
  EnvDjangoS3BucketJobs:
    Description: S3 bucket address to uploads jobs CV
    Type: String
    Default: 'jobs.upload'
  EnvDjangoCaptchaId:
    Description: Google Captcha ID
    Type: String
  EnvDjangoCaptchaSecret:
    Description: Google Captcha secret
    Type: String

Mappings:
  AppEnvironmentMap:
    production:
      env: "Site-Production"
      domain: "site"
      djangoEnv: "config.settings.production"
    staging:
      env: "Site-Staging"
      domain: "site-staging"
      djangoEnv: "config.settings.staging"

Conditions:
  ShouldCreateNewEBApp: !Equals [!Ref AppName, ""]

Resources:
  DjangoApp:
    Condition: ShouldCreateNewEBApp # only create a new app if an existing app name was not supplied as a parameter
    Type: AWS::ElasticBeanstalk::Application
    Properties:
      ApplicationName: Website
      Description: "myTomorrows new site"

  DjangoAppVersion:
    Type: AWS::ElasticBeanstalk::ApplicationVersion
    Properties:
      ApplicationName: !If [ShouldCreateNewEBApp, !Ref DjangoApp, !Ref AppName]
      Description: "Version 1.0.0"
      SourceBundle:
        # Note: the expected global EB deploy bucket on S3 should be setup in a separate CF template
        S3Bucket: !ImportValue CrowdsourcingS3DeployBucket # TODO: rename and relaunch S3 bucket
        S3Key: website.zip

  DjangoConfiguration:
    Type: AWS::ElasticBeanstalk::ConfigurationTemplate
    Properties:
      ApplicationName: !If [ShouldCreateNewEBApp, !Ref DjangoApp, !Ref AppName]
      Description: "ElasticBeanstalk configuration settings"
      SolutionStackName: "64bit Amazon Linux 2017.03 v2.4.1 running Python 3.4"
      OptionSettings:
        - Namespace: aws:autoscaling:asg
          OptionName: MinSize
          Value: 1
        - Namespace: aws:autoscaling:asg
          OptionName: MaxSize
          Value: 1
        - Namespace: aws:elasticbeanstalk:environment
          OptionName: EnvironmentType
          Value: LoadBalanced # for non-production or to save money, use 'SingleInstance' instead

        # Secrets and dynamic state entered as CF params and stored for the Beanstalk EC2 ENV
        # TODO: get secrets from KMS to not store in plaintext ENV variable
        - Namespace: aws:elasticbeanstalk:application:environment
          OptionName: DATABASE_HOST
          Value: !Ref EnvDbDjangoHostname
        - Namespace: aws:elasticbeanstalk:application:environment
          OptionName: DATABASE_PORT
          Value: !Ref EnvDbDjangoPort
        - Namespace: aws:elasticbeanstalk:application:environment
          OptionName: DATABASE_USER
          Value: !Ref EnvDbDjangoUsername
        - Namespace: aws:elasticbeanstalk:application:environment
          OptionName: DATABASE_PASSWORD
          Value: !Ref EnvDbDjangoPassword
        - Namespace: aws:elasticbeanstalk:application:environment
          OptionName: DATABASE_DBNAME
          Value: !Ref EnvDbDjangoName
        - Namespace: aws:elasticbeanstalk:application:environment
          OptionName: DJANGO_SETTINGS_MODULE
          Value: !Join ["", [!FindInMap [AppEnvironmentMap, !Ref AppEnvironment, "djangoEnv"]]]
        - Namespace: aws:elasticbeanstalk:application:environment
          OptionName: DJANGO_DEBUG
          Value: !Ref EnvDjangoDebug
        - Namespace: aws:elasticbeanstalk:application:environment
          OptionName: DJANGO_AWS_ACCESS_KEY_ID
          Value: !Ref EnvDjangoAWSID
        - Namespace: aws:elasticbeanstalk:application:environment
          OptionName: DJANGO_AWS_SECRET_ACCESS_KEY
          Value: !Ref EnvDjangoAWSSecret
        - Namespace: aws:elasticbeanstalk:application:environment
          OptionName: DJANGO_AWS_STORAGE_BUCKET_NAME
          Value: !Ref EnvDjangoS3Bucket
        - Namespace: aws:elasticbeanstalk:application:environment
          OptionName: DJANGO_AWS_STORAGE_BUCKET_NAME_JOBS
          Value: !Ref EnvDjangoS3BucketJobs
        - Namespace: aws:elasticbeanstalk:application:environment
          OptionName: DJANGO_GOOGLE_CAPTCHA_ID
          Value: !Ref EnvDjangoCaptchaId
        - Namespace: aws:elasticbeanstalk:application:environment
          OptionName: DJANGO_GOOGLE_CAPTCHA_SECRET
          Value: !Ref EnvDjangoCaptchaSecret


        # configure load balancer options for SSL certificate attachment
        - Namespace: aws:elb:listener:443
          OptionName: InstanceProtocol
          Value: HTTP
        - Namespace: aws:elb:listener:443
          OptionName: InstancePort
          Value: 80
        - Namespace: aws:elb:listener:443
          OptionName: ListenerEnabled
          Value: true
        - Namespace: aws:elb:listener:443
          OptionName: ListenerProtocol
          Value: HTTPS
        - Namespace: aws:elb:listener:443
          OptionName: SSLCertificateId
          Value: !Ref DjangoSslCertificate
        - Namespace: aws:elb:listener:80
          OptionName: ListenerEnabled
          Value: false

  DjangoSslCertificate:
    Type: "AWS::CertificateManager::Certificate"
    Properties:
      DomainName: "*.mytomorrows.com"
      Tags:
        - Key: Name
          Value: Django SSL Certificate

  DjangoEnv:
    Type: AWS::ElasticBeanstalk::Environment
    Properties:
      ApplicationName: !If [ShouldCreateNewEBApp, !Ref DjangoApp, !Ref AppName]
      Description:  "AWS Elastic Beanstalk Environment running Python Search API in Django"
      EnvironmentName: !Join ["", [!FindInMap [AppEnvironmentMap, !Ref AppEnvironment, "env"]]]
      TemplateName: !Ref DjangoConfiguration
      VersionLabel: !Ref DjangoAppVersion

  DjangoDNS:
    Type: AWS::Route53::RecordSet
    Properties:
      HostedZoneName: 'mytomorrows.com.'
      Comment: A Record subdomain for EC2 app instance
      Name: !Join [".", [!FindInMap [AppEnvironmentMap, !Ref AppEnvironment, "domain"], "mytomorrows.com"]]
      Type: CNAME
      TTL: '300'
      ResourceRecords:
        - !GetAtt DjangoEnv.EndpointURL

Outputs:
  DjangoDomain:
    Value: !GetAtt DjangoEnv.EndpointURL
  DjangoDNS:
    Value: !Ref DjangoDNS
packages:
  yum:
    libjpeg-turbo-devel: []
    libpng-devel: []
    libffi-devel: []
    openssl-devel: []