Skip to content

Commit

Permalink
Merge pull request #9033 from ministryofjustice/Update_101224_1
Browse files Browse the repository at this point in the history
Update_101224_1
  • Loading branch information
nbuckingham72 authored Dec 10, 2024
2 parents feb227f + 0bd764f commit 730a7ec
Show file tree
Hide file tree
Showing 5 changed files with 251 additions and 17 deletions.
29 changes: 27 additions & 2 deletions terraform/environments/ppud/eventbridge.tf
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,31 @@ resource "aws_cloudwatch_event_target" "trigger_lambda_target_ppud_elb_report_pr
arn = aws_lambda_function.terraform_lambda_func_ppud_elb_report_prod[0].arn
}

# Eventbridge rule to invoke the WAM ELB report lambda function every weekday at 20:15

resource "aws_lambda_permission" "allow_eventbridge_invoke_wam_elb_report_prod" {
count = local.is-production == true ? 1 : 0
statement_id = "AllowEventBridgeInvoke"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.terraform_lambda_func_wam_elb_report_prod[0].function_name
principal = "events.amazonaws.com"
source_arn = aws_cloudwatch_event_rule.daily_schedule_wam_elb_report_prod[0].arn
}

resource "aws_cloudwatch_event_rule" "daily_schedule_wam_elb_report_prod" {
count = local.is-production == true ? 1 : 0
name = "wam-elb-report-daily-weekday-schedule"
description = "Trigger Lambda at 20:15 UTC on weekdays"
schedule_expression = "cron(15 20 ? * MON-FRI *)"
}

resource "aws_cloudwatch_event_target" "trigger_lambda_target_wam_elb_report_prod" {
count = local.is-production == true ? 1 : 0
rule = aws_cloudwatch_event_rule.daily_schedule_wam_elb_report_prod[0].name
target_id = "wam_elb_report"
arn = aws_lambda_function.terraform_lambda_func_wam_elb_report_prod[0].arn
}

# Eventbridge rule to invoke the PPUD Email Report lambda function every Monday at 07:00

