<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Simon Green</title>
    <description>The latest articles on DEV Community by Simon Green (@simongreen).</description>
    <link>https://dev.to/simongreen</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1172637%2F1a3c02c7-b95e-4113-a91f-6a07c6af11c2.jpg</url>
      <title>DEV Community: Simon Green</title>
      <link>https://dev.to/simongreen</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/simongreen"/>
    <language>en</language>
    <item>
      <title>Integrating 'Session Counts'</title>
      <dc:creator>Simon Green</dc:creator>
      <pubDate>Wed, 27 Mar 2024 13:35:32 +0000</pubDate>
      <link>https://dev.to/simongreen/creating-session-counts-3nop</link>
      <guid>https://dev.to/simongreen/creating-session-counts-3nop</guid>
      <description>&lt;h2&gt;
  
  
  Current status
&lt;/h2&gt;

&lt;p&gt;Shortly after completing the Cloud Resume Challenge I decided to revisit it and choose a selection of the suggested &lt;strong&gt;modifications&lt;/strong&gt; to continue adding development features into the application, ie. resume page. &lt;/p&gt;

&lt;p&gt;One of the key features of the project is the 'visitor counter', an integer that is retrieved from a DynamoDB table through a number of processes, this number is simply incrementing by 1 each time the page is reloaded or refreshed. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0ivof9629yjlkqq5bi8j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0ivof9629yjlkqq5bi8j.png" alt="Image description" width="313" height="117"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Counting distinct sessions
&lt;/h2&gt;

&lt;p&gt;I wanted to update this feature so that it counts the perceived browser sessions on the page, rather than the page 'load count'. To do this I needed to get and pass the &lt;strong&gt;ip_address&lt;/strong&gt; and &lt;strong&gt;user_agent&lt;/strong&gt; retrieved by API Gateway into the Lambda/Python function for evaluation.&lt;/p&gt;

&lt;p&gt;The Python function needs to: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Concatenate these two string values &lt;/li&gt;
&lt;li&gt;Add them to the database &lt;/li&gt;
&lt;li&gt;Count the distinct values of these concatenated strings and &lt;/li&gt;
&lt;li&gt;Return the unique value as the session_count&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Counting distinct active sessions
&lt;/h2&gt;

&lt;p&gt;Using this data and taking it a step further, I also wanted to count the active sessions within a &lt;strong&gt;1 hour window&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;To do this, together with the session_id I would need to add a timestamps for &lt;strong&gt;first_accessed&lt;/strong&gt; (using 'datetime.now()' from Python) and also calculate the &lt;strong&gt;last_accessed&lt;/strong&gt; timestamp value of the session_id. &lt;/p&gt;

&lt;p&gt;This involved 3 key steps and some pretty interesting logic that I have summarised below:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1) Check if the unique session_id already exists in the database:&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If no &amp;gt;&amp;gt;&lt;/strong&gt; add to the database a new row containing 1) session_id, 2) first_accessed timestamp as 'now', and 3) last_accessed timestamp also as 'now', &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If yes &amp;gt;&amp;gt;&lt;/strong&gt; update the 'last_accessed' timestamp as current time/date&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2) Distinct sessions: Count the number of distinct session_ids in the database:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3) Active sessions: Count the number of sessions within the last hour:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;-- Scan the table, filtering out rows where the last_accessed timestamp is greater than '1 hour ago' &lt;/p&gt;

&lt;p&gt;-- Retrieve and count all remaining session_ids&lt;/p&gt;

&lt;p&gt;The Python code for this can be seen below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# SESSION COUNT ADDED - NON-GDPR COMPLIANT

import json
import boto3
from datetime import datetime, timedelta
import hashlib

# Initialize DynamoDB client
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('session_count_hash_table')

def lambda_handler(event, context):

    ################### CREATE UNIQUE IDENTIFIER ###################

    ip_address = event.get('requestContext', {}).get('identity', {}).get('sourceIp', 'Unknown IP')
    user_agent = event.get('requestContext', {}).get('identity', {}).get('userAgent', 'Unknown UserAgent')

    # Generate a unique session ID based on IP address and user agent
    session_id = f"{ip_address}-{user_agent}"


    ################### ACTIVE SESSION COUNT ###################

    # Check if the session exists in DynamoDB
    response = table.get_item(Key={'session_id': session_id}) # sends a request to DynamoDB to retrieve the item that matches the specified key
    session_data = response.get('Item')

    if session_data:
        # Update the session timestamp
        table.update_item(
            Key={'session_id': session_id},
            UpdateExpression='SET last_accessed = :val', # sets contain only unique elements
            ExpressionAttributeValues={':val': datetime.now().isoformat()}
        )
    else:
        # Create a new session entry
        table.put_item(
            Item={
                'session_id': session_id,
                'first_accessed': datetime.now().isoformat(),
                'last_accessed': datetime.now().isoformat()
            }
        )

    # Count the number of active sessions within the last hour
    hour_ago = datetime.now() - timedelta(hours=1)
    response_active = table.scan(
        FilterExpression='last_accessed &amp;gt; :val',
        ExpressionAttributeValues={':val': hour_ago.isoformat()}
    )
    active_sessions = len(response_active['Items'])


    ################### DISTINCT SESSION COUNT ###################

    # Retrieve all session IDs from the database
    response_all = table.scan()
    session_ids = set([item['session_id'] for item in response_all['Items']])

    # Calculate the distinct count of session IDs
    unique_session_count = len(session_ids)


    ################### WHAT TO RETURN (json string)  ###################

    response = {
        "statusCode": 200,
        # HTTP headers that will be included in the response sent back to the client
        "headers": {
            "Content-Type": "application/json",
            "Access-Control-Allow-Origin": "*",  # Allows requests from any origin
            "Access-Control-Allow-Credentials": "true",  # Required for cookies, authorization headers with HTTPS 
            "Access-Control-Allow-Methods": "OPTIONS,GET,PUT,POST,DELETE",  # Allowed request methods
            "Access-Control-Allow-Headers": "Content-Type,Authorization",  # Allowed request headers
        },
        "body": json.dumps({  # converts a Python dictionary to a JSON-formatted string
            # Your response body
            'message': 'Session counts updated successfully',
            'unique_session_count': unique_session_count,
            'active_sessions': active_sessions   
        }),
    }

    return response

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After some configuration and testing this worked well and updated a new DynamoDB table (session_count_table) updating the relevant columns where and when needed, as shown below. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5gtwuttt2snxscarquhb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5gtwuttt2snxscarquhb.png" alt="Image description" width="800" height="237"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Making it GDPR compliant
&lt;/h2&gt;

&lt;p&gt;As an IP address can potentially be used for location tracking, it is possible that collecting the IP address together with the browser user agent data could be a breach of GDPR.&lt;/p&gt;

&lt;p&gt;GDPR (General Data Protection Regulation) is a European Union regulation on information privacy in the European Union and the European Economic Area. The GDPR is an important component of EU privacy law and human rights law, in particular Article 8 of the Charter of Fundamental Rights of the European Union.&lt;/p&gt;

&lt;p&gt;To eliminate this issue I replaced the session_id string with a consistent hash, (ie. for each distinct session_id, a constant hash is generated) removing the need to store IP addresses.&lt;/p&gt;

&lt;p&gt;The session_hash is created using the SHA-256 cryptographic hash function provided by the hashlib module and provides a hexadecimal string as the hash, which will be &lt;strong&gt;consistent for the same input&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The important line of Python code added is to create the session_hash, as seen below: &lt;/p&gt;

&lt;p&gt;&lt;code&gt;import hashlib&lt;br&gt;
~&lt;br&gt;
session_hash = hashlib.sha256(session_id.encode()).hexdigest()&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;And from that point on replace session_id with session_hash so as to perform calculations on the distinct hash values, as seen below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# SESSION COUNT ADDED - GDPR COMPLIANT

import json
import boto3
from datetime import datetime, timedelta
import hashlib

# Initialize DynamoDB client
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('session_count_hash_table')

def lambda_handler(event, context):

    ################### CREATE UNIQUE IDENTIFIER ###################

    ip_address = event.get('requestContext', {}).get('identity', {}).get('sourceIp', 'Unknown IP')
    user_agent = event.get('requestContext', {}).get('identity', {}).get('userAgent', 'Unknown UserAgent')

    # Generate a unique session ID based on IP address and user agent
    session_id = f"{ip_address}-{user_agent}"

    # Convert the session id into a consistant sha256 hash, and use session_hash from here on
    session_hash = hashlib.sha256(session_id.encode()).hexdigest()


    ################### ACTIVE SESSION COUNT ###################

    # Check if the session exists in DynamoDB
    response = table.get_item(Key={'session_hash': session_hash}) # sends a request to DynamoDB to retrieve the item that matches the specified key
    session_data = response.get('Item')

    if session_data:
        # Update the session timestamp
        table.update_item(
            Key={'session_hash': session_hash},
            UpdateExpression='SET last_accessed = :val',
            ExpressionAttributeValues={':val': datetime.now().isoformat()}
        )
    else:
        # Create a new session entry
        table.put_item(
            Item={
                'session_hash': session_hash,
                'first_accessed': datetime.now().isoformat(),
                'last_accessed': datetime.now().isoformat()
            }
        )

    # Count the number of active sessions within the last hour
    hour_ago = datetime.now() - timedelta(hours=1)
    response_active = table.scan(
        FilterExpression='last_accessed &amp;gt; :val',
        ExpressionAttributeValues={':val': hour_ago.isoformat()}
    )
    active_sessions = len(response_active['Items'])


    ################### DISTINCT SESSION COUNT ###################

    # Retrieve all session IDs from the database
    response_all = table.scan()
    session_ids = set([item['session_hash'] for item in response_all['Items']])

    # Calculate the distinct count of session IDs
    unique_session_count = len(session_ids)


    ################### WHAT TO RETURN (json string)  ###################

    response = {
        "statusCode": 200,
        # HTTP headers that will be included in the response sent back to the client
        "headers": {
            "Content-Type": "application/json",
            "Access-Control-Allow-Origin": "*",  # Allows requests from any origin
            "Access-Control-Allow-Credentials": "true",  # Required for cookies, authorization headers with HTTPS 
            "Access-Control-Allow-Methods": "OPTIONS,GET,PUT,POST,DELETE",  # Allowed request methods
            "Access-Control-Allow-Headers": "Content-Type,Authorization",  # Allowed request headers
        },
        "body": json.dumps({  # converts a Python dictionary to a JSON-formatted string
            # Your response body
            'message': 'Session counts updated successfully',
            'unique_session_count': unique_session_count,
            'active_sessions': active_sessions   
        }),
    }

    return response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An example of the session_count_hash_table can be seen below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0lte5yi48stdv0c9zxc5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0lte5yi48stdv0c9zxc5.png" alt="Image description" width="800" height="283"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To complete this part of the project I needed to configure the following: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Update &lt;strong&gt;AWS IAM&lt;/strong&gt; settings to allow &lt;strong&gt;Lambda&lt;/strong&gt; to connect with the new &lt;strong&gt;DynamoDB&lt;/strong&gt; table&lt;/li&gt;
