├── .gitignore ├── LICENSE.md ├── README.md ├── cloudwatch_event.tf ├── deploy.sh ├── lambda.tf ├── lambda ├── app.py ├── requirements.txt └── template.html ├── main.tf ├── policies ├── iam_assume_policy.json └── ses_send_email.json └── terraform.tfvars /.gitignore: -------------------------------------------------------------------------------- 1 | lambda.zip 2 | terraform.tfstate 3 | terraform.tfstate.backup 4 | .terraform/ 5 | .idea/ -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2019 Pubudu Jayawardana 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Weather Notifier (Deploy with Terraform) 2 | 3 | Serverless application to receive weather notification via email for a specific location. 4 | 5 | ### Services Used 6 | * Lambda 7 | * CloudWatch Events 8 | * SES 9 | * Accuweather API 10 | 11 | ### Prerequisites 12 | * Terraform installed and configured with your CLI in your local machine. 13 | * Verified SES email address to send email. 14 | * Register Accuweather and get APIKey. 15 | * Find LocationId from Accuweather **[here](https://developer.accuweather.com/accuweather-locations-api/apis/get/locations/v1/cities/geoposition/search)** where you need to get the weather information 16 | 17 | ### Usage 18 | 1. Clone the repository. 19 | 20 | 2. Add required variable values to `terraform.tfvars` 21 | 22 | 3. Change permission of deploy.sh 23 | ````bash 24 | chmod 775 deploy.sh 25 | ```` 26 | 4. Run 27 | ```bash 28 | ./deploy.sh 29 | ``` 30 | 31 | 5. This will download necessary packages and prompts to continue creating resources in AWS. -------------------------------------------------------------------------------- /cloudwatch_event.tf: -------------------------------------------------------------------------------- 1 | variable "EVENT_SCHEDULE" {} 2 | 3 | resource "aws_cloudwatch_event_rule" "cloudwatch_schedular" { 4 | name = "weather_notification_trigger" 5 | description = "Trigger weather notification for the given time" 6 | schedule_expression = "cron(${var.EVENT_SCHEDULE})" 7 | } 8 | 9 | resource "aws_cloudwatch_event_target" "event_target" { 10 | rule = aws_cloudwatch_event_rule.cloudwatch_schedular.name 11 | arn = aws_lambda_function.main.arn 12 | } 13 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | cd ./lambda 3 | pip install -r requirements.txt -t . 4 | rm -f ../lambda.zip 5 | zip -r ../lambda.zip * 6 | cd .. 7 | terraform apply -------------------------------------------------------------------------------- /lambda.tf: -------------------------------------------------------------------------------- 1 | variable "API_KEY" {} 2 | variable "EMAILS" {} 3 | variable "FROM_EMAIL" {} 4 | variable "LOCATION_ID" {} 5 | 6 | resource "aws_iam_policy" "ses_send_email" { 7 | policy = data.template_file.ses_send_email_policy.rendered 8 | } 9 | 10 | resource "aws_iam_role_policy_attachment" "lambda-ses-send-email" { 11 | policy_arn = aws_iam_policy.ses_send_email.arn 12 | role = aws_iam_role.weather-app-policy.name 13 | } 14 | 15 | resource "aws_iam_role" "weather-app-policy" { 16 | assume_role_policy = data.template_file.lambda_assume_role_policy.rendered 17 | } 18 | 19 | resource "aws_iam_role_policy_attachment" "lambda-policy-role-attachment" { 20 | policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" 21 | role = aws_iam_role.weather-app-policy.name 22 | } 23 | 24 | resource "aws_lambda_function" "main" { 25 | function_name = "weather-notifier" 26 | filename = "lambda.zip" 27 | handler = "app.lambda_handler" 28 | role = aws_iam_role.weather-app-policy.arn 29 | runtime = "python3.7" 30 | tracing_config.mode = "PassThrough" 31 | source_code_hash = filebase64sha256("lambda.zip") 32 | environment { 33 | variables = { 34 | API_KEY = var.API_KEY 35 | EMAILS = var.EMAILS 36 | FROM_EMAIL = var.FROM_EMAIL 37 | LOCATION_ID = var.LOCATION_ID 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lambda/app.py: -------------------------------------------------------------------------------- 1 | import os 2 | from botocore.vendored import requests 3 | import time 4 | import jinja2 5 | import boto3 6 | import json 7 | 8 | ses_client = boto3.client('ses') 9 | 10 | templateLoader = jinja2.FileSystemLoader(searchpath="./") 11 | templateEnv = jinja2.Environment(loader=templateLoader) 12 | TEMPLATE_FILE = "template.html" 13 | template = templateEnv.get_template(TEMPLATE_FILE) 14 | EMAIL_SOURCE = 'Daily Weather Report <' + os.environ['FROM_EMAIL'] + '>' 15 | 16 | 17 | def render(data): 18 | return template.render({'results': data}) 19 | 20 | 21 | def lambda_handler(event, context): 22 | url = 'http://dataservice.accuweather.com/forecasts/v1/hourly/12hour/' + os.environ['LOCATION_ID'] + '?apikey=' + \ 23 | os.environ['API_KEY'] 24 | 25 | result = requests.get(url).json() 26 | 27 | final_result = [] 28 | for data in result: 29 | result_object = { 30 | 'time_org': data['DateTime'], 31 | 'time': time.strftime('%Y-%m-%d %I:%M %p', time.localtime(data['EpochDateTime'])), 32 | 'icon_phrase': data['IconPhrase'], 33 | 'reception': data['PrecipitationIntensity'] + ' ' + data['PrecipitationType'] if data[ 34 | 'HasPrecipitation'] == True else '', 35 | 'probability': data['PrecipitationProbability'], 36 | 'icon': 'https://developer.accuweather.com/sites/default/files/' + "{:02}".format(data['WeatherIcon']) + '-s.png' 37 | } 38 | 39 | final_result.append(result_object) 40 | 41 | emails = os.environ.get('EMAILS').split(",") 42 | result = ses_client.send_email( 43 | Destination={ 44 | 'ToAddresses': emails, 45 | }, 46 | Message={ 47 | 'Body': { 48 | 'Html': { 49 | 'Charset': 'UTF-8', 50 | 'Data': render(final_result), 51 | }, 52 | }, 53 | 'Subject': { 54 | 'Charset': 'UTF-8', 55 | 'Data': 'Today\'s Weather', 56 | }, 57 | }, 58 | Source=EMAIL_SOURCE, 59 | ) 60 | 61 | return { 62 | 'statusCode': 200, 63 | 'body': json.dumps(result) 64 | } 65 | 66 | 67 | if __name__ == '__main__': 68 | lambda_handler({}, {}) -------------------------------------------------------------------------------- /lambda/requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | Jinja2 -------------------------------------------------------------------------------- /lambda/template.html: -------------------------------------------------------------------------------- 1 | 2 | Weather Notification 3 | 4 | 5 |
7 | 9 | 10 | 13 | 14 | 15 | 18 | 21 | 22 | {% for result in results %} 23 | 24 | 27 | 30 | 34 | 37 | 40 | 41 | {% endfor %} 42 |
11 | Time 12 | 16 | Reception 17 | 19 | Probability 20 |
25 | {{result['time']}} 26 | 28 | {{result['icon_phrase']}} 29 | 31 | 33 | 35 | {{result['reception']}} 36 | 38 | {{result['probability']}}% 39 |
43 |
44 | 45 | 46 | -------------------------------------------------------------------------------- /main.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | profile = "default" 3 | region = "eu-central-1" 4 | } 5 | 6 | data "template_file" "lambda_assume_role_policy" { 7 | template = file("./policies/iam_assume_policy.json") 8 | } 9 | 10 | data "template_file" "ses_send_email_policy" { 11 | template = file("./policies/ses_send_email.json") 12 | } -------------------------------------------------------------------------------- /policies/iam_assume_policy.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Action": "sts:AssumeRole", 6 | "Principal": { 7 | "Service": "lambda.amazonaws.com" 8 | }, 9 | "Effect": "Allow", 10 | "Sid": "" 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /policies/ses_send_email.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Sid": "VisualEditor0", 6 | "Effect": "Allow", 7 | "Action": "ses:SendEmail", 8 | "Resource": "*" 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /terraform.tfvars: -------------------------------------------------------------------------------- 1 | API_KEY="" # API Key from Accuweather 2 | EMAILS="" # Comma separated email addressses to send the notification 3 | FROM_EMAIL="" # From address for the notification email. This must be verified in SES 4 | LOCATION_ID="" # location id from Accuweather 5 | EVENT_SCHEDULE="35 5,12 * * ? *" #Cloudwatch event scheudler cron expression as per: https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/ScheduledEvents.html#CronExpressions 6 | --------------------------------------------------------------------------------