resource "aws_lambda_permission" "allow_eventbridge_invoke_ppud_email_report_prod" {
Expand All @@ -66,8 +91,8 @@ resource "aws_lambda_permission" "allow_eventbridge_invoke_ppud_email_report_pro
resource "aws_cloudwatch_event_rule" "weekly_schedule_ppud_email_report_prod" {
count = local.is-production == true ? 1 : 0
name = "ppud-email-report-weekly-schedule"
description = "Trigger Lambda at 07:00 UTC each Monday"
schedule_expression = "cron(0 7 ? * MON *)"
description = "Trigger Lambda at 07:15 UTC each Monday"
schedule_expression = "cron(15 7 ? * MON *)"
}

resource "aws_cloudwatch_event_target" "trigger_lambda_target_ppud_email_report_prod" {
Expand Down
59 changes: 57 additions & 2 deletions terraform/environments/ppud/lambda.tf
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,7 @@ resource "aws_lambda_permission" "allow_lambda_to_query_cloudwatch_send_cpu_grap

resource "aws_lambda_function" "terraform_lambda_func_send_cpu_graph_dev" {
# checkov:skip=CKV_AWS_117: "PPUD Lambda functions do not require VPC access and can run in no-VPC mode"
# checkov:skip=CKV_AWS_272: "PPUD Lambda code signing temporarily disabled for maintenance purposes"
count = local.is-development == true ? 1 : 0
filename = "${path.module}/lambda_scripts/send_cpu_graph_dev.zip"
function_name = "send_cpu_graph"
Expand Down Expand Up @@ -455,6 +456,7 @@ resource "aws_lambda_permission" "allow_lambda_to_query_cloudwatch_send_cpu_grap
}

resource "aws_lambda_function" "terraform_lambda_func_send_cpu_graph_prod" {
# checkov:skip=CKV_AWS_272: "PPUD Lambda code signing temporarily disabled for maintenance purposes"
count = local.is-production == true ? 1 : 0
filename = "${path.module}/lambda_scripts/send_cpu_graph_prod.zip"
function_name = "send_cpu_graph"
Expand Down Expand Up @@ -517,7 +519,7 @@ resource "aws_lambda_permission" "allow_lambda_to_query_cloudwatch_ppud_email_re
}

resource "aws_lambda_function" "terraform_lambda_func_ppud_email_report_prod" {
# checkov:skip=CKV_AWS_117: "PPUD Lambda functions do not require VPC access and can run in no-VPC mode"
# checkov:skip=CKV_AWS_272: "PPUD Lambda code signing temporarily disabled for maintenance purposes"
count = local.is-production == true ? 1 : 0
filename = "${path.module}/lambda_scripts/ppud_email_report_prod.zip"
function_name = "ppud_email_report"
Expand Down Expand Up @@ -569,7 +571,7 @@ resource "aws_lambda_permission" "allow_lambda_to_query_cloudwatch_ppud_elb_repo
}

resource "aws_lambda_function" "terraform_lambda_func_ppud_elb_report_prod" {
# checkov:skip=CKV_AWS_117: "PPUD Lambda functions do not require VPC access and can run in no-VPC mode"
# checkov:skip=CKV_AWS_272: "PPUD Lambda code signing temporarily disabled for maintenance purposes"
count = local.is-production == true ? 1 : 0
filename = "${path.module}/lambda_scripts/ppud_elb_report_prod.zip"
function_name = "ppud_elb_report"
Expand Down Expand Up @@ -607,6 +609,58 @@ data "archive_file" "zip_the_ppud_elb_report_prod" {
output_path = "${path.module}/lambda_scripts/ppud_elb_report_prod.zip"
}

###################################################
# Lambda Function to graph WAM ELB Requests - PROD
###################################################

resource "aws_lambda_permission" "allow_lambda_to_query_cloudwatch_wam_elb_report_prod" {
count = local.is-production == true ? 1 : 0
statement_id = "AllowAccesstoCloudWatch"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.terraform_lambda_func_wam_elb_report_prod[0].function_name
principal = "cloudwatch.amazonaws.com"
source_arn = "arn:aws:cloudwatch:eu-west-2:${local.environment_management.account_ids["ppud-production"]}:*"
}

resource "aws_lambda_function" "terraform_lambda_func_wam_elb_report_prod" {
# checkov:skip=CKV_AWS_272: "PPUD Lambda code signing temporarily disabled for maintenance purposes"
count = local.is-production == true ? 1 : 0
filename = "${path.module}/lambda_scripts/wam_elb_report_prod.zip"
function_name = "wam_elb_report"
role = aws_iam_role.lambda_role_cloudwatch_get_metric_data_prod[0].arn
handler = "wam_elb_report_prod.lambda_handler"
runtime = "python3.12"
timeout = 300
depends_on = [aws_iam_role_policy_attachment.attach_lambda_policy_cloudwatch_get_metric_data_to_lambda_role_cloudwatch_get_metric_data_prod]
reserved_concurrent_executions = 5
# code_signing_config_arn = "arn:aws:lambda:eu-west-2:${local.environment_management.account_ids["ppud-production"]}:code-signing-config:csc-0bafee04a642a41c1"
dead_letter_config {
target_arn = aws_sqs_queue.lambda_queue_prod[0].arn
}
tracing_config {
mode = "Active"
}
layers = [
"arn:aws:lambda:eu-west-2:${data.aws_ssm_parameter.klayers_account_prod[0].value}:layer:Klayers-p312-numpy:8",
"arn:aws:lambda:eu-west-2:${data.aws_ssm_parameter.klayers_account_prod[0].value}:layer:Klayers-p312-pillow:1",
aws_lambda_layer_version.lambda_layer_matplotlib_prod[0].arn
]
# VPC configuration
vpc_config {
subnet_ids = [data.aws_subnet.private_subnets_b.id]
security_group_ids = [aws_security_group.PPUD-Mail-Server[0].id]
}
}

# Archive the zip file

data "archive_file" "zip_the_wam_elb_report_prod" {
count = local.is-production == true ? 1 : 0
type = "zip"
source_dir = "${path.module}/lambda_scripts/"
output_path = "${path.module}/lambda_scripts/wam_elb_report_prod.zip"
}

####################################################
# Lambda Function to graph Memory Utilization - PROD
####################################################
Expand All @@ -621,6 +675,7 @@ resource "aws_lambda_permission" "allow_lambda_to_query_cloudwatch_send_memory_g
}

resource "aws_lambda_function" "terraform_lambda_func_send_memory_graph_prod" {
# checkov:skip=CKV_AWS_272: "PPUD Lambda code signing temporarily disabled for maintenance purposes"
count = local.is-production == true ? 1 : 0
filename = "${path.module}/lambda_scripts/send_memory_graph_prod.zip"
function_name = "send_memory_graph"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
# Configuration
CURRENT_DATE = datetime.now().strftime('%a %d %b %Y')
SENDER = '[email protected]'
RECIPIENTS = ['[email protected]']
RECIPIENTS = ['[email protected]', '[email protected]']
SUBJECT = f'AWS PPUD Load Balancer Report - {CURRENT_DATE}'
AWS_REGION = 'eu-west-2'
ELB_NAME = "app/PPUD-ALB/9d129853721723f4" # Replace with your ELB name
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
# Python script to retrieve emails send data from S3, count the number of outgoing emails
# graph it and email it to end users via the internal mail relay.
# Nick Buckingham
# 9 December 2024

import boto3
import os
os.environ['MPLCONFIGDIR'] = "/tmp/graph"
Expand All @@ -24,8 +19,10 @@
TODAY = datetime.now()
YESTERDAY = TODAY - timedelta(days=1)
YESTERDAY_DATE = YESTERDAY.strftime('%a %d %b %Y')
bucket_name = 'moj-lambda-layers-prod'
file_names = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
WEEK_AGO = TODAY - timedelta(days=7)
WEEK_AGO_DATE = WEEK_AGO.strftime('%a %d %b %Y')
S3_BUCKET = 'moj-lambda-layers-prod'
FILE_NAMES = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
SENDER = '[email protected]'
RECIPIENTS = ['[email protected]']
SUBJECT = f'AWS PPUD Email Report - {CURRENT_DATE}'
Expand All @@ -34,8 +31,6 @@
# SMTP Configuration
SMTP_SERVER = "10.27.9.39"
SMTP_PORT = 25
MAIL_FROM = "[email protected]"
EMAIL_TO = ["[email protected]"]

def retrieve_file_from_s3(bucket, key):
response = s3.get_object(Bucket=bucket, Key=key)
Expand Down Expand Up @@ -81,7 +76,7 @@ def send_email_with_graph(graph_base64):
<html>
<body>
<p>Hi Team,</p>
<p>Please find below the PPUD email report for the week ending {YESTERDAY_DATE}.</p>
<p>Please find below the PPUD email report for the week of {WEEK_AGO_DATE} to {YESTERDAY_DATE}.</p>
<img src="data:image/png;base64,{graph_base64}" alt="PPUD Email Report" />
<p>This is an automated email.</p>
</body>
Expand Down Expand Up @@ -123,8 +118,8 @@ def lambda_handler(event, context):
pattern = r'to=<'
data = {}

for file_name in file_names:
content = retrieve_file_from_s3(bucket_name, file_name)
for file_name in FILE_NAMES:
content = retrieve_file_from_s3(S3_BUCKET, file_name)
count = count_occurrences(content, pattern)
data[file_name] = count

Expand Down
159 changes: 159 additions & 0 deletions terraform/environments/ppud/lambda_scripts/wam_elb_report_prod.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
# Python script to retrieve elastic load balancer data from cloudwatch, count the connections per 15 minutes
# graph it and email it to end users via the internal mail relay.
# Nick Buckingham
# 9 December 2024

import boto3
import os
os.environ['MPLCONFIGDIR'] = "/tmp/graph"
import matplotlib.pyplot as plt
from datetime import datetime, timedelta
import io
import base64
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from botocore.exceptions import NoCredentialsError, PartialCredentialsError

# Configuration
CURRENT_DATE = datetime.now().strftime('%a %d %b %Y')
SENDER = '[email protected]'
RECIPIENTS = ['[email protected]']
SUBJECT = f'AWS WAM Load Balancer Report - {CURRENT_DATE}'
AWS_REGION = 'eu-west-2'
ELB_NAME = "app/WAM-ALB-PROD/bfc963544454bdde" # Replace with your ELB name

# SMTP Configuration
SMTP_SERVER = "10.27.9.39"
SMTP_PORT = 25

# Initialize AWS clients
cloudwatch = boto3.client("cloudwatch", region_name=AWS_REGION)
#ses = boto3.client("ses", region_name=AWS_REGION)

def get_elb_request_counts(ELB_NAME):
"""Fetches daily connection counts for the ELB from CloudWatch."""
# Calculate the start and end time for the day
#start_time = datetime(2024, 12, 8, 6, 0, 0) # 08:00 UTC, 28 Nov 2024
#end_time = datetime(2024, 12, 8, 20, 10, 0) # 17:00 UTC, 28 Nov 2024
current_time = datetime.utcnow()
end_time = datetime.utcnow()
start_time = end_time - timedelta(hours=14)

response = cloudwatch.get_metric_statistics(
Namespace="AWS/ApplicationELB",
MetricName="RequestCount",
Dimensions=[
{"Name": "LoadBalancer", "Value": ELB_NAME},
# {'Name': 'TargetGroup', 'Value': 'PPUD'},
# {'Name': 'AvailabilityZone', 'Value': 'eu-west-2c'}
],
StartTime=start_time,
EndTime=end_time,
Period=900, # 15 minute intervals
Statistics=["Sum"]
)

data_points = sorted(response["Datapoints"], key=lambda x: x["Timestamp"])
return [(dp["Timestamp"].strftime('%H:%M'), dp["Sum"]) for dp in data_points]

def create_graph(request_data):
"""Plots the graph of requests and returns it as an in-memory file."""
times, requests = zip(*request_data)
plt.figure(figsize=(20, 6))
plt.plot(times, requests, color="blue")
plt.title(f"Requests to the WAM Load Balancer on {CURRENT_DATE} (Every 15 Minutes)")
plt.xlabel("Time (UTC)")
plt.ylabel("Number of Requests")
plt.xticks(rotation=45)
plt.grid(axis="y", linestyle="--", linewidth=0.7, alpha=0.7)
plt.tight_layout()

# graph_path = "elb_daily_connections.png"
# plt.savefig(graph_path)
# plt.close()
# return graph_path

# Save the graph to a temporary buffer
temp_file = "/tmp/elb_daily_connections.png"
plt.savefig(temp_file)
plt.close()

# Read the image and encode it to base64
with open(temp_file, "rb") as image_file:
encoded_string = base64.b64encode(image_file.read()).decode("utf-8")

# Cleanup temporary file
os.remove(temp_file)
return encoded_string

def email_image_to_users(graph_base64):
"""
Send an email with the graph embedded in the email body using AWS SES.
"""
ses_client = boto3.client("ses", region_name=AWS_REGION)

# Email body with the embedded image
email_body = f"""
<html>
<body>
<p>Hi Team,</p>
<p>Please find below the WAM Elastic Load Balancer report for {CURRENT_DATE}.</p>
<img src="data:image/png;base64,{graph_base64}" alt="WAM ELB Report" />
<p>This is an automated email.</p>
</body>
</html>
"""

# Create the email message
msg = MIMEMultipart("alternative")
msg["From"] = SENDER
msg["To"] = ", ".join(RECIPIENTS)
msg["Subject"] = SUBJECT

# Attach the HTML body
msg.attach(MIMEText(email_body, "html"))

# Send the email with AWS SES
# try:
# response = ses_client.send_raw_email(
# Source=SENDER,
# Destinations=RECIPIENTS,
# RawMessage={"Data": msg.as_string()},
# )
# print("Email sent! Message ID:", response["MessageId"])
# except Exception as e:
# print("Error sending email:", e)
# raise

# Send the email with an EC2 Instance Mail Relay
try:
with smtplib.SMTP(SMTP_SERVER, SMTP_PORT) as server:
# server.starttls()
# server.login(SENDER, EMAIL_PASSWORD)
server.sendmail(SENDER, RECIPIENTS, msg.as_string())
print("Email sent successfully.")
except Exception as e:
print(f"Error sending email: {e}")


def lambda_handler(event, context):
try:
# Get hourly request counts
request_data = get_elb_request_counts(ELB_NAME)
if not request_data:
print("No data found for the specified ELB.")
return

# Create graph
#temp_file = plot_graph(request_data)
graph_base64 = create_graph(request_data)

# Send email
email_image_to_users(graph_base64)
print("Process completed successfully.")

except (NoCredentialsError, PartialCredentialsError) as cred_error:
print(f"Credential issue: {cred_error}")
except Exception as e:
print(f"An error occurred: {e}")

0 comments on commit 730a7ec

Please sign in to comment.