&lt;li&gt;Add a new endpoint to the existing &lt;strong&gt;API Gateway&lt;/strong&gt; to connect with the new &lt;strong&gt;Lambda&lt;/strong&gt; service for session counts&lt;/li&gt;
&lt;li&gt;Update &lt;strong&gt;Terraform&lt;/strong&gt; API Gateway and Lambda configurations to include these updates as Infrastructure as Code&lt;/li&gt;
&lt;li&gt;Update the &lt;strong&gt;frontend webpage&lt;/strong&gt; to invoke and receive data from API Gateway and display that data on the page.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The frontend update can be seen below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5sefyiz4y23r3qyt01nl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5sefyiz4y23r3qyt01nl.png" alt="Image description" width="321" height="189"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With this update in place, I can now see additional metrics that loosely relate to visitors to the webpage and have gained additional experience in implementing additional features to an existing application.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>learning</category>
      <category>cloud</category>
      <category>fullstack</category>
    </item>
    <item>
      <title>Part 4b: Problem solving and debugging</title>
      <dc:creator>Simon Green</dc:creator>
      <pubDate>Fri, 16 Feb 2024 11:30:41 +0000</pubDate>
      <link>https://dev.to/simongreen/part-4b-problem-solving-and-debugging-4eij</link>
      <guid>https://dev.to/simongreen/part-4b-problem-solving-and-debugging-4eij</guid>
      <description>&lt;h2&gt;
  
  
  Included in this chapter...
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Objective&lt;/li&gt;
&lt;li&gt;How I resolve technical issues...&lt;/li&gt;
&lt;li&gt;Issue 1: API Gateway Internal Server Error&lt;/li&gt;
&lt;li&gt;Issue 2: Malformed Lambda proxy response&lt;/li&gt;
&lt;li&gt;Issue 3: Git push, file exceeding limit&lt;/li&gt;
&lt;li&gt;Issue 4: GitLab CI Variables&lt;/li&gt;
&lt;li&gt;Issue 5: CloudFront - Failed to contact the origin&lt;/li&gt;
&lt;li&gt;Issue 6: API Gateway: Missing Authentication Token&lt;/li&gt;
&lt;li&gt;Issue 7: CSS intermittently not loading&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Objective
&lt;/h2&gt;

&lt;p&gt;This post is the second part of my previous post (&lt;a href="https://dev.to/simongreen/blog-part-4-infrastructure-as-code-continuous-development-and-continuous-integration-c0"&gt;Part 4a: Terraform IaC, GitLab CI and automated testing&lt;/a&gt;)  where I explain seven key technical issues I faced during the development of the AWS / Terraform IaC part of the challenge and I explain how I was able to resolve each.&lt;/p&gt;




&lt;h2&gt;
  
  
  My process for resolving technical issues...
&lt;/h2&gt;

&lt;p&gt;Issues can arise when adding a new implementation or as a result of &lt;strong&gt;making changes&lt;/strong&gt; to an existing configuration.&lt;/p&gt;

&lt;p&gt;My approach to handling an issue goes something like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Gather the available error logs&lt;/li&gt;
&lt;li&gt;Understand the information flow and where in that flow the error is being generated&lt;/li&gt;
&lt;li&gt;Research &lt;/li&gt;
&lt;li&gt;Testing&lt;/li&gt;
&lt;li&gt;Documenting &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Research can take the form of reading the output logs from various sources (or creating them if necessary), reading official documentation, blogs, community forums etc. The latter options often times can be old and it’s good to understand that the technology in question is changing fast, however they can often times provide solutions or clues that can be investigated further.&lt;/p&gt;

&lt;p&gt;When resolving issues, I typically make &lt;strong&gt;one change at a time&lt;/strong&gt; and test frequently to help identify the cause of the issue, and  take steps to be able to roll back to a previous state if necessary. &lt;/p&gt;

&lt;p&gt;The research and testing steps are typically a &lt;strong&gt;continuous cycle&lt;/strong&gt; until the problem is resolved. Sometimes breaking the issue down into smaller elements for testing works, sometimes I find that a different approach is needed to achieve the required outcome, but "all" issues can be fixed with persistence and patience. &lt;/p&gt;

&lt;p&gt;With that said, here are some issues I experienced...&lt;/p&gt;




&lt;h3&gt;
  
  
  Issue 1: API Gateway Internal Server Error
&lt;/h3&gt;

&lt;p&gt;With the API Gateway and Lambda created as IaC, I tried to invoke the Lambda function from the API Gateway console using the ‘Test Resource’ feature and was continuously receiving a “500 Internal Server Error” response code, as seen below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz5e855cle8h3xto78xiw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz5e855cle8h3xto78xiw.png" alt="Internal server error message 1" width="800" height="338"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1ah23vv18n3ow5r98sph.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1ah23vv18n3ow5r98sph.png" alt="Internal server error message 2" width="800" height="152"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The logs indicate there are &lt;strong&gt;Invalid permissions on the Lambda function&lt;/strong&gt;, indicating a possible IAM issue to resolve in the Terraform config. &lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Issue 1: Solution
&lt;/h3&gt;

