static/Reliability/Common/Code/CloudFormation/staticwebapp-active-passive.yaml [271:565]:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TargetGroupARNs:
        - !Ref ALB1TargetGroup
      Tags:
        - Key: Name
          Value: !Sub '${NamingPrefix}'
          PropagateAtLaunch: true
    UpdatePolicy:
      AutoScalingReplacingUpdate:
        WillReplace: 'true'

  Web1LaunchConfig:
    Type: 'AWS::AutoScaling::LaunchConfiguration'
    #https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-as-launchconfig.html
    Properties:
      ImageId: !Ref LatestAmiId
      IamInstanceProfile: !Ref Web1InstanceInstanceProfile
      InstanceType: !Ref InstanceType
      SecurityGroups:
        - !Ref Web1InstanceSecurityGroup
      UserData:
        Fn::Base64: !Sub |
          #!/bin/bash
          yum -y update
          sleep 30 # prevent "Error: Rpmdb changed underneath us"
          yum install python3 python-virtualenv python-pip -y
          python3 -m venv /home/ec2-user/venv
          source /home/ec2-user/venv/bin/activate
          pip install boto3
          pip install ec2_metadata
          wget -O server.py ${ServerCodeUrl}
          python3 server.py -p 80 -r ${AWS::Region}

  WebInstancePolicy:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      Description: 'Policy used by EC2 WebServers'
      ManagedPolicyName: !Sub '${NamingPrefix}-EC2-Policy-${AWS::StackName}'
      Path: /
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action:
              - 'ec2:DescribeInstanceStatus'
            Resource: '*'
            # VPC Flow Logs: https://docs.aws.amazon.com/vpc/latest/userguide/flow-logs-cwl.html
          - Effect: Allow
            Action: 
              - 'logs:CreateLogGroup'
              - 'logs:CreateLogStream'
              - 'logs:PutLogEvents'
              - 'logs:DescribeLogStreams'
            Resource: 'arn:aws:logs:*:*:*'
            # CloudWatch: Required to create metrics" https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/permissions-reference-cw.html
          - Effect: Allow
            Action:
              - 'cloudwatch:PutMetricData'
            Resource: '*'
            # SSM: Our web app code on EC2 reads an SSM Parameter as part of this lab
          - Effect: Allow
            Action: 
              - 'ssm:GetParameter'
            Resource: '*'

  # IAM Role for EC2 servers including access to DynamoDB (DDB)
  Web1InstanceRole:
    Type: 'AWS::IAM::Role'
    DependsOn: WebInstancePolicy
    Properties:
      RoleName: !Sub '${NamingPrefix}-EC2-Role-${AWS::StackName}'
      Path: /
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - ec2.amazonaws.com
            Action:
              - 'sts:AssumeRole'
      ManagedPolicyArns:
        - !Ref WebInstancePolicy
        - 'arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore'
      # In addition to the ManagedPolicy, give it access to DDB
      Policies:
        - PolicyName: ddbReadOnly
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - 'dynamodb:BatchGetItem'
                  - 'dynamodb:Describe*'
                  - 'dynamodb:List*'
                  - 'dynamodb:GetItem'
                  - 'dynamodb:Query'
                  - 'dynamodb:Scan'
                Resource: 'arn:aws:dynamodb:*:*:table/*'

  # IAM Role for EC2 servers WITHOUT access to DynamoDB (DDB)
  Web2InstanceRole:
    Type: 'AWS::IAM::Role'
    DependsOn: WebInstancePolicy
    Properties:
      RoleName: !Sub '${NamingPrefix}-EC2-noDDB-Role-${AWS::StackName}'
      Path: /
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - ec2.amazonaws.com
            Action:
              - 'sts:AssumeRole'
      ManagedPolicyArns:
        - !Ref WebInstancePolicy
        - 'arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore'

  Web1InstanceInstanceProfile:
    Type: 'AWS::IAM::InstanceProfile'
    Properties:
      InstanceProfileName: !Sub '${NamingPrefix}-EC2-Role-${AWS::StackName}'
      Path: /Web1InstanceEc2Role/
      Roles:
        - !Ref Web1InstanceRole

  Web2InstanceInstanceProfile:
    Type: 'AWS::IAM::InstanceProfile'
    Properties:
      InstanceProfileName: !Sub '${NamingPrefix}-EC2-noDDB-Role-${AWS::StackName}'
      Path: /Web2InstanceEc2Role/
      Roles:
        - !Ref Web2InstanceRole

  LoadDynamoDBLambdaRole:
    Type: 'AWS::IAM::Role'
    Properties:
      RoleName: !Sub '${NamingPrefix}-Lambda-${AWS::StackName}'
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - 'sts:AssumeRole'
      Path: /
      Policies:
        - PolicyName: ddbFull
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - 'dynamodb:BatchWriteItem'
                Resource: 'arn:aws:dynamodb:*:*:table/RecommendationService'
        - PolicyName: cloudwatch
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action: 
                  - 'logs:CreateLogGroup'
                  - 'logs:CreateLogStream'
                  - 'logs:PutLogEvents'
                  - 'logs:DescribeLogStreams'
                Resource: 'arn:aws:logs:*:*:*'
              - Effect: Allow
                Action:
                  - 'cloudwatch:PutMetricData'
                Resource: '*'

  LoadDynamoDBLambda:
      Type: 'AWS::Lambda::Function'
      Properties:
        FunctionName: 'LoadDynamoDBLambda'
        Handler: 'index.handler'
        Runtime: 'python3.6'
        Timeout: '30'
        Role: !GetAtt LoadDynamoDBLambdaRole.Arn
        Code:
          ZipFile: !Sub |
            import boto3
            import urllib.request
            import cfnresponse
            import logging
            import signal
            import json
            import traceback

            LOGGER = logging.getLogger()
            LOGGER.setLevel(logging.INFO)

            def populate_ddb_table():
                # Get the service resource.
                LOGGER.info('create client')
                client = boto3.client('dynamodb', '${AWS::Region}')
                
                # @TODO, make bucket/object configurable
                LOGGER.info('open file')
                file = urllib.request.urlopen("https://aws-well-architected-labs-ohio.s3.us-east-2.amazonaws.com/Healthcheck/Data/RecommendationService.json")
                LOGGER.info('read file')
                text = file.read()
                LOGGER.info(text)
                LOGGER.info('convert to JSON')
                request_items = json.loads(text)
                LOGGER.info('write to DDB')
                response = client.batch_write_item(RequestItems=request_items)
                LOGGER.info('DONE')
                
                # @TODO add error handling 
                
                # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/dynamodb.html#DynamoDB.Client.batch_write_item


            def handler(event, context):
                signal.alarm(int((context.get_remaining_time_in_millis() / 1000) - 1))
                # Setup alarm for remaining runtime minus a second
                try:
                    LOGGER.info('EVENT RECEIVED: %s', event)
                    LOGGER.info('CONTEXT RECEIVED: %s', context)
                    if event['RequestType'] == 'Create':
                        LOGGER.info('CREATE')
                        populate_ddb_table()
                        cfnresponse.send(event, context, cfnresponse.SUCCESS,
                            {'Message': 'Resource creation successful!'})
                    elif event['RequestType'] == 'Update':
                        LOGGER.info('UPDATE')
                        populate_ddb_table()
                        cfnresponse.send(event, context, cfnresponse.SUCCESS,
                            {'Message': 'Resource update successful!'})
                    elif event['RequestType'] == 'Delete':
                        LOGGER.info('DELETE')
                        # do nothing, table will be deleted anyway
                        cfnresponse.send(event, context, cfnresponse.SUCCESS,
                            {'Message': 'Resource deletion successful!'})
                    else:
                        LOGGER.info('FAILED!')
                        cfnresponse.send(event, context, cfnresponse.FAILED,
                            {'Message': 'Unexpected event received from CloudFormation'})
                except Exception as e:
                    LOGGER.info(str(traceback.format_exception_only(e.__class__, e)))
                    LOGGER.info('FAILED!')
                    cfnresponse.send(event, context, cfnresponse.FAILED, {
                        'Message': 'Exception during processing'})

            def timeout_handler(_signal, _frame):
                raise Exception('Time exceeded')

            signal.signal(signal.SIGALRM, timeout_handler)

  CustomResource:
    Type: 'Custom::LoadDynamoDB'
    DependsOn: DynamoDBServiceMockTable
    Properties:
      ServiceToken: !GetAtt LoadDynamoDBLambda.Arn
      DynamoTableName: !Ref DynamoDBServiceMockTable

  RecommendationServiceEnabled:
    Type: AWS::SSM::Parameter
    Properties: 
      AllowedPattern: '^(true|false)$'
      Description: 'switch used to enable/disable the mocked RecommendationService'
      Name: 'RecommendationServiceEnabled'
      Tier: 'Standard'
      Type: 'String'
      Value: 'true'

