Skip to main content

Cloud Resume Challenge – Part 2

· 0 · 0 ·
cloudresume aws
Table of Contents
Cloud Resume Challenge - This article is part of a series.
Part 2: This Article

I’ve tackled some steps of the challenge out of turn here for a bit as it made sense to me to work on the Database/Lambda/API before working on the Javascript that requires them.

Step 8: Database>

Step 8: Database #

For this step I really leaned into trying to deploy via CloudFormation first rather than deploying via the GUI.
Our Database will consist of a DynamoDB as per the recommendation on the Cloud Resume Challenge website. Our table will consist of two attributes, record_id and record_count.
I ran into an issue not quite understanding how CloudFormation wanted to have the attributes presented, in the end I created just the key attribute of record_id and had to manually add the record_count attribute from the gui.

I followed the Cloud Foundation documentation on DynamoDB here
Here is my the relevant CloudFoundation YAML code:

VisitorCountTable:
  Type: AWS::DynamoDB::Table
  Properties:
  AttributeDefinitions: 
    -
      AttributeName: "record_id"
      AttributeType: "S"
  TableName: "Visitor_Count"
  ProvisionedThroughput: 
    ReadCapacityUnits: 5
    WriteCapacityUnits: 5
  KeySchema: 
    - 
      AttributeName: "record_id"
      KeyType: "HASH"

Once created, log into the AWS GUI and find your DynamoDB table. Click add item, add the attribute record_count with an value of 1

Step 8.5 and 10: Lambda and Python>

Step 8.5 and 10: Lambda and Python #

We’ll need to create two Lambda functions, one to get the visitor count and one to increase it.
I again went with the CloudFormation first approach and found I was starting to get the hang of it.
I followed the documentation on Lambda functions here

First I manually created a IAM role for my Lambda functions, I included the following policies

For this to work, we will need to create a couple of small Python scripts that the function will invoke

getvisitor.py simply connects to our DynamoDB database and returns the current value of the record_count attribute

import json
import boto3
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('Visitor_Count')
def lambda_handler(event, context):
    response = table.get_item(Key={
       'record_id':'0'
    })
    return response['Item']['record_count']

addvisitor.py is similar, this time increasing the current value of the record_count attribute.

import json
import boto3
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('Visitor_Count')
def lambda_handler(event, context):
    response = table.get_item(Key={
            'record_id':'0'
    })
    record_count = response['Item']['record_count']
    record_count = record_count + 1
    print(record_count)
    response = table.put_item(Item={
            'record_id':'0',
            'record_count': record_count
    })
    
    return "Records added successfully!"

We need to save and zip these two files up and upload them to a S3 bucket so the Lambda function can access

Now deploy it with the CloudFoundation YAML Code

    LambdaGetVisitor:
        Type: "AWS::Lambda::Function"
        Properties:
            FunctionName: "CloudResumeGetVisitor"
            Handler: "getvisitor.lambda_handler"
            Architectures: 
              - "x86_64"
            Code:
              S3Bucket: "cloudresumelambdafunctions"
              S3Key: "getvisitor.zip"

            Role: "arn:aws:iam::337461354076:role/CloudResume-LambdaFunctions"
            Runtime: "python3.9"

    LambdaAddVisitor:
        Type: "AWS::Lambda::Function"
        Properties:
            FunctionName: "CloudResumeAddVisitor"
            Handler: "addvisitor.lambda_handler"
            Architectures: 
              - "x86_64"
            Code:
              S3Bucket: "cloudresumelambdafunctions"
              S3Key: "addvisitor.zip"

            Role: "arn:aws:iam::337461354076:role/CloudResume-LambdaFunctions"
            Runtime: "python3.9"

All going well, you should have two shiny functions in your gui

Step 9: API>

Step 9: API #

We’ll need to create ourselves a REST API with two methods being GET to reference our LambdaGetVisitor function and POST to reference our LambdaAddVisitor function.

First we create the API Gateway object, as before I have preferred to go the CloudFormation root for this deployment

    ApiGateway:
        Type: "AWS::ApiGateway::RestApi"
        Properties:
            Name: "visitors"
            ApiKeySourceType: "HEADER"
            EndpointConfiguration: 
                Types: 
                  - "REGIONAL"

Pretty straightforward that one.
Next we move onto the GET method, I had a pretty rough time trying to figure this part out as pure CloudFormation code. I ended up completing this in the GUI, exported the code via Former2, deleting from GUI and redeploying with the newly written code.

    ApiGatewayGet:
        Type: "AWS::ApiGateway::Method"
        Properties:
            RestApiId: !Ref ApiGateway
            ResourceId: !GetAtt ApiGateway.RootResourceId
            HttpMethod: "GET"
            AuthorizationType: "NONE"
            ApiKeyRequired: false
            RequestParameters: {}
            MethodResponses: 
              - 
                ResponseModels: 
                    "application/json": "Empty"
                StatusCode: "200"
            Integration: 
                CacheNamespace: !GetAtt ApiGateway.RootResourceId
                ContentHandling: "CONVERT_TO_TEXT"
                IntegrationHttpMethod: "POST"
                IntegrationResponses: 
                  - 
                    ResponseTemplates: {}
                    StatusCode: "200"
                PassthroughBehavior: "WHEN_NO_MATCH"
                TimeoutInMillis: 29000
                Type: "AWS"
                Uri: !Sub "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${LambdaGetVisitor}/invocations"

Same as above for the POST method, deployed via gui, exported code, deleted and redeployed via CloudFormation

    ApiGatewayPost:
        Type: "AWS::ApiGateway::Method"
        Properties:
            RestApiId: !Ref ApiGateway
            ResourceId: !GetAtt ApiGateway.RootResourceId
            HttpMethod: "POST"
            AuthorizationType: "NONE"
            ApiKeyRequired: false
            RequestParameters: {}
            MethodResponses: 
              - 
                ResponseModels: 
                    "application/json": "Empty"
                StatusCode: "200"
            Integration: 
                CacheNamespace: !GetAtt ApiGateway.RootResourceId
                ContentHandling: "CONVERT_TO_TEXT"
                IntegrationHttpMethod: "POST"
                IntegrationResponses: 
                  - 
                    ResponseTemplates: {}
                    StatusCode: "200"
                PassthroughBehavior: "WHEN_NO_MATCH"
                TimeoutInMillis: 29000
                Type: "AWS"
                Uri: !Sub "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${LambdaAddVisitor}/invocations"

Once I deployed I went to the GUI to test my methods, but found both reported a permission denied error when I tried to run them.
When deploying from the GUI you get a prompt asking if you want to give your API method access to the Lambda function, obviously that didnt happen when deploying via CloudFormation. I found this help page that exactly explained my issue.

Here are my code snippets to address that problem

    APIGetPermissions:
        Type: AWS::Lambda::Permission
        Properties:
          Action: "lambda:InvokeFunction"
          FunctionName: !Ref LambdaGetVisitor
          Principal: "apigateway.amazonaws.com"
          SourceArn: arn:aws:execute-api:ap-southeast-2:337461354076:<api-id>/*/GET/

    APIPostPermissions:
        Type: AWS::Lambda::Permission
        Properties:
          Action: "lambda:InvokeFunction"
          FunctionName: !Ref LambdaAddVisitor
          Principal: "apigateway.amazonaws.com"
          SourceArn: arn:aws:execute-api:ap-southeast-2:337461354076:<api-id>/*/POST/

Thats it for now, part 3 will try and merge this all together and get a nice counter on our Resume



Cloud Resume Challenge - This article is part of a series.
Part 2: This Article