&lt;p&gt;From the &lt;a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_permission"&gt;Terraform documentation&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;execution_arn - Execution ARN part to be used in lambda_permission's source_arn when allowing API Gateway to invoke a Lambda function, e.g., arn:aws:execute-api:eu-west-2:123456789012:z4675bid1j, which can be concatenated with allowed stage, method and resource path.&lt;/em&gt;&lt;br&gt;
&lt;br&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;After some testing I was able to resolve this issue by simply &lt;strong&gt;concatenating a ‘/*’ to the end of the API Gateway ARN output value&lt;/strong&gt;, with this change it now allows invocation from &lt;strong&gt;any API Gateway stage, method or resource path&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;output "api_gateway_source_arn" {
 value = aws_api_gateway_rest_api.api_gateway_tf.execution_arn
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Corrected:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;output "api_gateway_source_arn" {
  value = join("",[aws_api_gateway_rest_api.api_gateway_tf.execution_arn,"/*"])
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;br&gt;&lt;br&gt;
With this updated API Gateway could successfully invoke the Lambda function however this then led to issue 2, detailed below.&lt;/p&gt;


&lt;h3&gt;
  
  
  Issue 2: Malformed Lambda proxy response
&lt;/h3&gt;

&lt;p&gt;With this issue again when trying to invoke the Lambda function I was receiving a &lt;strong&gt;502 Bad Gateway&lt;/strong&gt;, configuration error due to a *&lt;em&gt;Malformed Lambda proxy response&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;From the logs I could see that the request was being sent successfully (as it returned the ‘count’ value) however it was failing at the response.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsjiovlo31w0if2dfpdmi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsjiovlo31w0if2dfpdmi.png" alt="Image description" width="665" height="234"&gt;&lt;/a&gt; &lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Issue 2: Solution
&lt;/h3&gt;

&lt;p&gt;This took quite a bit of trial and error to find the solution but I managed to resolve this by updating the api_gateway.tf file and under the &lt;strong&gt;aws_api_gateway_integration&lt;/strong&gt; resource by changing the ‘type’ argument/configuration from &lt;strong&gt;type = ‘AWS_PROXY’&lt;/strong&gt; (for Lambda proxy integration) to &lt;strong&gt;type = ‘AWS’&lt;/strong&gt; (for AWS services).&lt;/p&gt;

&lt;p&gt;With this updated this returns the updated visitor count without any error.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_api_gateway_integration" "integration" {
  rest_api_id                   = aws_api_gateway_rest_api.api_gateway_tf.id
  resource_id                   = aws_api_gateway_resource.resource.id
  http_method                   = aws_api_gateway_method.method.http_method
  integration_http_method       = "POST"
  # type                        = "AWS_PROXY"       # &amp;lt;--- OLD
  type                          = "AWS"         # &amp;lt;--- UPDATED
  uri                           = var.lambda_function_arn
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;NOTE: I have later updated this config to use integration type "AWS_PROXY", to resolve the issue I needed to update the lambda/Python response to match the requirements of the API Gateway integration requirements. I now understand that this is an ideal way to receive a response in API Gateway when invoking Lambda. &lt;/p&gt;




&lt;h3&gt;
  
  
  Issue 3: Git push, file exceeding limit
&lt;/h3&gt;

&lt;p&gt;For Terraform to work correctly with AWS it downloads in the background an &lt;strong&gt;AWS Provider Binary file&lt;/strong&gt; (409MB) to the local repo. When pushing the latest updates to GitLab this file exceeds the upper limit of what you can upload. &lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Issue 3: Solution
&lt;/h3&gt;

&lt;p&gt;Using the Terminal I searched for large files within the project folder using this command:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;find cloud-resume-challenge -type f -size +200M&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;I then added these files to the &lt;strong&gt;.gitignore file&lt;/strong&gt;, essentially preventing the upload of the specified files.&lt;/p&gt;

&lt;p&gt;If you were to clone the repo onto another machine, these binary files would automatically be downloaded anyway.&lt;/p&gt;


&lt;h3&gt;
  
  
  Issue 4: GitLab CI Variables
&lt;/h3&gt;

&lt;p&gt;Whilst configuring the GitLab automation pipeline in the ´.gitlab-ci.yml´ file, within the GitLab CI/CD settings → Variables settings I had configured the &lt;strong&gt;AWS_ACCESS_KEY_ID&lt;/strong&gt; and &lt;strong&gt;AWS_SECRET_ACCESS_KEY&lt;/strong&gt; however I was continually seeing errors like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;upload failed: ./foo.txt to s3://gitlab-test-bucket-sg/foo.txt An error occurred (InvalidAccessKeyId) when calling the PutObject operation: &lt;strong&gt;The AWS Access Key Id you provided does not exist in our records&lt;/strong&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;…or…&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;upload failed: ./foo.txt to s3://gitlab-test-bucket-sg/foo.txt Unable to locate credentials&lt;/em&gt; &lt;br&gt;&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  Issue 4: Solution
&lt;/h2&gt;

&lt;p&gt;I found the solution was simply to &lt;strong&gt;uncheck the “Protect Variable” option within the GitLab UI&lt;/strong&gt; in the Variable configuration. The documentation states:&lt;/p&gt;

&lt;p&gt;“Protect variable Optional. If selected, the variable is only available in pipelines that run on &lt;a href="https://docs.gitlab.com/ee/user/project/protected_branches.html"&gt;protected branches&lt;/a&gt; or &lt;a href="https://docs.gitlab.com/ee/user/project/protected_tags.html"&gt;protected tags&lt;/a&gt;.”&lt;/p&gt;

&lt;p&gt;With the "Protect variable" option unchecked the pipeline worked 🙂&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0kznb997kf5mqlenribf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0kznb997kf5mqlenribf.png" alt="Image description" width="405" height="709"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h3&gt;
  
  
  Issue 5: CloudFront - Failed to contact the origin
&lt;/h3&gt;

&lt;p&gt;To help test the functionality of the &lt;strong&gt;CloudFront&lt;/strong&gt; configuration, The CloudFront console provides a 'Distribution domain name' &lt;strong&gt;URL&lt;/strong&gt; that should, in my case &lt;strong&gt;open the 'index.html' file in the browser&lt;/strong&gt;.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbuxmd9tarnknd3wqzl0l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbuxmd9tarnknd3wqzl0l.png" alt="Image description" width="620" height="321"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In my case, this Terraform created CloudFront service was opening an error page, indicating that it has &lt;strong&gt;Failed to contact the origin&lt;/strong&gt;.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa7ds8gq3nmn9nj3hwr9s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa7ds8gq3nmn9nj3hwr9s.png" alt="Image description" width="635" height="217"&gt;&lt;/a&gt;  &lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Issue 5: Solution
&lt;/h3&gt;

&lt;p&gt;The resolution to this issue was a two-fold solution. &lt;/p&gt;

&lt;p&gt;For the first I found there was a syntax error in the formatting of the local value in cloudfront.tf file, before the declared region variable I had used a hyphen, whereas this should be &lt;strong&gt;formatted with a period&lt;/strong&gt;, see below for the example. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Issue 1 of 2&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;locals {
    # Before and after:
    # s3_domain_name = "${var.bucket_name}.s3-website-${var.region}.amazonaws.com"
    s3_domain_name = "${var.bucket_name}.s3-website.${var.region}.amazonaws.com"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Issue 2 of 2&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With this syntax error corrected I was &lt;strong&gt;still unable to open the index.html file&lt;/strong&gt; using the '​​Distribution domain name', so after further investigation I found out that for the &lt;strong&gt;aws_cloudfront_distribution&lt;/strong&gt; resource configuration, the &lt;strong&gt;origin_protocol_policy&lt;/strong&gt; should be set to &lt;strong&gt;http-only&lt;/strong&gt; and not https-only.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_cloudfront_distribution" "cloudfront_dist_tf" {
  origin {
    origin_id       = local.s3_origin_id
    domain_name        = local.s3_domain_name
    custom_origin_config {
      http_port              = 80
      https_port             = 443
      # origin_protocol_policy = "https-only" # &amp;lt;--- Before
      origin_protocol_policy = "http-only" # &amp;lt;--- Updated
      origin_ssl_protocols   = ["TLSv1"]
    }
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this second issue now corrected, the CloudFront Distribution domain name now successfully opened the index.html file.&lt;/p&gt;




&lt;h3&gt;
  
  
  Issue 6: API Gateway: Missing Authentication Token
&lt;/h3&gt;

&lt;p&gt;With the Terraform structure mostly created, the API Gateway and Lambda were good and working well, however when trying to open the &lt;strong&gt;API Gateway Invoke URL&lt;/strong&gt; in the browser, I was receiving the error {"message":"&lt;strong&gt;Missing Authentication Token&lt;/strong&gt;"}.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh0bvgm42n66a8srfktlr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh0bvgm42n66a8srfktlr.png" alt="Image description" width="682" height="366"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F40ac2981n8odf0qr6ka2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F40ac2981n8odf0qr6ka2.png" alt="Image description" width="687" height="92"&gt;&lt;/a&gt;&lt;br&gt;
&lt;br&gt;&lt;/p&gt;

&lt;p&gt;In my case the expected result should return the raw, updated visitor count number into the browser, this is what is passed to the index.html file, see below for the expected result after Invoking the URL:&lt;br&gt;
&lt;br&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5b4cta65qbfs8j3hvpgh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5b4cta65qbfs8j3hvpgh.png" alt="Image description" width="190" height="45"&gt;&lt;/a&gt;  &lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Issue 6: Solution
&lt;/h3&gt;

&lt;p&gt;After a fair amount of investigation and trial and error with all types of API Gateway configuration I figured out that the provided 'Invoke URL' provided by API Gateway simply needed to be updated to match my Terraform settings. &lt;/p&gt;

&lt;p&gt;In creating the Terraform aws_api_gateway_resource, I named the path_part as "&lt;strong&gt;resource&lt;/strong&gt;".&lt;br&gt;
&lt;br&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_api_gateway_resource" "resource" {
  path_part   = "resource"
  parent_id   = aws_api_gateway_rest_api.api_gateway_tf.root_resource_id
  rest_api_id = aws_api_gateway_rest_api.api_gateway_tf.id
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So to be able to open the Invoke URL, I needed to &lt;strong&gt;concatenate '/resource'&lt;/strong&gt; to the end of the URL for the output, which I did in the following way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;output "invoke_url_full" {
  value = "${aws_api_gateway_deployment.deployment.invoke_url}/resource"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;br&gt;&lt;br&gt;
In addition, I also configured Terraform to output this updated URL locally to a text file which would be uploaded together with the other website files to the GitLab repo. Like this, as this URL is expected to change, the index.html file refers to this text file for the &lt;strong&gt;most recent Invoke URL, even if the Terraform resource is destroyed and re-created&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;With these two changes made, the updated URL finally returns the count in the browser.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fztets3wnsj030k8k7dvf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fztets3wnsj030k8k7dvf.png" alt="Image description" width="697" height="95"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Issue 7: CSS intermittently not loading
&lt;/h3&gt;

&lt;p&gt;This one was a front end issue I was experiencing where after uploading the website files to S3, opening the domain in the browser, sometimes it initially open with CSS but subsequent refreshes it would load with no CSS.&lt;/p&gt;

&lt;p&gt;Testing locally it worked fine, confirming the styles.css was good.&lt;/p&gt;

&lt;p&gt;Testing online and assuming it was a caching issue, I tried hard page reloads and testing in other browsers but it still resulted in the &lt;strong&gt;html page with no CSS&lt;/strong&gt;. Also checking the console output in Chrome Developer Tools, it showed that the CSS file was loaded with &lt;strong&gt;status code = 200&lt;/strong&gt;, however no CSS was being loaded. &lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Issue 7: Solution
&lt;/h3&gt;

&lt;p&gt;Digging further, I did a curl request that confirmed the styles.css file type was “&lt;strong&gt;text/html&lt;/strong&gt;”, as this is how it was configured when uploaded to the bucket by Terraform.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;curl -I https://www.simon-green.uk/styles.css | grep -i content-type&lt;/code&gt;&lt;br&gt;
&lt;br&gt;&lt;br&gt;
In Terraform I &lt;strong&gt;updated the file type for the styles.css file to be 'text/css'&lt;/strong&gt; when uploaded to S3, and thankfully this resolved the issue.&lt;br&gt;
&lt;br&gt;&lt;br&gt;
Output before:&lt;br&gt;
&lt;code&gt;content-type: text/html&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Output after:&lt;br&gt;
&lt;code&gt;content-type: text/css&lt;/code&gt;&lt;br&gt;
&lt;br&gt;&lt;br&gt;
However it's worth noting that to make this a more robust flow, I ended up updating the Terraform configuration so that it no longer uploads any website files to the bucket, instead Terraform creates an empty bucket and the files are then added using Git (after passing Cypress testing).  &lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this post, I've shared my approach to resolving technical issues I encountered during the AWS/Terraform Infrastructure as Code (IaC) part of challenge. It's all about methodical problem-solving, starting with gathering information, researching, and testing solutions.&lt;/p&gt;

&lt;p&gt;From API Gateway errors to CloudFront misconfigurations, each challenge required persistence and patience. By making one change at a time and testing frequently, I was able to pinpoint the root causes and implement effective solutions.&lt;/p&gt;

&lt;p&gt;Ultimately, navigating technical hurdles demands a blend of technical know-how and perseverance. By embracing a systematic approach and leveraging available resources, we can overcome any obstacle in our development journey.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>learning</category>
      <category>automation</category>
    </item>
    <item>
      <title>Part 4a: Terraform IaC, GitLab CI and automated testing</title>
      <dc:creator>Simon Green</dc:creator>
      <pubDate>Wed, 14 Feb 2024 15:10:48 +0000</pubDate>
      <link>https://dev.to/simongreen/blog-part-4-infrastructure-as-code-continuous-development-and-continuous-integration-c0</link>
      <guid>https://dev.to/simongreen/blog-part-4-infrastructure-as-code-continuous-development-and-continuous-integration-c0</guid>
      <description>&lt;h2&gt;
  
  
  Included in this chapter...
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Technology Used&lt;/li&gt;
&lt;li&gt;Objective&lt;/li&gt;
&lt;li&gt;Preparation&lt;/li&gt;
&lt;li&gt;Execution&lt;/li&gt;
&lt;li&gt;Architecture Diagram&lt;/li&gt;
&lt;li&gt;Summary&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Technology Used
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;AWS&lt;/li&gt;
&lt;li&gt;Cypress &lt;/li&gt;
&lt;li&gt;GitLab CI&lt;/li&gt;
&lt;li&gt;Terraform&lt;/li&gt;
&lt;li&gt;Visual Studio Code / bash / environment variables…&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Objective
&lt;/h2&gt;

&lt;p&gt;At the end of this step, the frontend website will look no different however the backend configuration will be completely reformed. The key differences will be:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The entire AWS configuration will have been set up as &lt;strong&gt;Infrastructure as Code using Terraform&lt;/strong&gt;, all deployable with only a few clicks.&lt;/li&gt;
&lt;li&gt;All configurations will live in a &lt;strong&gt;GitLab&lt;/strong&gt; repository dedicated to this project. &lt;/li&gt;
&lt;li&gt;If any changes have been made to the index.html file, when pushed to the GitLab repo, this will &lt;strong&gt;trigger a pipeline&lt;/strong&gt; that will run tests to ensure that updated content meets predefined rules.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Preparation
&lt;/h2&gt;

&lt;p&gt;I took a comprehensive deep-dive course on &lt;strong&gt;Terraform&lt;/strong&gt;  where I learnt how to use it from the ground up. Afterwards, with a good understanding of how it works, I began to apply this knowledge to create the Terraform configuration in parallel with the AWS console created infrastructure. &lt;/p&gt;

&lt;p&gt;The order of my creation was:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;DynamoDB &lt;/li&gt;
&lt;li&gt;IAM&lt;/li&gt;
&lt;li&gt;Lambda&lt;/li&gt;
&lt;li&gt;API Gateway&lt;/li&gt;
&lt;li&gt;S3&lt;/li&gt;
&lt;li&gt;CloudFront&lt;/li&gt;
&lt;li&gt;Certificate Manager / CloudFlare&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I also took courses in &lt;strong&gt;GitLab&lt;/strong&gt; and &lt;strong&gt;Cypress&lt;/strong&gt; so I was later able to configure a GitLab repo where I would store all the code as well as setting up an &lt;strong&gt;automation pipeline using GitLab CI and Cypress&lt;/strong&gt; to ensure the index.html file meets certain testing criteria before uploading to the S3 bucket.  &lt;/p&gt;

&lt;p&gt;I have separated this blog into two posts, this one you are reading, with an overview of how I solved this part of the Cloud Resume Challenge and a second post (&lt;a href="https://dev.to/simongreen/part-4b-problem-solving-and-debugging-4eij"&gt;Part 4b: Problem solving and debugging&lt;/a&gt;) detailing several issues I had and how I was able to overcome each of them.&lt;/p&gt;




&lt;h2&gt;
  
  
  Execution
&lt;/h2&gt;

&lt;p&gt;Within Terraform I decided to set up the configuration using a &lt;strong&gt;modular structure&lt;/strong&gt; as opposed to adding everything to the main.tf file. The project file structure I used is shown below. I set up the configuration this way as it allows for greater flexibility and readability.&lt;/p&gt;

&lt;h4&gt;
  
  
  Project file structure:
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;├── cypress
│   └── integration
│       └── index-resume-test-tf.cy.js
├── terraform_config
│   ├── acm_module
│   │   └── acm.tf
│   ├── api_gateway_module
│   │   └── api_gateway.tf
│   ├── cloudfront_module
│   │   └── cloudfront.tf
│   ├── dynamodb_module
│   │   └── dynamodb.tf
│   ├── iam_module
│   │   └── iam_role.tf
│   ├── lambda_module
│   │   ├── lambda_code.py
│   │   ├── lambda_code.zip
│   │   └── lambda.tf
│   ├── s3_module
│   │   └── s3.tf
│   └── main.tf
├── website_files
├── .gitlab-ci.yml
└── cypress.config.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;During the development of the Terraform IaC stage I have become comfortable using the &lt;strong&gt;Terraform Registry&lt;/strong&gt; for creating each service to match my AWS console created version.&lt;/p&gt;

&lt;p&gt;This part of the challenge involved a fairly solid chunk of work and along the way I encountered several issues that slowed down my progress but with a considerable amount of testing and persistence to see it through I was able to &lt;strong&gt;overcome each challenge&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This has been by far the most complicated part of the Challenge, &lt;strong&gt;the learning level was steep&lt;/strong&gt; however I have learnt a lot about using and configuring Terraform, setting up automation pipelines and inflight testing using GitLab and Cypress and I have successfully built an end-to-end application using these and other important cloud technologies. &lt;/p&gt;

&lt;p&gt;The end result can be seen here 🙂&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://dev.tourl"&gt;www.simon-green.uk&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture Diagram
&lt;/h2&gt;

&lt;p&gt;The diagram shown below illustrates the final overall end-to-end architecture that I created for this section and the project. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxtsipe979eq0q3i4xouw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxtsipe979eq0q3i4xouw.png" alt="Architecture Diagram" width="622" height="815"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;I have completed the Cloud Resume Challenge and in doing so I have gained  a wealth of &lt;strong&gt;knowledge and hands-on experience&lt;/strong&gt; in developing a cloud based application on AWS. I have encountered many issues but I have solved each in a &lt;strong&gt;logical and methodical manner&lt;/strong&gt; to overcome each challenge.&lt;/p&gt;

&lt;p&gt;It has been a fulfilling learning experience which I aim to continue building on through additional projects and ultimately a career move. &lt;/p&gt;

&lt;p&gt;Don't forget to check out my next post &lt;a href="https://dev.to/simongreen/part-4b-problem-solving-and-debugging-4eij"&gt;Part 4b: Problem solving and debugging&lt;/a&gt; to read about a selection of the issues I faced and how I was able to resolve them.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>terraform</category>
      <category>coding</category>
    </item>
    <item>
      <title>Part 3: Frontend / backend integration</title>
      <dc:creator>Simon Green</dc:creator>
      <pubDate>Mon, 23 Oct 2023 10:45:45 +0000</pubDate>
      <link>https://dev.to/simongreen/part-3-frontend-backend-integration-gb2</link>
      <guid>https://dev.to/simongreen/part-3-frontend-backend-integration-gb2</guid>
      <description>&lt;h2&gt;
  
  
  Included in this chapter...
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Step 1: API Gateway - Deploy API&lt;/li&gt;
&lt;li&gt;Step 2: Update index.html (hello JavaScript!)&lt;/li&gt;
&lt;li&gt;Step 3: API Gateway - Enable CORS&lt;/li&gt;
&lt;li&gt;Step 4: API Gateway - Re-deploy API&lt;/li&gt;
&lt;li&gt;Step 5: Testing in the browser&lt;/li&gt;
&lt;li&gt;Step 6: Throttling&lt;/li&gt;
&lt;li&gt;Summary&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ok, so we have the &lt;strong&gt;frontend created&lt;/strong&gt;, and the &lt;strong&gt;backend configured&lt;/strong&gt; and working, now it's time to &lt;strong&gt;connect the two together&lt;/strong&gt;!&lt;/p&gt;

&lt;p&gt;For this section I have broken it down into five steps&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1. Deploy the API onto a stage&lt;/li&gt;
&lt;li&gt;2. Update landing page index.html file with the necessary JavaScript code to invoke the API when the page is loaded and return a value &lt;/li&gt;
&lt;li&gt;3. Enabled CORS on the API &lt;/li&gt;
&lt;li&gt;4. Re-deployed the API&lt;/li&gt;
&lt;li&gt;5. Testing&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 1: API Gateway - Deploy API
&lt;/h2&gt;

&lt;p&gt;Following on from the previous tutorial and now with the API Gateway configured and triggering the Lambda function, it can now be deployed.&lt;/p&gt;

&lt;p&gt;In the &lt;strong&gt;API Gateway console&lt;/strong&gt;, navigate to the '&lt;strong&gt;API&lt;/strong&gt;' section and select the previously created API, in this case titled '&lt;strong&gt;api_lambda_update_visitor_count&lt;/strong&gt;', this will open the API's Resource section.&lt;/p&gt;

&lt;p&gt;In the '&lt;strong&gt;Resources&lt;/strong&gt;' section, click on the orange '&lt;strong&gt;Deploy API&lt;/strong&gt;' button and a dialogue box will open requesting the staging details of where to deploy the API.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stage = '&lt;strong&gt;New stage&lt;/strong&gt;'&lt;/li&gt;
&lt;li&gt;Stage name = &lt;strong&gt;add stage name&lt;/strong&gt; (for eg. 'stage_visitor_count_prod')&lt;/li&gt;
&lt;li&gt;Click '&lt;strong&gt;Deploy&lt;/strong&gt;'&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flsi2t749q6ipf1tf4w6u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flsi2t749q6ipf1tf4w6u.png" alt="Image description" width="800" height="766"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With the API now deployed to its stage, the following '&lt;strong&gt;Stage&lt;/strong&gt;' details will appear. Notice the provided '&lt;strong&gt;Invoke URL&lt;/strong&gt;', this will be needed shortly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F42css3lxkvwo76ha47b5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F42css3lxkvwo76ha47b5.png" alt="Image description" width="800" height="313"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Update index.html (hello JavaScript!)
&lt;/h2&gt;

&lt;p&gt;++++++++++++++++++++++++++++++++++++++++&lt;br&gt;
&lt;strong&gt;JavaScript side-note&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I have been using Python for around three years now but I have never needed to use JavaScript. I understood that for this challenge only a few lines of JavaScript code were required to be inserted into the index.html file to make this work. I did some background reading on the language and found the following two pages helpful that provided an overview and and advice on how to implement what I need, which turned out to be a *&lt;/em&gt;'fetch()' request**...&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;JavaScript language overview&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Language_overview"&gt;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Language_overview&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Fetch API – How to Make a GET Request and POST Request in JavaScript&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.freecodecamp.org/news/how-to-make-api-calls-with-fetch"&gt;https://www.freecodecamp.org/news/how-to-make-api-calls-with-fetch&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;++++++++++++++++++++++++++++++++++++++++&lt;/p&gt;

&lt;p&gt;The following JavaScript snippet contains a &lt;strong&gt;fetch&lt;/strong&gt; method and two &lt;strong&gt;then&lt;/strong&gt; methods which form a kind of step-function. &lt;/p&gt;

&lt;p&gt;I edited the following JavaScript in a text editor and for the 'fetch()' method I inserted the &lt;strong&gt;Invoke URL&lt;/strong&gt; taken from the recently deployed API stage (see previous step).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        fetch('https://xxxxxxxxx.execute-api.eu-west-3.amazonaws.com/stage_visitor_count_prod')
        .then(response =&amp;gt; response.json())
        .then((data) =&amp;gt; {
        document.getElementById('visitor_counter').innerText = data.Count
        })
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this bit of code updated, I inserted it between the &lt;strong&gt;head&lt;/strong&gt; tags in the &lt;strong&gt;index.html&lt;/strong&gt; file. &lt;/p&gt;

&lt;p&gt;When the page is loaded from the browser, this JavaScript will invoke the API and return an artefact / value for the '&lt;strong&gt;visitor_counter&lt;/strong&gt;', ready for insertion into the page.&lt;/p&gt;

&lt;p&gt;Now I have retrieved the value, I need to indicate where within the html I want it to be displayed, to do this I used &lt;strong&gt;&lt;span id=""&gt;&lt;/span&gt;&lt;/strong&gt; to place the value in the desired place:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;p&amp;gt;You are visitor number: &amp;lt;span id="visitor_counter" /&amp;gt;&amp;lt;/p&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the index.html file updated, it can now be &lt;strong&gt;re-uploaded to the S3 bucket&lt;/strong&gt; containing the website files to replace the existing version.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: API Gateway - Enable CORS
&lt;/h2&gt;

&lt;p&gt;Back in API Gateway, under the '&lt;strong&gt;Resources&lt;/strong&gt;' &amp;gt; '&lt;strong&gt;Resource details&lt;/strong&gt;' section, click on the white '&lt;strong&gt;Enable CORS&lt;/strong&gt;' button.&lt;/p&gt;

&lt;p&gt;In the page that opens, I updated the sections to configure the '&lt;strong&gt;Gateway responses&lt;/strong&gt;' and '&lt;strong&gt;Access-Control-Allow-Methods&lt;/strong&gt;' as shown below and clicked '&lt;strong&gt;Save&lt;/strong&gt;'.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9xysetegfyhkfjj26foj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9xysetegfyhkfjj26foj.png" alt="Image description" width="800" height="622"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: API Gateway - Re deploy API
&lt;/h2&gt;

&lt;p&gt;With CORS enabled I needed to redeploy the API again so back under '&lt;strong&gt;Resources&lt;/strong&gt;' click again on the orange '&lt;strong&gt;Deploy API&lt;/strong&gt;' button&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftazthjwk7n55t9bcrpk1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftazthjwk7n55t9bcrpk1.png" alt="Image description" width="800" height="298"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the '&lt;strong&gt;Deploy API&lt;/strong&gt;' dialogue box, select the previously created stage (ie. 'stage_visitor_count_prod') and click '&lt;strong&gt;Deploy&lt;/strong&gt;'.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7h4245cmolrh279asqf2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7h4245cmolrh279asqf2.png" alt="Image description" width="800" height="544"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5: Testing in the browser
&lt;/h2&gt;

&lt;p&gt;Now the basic config is complete, opening the live website URL I now see the visitor counter displaying correctly on the page. If the page is refreshed the value updates by 1, &lt;strong&gt;success&lt;/strong&gt;!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F01m0vdgzbhqj55nsas06.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F01m0vdgzbhqj55nsas06.png" alt="Image description" width="604" height="234"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 6: Throttling
&lt;/h2&gt;

&lt;p&gt;As the API is public facing I wanted to add an additional precaution to prevent any abuse from any excessive API calls, to do this I applied some throttling conditions to the API Gateway stage.&lt;/p&gt;

&lt;p&gt;Open API Gateway in the console and click on '&lt;strong&gt;Stages&lt;/strong&gt;'. As shown in this screen, the '&lt;strong&gt;Rate&lt;/strong&gt;' and '&lt;strong&gt;Burst&lt;/strong&gt;' indicate the current throttling limits, which for this case is too high. Click '&lt;strong&gt;Edit&lt;/strong&gt;' to make the changes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F59exac5cnlxll1bbu85p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F59exac5cnlxll1bbu85p.png" alt="Image description" width="800" height="314"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the next screen scroll down to the '&lt;strong&gt;Additional settings&lt;/strong&gt;' section to find the throttling settings. As I don't expect a huge impact for this given use case I reduced the limits considerably.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F34f5fhusi06zptmftg7k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F34f5fhusi06zptmftg7k.png" alt="Image description" width="800" height="712"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I can now see that these settings have been applied and the API is now much more resilient to attacks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fouwtvrl149s9hw8qn0p1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fouwtvrl149s9hw8qn0p1.png" alt="Image description" width="800" height="314"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;This was an interesting section, mainly focussed around the config, I now have a more solid understanding of how the individual components work and what is needed to get them to work together, as well as learning about and using JavaScript!&lt;/p&gt;

&lt;p&gt;With this solution primarily built using the AWS console, the next step of the challenge will be to represent the cloud resources as configuration and implement a &lt;strong&gt;continuous integration (CI) deployment pipeline&lt;/strong&gt;. For this I will be using a combination of &lt;strong&gt;GitLab&lt;/strong&gt; and &lt;strong&gt;Terraform&lt;/strong&gt; to create my &lt;strong&gt;Infrastructure as Code&lt;/strong&gt; (IaC).&lt;/p&gt;

&lt;p&gt;This is great stuff and I'm really enjoying what I'm learning, onwards and upwards!&lt;/p&gt;

&lt;p&gt;Thanks for reading!&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Part 2: Configure the backend</title>
      <dc:creator>Simon Green</dc:creator>
      <pubDate>Wed, 04 Oct 2023 11:52:59 +0000</pubDate>
      <link>https://dev.to/simongreen/part-2-configure-the-backend-2gm9</link>
      <guid>https://dev.to/simongreen/part-2-configure-the-backend-2gm9</guid>
      <description>&lt;h2&gt;
  
  
  Included in this chapter...
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Objective&lt;/li&gt;
&lt;li&gt;AWS services used&lt;/li&gt;
&lt;li&gt;Step 1: Create the DynamoDB table&lt;/li&gt;
&lt;li&gt;Step 2: Create the IAM Role&lt;/li&gt;
&lt;li&gt;Step 3: Create the AWS Lambda function&lt;/li&gt;
&lt;li&gt;Step 4: Create the API Gateway configuration&lt;/li&gt;
&lt;li&gt;Summary&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Objective
&lt;/h2&gt;

&lt;p&gt;For this part of implementing the &lt;strong&gt;website’s visitor counter&lt;/strong&gt; I will be working on several steps as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a &lt;strong&gt;DynamoDB&lt;/strong&gt; table that will contain a single row of two columns, the latter column will contain the current visitor count value. &lt;/li&gt;
&lt;li&gt;Create the &lt;strong&gt;IAM policy&lt;/strong&gt; and &lt;strong&gt;role&lt;/strong&gt; to allow &lt;strong&gt;Lambda&lt;/strong&gt; to access and update &lt;strong&gt;DynamoDB&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Create a &lt;strong&gt;Lambda&lt;/strong&gt; function that will 1) access &lt;strong&gt;DynamoDB&lt;/strong&gt; and get the current visitor count, increase it by 1 and 2) replace replace the value back into &lt;strong&gt;DynamoDB&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Create an &lt;strong&gt;API Gateway&lt;/strong&gt; that will invoke the above &lt;strong&gt;Lambda&lt;/strong&gt; function, the API will later be configured to CloudFront and triggered when the resume page is opened.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5shb0qjp97ncumgdxbxx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5shb0qjp97ncumgdxbxx.png" alt="Image description" width="559" height="158"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  AWS services used
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;DynamoDB&lt;/li&gt;
&lt;li&gt;IAM&lt;/li&gt;
&lt;li&gt;AWS Lambda&lt;/li&gt;
&lt;li&gt;API Gateway&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 1: DynamoDB - create the table
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Open DynamoDB from the AWS console&lt;/li&gt;
&lt;li&gt;Select the region as ‘&lt;strong&gt;Europe (Paris)&lt;/strong&gt;’, ‘&lt;strong&gt;eu-west-3’&lt;/strong&gt;- Click on ‘&lt;strong&gt;Create table&lt;/strong&gt;’&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftlfij7i4el6u6s3snp9z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftlfij7i4el6u6s3snp9z.png" alt="Image description" width="800" height="118"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the following '&lt;strong&gt;Create table&lt;/strong&gt;' page, enter the following details:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Table details&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Add the table name&lt;/li&gt;
&lt;li&gt;Partition key

&lt;ul&gt;
&lt;li&gt;Add ‘&lt;strong&gt;id&lt;/strong&gt;’ and select ‘&lt;strong&gt;String&lt;/strong&gt;’&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Table settings&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Select ‘&lt;strong&gt;Customize settings&lt;/strong&gt;’&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read/write capacity settings&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Select ‘&lt;strong&gt;On-demand&lt;/strong&gt;’&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scroll to bottom&lt;/strong&gt; 

&lt;ul&gt;
&lt;li&gt;and click on ‘&lt;strong&gt;Create table&lt;/strong&gt;’&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fekpg9h37cetct3ug77cs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fekpg9h37cetct3ug77cs.png" alt="Image description" width="800" height="1092"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F19gv82fymthggrteizcm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F19gv82fymthggrteizcm.png" alt="Image description" width="800" height="1072"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu35z5oyx8ztk2e98bfhs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu35z5oyx8ztk2e98bfhs.png" alt="Image description" width="800" height="288"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;-- &lt;/p&gt;

&lt;p&gt;The below image shows how the table will look once set up. Note that the row count will not increase, only value under '&lt;strong&gt;visitor_count&lt;/strong&gt;' will be updated.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5r135nr51boj3ngu960l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5r135nr51boj3ngu960l.png" alt="visitor count" width="766" height="162"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: IAM - Create the Role
&lt;/h2&gt;

&lt;p&gt;To enable Lambda to securely access DynamoDB I will need to &lt;strong&gt;create a policy and attach it to an IAM role&lt;/strong&gt;, the role will later be attached to the Lambda function. The  role will be based on a policy of &lt;strong&gt;allowing ‘read’, ‘write’ and ‘update’ operations&lt;/strong&gt; when Lambda connects to DynamoDB.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open the IAM console&lt;/li&gt;
&lt;li&gt;Click on ‘&lt;strong&gt;Policies&lt;/strong&gt;’ and then ‘&lt;strong&gt;Create Policy&lt;/strong&gt;’&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd7ajjoriy91n1u0gd9j1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd7ajjoriy91n1u0gd9j1.png" alt="Image description" width="800" height="394"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h4&gt;
  
  
  Policy creation (AWS step 1 of 2): Specify permissions
&lt;/h4&gt;

&lt;p&gt;Click on the ‘&lt;strong&gt;JSON&lt;/strong&gt;’ tab and enter the below code into the ‘&lt;strong&gt;Policy editor&lt;/strong&gt;’ &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fabj7q4lpkgbe8apwal22.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fabj7q4lpkgbe8apwal22.png" alt="Image description" width="800" height="691"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;IAM policy for Lambda so it can access DynamoDB:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "dynamodb:GetItem",
                "dynamodb:PutItem",
                "dynamodb:UpdateItem",
                "dynamodb:DescribeTable"
            ],
            "Effect": "Allow",
            "Resource": "arn:aws:dynamodb:eu-west-3:************:table/visitor_count_table"
        }
    ]
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h4&gt;
  
  
  Policy creation (AWS step 2 of 2): Review and create
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Add a name for the policy (for example 'access_dynamodb_from_lambda')&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ful93lw6hqf43r240ciw9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ful93lw6hqf43r240ciw9.png" alt="Image description" width="800" height="796"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Policy should now be created. You can click on the ‘&lt;strong&gt;+/-&lt;/strong&gt;’ button on the left of the policy name to view more details.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F88uv454keq19stzxgdgx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F88uv454keq19stzxgdgx.png" alt="Image description" width="800" height="633"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Next, on the left-hand side of the console, click on '&lt;strong&gt;Roles&lt;/strong&gt;' and '&lt;strong&gt;Create role&lt;/strong&gt;'. Here I'll be attaching the policy to the role. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcrcbfzu2f7ugbirqo12d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcrcbfzu2f7ugbirqo12d.png" alt="Image description" width="800" height="338"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h4&gt;
  
  
  Role creation (AWS step 1 of 3): Select trusted entity
&lt;/h4&gt;

&lt;p&gt;On the ‘&lt;strong&gt;Select trusted entity&lt;/strong&gt;’ page, select ‘&lt;strong&gt;AWS service&lt;/strong&gt;’ and under &lt;strong&gt;Use case&lt;/strong&gt; select ‘&lt;strong&gt;Lambda&lt;/strong&gt;’, then click on ‘&lt;strong&gt;Next&lt;/strong&gt;’&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frujcm7bghfipld0387al.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frujcm7bghfipld0387al.png" alt="Image description" width="800" height="557"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Role creation (AWS step 2 of 3): Add permissions
&lt;/h4&gt;

&lt;p&gt;On the ‘&lt;strong&gt;Add permissions&lt;/strong&gt;’ screen add the following two permissions by searching for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;AWSLambdaBasicExecutionRole&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;access_dynamodb_from_lambda&lt;/strong&gt; (as created in the above step)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then scroll to the bottom and click ‘&lt;strong&gt;Create role&lt;/strong&gt;’&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhi7rypl8q6pufowxo1f7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhi7rypl8q6pufowxo1f7.png" alt="Image description" width="800" height="246"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Role creation (AWS step 3 of 3): Name, review, and create
&lt;/h4&gt;

&lt;p&gt;Enter a name for the role (for example:  'lambda_dynamodb_role'), scroll to the bottom and click ‘&lt;strong&gt;Create role&lt;/strong&gt;’.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffrf3bz06co65lr6skogr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffrf3bz06co65lr6skogr.png" alt="Image description" width="800" height="298"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Create the AWS Lambda function
&lt;/h2&gt;

&lt;p&gt;In this step I´ll be 1) setting up the Lambda function that will connect to DynamoDB, 2) get a value, 3) perform a calculation and 4) return (replace) it back into the database. Let's get started...&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open AWS Lambda in the AWS console&lt;/li&gt;
&lt;li&gt;Select the region as ‘&lt;strong&gt;Europe (Paris)&lt;/strong&gt;’, ‘&lt;strong&gt;eu-west-3’&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Click on ‘&lt;strong&gt;Create function&lt;/strong&gt;’&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fadbsrvrp3shk36wpyqsy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fadbsrvrp3shk36wpyqsy.png" alt="Image description" width="800" height="120"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the ‘&lt;strong&gt;Create function&lt;/strong&gt;’ section I selected the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;‘&lt;strong&gt;Author from scratch&lt;/strong&gt;’&lt;/li&gt;
&lt;li&gt;'&lt;strong&gt;Basic information&lt;/strong&gt;'

&lt;ul&gt;
&lt;li&gt;‘Function name’

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Crc_lambda_func_dynamodb_update&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Runtime

&lt;ul&gt;
&lt;li&gt;‘&lt;strong&gt;Python 3.11&lt;/strong&gt;’&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Permissions&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Expand ‘&lt;strong&gt;Change default execution role&lt;/strong&gt;’&lt;/li&gt;
&lt;li&gt;Under ‘&lt;strong&gt;Execution role&lt;/strong&gt;’, select ‘&lt;strong&gt;Use an existing role&lt;/strong&gt;’&lt;/li&gt;
&lt;li&gt;Under ‘&lt;strong&gt;Existing role&lt;/strong&gt;’ locate the IAM role created above, in my case it is ‘&lt;strong&gt;lambda_dynamodb_role&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Then click the orange ‘&lt;strong&gt;Create function&lt;/strong&gt;’ button at the bottom.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy79wx3tn9j9m57lmapah.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy79wx3tn9j9m57lmapah.png" alt="Image description" width="800" height="704"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When created, the following screen will appear:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frvfavo7knsm2csyfa7xi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frvfavo7knsm2csyfa7xi.png" alt="Image description" width="800" height="602"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h4&gt;
  
  
  An overview of the Python script
&lt;/h4&gt;

&lt;p&gt;As a reminder the DynamoDB table has been set up like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fukgirr4jj8jkfj2nugm0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fukgirr4jj8jkfj2nugm0.png" alt="Image description" width="766" height="162"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The following Python code that I developed was pasted into the ‘&lt;strong&gt;lambda_function.py&lt;/strong&gt;’ file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import json
import boto3
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('visitor_count_table')

def lambda_handler(event, context):
# The 'event' object contains information from the invoking service
# The 'context' object is passed to your function by Lambda at runtime
    try:
        # 1. Get Item
        response = table.get_item(Key={
                'id':'visitors'
        })
        visitor_count = response['Item']['visitor_count']
        visitor_count += 1
        # timestamp
        date_time = datetime.now()
        str_date_time = date_time.strftime("%Y-%m-%d, %H:%M:%S")
        # print("Current timestamp", str_date_time)
        # 2. Put Item
        response = table.put_item(Item={
                'id':'visitors',
                'visitor_count': visitor_count,
                'timestamp_utc': str_date_time
        })
        return{'Count':visitor_count}
        status_code = response["ResponseMetadata"]["HTTPStatusCode"]
        print("HTTPStatusCode = {}".format(status_code))
        print("Successfully updated - visitor_count now = {}".format(visitor_count))
    except:
        # timestamp
        date_time = datetime.now()
        str_date_time = date_time.strftime("%Y-%m-%d, %H:%M:%S")
        # print("Current timestamp", str_date_time)
        # If the item doesn't exist, create it
        table.put_item(
            Item={
                'id': 'visitors',
                'visitor_count': 1,
                'timestamp_utc': str_date_time
            }
        )
        return{'Count':visitor_count}
        status_code = response["ResponseMetadata"]["HTTPStatusCode"]
        print("HTTPStatusCode = {}".format(status_code))
        print("visitor_count did not exist, recreated, visitor_count now equals: 1")

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is an overview of what it does:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Declares that the Lambda function will be working with DynamoDB&lt;/li&gt;
&lt;li&gt;States the DynamoDB table '&lt;strong&gt;visitor_count_table&lt;/strong&gt;'&lt;/li&gt;
&lt;li&gt;Function:

&lt;ul&gt;
&lt;li&gt;get_item: return the row where 'id' = 'visitors'

&lt;ul&gt;
&lt;li&gt;Declare 'visitor_count' from the DynamoDB table as a variable &lt;/li&gt;
&lt;li&gt;Increase the count by 1&lt;/li&gt;
&lt;li&gt;Put_item:  replace the visitors row with the new value&lt;/li&gt;
&lt;li&gt;Returns the updated visitor_count value&lt;/li&gt;
&lt;li&gt;Print the httpstatus code to the console&lt;/li&gt;
&lt;li&gt;Print confirmation that operation has been performed with new count&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Step 4: Create the API Gateway configuration
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Open API Gateway in the AWS console&lt;/li&gt;
&lt;li&gt;Select the region as '&lt;strong&gt;Europe (Paris)&lt;/strong&gt;', '&lt;strong&gt;eu-west-3'&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Click on '&lt;strong&gt;Create API&lt;/strong&gt;'&lt;/li&gt;
&lt;li&gt;Choose the API type '&lt;strong&gt;REST API&lt;/strong&gt;' and click '&lt;strong&gt;Build&lt;/strong&gt;'&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ubuki7da7fwyz4da6rd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ubuki7da7fwyz4da6rd.png" alt="Image description" width="800" height="1200"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Create REST API&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API details

&lt;ul&gt;
&lt;li&gt;‘&lt;strong&gt;New API&lt;/strong&gt;’ &lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Add an API name 
-(for example: '&lt;strong&gt;api_lambda_update_visitor_count&lt;/strong&gt;')&lt;/li&gt;
&lt;li&gt;API endpoint should be ‘&lt;strong&gt;Regional&lt;/strong&gt;’&lt;/li&gt;
&lt;li&gt;Click the orange ‘&lt;strong&gt;Create API&lt;/strong&gt;’ button.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxtglaxjkswkn2u39sv4i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxtglaxjkswkn2u39sv4i.png" alt="Image description" width="800" height="607"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resources&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In the ‘&lt;strong&gt;Methods&lt;/strong&gt;’ section, click on ‘&lt;strong&gt;Create method&lt;/strong&gt;’&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa3cqt4tfkylb0ca2txj8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa3cqt4tfkylb0ca2txj8.png" alt="Image description" width="800" height="274"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here I need to create a ‘&lt;strong&gt;GET&lt;/strong&gt;’ method that I did with these steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For ‘&lt;strong&gt;Method type&lt;/strong&gt;’, select ‘&lt;strong&gt;GET&lt;/strong&gt;’&lt;/li&gt;
&lt;li&gt;For ‘&lt;strong&gt;Integration type&lt;/strong&gt;’, select ‘&lt;strong&gt;Lambda function&lt;/strong&gt;’&lt;/li&gt;
&lt;li&gt;Select the Availability Zone, the same as used for the Lambda function (eu-west-3)&lt;/li&gt;
&lt;li&gt;From the drop down menu, select the Lambda function that we previously created&lt;/li&gt;
&lt;li&gt;Click ‘&lt;strong&gt;Create method&lt;/strong&gt;’&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6730yhhjklxxj6lgaqpq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6730yhhjklxxj6lgaqpq.png" alt="Create method" width="800" height="870"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Back on the ‘Resources’ Page&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open up the ‘&lt;strong&gt;Test&lt;/strong&gt;’ tab&lt;/li&gt;
&lt;li&gt;Then click on the orange ‘&lt;strong&gt;Test&lt;/strong&gt;’ button&lt;/li&gt;
&lt;li&gt;This should invoke the Lambda function&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv0tody30elf2ylwmbxlq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv0tody30elf2ylwmbxlq.png" alt="Resources Get method" width="800" height="589"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Checking the DynamoDB table, the visitor_count value should now increment by 1 each time the API is triggered. &lt;/p&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;This was a challenging section to complete, mainly focussed around getting the Lambda function to replace the updated value back into the database. As a result I now have a much better understanding of how this works.&lt;/p&gt;

&lt;p&gt;Aside from this, I found it just a simple case of connecting the individual components together.&lt;/p&gt;

&lt;p&gt;Next step, connecting the frontend and the backend! &lt;/p&gt;

&lt;p&gt;Thanks for reading!&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Part 1: Building the frontend</title>
      <dc:creator>Simon Green</dc:creator>
      <pubDate>Wed, 04 Oct 2023 09:16:01 +0000</pubDate>
      <link>https://dev.to/simongreen/part-1-building-the-frontend-227p</link>
      <guid>https://dev.to/simongreen/part-1-building-the-frontend-227p</guid>
      <description>&lt;h2&gt;
  
  
  Included in this chapter...
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Objective&lt;/li&gt;
&lt;li&gt;AWS services used&lt;/li&gt;
&lt;li&gt;S3 bucket&lt;/li&gt;
&lt;li&gt;AWS Certificate Manager (ACM)&lt;/li&gt;
&lt;li&gt;CloudFlare (&lt;a href="http://www.cloudflare.com"&gt;www.cloudflare.com&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;AWS CloudFront&lt;/li&gt;
&lt;li&gt;Testing&lt;/li&gt;
&lt;li&gt;Summary&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Objective
&lt;/h2&gt;

&lt;p&gt;To begin the first part of the project I needed to create a simple landing page that end users will be able to open from the web, this will act as my single-page online resume. &lt;/p&gt;

&lt;p&gt;I used a html/css template that I used to display and stylise  my resume details. The two files were then stored in an S3 bucket enabled for static website hosting.&lt;/p&gt;

&lt;p&gt;One of the requirements for this section is that the S3 website &lt;strong&gt;URL should default to use https&lt;/strong&gt;, to enable this I needed to use AWS CloudFront.&lt;/p&gt;

&lt;p&gt;I also purchased a domain name (simon-green.uk) from a third-party registrar (CloudFlare) that I would later need to configure to point to CloudFront. &lt;/p&gt;

&lt;p&gt;The following is the step-by-step process I used to overcome this part of the challenge.&lt;/p&gt;




&lt;h3&gt;
  
  
  AWS services used for this part:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Amazon S3&lt;/li&gt;
&lt;li&gt;CloudFront&lt;/li&gt;
&lt;li&gt;Amazon Certificate Manager (ACM)&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  S3 bucket
&lt;/h3&gt;

&lt;p&gt;With the website files added to the bucket, &lt;strong&gt;initial testing&lt;/strong&gt; was done locally and &lt;strong&gt;using the S3 website endpoint URL&lt;/strong&gt; that can be found on the bucket page, 'Properties' tab, at the bottom in the 'Static website hosting' section.&lt;/p&gt;

&lt;p&gt;One important thing to note here is that the &lt;strong&gt;bucket had to be named with the 'www' subdomain and the root domain&lt;/strong&gt;, ie '&lt;a href="http://www.simon-green.uk"&gt;www.simon-green.uk&lt;/a&gt;', otherwise the functionality would not work.&lt;/p&gt;




&lt;h3&gt;
  
  
  AWS Certificate Manager (ACM)
&lt;/h3&gt;

&lt;p&gt;I purchased a domain name from CloudFlare which I then needed to route to CloudFront by configuring certificates and DNS settings, CloudFront here will point to the S3 bucket and will also be used to enforce an https connection.&lt;/p&gt;

&lt;p&gt;The below diagram shows the process to request a Public Certificate using Amazon Certificate Manager (ACM)&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fya4fv122d2jptdftkiiq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fya4fv122d2jptdftkiiq.png" alt="Image description" width="800" height="320"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;To request a Certificate:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open &lt;strong&gt;Amazon Certificate Manager (ACM)&lt;/strong&gt; and click on '&lt;strong&gt;Request a certificate&lt;/strong&gt;'&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7c9x3ld0lltc1kp14m5g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7c9x3ld0lltc1kp14m5g.png" alt="ACM" width="800" height="328"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;ul&gt;
&lt;li&gt;Request '&lt;strong&gt;Public certificate&lt;/strong&gt;'&lt;/li&gt;
&lt;li&gt;Add '&lt;strong&gt;Fully qualified domain name&lt;/strong&gt;' (simon-green.uk)&lt;/li&gt;
&lt;li&gt;Validation method should be &lt;strong&gt;DNS validation&lt;/strong&gt; (and not 'Email validation)'&lt;/li&gt;
&lt;li&gt;Click the orange '&lt;strong&gt;Request&lt;/strong&gt;' button at the bottom&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7gkapy4ad2p2u4i9mdme.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7gkapy4ad2p2u4i9mdme.png" alt="request_certificate" width="800" height="373"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;Now under &lt;strong&gt;ACM&lt;/strong&gt; → &lt;strong&gt;List certificates&lt;/strong&gt;, I see the requested certificate with &lt;strong&gt;Status = 'Pending Validation'&lt;/strong&gt;. This won't resolve until I complete the next step/s.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5j5t7e51gzkas0b1jaw6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5j5t7e51gzkas0b1jaw6.png" alt="cert pend val" width="800" height="174"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;Click on the blue '&lt;strong&gt;Certificate ID&lt;/strong&gt;' and in the '&lt;strong&gt;Domains&lt;/strong&gt;' section, I'm interested in the string values under '&lt;strong&gt;CNAME name&lt;/strong&gt;' and the '&lt;strong&gt;CNAME value&lt;/strong&gt;'. These will be used to create DNS records on CloudFlare.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx7pezg23eofy6a4h03qi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx7pezg23eofy6a4h03qi.png" alt="domain details" width="800" height="159"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  CloudFlare
&lt;/h3&gt;

&lt;p&gt;In the third-party &lt;strong&gt;CloudFlare&lt;/strong&gt; console I navigated to the '&lt;strong&gt;Domain name management console&lt;/strong&gt; and on the left-hand side clicked on '&lt;strong&gt;DNS&lt;/strong&gt;', here under &lt;strong&gt;DNS management for '[Domain Name]&lt;/strong&gt;' -  click on '&lt;strong&gt;Add record&lt;/strong&gt;' which will expand the box where you can input details.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz75q58mh17qevb368g7i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz75q58mh17qevb368g7i.png" alt="CloudFlare 1" width="800" height="211"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;Enter the following details:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Type = &lt;strong&gt;CNAME&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Name = &lt;strong&gt;pasted in from ACM, 'CNAME name'&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Target = &lt;strong&gt;pasted in from ACM, 'CNAME value'&lt;/strong&gt; &lt;/li&gt;
&lt;li&gt;Proxy status = &lt;strong&gt;OFF&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;TTL = &lt;strong&gt;Auto&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Add comment if needed&lt;/li&gt;
&lt;li&gt;Click ‘&lt;strong&gt;Save&lt;/strong&gt;’&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fufw3wc92w8y2z1afn166.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fufw3wc92w8y2z1afn166.png" alt="Image description" width="800" height="525"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;With this done, back in &lt;strong&gt;ACM&lt;/strong&gt;, the &lt;strong&gt;previously requested certificate will soon show 'Status = Issued'&lt;/strong&gt; (in my experience this typically took less than 5 minutes.)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa3hnnf53q68s9jw7hcun.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa3hnnf53q68s9jw7hcun.png" alt="ISSUED" width="800" height="140"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  AWS CloudFront
&lt;/h3&gt;

&lt;p&gt;Now I need to create a &lt;strong&gt;CloudFront distribution&lt;/strong&gt;, this will act as the public accessible endpoint (that CloudFlare will point to), the distribution will then server content from the S3 bucket hosting the website using &lt;strong&gt;https&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In &lt;strong&gt;CloudFront&lt;/strong&gt;, click '&lt;strong&gt;Create distribution&lt;/strong&gt;' and update the following &lt;strong&gt;four&lt;/strong&gt; sections:  &lt;/p&gt;



&lt;h4&gt;
  
  
  1. (Create distribution) - Orign
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;For the '&lt;strong&gt;Origin domain&lt;/strong&gt;' drop down, select the S3 bucket where the website is hosted&lt;/li&gt;
&lt;li&gt;There will be a warning box appear suggesting to '&lt;strong&gt;Use website endpoint&lt;/strong&gt;' rather than the 'bucket endpoint'&lt;/li&gt;
&lt;li&gt;Click the button '&lt;strong&gt;Use website endpoint&lt;/strong&gt;'&lt;/li&gt;
&lt;li&gt;Origin path = leave &lt;strong&gt;empty&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Name = &lt;strong&gt;leave as is&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Origin access = &lt;strong&gt;Public&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Enable Origin Shield = &lt;strong&gt;No&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp0wfq2ozr5fe2l6uu1ak.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp0wfq2ozr5fe2l6uu1ak.png" alt="Image description" width="800" height="423"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fis9vfy1t7j0nyt8bww19.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fis9vfy1t7j0nyt8bww19.png" alt="Icloudfront1" width="800" height="686"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;h4&gt;
  
  
  2. (Create distribution) - Default cache behavior
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Compress objects automatically = &lt;strong&gt;No&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Viewer Protocol Policy = &lt;strong&gt;Redirect HTTP to HTTPS&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Cache key and origin requests

&lt;ul&gt;
&lt;li&gt;Select '&lt;strong&gt;Cache policy and origin request policy (recommended)&lt;/strong&gt;'&lt;/li&gt;
&lt;li&gt;Under '&lt;strong&gt;Cache Policy&lt;/strong&gt;' select &lt;strong&gt;'Caching disabled'&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8j4iaa5rtguy8092yksc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8j4iaa5rtguy8092yksc.png" alt="Image description" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;h4&gt;
  
  
  3. (Create distribution) - Web Application Firewall (WAF)
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Select '&lt;strong&gt;Do not enable security protections&lt;/strong&gt;'&lt;/li&gt;
&lt;/ul&gt;



&lt;h4&gt;
  
  
  4.Create distribution) - Settings
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Select '&lt;strong&gt;Use only North America and Europe&lt;/strong&gt;'&lt;/li&gt;
&lt;li&gt;In section '&lt;strong&gt;Alternate domain name (CNAME)&lt;/strong&gt;'

&lt;ul&gt;
&lt;li&gt;Click on the '&lt;strong&gt;Add item&lt;/strong&gt;' button&lt;/li&gt;
&lt;li&gt;Enter domain name ('&lt;strong&gt;simon-green.uk&lt;/strong&gt;')&lt;/li&gt;
&lt;li&gt;Again click ‘&lt;strong&gt;Add item&lt;/strong&gt;’ and add '&lt;strong&gt;&lt;a href="http://www.simon-green.uk"&gt;www.simon-green.uk&lt;/a&gt;&lt;/strong&gt;'

&lt;ul&gt;
&lt;li&gt;Under '&lt;strong&gt;Custom SSL certificate - optional&lt;/strong&gt;'&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Click on '&lt;strong&gt;Choose certificate&lt;/strong&gt;' box&lt;/li&gt;
&lt;li&gt;Click the drop-down and under the heading '&lt;strong&gt;ACM certificates&lt;/strong&gt;', select the  certificate that was created in above step (it starts with the domain name), see below screenshot, in my case it is the only option.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Scroll to the bottom and click '&lt;strong&gt;Create Distribution&lt;/strong&gt;'&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6bxuoolon9yv0uc09i6f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6bxuoolon9yv0uc09i6f.png" alt="Image description" width="800" height="301"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  With the distribution created
&lt;/h3&gt;

&lt;p&gt;The CloudFront distribution will be in a '&lt;strong&gt;Deploying&lt;/strong&gt;' status for &lt;strong&gt;around 5 minutes&lt;/strong&gt; until 'Deploying' is replaced with a '&lt;strong&gt;Last modified date&lt;/strong&gt;'.&lt;/p&gt;

&lt;p&gt;The final step is to &lt;strong&gt;point&lt;/strong&gt; the &lt;strong&gt;domain name&lt;/strong&gt; on CloudFlare &lt;strong&gt;to&lt;/strong&gt; the &lt;strong&gt;CloudFront&lt;/strong&gt; distribution's URL.&lt;/p&gt;

&lt;p&gt;Once the distribution has finished 'Deploying', open it and under '&lt;strong&gt;General&lt;/strong&gt;' →' '&lt;strong&gt;Details&lt;/strong&gt;' click to copy the '&lt;strong&gt;Distribution domain name&lt;/strong&gt;'.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdnnjh3g5sqzhan4j460q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdnnjh3g5sqzhan4j460q.png" alt="Image description" width="800" height="218"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now navigate one more time to the &lt;strong&gt;CloudFlare DNS section&lt;/strong&gt; and click '&lt;strong&gt;Add record&lt;/strong&gt;' and complete it with the following details:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Type = &lt;strong&gt;CNAME&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Name = &lt;strong&gt;domain name&lt;/strong&gt; (simon-green.uk)&lt;/li&gt;
&lt;li&gt;Target = &lt;strong&gt;paste in the CloudFront 'Distribution domain name' URL&lt;/strong&gt;, from above. 

&lt;ul&gt;
&lt;li&gt;Important, you need to remove the network protocol ('https://')&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Click '&lt;strong&gt;Save&lt;/strong&gt;'&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhqdvi0t5zhdo7wn9684v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhqdvi0t5zhdo7wn9684v.png" alt="Image description" width="800" height="461"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Testing
&lt;/h3&gt;

&lt;p&gt;After some &lt;strong&gt;five minutes&lt;/strong&gt; to allow for propagation, I tested the following and worked well on &lt;strong&gt;various browsers and devices&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The following addresses opened the site (resolving to https)

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;simon-green.uk&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="http://www.simon-green.uk"&gt;www.simon-green.uk&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;If an http request was served (&lt;strong&gt;http&lt;/strong&gt;://&lt;a href="http://www.simon-green.uk"&gt;&lt;/a&gt;&lt;a href="http://www.simon-green.uk"&gt;www.simon-green.uk&lt;/a&gt;), it automatically resolved to https (&lt;strong&gt;https&lt;/strong&gt;://&lt;a href="http://www.simon-green.uk"&gt;&lt;/a&gt;&lt;a href="http://www.simon-green.uk"&gt;www.simon-green.uk&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also when confirming the &lt;strong&gt;Domain name / SLL certificate&lt;/strong&gt; (using   &lt;a href="https://dnschecker.org/ssl-certificate-examination.php"&gt;dnschecker.org&lt;/a&gt;) it shows it was successfully issued by Amazon.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgvm8frk0bthd7elgvlm5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgvm8frk0bthd7elgvlm5.png" alt="Image description" width="800" height="345"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Summary
&lt;/h3&gt;

&lt;p&gt;Initially I had some issues with incorrect config, where the webpage was displaying correctly on some devices but was returning &lt;strong&gt;403 (Service Unavailable)&lt;/strong&gt; on others. &lt;/p&gt;

&lt;p&gt;The solution was in understanding the correct configuration of &lt;strong&gt;DNS certificates&lt;/strong&gt; and &lt;strong&gt;DNS settings&lt;/strong&gt; on both &lt;strong&gt;ACM&lt;/strong&gt; and &lt;strong&gt;CloudFlare&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;This was an interesting part of the project, I now have a much better understanding of the components and how the configuration works (especially with a third-party involved). Also the testing and resolution process was &lt;/p&gt;

&lt;p&gt;Next step, configure the back end...&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloudresumechallenge</category>
      <category>tutorial</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Into the Cloud...</title>
      <dc:creator>Simon Green</dc:creator>
      <pubDate>Mon, 02 Oct 2023 12:25:57 +0000</pubDate>
      <link>https://dev.to/simongreen/cloud-resume-challenge-intro-4co7</link>
      <guid>https://dev.to/simongreen/cloud-resume-challenge-intro-4co7</guid>
      <description>&lt;p&gt;Welcome to this series of blog posts where I will be documenting my journey into the cloud, in particular the completion of the Cloud Resume Challenge!&lt;/p&gt;




&lt;h2&gt;
  
  
  My background
&lt;/h2&gt;

&lt;p&gt;I have over 10 years of broad data and analytical experience,   with a core background in &lt;strong&gt;Trust and Safety&lt;/strong&gt; and &lt;strong&gt;Product Analytics&lt;/strong&gt; roles at &lt;strong&gt;letgo/OLX&lt;/strong&gt;. I feel comfortable using IT, I have solid experience using &lt;strong&gt;SQL&lt;/strong&gt;, &lt;strong&gt;Python&lt;/strong&gt; and other analytics and automation tools to solve complex business problem. &lt;/p&gt;

&lt;p&gt;I have loved building and fixing things since I was a child, always taking things apart and learning how they work before putting them back together. &lt;/p&gt;

&lt;p&gt;In 2018 I began using SQL to help detect fraudulent account and analyse rule performance from 'big data' Redshift databases. In 2019 I began learning Python and soon developed automation workflows saving hours of manual work. I then became interested in &lt;strong&gt;machine learning&lt;/strong&gt; and created a text detection model using PyTorch that could detect with good accuracy prohibited contact details within product listing photos.&lt;/p&gt;

&lt;p&gt;Towards the end of 2023 I decided I wanted to change career direction and focus more on cloud computing especially involving AWS. &lt;/p&gt;




&lt;h2&gt;
  
  
  My new direction
&lt;/h2&gt;

&lt;p&gt;I decided to focus on AWS as it is used by both Letgo and OLX  as their cloud computing platform, also AWS has the majority global market share so it was the logical platform to begin focussing on.&lt;/p&gt;

&lt;p&gt;After some months of studying I achieved the intermediate level &lt;a href="https://www.credly.com/badges/5d851d0c-792e-4376-86c9-591e1b239147"&gt;&lt;strong&gt;AWS Certified Developer – Associate&lt;/strong&gt;&lt;/a&gt; certification in September 2023. With this in hand I immediately started using AWS to build personal projects, this is where the fun begins.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffsgsxvh1k8z27vi3uny7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffsgsxvh1k8z27vi3uny7.png" alt="cert" width="256" height="284"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Cloud Resume Challenge
&lt;/h2&gt;

&lt;p&gt;To gain some solid experience using AWS I decided to do the &lt;a href="https://cloudresumechallenge.dev/docs/the-challenge/aws/"&gt;&lt;strong&gt;The Cloud Resume Challenge&lt;/strong&gt;&lt;/a&gt;, developed by Forrest Brazeal who is an 'AWS Serverless Hero' who previously worked as 'Head of Developer Media' at Google Cloud. The resume challenge he devised tells you what you need to do, but not how to do it, therefore the challenger needs to learn and figure out how to accomplish each of the steps with very little guidance except for the expected output. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjqkllhgyukqsxercu1th.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjqkllhgyukqsxercu1th.png" alt="book cover" width="226" height="330"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Below is a summary of the &lt;strong&gt;Cloud Resume Challenge&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Building the frontend:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a simple html page and host it on S3&lt;/li&gt;
&lt;li&gt;Purchase a domain name &lt;/li&gt;
&lt;li&gt;Use AWS CloudFront to only allow https connections&lt;/li&gt;
&lt;li&gt;Configure all certificates etc&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Configure the backend (visitor counter):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build the &lt;strong&gt;website visitor counter&lt;/strong&gt; API using DynamoDB, Lambda (Python) and API Gateway&lt;/li&gt;
&lt;li&gt;All config and logic will be achieved using these services, nothing templated.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Frontend / backend integration:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Connecting everything together&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Infrastructure as Code:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Configure the AWS services for this project as IaC&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Automation / CI&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Introducing testing and automation &lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;To briefly skip to the end of the project, the following diagram shows an overview of what I have built:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdxxoqr1srg7pfnk4fyiu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdxxoqr1srg7pfnk4fyiu.png" alt="Image description" width="800" height="1051"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;The following series of blog posts documents the actions I took to successfully complete the Cloud Resume Challenge, partly for my benefit (so I can recall the steps at a later date) and partly to share with anyone who may be interested.&lt;/p&gt;

&lt;p&gt;A lot of reading, learning, tutorials/courses, testing and trial and error has happened from my side before this documentation could been written. My main aim here was to learn as much as possible through hands-on experience. So although I get straight into how I did it, in the majority of cases there was a fairly steep, yet overall enjoyable learning curve. &lt;/p&gt;

&lt;p&gt;Parts 1-3 involved clicking around in the AWS console, whereas part 4 involved configuring the entire backend as IaC using Terraform.  &lt;/p&gt;

&lt;p&gt;So onto the next post: Part 1: Building the frontend&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>blog</category>
    </item>
  </channel>
</rss>