########################################################################

Outputs:
  WebsiteURL:
    Value: !Join
      - ''
      - - 'http://'
        - !GetAtt 
          - ALB1LoadBalancer
          - DNSName
        - /
    Description: Static Website
    
########################################################################

Metadata: 
  AWS::CloudFormation::Interface: 
    ParameterGroups: 
      - 
        Label: 
          default: "General Configuration"
        Parameters: 
          - NamingPrefix
      -
        Label:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -



static/Reliability/Common/Code/CloudFormation/staticwebapp.yaml [256:550]:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      TargetGroupARNs:
        - !Ref ALB1TargetGroup
      Tags:
        - Key: Name
          Value: !Sub '${NamingPrefix}'
          PropagateAtLaunch: true
    UpdatePolicy:
      AutoScalingReplacingUpdate:
        WillReplace: 'true'

  Web1LaunchConfig:
    Type: 'AWS::AutoScaling::LaunchConfiguration'
    #https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-as-launchconfig.html
    Properties:
      ImageId: !Ref LatestAmiId
      IamInstanceProfile: !Ref Web1InstanceInstanceProfile
      InstanceType: !Ref InstanceType
      SecurityGroups:
        - !Ref Web1InstanceSecurityGroup
      UserData:
        Fn::Base64: !Sub |
          #!/bin/bash
          yum -y update
          sleep 30 # prevent "Error: Rpmdb changed underneath us"
          yum install python3 python-virtualenv python-pip -y
          python3 -m venv /home/ec2-user/venv
          source /home/ec2-user/venv/bin/activate
          pip install boto3
          pip install ec2_metadata
          wget -O server.py ${ServerCodeUrl}
          python3 server.py -p 80 -r ${AWS::Region}

  WebInstancePolicy:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      Description: 'Policy used by EC2 WebServers'
      ManagedPolicyName: !Sub '${NamingPrefix}-EC2-Policy-${AWS::StackName}'
      Path: /
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action:
              - 'ec2:DescribeInstanceStatus'
            Resource: '*'
            # VPC Flow Logs: https://docs.aws.amazon.com/vpc/latest/userguide/flow-logs-cwl.html
          - Effect: Allow
            Action: 
              - 'logs:CreateLogGroup'
              - 'logs:CreateLogStream'
              - 'logs:PutLogEvents'
              - 'logs:DescribeLogStreams'
            Resource: 'arn:aws:logs:*:*:*'
            # CloudWatch: Required to create metrics" https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/permissions-reference-cw.html
          - Effect: Allow
            Action:
              - 'cloudwatch:PutMetricData'
            Resource: '*'
            # SSM: Our web app code on EC2 reads an SSM Parameter as part of this lab
          - Effect: Allow
            Action: 
              - 'ssm:GetParameter'
            Resource: '*'

  # IAM Role for EC2 servers including access to DynamoDB (DDB)
  Web1InstanceRole:
    Type: 'AWS::IAM::Role'
    DependsOn: WebInstancePolicy
    Properties:
      RoleName: !Sub '${NamingPrefix}-EC2-Role-${AWS::StackName}'
      Path: /
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - ec2.amazonaws.com
            Action:
              - 'sts:AssumeRole'
      ManagedPolicyArns:
        - !Ref WebInstancePolicy
        - 'arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore'
      # In addition to the ManagedPolicy, give it access to DDB
      Policies:
        - PolicyName: ddbReadOnly
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - 'dynamodb:BatchGetItem'
                  - 'dynamodb:Describe*'
                  - 'dynamodb:List*'
                  - 'dynamodb:GetItem'
                  - 'dynamodb:Query'
                  - 'dynamodb:Scan'
                Resource: 'arn:aws:dynamodb:*:*:table/*'

  # IAM Role for EC2 servers WITHOUT access to DynamoDB (DDB)
  Web2InstanceRole:
    Type: 'AWS::IAM::Role'
    DependsOn: WebInstancePolicy
    Properties:
      RoleName: !Sub '${NamingPrefix}-EC2-noDDB-Role-${AWS::StackName}'
      Path: /
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - ec2.amazonaws.com
            Action:
              - 'sts:AssumeRole'
      ManagedPolicyArns:
        - !Ref WebInstancePolicy
        - 'arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore'

  Web1InstanceInstanceProfile:
    Type: 'AWS::IAM::InstanceProfile'
    Properties:
      InstanceProfileName: !Sub '${NamingPrefix}-EC2-Role-${AWS::StackName}'
      Path: /Web1InstanceEc2Role/
      Roles:
        - !Ref Web1InstanceRole

  Web2InstanceInstanceProfile:
    Type: 'AWS::IAM::InstanceProfile'
    Properties:
      InstanceProfileName: !Sub '${NamingPrefix}-EC2-noDDB-Role-${AWS::StackName}'
      Path: /Web2InstanceEc2Role/
      Roles:
        - !Ref Web2InstanceRole

  LoadDynamoDBLambdaRole:
    Type: 'AWS::IAM::Role'
    Properties:
      RoleName: !Sub '${NamingPrefix}-Lambda-${AWS::StackName}'
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - 'sts:AssumeRole'
      Path: /
      Policies:
        - PolicyName: ddbFull
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - 'dynamodb:BatchWriteItem'
                Resource: 'arn:aws:dynamodb:*:*:table/RecommendationService'
        - PolicyName: cloudwatch
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action: 
                  - 'logs:CreateLogGroup'
                  - 'logs:CreateLogStream'
                  - 'logs:PutLogEvents'
                  - 'logs:DescribeLogStreams'
                Resource: 'arn:aws:logs:*:*:*'
              - Effect: Allow
                Action:
                  - 'cloudwatch:PutMetricData'
                Resource: '*'

  LoadDynamoDBLambda:
      Type: 'AWS::Lambda::Function'
      Properties:
        FunctionName: 'LoadDynamoDBLambda'
        Handler: 'index.handler'
        Runtime: 'python3.6'
        Timeout: '30'
        Role: !GetAtt LoadDynamoDBLambdaRole.Arn
        Code:
          ZipFile: !Sub |
            import boto3
            import urllib.request
            import cfnresponse
            import logging
            import signal
            import json
            import traceback

            LOGGER = logging.getLogger()
            LOGGER.setLevel(logging.INFO)

            def populate_ddb_table():
                # Get the service resource.
                LOGGER.info('create client')
                client = boto3.client('dynamodb', '${AWS::Region}')
                
                # @TODO, make bucket/object configurable
                LOGGER.info('open file')
                file = urllib.request.urlopen("https://aws-well-architected-labs-ohio.s3.us-east-2.amazonaws.com/Healthcheck/Data/RecommendationService.json")
                LOGGER.info('read file')
                text = file.read()
                LOGGER.info(text)
                LOGGER.info('convert to JSON')
                request_items = json.loads(text)
                LOGGER.info('write to DDB')
                response = client.batch_write_item(RequestItems=request_items)
                LOGGER.info('DONE')
                
                # @TODO add error handling 
                
                # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/dynamodb.html#DynamoDB.Client.batch_write_item


            def handler(event, context):
                signal.alarm(int((context.get_remaining_time_in_millis() / 1000) - 1))
                # Setup alarm for remaining runtime minus a second
                try:
                    LOGGER.info('EVENT RECEIVED: %s', event)
                    LOGGER.info('CONTEXT RECEIVED: %s', context)
                    if event['RequestType'] == 'Create':
                        LOGGER.info('CREATE')
                        populate_ddb_table()
                        cfnresponse.send(event, context, cfnresponse.SUCCESS,
                            {'Message': 'Resource creation successful!'})
                    elif event['RequestType'] == 'Update':
                        LOGGER.info('UPDATE')
                        populate_ddb_table()
                        cfnresponse.send(event, context, cfnresponse.SUCCESS,
                            {'Message': 'Resource update successful!'})
                    elif event['RequestType'] == 'Delete':
                        LOGGER.info('DELETE')
                        # do nothing, table will be deleted anyway
                        cfnresponse.send(event, context, cfnresponse.SUCCESS,
                            {'Message': 'Resource deletion successful!'})
                    else:
                        LOGGER.info('FAILED!')
                        cfnresponse.send(event, context, cfnresponse.FAILED,
                            {'Message': 'Unexpected event received from CloudFormation'})
                except Exception as e:
                    LOGGER.info(str(traceback.format_exception_only(e.__class__, e)))
                    LOGGER.info('FAILED!')
                    cfnresponse.send(event, context, cfnresponse.FAILED, {
                        'Message': 'Exception during processing'})

            def timeout_handler(_signal, _frame):
                raise Exception('Time exceeded')

            signal.signal(signal.SIGALRM, timeout_handler)

  CustomResource:
    Type: 'Custom::LoadDynamoDB'
    DependsOn: DynamoDBServiceMockTable
    Properties:
      ServiceToken: !GetAtt LoadDynamoDBLambda.Arn
      DynamoTableName: !Ref DynamoDBServiceMockTable

  RecommendationServiceEnabled:
    Type: AWS::SSM::Parameter
    Properties: 
      AllowedPattern: '^(true|false)$'
      Description: 'switch used to enable/disable the mocked RecommendationService'
      Name: 'RecommendationServiceEnabled'
      Tier: 'Standard'
      Type: 'String'
      Value: 'true'

########################################################################

Outputs:
  WebsiteURL:
    Value: !Join
      - ''
      - - 'http://'
        - !GetAtt 
          - ALB1LoadBalancer
          - DNSName
        - /
    Description: Static Website
    
########################################################################

Metadata: 
  AWS::CloudFormation::Interface: 
    ParameterGroups: 
      - 
        Label: 
          default: "General Configuration"
        Parameters: 
          - NamingPrefix
      - 
        Label: 
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -



