Building a weather monitor with AWS SAM and Terraform — part 3
Source code can be found here
This article is the “visualiser” part of the weather monitor. It is comprised of a Lambda function and S3 bucket. The Lambda function pulls weather data from DynamoDB, generates index.html
and pushes it up to S3. S3 is configured as static website.
I used Terraform to provision S3 static website because Terraform has good documentation about S3. I also referred to this article.
Of course I can front the S3 with CloudFront to make it support SSL/TLS, but I’ll skip that for now.
Sample index.html
I created a sample html to help me build the UI. The Lambda replaces the x-axis with datetime, and y-axis with temperature. The generated index.html
looks like this:
S3 Configurations
The bucket policy was obtained from this article on AWS
Converting it to Terraform:
.deploy/terraform/static-site/iam.tf
data “aws_iam_policy_document” “website_policy” {
statement {
actions = [
“s3:GetObject”
]
principals {
identifiers = [“*”]
type = “AWS”
}
resources = [
“arn:aws:s3:::${var.bucket_name}/*”
]
}
}
The bucket also needs to enable all public access:
And this part does exactly that:
.deploy/terraform/static-site/s3.tf
resource "aws_s3_bucket_public_access_block" "public_access_block" {
bucket = aws_s3_bucket.website_bucket.id
block_public_acls = false
block_public_policy = false
ignore_public_acls = false
restrict_public_buckets = false
}
Final Configured Bucket
The final configured bucket looks like this:
Lambda querying DynamoDB
The primary key of the table is a composite key: “city” is the partition key, and “datetime” is the sort key.
When Lambda queries the table, it has to speficy the partition key:
src/app.ts
var params: AWS.DynamoDB.DocumentClient.QueryInput = {
TableName: 'newweatherdata',
KeyConditionExpression: 'city = :partitionKeyValue',
ExpressionAttributeValues: { ':partitionKeyValue': 'Sydney' },
ScanIndexForward: false,
Limit: 10
}
By default, when we query DynamoDB, the data will be retrieved in ascending order of sort key (datetime). That’s not what we want; we want to get the latest datetime (decending order). To achieve that, we use ScanIndexForward: false
.
This is how the queried data looks like in decending order:
{
Items: [
{
city: 'Sydney',
humidity: 61,
datetime: 1692519781720, //20 August 2023 08:23:01.720
temperature: 16.34
},
{
city: 'Sydney',
humidity: 61,
datetime: 1692519721614, //20 August 2023 08:22:01.614
temperature: 16.42
},
{
city: 'Sydney',
humidity: 61,
datetime: 1692519662220, //20 August 2023 08:21:02.220
temperature: 16.42
}
],
Count: 3,
ScannedCount: 3
}
But to get the data to display in index.html
, we have to loop from the lowest datetime to highest datetime:
That’s why I used reverse()
to reverse the array again when generating data and generating labels:
const generateData = (items: Weather[]): string => {
const reversedItems = items.slice().reverse();
var data = `data: [`
reversedItems.forEach((item: Weather, index) => {
data += item.temperature;
if (index < items.length - 1) {
data += `, `;
}
})
data += `],`
return data
}
You’re a good Rails developer. But have you ever built a Rails stack from scratch?
I’ve written a guide to help people do exactly that, with the help of Docker and AWS Copilot. You will learn how to build a CI/CD pipeline on Github Actions, deploying to multiple AWS accounts, DB migration, and more.