From da0d37178d2b76cebd4378a97e8532da6600a0fd Mon Sep 17 00:00:00 2001 From: Hammy Date: Fri, 12 Feb 2021 18:05:26 +0000 Subject: [PATCH] Add tutorial code for email generation --- cuckoo/README.md | 1 + cuckoo/commands_help.txt | 151 +++++++++++++++++++++++++++++++++++++ cuckoo/cuckoo.py | 159 +++++++++++++++++++++++++++++++++++++++ cuckoo/requirements.txt | 2 + cuckoo/setup.sh | 35 +++++++++ cuckoo/windows_setup.bat | 37 +++++++++ 6 files changed, 385 insertions(+) create mode 100644 cuckoo/README.md create mode 100644 cuckoo/commands_help.txt create mode 100644 cuckoo/cuckoo.py create mode 100644 cuckoo/requirements.txt create mode 100644 cuckoo/setup.sh create mode 100644 cuckoo/windows_setup.bat diff --git a/cuckoo/README.md b/cuckoo/README.md new file mode 100644 index 0000000..0b15b2e --- /dev/null +++ b/cuckoo/README.md @@ -0,0 +1 @@ +# Cuckoo diff --git a/cuckoo/commands_help.txt b/cuckoo/commands_help.txt new file mode 100644 index 0000000..8d18042 --- /dev/null +++ b/cuckoo/commands_help.txt @@ -0,0 +1,151 @@ +# This file should help you with the commands we'll be working through in this module. +# I'll also include documentation and notes where I think they might be useful. +# Commands will be unindented and uncommented (no # symbol at the beginning) + +# Part 1 - S3 Uploads +# When you open up your demos file you'll see a templates folder. +# Along with licence information are three modified html email templates. +# Each template is ready to use with Jinja2 but we'll need to move it to an S3 bucket. +# Keep in mind that bucket names are *globally* unique. Meaning you can't use mine! +# Replace gpc-email-templates with your bucket name. +# Also use the region you've been using throughout the course. +# The \ character is to escape line breaks and make the code more readable +# Here is the command to create a bucket with the AWS CLI: + +aws s3 mb s3://gpc-email-templates + +# Confirm the bucket was created + +aws s3 ls + +# Upload the templates to your s3 bucket +# Make sure you're in the /templates folder in your terminal first + +aws s3 cp ./templates s3://gpc-email-templates --recursive + +# Confirm the files were moved to s3 + +aws s3 ls gpc-email-templates + +# Notes: +# Keep in mind that Lambda cron() scheduling is done on UTC. +# To get a cron expression in your timezone you may need to change the values +# 7am EST - cron(0 12 ? * MON-FRI *) +# Noon EST - cron(0 17 ? * MON-FRI *) +# 5pm EST - cron(0 22 ? * MON-FRI *) +# Keep in mind time zone differences like Daylight Savings! + +# To add CloudWatch Events rules: +aws events put-rule \ +--name come_to_work \ +--schedule-expression 'cron(0 12 ? * MON-FRI *)' + +aws events put-rule \ +--name daily_tasks \ +--schedule-expression 'cron(0 17 ? * MON-FRI *)' + +aws events put-rule \ +--name pickup \ +--schedule-expression 'cron(0 22 ? * MON-FRI *)' + + +# With the events created make sure to modify your code skeleton +# Come back for these commands when you need to test locally + +# Testing the function from the command line: +# Make sure you're in the directory with the cuckoo.py file +# Open a Python shell + +python3 + +# In the python shell I'll put commands after >>> +# import the function + +>>> import cuckoo + +# Call the handler function and give it a sample event +# This event can either be the event ARN text or something else +# that contains text that contains some of the text that the +# function is looking for. +# The text should contain: +# 'come_to_work' or 'daily_tasks' or 'pickup' +# We'll also put them into the following format to match +# the way they will be passed to the function by the AWS event + +>>> cuckoo.handler({'resources':['some text containing the word pickup']},'context') + +>>> cuckoo.handler({'resources':['arn:aws:events:us-east-1:444510800003:rule/come_to_work']}, 'context') + +>>> cuckoo.handler({'resources':['a great daily_tasks test']},'context') + +# If successful you won't see any output so check your email for the results! + +# Setup and Deployment +# +# To take a look at creating our function package locally you can open +# the setup.sh file in a text editor. +# +# Deploying your function with the AWS CLI +# First make sure you've created your zip function package +# and you're in the same directory as that package.zip file +# Then list the roles on your account. +# We'll need to look for the role we created for this function earlier + +aws iam list-roles + +# We'll see something like this in the output: +# arn:aws:iam::444510800003:role/gpc_cuckoo_role +# Copy that role ARN because you'll need to replace the value +# after --role in the below command: + +aws lambda create-function \ +--function-name gpc_cuckoo \ +--runtime python3.7 \ +--role arn:aws:iam::444510800003:role/gpc_cuckoo_role \ +--handler cuckoo.handler \ +--zip-file fileb://./package.zip + +# Configure events to trigger the function +# +# +# List events + +aws events list-rules + +# We'll have the function trust events from each +# of these Cloudwatch Events + +aws lambda add-permission \ +--function-name gpc_cuckoo \ +--statement-id 1 \ +--action 'lambda:InvokeFunction' \ +--principal events.amazonaws.com \ +--source-arn arn:aws:events:us-east-1:444510800003:rule/pickup + +aws lambda add-permission \ +--function-name gpc_cuckoo \ +--statement-id 2 \ +--action 'lambda:InvokeFunction' \ +--principal events.amazonaws.com \ +--source-arn arn:aws:events:us-east-1:444510800003:rule/daily_tasks + +aws lambda add-permission \ +--function-name gpc_cuckoo \ +--statement-id 3 \ +--action 'lambda:InvokeFunction' \ +--principal events.amazonaws.com \ +--source-arn arn:aws:events:us-east-1:444510800003:rule/come_to_work + +# This will add the lambda function to each of the rules to be triggered +aws events put-targets \ +--rule daily_tasks \ +--targets '{"Id" : "1", "Arn": "arn:aws:lambda:us-east-1:444510800003:function:gpc_cuckoo"}' + +aws events put-targets \ +--rule come_to_work \ +--targets '{"Id" : "1", "Arn": "arn:aws:lambda:us-east-1:444510800003:function:gpc_cuckoo"}' + +aws events put-targets \ +--rule pickup \ +--targets '{"Id" : "1", "Arn": "arn:aws:lambda:us-east-1:444510800003:function:gpc_cuckoo"}' + diff --git a/cuckoo/cuckoo.py b/cuckoo/cuckoo.py new file mode 100644 index 0000000..7901a0b --- /dev/null +++ b/cuckoo/cuckoo.py @@ -0,0 +1,159 @@ +import datetime +import boto3 +from jinja2 import Template + +# Start of some things you need to change +# +# +# Recipient emails or domains in the AWS Email Sandbox must be verified +# You'll want to change this to the email you verify in SES +FROM_ADDRESS = 'globomanticspetcare@gmail.com' +REPLY_TO_ADDRESS = 'globomanticspetcare@gmail.com' + +CLIENTS = [ + { + # You'll need to verify this email + 'email': 'f_mcorey@yahoo.com', + 'first_name': 'Fernando', + 'last_name': 'Medina Corey', + 'pet_name': 'Riley' + }, +] + +EMPLOYEES = [ + { + # You'll need to verify this email + 'email': 'springfield.homer@yahoo.com', + 'first_name': 'Homer', + 'last_name': 'Simpson' + }, +] + +# Change to the bucket you create on your AWS account +TEMPLATE_S3_BUCKET = 'gpc-email-templates' + + +# +# +# End of things you need to change + +def get_template_from_s3(key): + """Loads and returns html template from Amazon S3""" + s3 = boto3.client('s3') + s3_file = s3.get_object( + Bucket=TEMPLATE_S3_BUCKET, + Key=key + ) + try: + template = Template(s3_file['Body'].read().decode('utf-8')) + except Exception as e: + print('Failed to load template') + raise e + return template + + +def render_come_to_work_template(employee_first_name): + template = get_template_from_s3('come_to_work.html') + html_email = template.render(first_name=employee_first_name) + plaintext_email = 'Hello {0}, \nPlease remember to be into work by 8am'.format(employee_first_name) + return html_email, plaintext_email + + +def render_daily_tasks_template(): + template = get_template_from_s3('daily_tasks.html') + tasks = { + 'Monday': '- Clean the dog areas\n', + 'Tuesday': '- Clean the cat areas\n', + 'Wednesday': '- Feed the aligator\n', + 'Thursday': '- Clean the dog areas\n', + 'Friday': '- Clean the cat areas\n', + 'Saturday': '- Relax! Play with the puppies! It\'s the weekend!', + 'Sunday': '- Relax! Play with the puppies! It\'s the weekend!' + } + # Gets an integer value from 0 to 6 for today (Monday - Sunday) + # Keep in mind this will run in GMT and you will need to adjust runtimes accordingly + days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'] + today = days[datetime.date.today().weekday()] + html_email = template.render( + day_of_week=today, + daily_tasks=tasks[today] + ) + plaintext_email = ( + "Remember to do all of these today:\n" + "- Feed the dogs\n" + "- Feed the rabbits\n" + "- Feed the cats\n" + "- Feed the turtles\n" + "- Walk the dogs\n" + "- Empty cat litterboxes\n" + "{0}".format(tasks[today]) + ) + return html_email, plaintext_email + + +def render_pickup_template(client_first_name, client_pet_name): + template = get_template_from_s3('pickup.html') + html_email = template.render( + first_name=client_first_name, + pet_name=client_pet_name + ) + plaintext_email = ( + 'Hello {0}, \nPlease remember to ' + 'pickup {1} by 7pm!'.format( + client_first_name, + client_pet_name + ) + ) + return html_email, plaintext_email + + +def send_email(html_email, plaintext_email, subject, recipients): + try: + ses = boto3.client('ses') + response = ses.send_email( + Source=FROM_ADDRESS, + Destination={ + 'ToAddresses': [recipients], + 'CcAddresses': [], + 'BccAddresses': [] + }, + Message={ + 'Subject': { + 'Data': subject, + }, + 'Body': { + 'Text': { + 'Data': plaintext_email + }, + 'Html': { + 'Data': html_email + } + } + }, + ReplyToAddresses=[ + REPLY_TO_ADDRESS, + ] + ) + except Exception as e: + print('Failed to send message via SES') + print(e) + raise e + + +def handler(event, context): + event_trigger = event['resources'][0] + print('event triggered by ' + event_trigger) + if 'come_to_work' in event_trigger: + for employee in EMPLOYEES: + html_email, plaintext_email = render_come_to_work_template(employee['first_name']) + send_email(html_email, plaintext_email, 'Work Schedule Reminder', employee['email']) + elif 'daily_tasks' in event_trigger: + for employee in EMPLOYEES: + html_email, plaintext_email = render_daily_tasks_template() + send_email(html_email, plaintext_email, 'Daily Tasks Reminder', employee['email']) + elif 'pickup' in event_trigger: + for client in CLIENTS: + html_email, plaintext_email = render_pickup_template(client['first_name'], client['pet_name']) + send_email(html_email, plaintext_email, 'Pickup Reminder', client['email']) + else: + return 'No template for this trigger!' diff --git a/cuckoo/requirements.txt b/cuckoo/requirements.txt new file mode 100644 index 0000000..429e5af --- /dev/null +++ b/cuckoo/requirements.txt @@ -0,0 +1,2 @@ +Jinja2==2.11.1 +MarkupSafe==1.1.1 \ No newline at end of file diff --git a/cuckoo/setup.sh b/cuckoo/setup.sh new file mode 100644 index 0000000..1a7c8c6 --- /dev/null +++ b/cuckoo/setup.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +# Create and initialize a Python Virtual Environment +echo "Creating virtual environment - .venv" +python3 -m venv .venv + +echo "sourcing virtual environment - .venv" +source .venv/bin/activate + +# Create a directory to put things in +echo "Creating 'setup' directory" +mkdir setup + +# Move the relevant files into setup directory +echo "Moving function file(s) to setup dir" +cp cuckoo.py setup/ +cd ./setup + +# Install requirements +echo "pip installing requirements from requirements file in target directory" +pip install -r ../requirements.txt -t . + +# Prepares the deployment package +echo "Zipping package" +zip -r ../package.zip ./* + +# Remove the setup directory used +echo "Removing setup directory and virtual environment" +cd .. +rm -r ./setup +deactivate +rm -r .venv +# changing dirs back to dir from before +echo "Opening folder containg function package - 'package.zip'" +open . \ No newline at end of file diff --git a/cuckoo/windows_setup.bat b/cuckoo/windows_setup.bat new file mode 100644 index 0000000..1524adc --- /dev/null +++ b/cuckoo/windows_setup.bat @@ -0,0 +1,37 @@ +rem Create and initialize a Python Virtual Environment +echo "Creating the virtual environment - .venv" +python3 -m venv .venv + +echo "starting the virtual environment - .venv" +call .venv\Scripts\activate.bat + +rem Create a directory to put things in +echo "Creating 'setup' directory" +mkdir setup + +rem Move the relevant files into setup directory +echo "Moving function file(s) to setup dir" +xcopy cuckoo.py setup\ /Q /R /Y +cd .\setup + +rem Install requirements +echo "pip installing requirements from requirements file in target directory" +pip install -r ..\requirements.txt -t . + +rem Prepares the deployment package +echo "Setting up your 7zip PATH - This assumes the installation location of 7zip is in C:\Program Files\7-Zip\" +set PATH=%PATH%;C:\Program Files\7-Zip\ + +echo "Zipping package" +7z a -r ..\package.zip .\* + +rem Remove the setup directory used +echo "Removing setup directory and virtual environment" +cd .. +rd /Q /S .\setup +call .venv\Scripts\deactivate.bat +rd /Q /S .\.venv + +rem changing dirs back to dir from before +echo "Opening folder containg function package - 'package.zip'" +explorer . \ No newline at end of file