From fe5c2ef279b3190b242df08de2b680b4de5cac4c Mon Sep 17 00:00:00 2001 From: lian-manonog Date: Mon, 12 Aug 2024 16:51:56 +0100 Subject: Completed the main.tf and vars.tf file. Currently: figuring out the configurations for AWS IAM USERS --- terraform/main.tf | 26 ++++++++++++++++++++++++++ terraform/vars.tf | 23 +++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 terraform/main.tf create mode 100644 terraform/vars.tf diff --git a/terraform/main.tf b/terraform/main.tf new file mode 100644 index 0000000..3ca9a3d --- /dev/null +++ b/terraform/main.tf @@ -0,0 +1,26 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~>5.0" + } + } + backend "s3" { + bucket = "bentley-secrets" + key = "bentley-project/terraform.tfstate" + region = "eu-west-2" + } +} + +provider "aws" { + region = "eu-west-2" + default_tags { + tags = { + ProjectName = "Terrific-Totes" + Team = "Team-Bentley" + Environment = "Dev" + GitHubRepo = "de-project-bentley" + ManagedBy = "Terraform" + } + } +} \ No newline at end of file diff --git a/terraform/vars.tf b/terraform/vars.tf new file mode 100644 index 0000000..166f2c5 --- /dev/null +++ b/terraform/vars.tf @@ -0,0 +1,23 @@ +variable "s3_extract_bucket_name" { + type = string + default = "extract-bucket" +} + +variable "s3_transform_bucket_name" { + type = string + default = "transform-bucket" +} + +variable "extract_lambda_name" { + type = string + default = "extract-lambda" +} + +variable "transform_lambda_name" { + type = string + default = "transform-lambda" +} + +data "aws_caller_identity" "current" {} + +data "aws_region" "current" {} \ No newline at end of file -- cgit v1.2.3 From ef770c1ea4ee633489323a8ab321b1214b51a770 Mon Sep 17 00:00:00 2001 From: Ellie Date: Mon, 12 Aug 2024 16:57:11 +0100 Subject: chore: add aws_iam_role --- terraform/iam.tf | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 terraform/iam.tf diff --git a/terraform/iam.tf b/terraform/iam.tf new file mode 100644 index 0000000..7501373 --- /dev/null +++ b/terraform/iam.tf @@ -0,0 +1,29 @@ +# define + +resource "aws_iam_role" "bentley_service_role" { + assume_role_policy = < Date: Tue, 13 Aug 2024 09:47:21 +0100 Subject: [feat]/add eventbridge schedule and step function trigger 1 of 2 --- terraform/events.tf | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 terraform/events.tf diff --git a/terraform/events.tf b/terraform/events.tf new file mode 100644 index 0000000..25fb35b --- /dev/null +++ b/terraform/events.tf @@ -0,0 +1,52 @@ +resource "aws_cloudwatch_event_rule" "lambda_trigger" { + name = "lambda-scheduled-trigger" + description = "Schedule to trigger the Lambda function" + schedule_expression = "rate(30 minutes)" + +# event_pattern = jsonencode({ +# detail-type = [ +# "AWS Console Sign In via CloudTrail" +# ] +# }) +} + + +resource "aws_cloudwatch_event_target" "lambda" { + rule = aws_cloudwatch_event_rule.lambda_trigger.name + target_id = "TargetFunctionV1" + arn = aws_lambda_function.my_lambda_function.arn +} + + + +resource "aws_lambda_permission" "allow_eventbridge" { + statement_id = "AllowExecutionFromEventBridge" + action = "lambda:InvokeFunction" + function_name = aws_lambda_function.my_lambda_function.function_name + principal = "events.amazonaws.com" + source_arn = aws_cloudwatch_event_rule.lambda_trigger.arn +} + + +# below is step function 1 +resource "aws_lambda_permission" "allow_s3_ingestion" { + statement_id = "AllowS3InvokeLambdaTransform" + action = "lambda:InvokeFunction" + function_name = aws_lambda_function.lambda_transform.function_name + principal = "s3.amazonaws.com" + source_arn = aws_s3_bucket.extract.arn +} + + +resource "aws_s3_bucket_notification" "extract_bucket_notification" { + bucket = aws_s3_bucket.extract.id + + lambda_function { + events = ["s3:ObjectCreated:*"] + lambda_function_arn = aws_lambda_function.lambda_transform.arn + } + + depends_on = [aws_lambda_permission.allow_s3_ingestion] +} + +# need to duplicate and replace "2" with "3" \ No newline at end of file -- cgit v1.2.3 From e3e9817f4e88afc8eb89e0b18a7fe8b1f381e0d4 Mon Sep 17 00:00:00 2001 From: T-Aji Date: Tue, 13 Aug 2024 09:57:14 +0100 Subject: [feat]/add step function trigger 2 of 2 --- terraform/events.tf | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/terraform/events.tf b/terraform/events.tf index 25fb35b..4d68a23 100644 --- a/terraform/events.tf +++ b/terraform/events.tf @@ -49,4 +49,24 @@ resource "aws_s3_bucket_notification" "extract_bucket_notification" { depends_on = [aws_lambda_permission.allow_s3_ingestion] } -# need to duplicate and replace "2" with "3" \ No newline at end of file +###### + +resource "aws_lambda_permission" "allow_s3_transfrom_bucket" { + statement_id = "AllowS3InvokeLambdaTransform" + action = "lambda:InvokeFunction" + function_name = aws_lambda_function.lambda_transform.function_name + principal = "s3.amazonaws.com" + source_arn = aws_s3_bucket.transform.arn +} + + +resource "aws_s3_bucket_notification" "transform_bucket_notification" { + bucket = aws_s3_bucket.transform.id + + lambda_function { + events = ["s3:ObjectCreated:*"] + lambda_function_arn = aws_lambda_function.lambda_transform.arn + } + + depends_on = [aws_lambda_permission.allow_s3_transform] +} \ No newline at end of file -- cgit v1.2.3 From c8d8d2cee4262782890ea68cda8fc86f61098b09 Mon Sep 17 00:00:00 2001 From: Ang Bel Date: Tue, 13 Aug 2024 10:14:01 +0100 Subject: s3 buckets in tf and initial blank lambda py files set-up in src folder for the next task of lambda tf set up --- src/extract_lambda.py | 0 src/load_lambda.py | 0 src/transform_lambda.py | 0 terraform/s3.tf | 17 +++++++++++++++++ terraform/vars.tf | 5 +++++ 5 files changed, 22 insertions(+) create mode 100644 src/extract_lambda.py create mode 100644 src/load_lambda.py create mode 100644 src/transform_lambda.py create mode 100644 terraform/s3.tf diff --git a/src/extract_lambda.py b/src/extract_lambda.py new file mode 100644 index 0000000..e69de29 diff --git a/src/load_lambda.py b/src/load_lambda.py new file mode 100644 index 0000000..e69de29 diff --git a/src/transform_lambda.py b/src/transform_lambda.py new file mode 100644 index 0000000..e69de29 diff --git a/terraform/s3.tf b/terraform/s3.tf new file mode 100644 index 0000000..bfe891e --- /dev/null +++ b/terraform/s3.tf @@ -0,0 +1,17 @@ +resource "aws_s3_bucket" "extract_bucket" { + bucket = "${var.s3_extract_bucket_name}" +} + +resource "aws_s3_bucket" "transform_bucket" { + bucket = "${var.s3_transform_bucket_name}" +} + +resource "aws_s3_bucket" "lambda_bucket" { + bucket = "${var.s3_code_bucket_name}" +} + +resource "aws_s3_object" "extract_lambda_code" { + bucket = aws_s3_bucket.s3_code_bucket_name.bucket + key = "${var.extract_lambda_name}/function_e.zip" + source = "${path.module}/../function_e.zip" +} \ No newline at end of file diff --git a/terraform/vars.tf b/terraform/vars.tf index 166f2c5..fa84222 100644 --- a/terraform/vars.tf +++ b/terraform/vars.tf @@ -8,6 +8,11 @@ variable "s3_transform_bucket_name" { default = "transform-bucket" } +variable "s3_code_bucket_name" { + type = string + default = "lambda-bucket" +} + variable "extract_lambda_name" { type = string default = "extract-lambda" -- cgit v1.2.3 From c75e650dbeb1390336d15487a2c87c53337cd8dc Mon Sep 17 00:00:00 2001 From: Ellie Date: Tue, 13 Aug 2024 11:25:33 +0100 Subject: infra(tf): add s3 policy for list & write --- terraform/iam.tf | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/terraform/iam.tf b/terraform/iam.tf index 7501373..b9919a5 100644 --- a/terraform/iam.tf +++ b/terraform/iam.tf @@ -1,5 +1,3 @@ -# define - resource "aws_iam_role" "bentley_service_role" { assume_role_policy = < Date: Tue, 13 Aug 2024 11:30:28 +0100 Subject: database connection added to func --- src/extract_lambda.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/extract_lambda.py b/src/extract_lambda.py index e69de29..7d56c66 100644 --- a/src/extract_lambda.py +++ b/src/extract_lambda.py @@ -0,0 +1,32 @@ +from pg8000.native import Connection, Error, DatabaseError, InterfaceError +from dotenv import load_dotenv +import os + +load_dotenv() + +def extract(): + +# temporary credentials for dev- will not have access when uploaded + + database = os.getenv('database') + user = os.getenv('user') + password = os.getenv('password') + host = os.getenv('host') + port = os.getenv('port') + + + try: + db = Connection.run( + database=database, + user=user, + password=password, + host=host, + port=port + ) + except DatabaseError as e: + print(e) + except InterfaceError as i: + print(i) + + + \ No newline at end of file -- cgit v1.2.3 From 65e470c0bce51381da8f401f0ba07bd20a76071f Mon Sep 17 00:00:00 2001 From: Ellie Date: Tue, 13 Aug 2024 11:55:00 +0100 Subject: infra(tf): add wip write policy and attach policy --- terraform/iam.tf | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/terraform/iam.tf b/terraform/iam.tf index b9919a5..dda4d74 100644 --- a/terraform/iam.tf +++ b/terraform/iam.tf @@ -22,17 +22,12 @@ resource "aws_iam_role" "bentley_service_role" { EOF } +# lambda setup + + # s3 setup -# allows to list and retrieve s3 buckets, and allows retention/tagging/access control settings +# allows allows retention/tagging/access control settings data "aws_iam_policy_document" "s3_data_policy_doc" { - statement { - actions = [ - "s3:ListAllMyBuckets", - "s3:GetBucketLocation" - ] - resources = ["arn:aws:s3:::*"] - } - statement { actions = [ "s3:PutObject", @@ -41,8 +36,22 @@ data "aws_iam_policy_document" "s3_data_policy_doc" { "s3:PutObjectAcl" ] resources = [ - "${aws_s3_bucket.data_bucket.arn}/*", - "${aws_s3_bucket.code_bucket.arn}/*" + "${aws_s3_bucket.extract_bucket.arn}/*", + "${aws_s3_bucket.transform_bucket.arn}/*", + "${aws_s3_bucket.lambda_bucket.arn}/*", ] } -} \ No newline at end of file +} + +# write policy +resource "aws_iam_policy" "s3_policy" { + policy = data.aws_iam_policy_document.s3_data_policy_doc.json +} + +# attach policy to role +resource "aws_iam_role_policy_attachment" "s3_policy_attachment" { + role = aws_iam_role.bentley_service_role.name + policy_arn = aws_iam_policy.s3_policy.arn +} + +# lambda setup -- cgit v1.2.3 From 936eee1eb44d8bfdbd148d22b749966e9606fb46 Mon Sep 17 00:00:00 2001 From: Ellie Date: Tue, 13 Aug 2024 11:58:02 +0100 Subject: infra(tf): add wip lambda role --- terraform/iam.tf | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/terraform/iam.tf b/terraform/iam.tf index dda4d74..10b8749 100644 --- a/terraform/iam.tf +++ b/terraform/iam.tf @@ -23,6 +23,9 @@ resource "aws_iam_role" "bentley_service_role" { } # lambda setup +resource "aws_iam_role" "lambda_role" { + assume_role_policy = data.aws_iam_policy_document.bentley_service_role.json +} # s3 setup @@ -44,14 +47,14 @@ data "aws_iam_policy_document" "s3_data_policy_doc" { } # write policy -resource "aws_iam_policy" "s3_policy" { +resource "aws_iam_policy" "s3_write_policy" { policy = data.aws_iam_policy_document.s3_data_policy_doc.json } # attach policy to role resource "aws_iam_role_policy_attachment" "s3_policy_attachment" { - role = aws_iam_role.bentley_service_role.name - policy_arn = aws_iam_policy.s3_policy.arn + role = aws_iam_role.lambda_role.name + policy_arn = aws_iam_policy.s3_write_policy.arn } # lambda setup -- cgit v1.2.3 From eb09f0f6a42e2a2ce9529492a47a34f782ffad53 Mon Sep 17 00:00:00 2001 From: Ellie Date: Tue, 13 Aug 2024 12:17:52 +0100 Subject: infra(tf): clean-up code & init lambda iam setup --- terraform/iam.tf | 123 +++++++++++++++++++++++++++++++++---------------------- 1 file changed, 74 insertions(+), 49 deletions(-) diff --git a/terraform/iam.tf b/terraform/iam.tf index 10b8749..ecc63b1 100644 --- a/terraform/iam.tf +++ b/terraform/iam.tf @@ -1,60 +1,85 @@ -resource "aws_iam_role" "bentley_service_role" { - assume_role_policy = < Date: Tue, 13 Aug 2024 12:30:20 +0100 Subject: replacement for events.tf variable placeholders, s3 bucket notifications, lambda.tf set up (function, zip, permissions), code bucket to store zipped lambdas --- events - TBD?.tf | 52 +++++++++++++++++++++++++++++++++++++ events.tf | 52 ------------------------------------- terraform/events.tf | 23 ++++++++--------- terraform/lambda.tf | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++++ terraform/s3.tf | 39 ++++++++++++++++++++++------ terraform/vars.tf | 5 ++++ 6 files changed, 173 insertions(+), 72 deletions(-) create mode 100644 events - TBD?.tf delete mode 100644 events.tf create mode 100644 terraform/lambda.tf diff --git a/events - TBD?.tf b/events - TBD?.tf new file mode 100644 index 0000000..25fb35b --- /dev/null +++ b/events - TBD?.tf @@ -0,0 +1,52 @@ +resource "aws_cloudwatch_event_rule" "lambda_trigger" { + name = "lambda-scheduled-trigger" + description = "Schedule to trigger the Lambda function" + schedule_expression = "rate(30 minutes)" + +# event_pattern = jsonencode({ +# detail-type = [ +# "AWS Console Sign In via CloudTrail" +# ] +# }) +} + + +resource "aws_cloudwatch_event_target" "lambda" { + rule = aws_cloudwatch_event_rule.lambda_trigger.name + target_id = "TargetFunctionV1" + arn = aws_lambda_function.my_lambda_function.arn +} + + + +resource "aws_lambda_permission" "allow_eventbridge" { + statement_id = "AllowExecutionFromEventBridge" + action = "lambda:InvokeFunction" + function_name = aws_lambda_function.my_lambda_function.function_name + principal = "events.amazonaws.com" + source_arn = aws_cloudwatch_event_rule.lambda_trigger.arn +} + + +# below is step function 1 +resource "aws_lambda_permission" "allow_s3_ingestion" { + statement_id = "AllowS3InvokeLambdaTransform" + action = "lambda:InvokeFunction" + function_name = aws_lambda_function.lambda_transform.function_name + principal = "s3.amazonaws.com" + source_arn = aws_s3_bucket.extract.arn +} + + +resource "aws_s3_bucket_notification" "extract_bucket_notification" { + bucket = aws_s3_bucket.extract.id + + lambda_function { + events = ["s3:ObjectCreated:*"] + lambda_function_arn = aws_lambda_function.lambda_transform.arn + } + + depends_on = [aws_lambda_permission.allow_s3_ingestion] +} + +# need to duplicate and replace "2" with "3" \ No newline at end of file diff --git a/events.tf b/events.tf deleted file mode 100644 index 25fb35b..0000000 --- a/events.tf +++ /dev/null @@ -1,52 +0,0 @@ -resource "aws_cloudwatch_event_rule" "lambda_trigger" { - name = "lambda-scheduled-trigger" - description = "Schedule to trigger the Lambda function" - schedule_expression = "rate(30 minutes)" - -# event_pattern = jsonencode({ -# detail-type = [ -# "AWS Console Sign In via CloudTrail" -# ] -# }) -} - - -resource "aws_cloudwatch_event_target" "lambda" { - rule = aws_cloudwatch_event_rule.lambda_trigger.name - target_id = "TargetFunctionV1" - arn = aws_lambda_function.my_lambda_function.arn -} - - - -resource "aws_lambda_permission" "allow_eventbridge" { - statement_id = "AllowExecutionFromEventBridge" - action = "lambda:InvokeFunction" - function_name = aws_lambda_function.my_lambda_function.function_name - principal = "events.amazonaws.com" - source_arn = aws_cloudwatch_event_rule.lambda_trigger.arn -} - - -# below is step function 1 -resource "aws_lambda_permission" "allow_s3_ingestion" { - statement_id = "AllowS3InvokeLambdaTransform" - action = "lambda:InvokeFunction" - function_name = aws_lambda_function.lambda_transform.function_name - principal = "s3.amazonaws.com" - source_arn = aws_s3_bucket.extract.arn -} - - -resource "aws_s3_bucket_notification" "extract_bucket_notification" { - bucket = aws_s3_bucket.extract.id - - lambda_function { - events = ["s3:ObjectCreated:*"] - lambda_function_arn = aws_lambda_function.lambda_transform.arn - } - - depends_on = [aws_lambda_permission.allow_s3_ingestion] -} - -# need to duplicate and replace "2" with "3" \ No newline at end of file diff --git a/terraform/events.tf b/terraform/events.tf index 4d68a23..0196dc3 100644 --- a/terraform/events.tf +++ b/terraform/events.tf @@ -11,18 +11,17 @@ resource "aws_cloudwatch_event_rule" "lambda_trigger" { } -resource "aws_cloudwatch_event_target" "lambda" { +resource "aws_cloudwatch_event_target" "extract_lambda_cw_event" { rule = aws_cloudwatch_event_rule.lambda_trigger.name target_id = "TargetFunctionV1" - arn = aws_lambda_function.my_lambda_function.arn + arn = aws_lambda_function.extract_lambda.arn #replaced lambda name placeholder } - resource "aws_lambda_permission" "allow_eventbridge" { statement_id = "AllowExecutionFromEventBridge" action = "lambda:InvokeFunction" - function_name = aws_lambda_function.my_lambda_function.function_name + function_name = aws_lambda_function.extract_lambda.function_name #replaced lambda name placeholder principal = "events.amazonaws.com" source_arn = aws_cloudwatch_event_rule.lambda_trigger.arn } @@ -32,18 +31,18 @@ resource "aws_lambda_permission" "allow_eventbridge" { resource "aws_lambda_permission" "allow_s3_ingestion" { statement_id = "AllowS3InvokeLambdaTransform" action = "lambda:InvokeFunction" - function_name = aws_lambda_function.lambda_transform.function_name + function_name = aws_lambda_function.transform_lambda.function_name #replaced lambda name placeholder principal = "s3.amazonaws.com" - source_arn = aws_s3_bucket.extract.arn + source_arn = aws_s3_bucket.extract_bucket.arn #replaced bucket name placeholder } resource "aws_s3_bucket_notification" "extract_bucket_notification" { - bucket = aws_s3_bucket.extract.id + bucket = aws_s3_bucket.extract_bucket.id #replaced bucket name placeholder lambda_function { events = ["s3:ObjectCreated:*"] - lambda_function_arn = aws_lambda_function.lambda_transform.arn + lambda_function_arn = aws_lambda_function.transform_lambda.arn #replaced lambda name placeholder } depends_on = [aws_lambda_permission.allow_s3_ingestion] @@ -54,18 +53,18 @@ resource "aws_s3_bucket_notification" "extract_bucket_notification" { resource "aws_lambda_permission" "allow_s3_transfrom_bucket" { statement_id = "AllowS3InvokeLambdaTransform" action = "lambda:InvokeFunction" - function_name = aws_lambda_function.lambda_transform.function_name + function_name = aws_lambda_function.transform_lambda.function_name #replaced lambda name placeholder principal = "s3.amazonaws.com" - source_arn = aws_s3_bucket.transform.arn + source_arn = aws_s3_bucket.transform_bucket.arn #replaced bucket name placeholder } resource "aws_s3_bucket_notification" "transform_bucket_notification" { - bucket = aws_s3_bucket.transform.id + bucket = aws_s3_bucket.transform_bucket.id #replaced bucket name placeholder lambda_function { events = ["s3:ObjectCreated:*"] - lambda_function_arn = aws_lambda_function.lambda_transform.arn + lambda_function_arn = aws_lambda_function.transform_lambda.arn #replaced lambda name placeholder } depends_on = [aws_lambda_permission.allow_s3_transform] diff --git a/terraform/lambda.tf b/terraform/lambda.tf new file mode 100644 index 0000000..09d6697 --- /dev/null +++ b/terraform/lambda.tf @@ -0,0 +1,74 @@ +### EXTRACT LAMBDA SET UP +data "archive_file" "extract_lambda_zip" { + type = "zip" + source_file = "${path.module}/../src/extract_lambda.py" + output_path = "${path.module}/../extract_function.zip" +} + +resource "aws_lambda_function" "extract_lambda" { + function_name = "${var.extract_lambda_name}" + s3_bucket = aws_s3_bucket.lambda_bucket.bucket + s3_key = "extract_lambda/extract_function.zip" + role = aws_iam_role.PLACEHOLDER_extract_lambda_role.arn # << lambda role placehodler + handler = "extract_lambda.lambda_handler" # << check that the function is called lambda handler + runtime = "python3.11" + environment { + variables = { + output = aws_s3_bucket.extract_bucket.bucket + } + } +} + +resource "aws_lambda_permission" "allow_to_write_to_s3_extract_bucket" { + action = "lambda:InvokeFunction" + function_name = aws_lambda_function.extract_lambda.function_name + principal = "s3.amazonaws.com" + source_arn = aws_s3_bucket.extract_bucket.arn +} + + +### TRANSFORM LAMBDA SET UP +data "archive_file" "transform_lambda_zip" { + type = "zip" + source_file = "${path.module}/../src/transform_lambda.py" + output_path = "${path.module}/../transform_function.zip" +} + +resource "aws_lambda_function" "transform_lambda" { + function_name = "${var.transform_lambda_name}" + s3_bucket = aws_s3_bucket.lambda_bucket.bucket + s3_key = "transform_lambda/transform_function.zip" + role = aws_iam_role.PLACEHOLDER_transform_lambda_role.arn # << lambda role placehodler + handler = "transform_lambda.lambda_handler" # << check that the function is called lambda handler + runtime = "python3.11" + environment { + variables = { + output = aws_s3_bucket.transform_bucket.bucket + } + } +} + +resource "aws_lambda_permission" "allow_to_write_to_s3_transform_bucket" { + action = "lambda:InvokeFunction" + function_name = aws_lambda_function.transform_lambda.function_name + principal = "s3.amazonaws.com" + source_arn = aws_s3_bucket.transform_bucket.arn +} + + +### LOAD LAMBDA SET UP +data "archive_file" "load_lambda_zip" { + type = "zip" + source_file = "${path.module}/../src/load_lambda.py" + output_path = "${path.module}/../load_function.zip" +} + +resource "aws_lambda_function" "load_lambda" { + function_name = "${var.load_lambda_name}" + s3_bucket = aws_s3_bucket.lambda_bucket.bucket + s3_key = "load_lambda/load_function.zip" + role = aws_iam_role.PLACEHOLDER_load_lambda_role.arn # << lambda role placehodler + handler = "load_lambda.lambda_handler" # << check that the function is called lambda handler + runtime = "python3.11" +} + diff --git a/terraform/s3.tf b/terraform/s3.tf index bfe891e..8cb65ef 100644 --- a/terraform/s3.tf +++ b/terraform/s3.tf @@ -1,17 +1,40 @@ +### EXTRACT BUCKET SET-UP resource "aws_s3_bucket" "extract_bucket" { bucket = "${var.s3_extract_bucket_name}" } +resource "aws_s3_object" "extract_lambda_code" { + bucket = aws_s3_bucket.s3_code_bucket_name.bucket + key = "${var.extract_lambda_name}/extract_function.zip" + source = "${path.module}/../extract_function.zip" +} # << can't figure out how this is being used but we seem to need it + +resource "aws_s3_bucket_notification" "extract_bucket_notification" { + bucket = aws_s3_bucket.extract_bucket.id + lambda_function { + lambda_function_arn = aws_lambda_function.extract_lambda.arn + events = ["s3:ObjectCreated:*"] + } + depends_on = [aws_lambda_permission.allow_to_write_to_s3_extract_bucket] +} # << is this the correct permission dependency? + + +### TRANSFORM BUCKET SET-UP resource "aws_s3_bucket" "transform_bucket" { bucket = "${var.s3_transform_bucket_name}" } -resource "aws_s3_bucket" "lambda_bucket" { - bucket = "${var.s3_code_bucket_name}" -} - -resource "aws_s3_object" "extract_lambda_code" { +resource "aws_s3_object" "transform_lambda_code" { bucket = aws_s3_bucket.s3_code_bucket_name.bucket - key = "${var.extract_lambda_name}/function_e.zip" - source = "${path.module}/../function_e.zip" -} \ No newline at end of file + key = "${var.transform_lambda_name}/transform_function.zip" + source = "${path.module}/../transform_function.zip" +} # << can't figure out how this is being used but we seem to need it + +resource "aws_s3_bucket_notification" "transform_bucket_notification" { + bucket = aws_s3_bucket.transform_bucket.id + lambda_function { + lambda_function_arn = aws_lambda_function.transform_lambda.arn + events = ["s3:ObjectCreated:*"] + } + depends_on = [aws_lambda_permission.allow_to_write_to_s3_transform_bucket] +} # << is this the correct permission dependency? diff --git a/terraform/vars.tf b/terraform/vars.tf index fa84222..cc9348a 100644 --- a/terraform/vars.tf +++ b/terraform/vars.tf @@ -23,6 +23,11 @@ variable "transform_lambda_name" { default = "transform-lambda" } +variable "load_lambda_name" { + type = string + default = "load-lambda" +} + data "aws_caller_identity" "current" {} data "aws_region" "current" {} \ No newline at end of file -- cgit v1.2.3 From 974a8018f79d8592cbd6a59b1b26a9d288975328 Mon Sep 17 00:00:00 2001 From: T-Aji Date: Tue, 13 Aug 2024 12:30:42 +0100 Subject: dumps data to csv --- src/extract_lambda.py | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/src/extract_lambda.py b/src/extract_lambda.py index 7d56c66..8317ef8 100644 --- a/src/extract_lambda.py +++ b/src/extract_lambda.py @@ -1,22 +1,25 @@ from pg8000.native import Connection, Error, DatabaseError, InterfaceError from dotenv import load_dotenv import os +import boto3 +import csv +from botocore.exceptions import ClientError load_dotenv() -def extract(): +def lambda_handler(event, context): + client = boto3.client('s3') # temporary credentials for dev- will not have access when uploaded - + database = os.getenv('database') user = os.getenv('user') password = os.getenv('password') host = os.getenv('host') port = os.getenv('port') - try: - db = Connection.run( + db = Connection( database=database, user=user, password=password, @@ -27,6 +30,25 @@ def extract(): print(e) except InterfaceError as i: print(i) - + #replace prints with upload to cloudwatch logs + + tables = db.run("SELECT table_name FROM information_schema.tables WHERE table_schema='public' AND table_type='BASE TABLE';") + for table in tables: + table_name = table[0] + rows = db.run(f"SELECT * FROM {table_name};") + # this saves the csv files to the repo root before writing to s3, this is unnecessary. how will the lambda behave when it attempts to save files? + with open(f"{table_name}.csv", "w", newline='') as file: + writer = csv.writer(file) + writer.writerow([desc["name"] for desc in db.columns(f"SELECT * FROM {table_name};")]) + writer.writerows(rows) + try: + client.upload_file(file, Bucket='ingestion-bucket', Object_name=table_name) + + except ClientError as e: + print(e) + #replace print with upload to cloudwatch logs + + if db: + db.close() \ No newline at end of file -- cgit v1.2.3 From 3c824df60374380d044cb9181672fa76b610d84f Mon Sep 17 00:00:00 2001 From: Ellie Date: Tue, 13 Aug 2024 12:30:53 +0100 Subject: infra(tf): clean-up code --- terraform/iam.tf | 69 +++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 56 insertions(+), 13 deletions(-) diff --git a/terraform/iam.tf b/terraform/iam.tf index ecc63b1..bb8d932 100644 --- a/terraform/iam.tf +++ b/terraform/iam.tf @@ -4,7 +4,7 @@ ######################################################################## # DEFINE MULTI-SERVICE ROLE (lambda, s3, cloudwatch, events) -resource "aws_iam_role" "multi_service_role" { +resource "aws_iam_role" "bentley_multi_service_role" { name = "multi_service_role" assume_role_policy = jsonencode({ @@ -61,6 +61,61 @@ resource "aws_iam_policy" "s3_access_policy" { ) } +######################################################################## +# LAMBDA SETUP +# Description: Allows Lambda permission to write to Cloudwatch logs +######################################################################## + +resource "aws_iam_policy" "lambda_execution_policy" { + name = "lambda_execution_policy" + path = "/" + description = "IAM policy for Lambda execution" + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Effect = "Allow" + Action = [ + "lambda:InvokeFunction", + "lambda:GetFunction" + ] + Resource = "*" + } + ] + } + ) +} + +######################################################################## +# CLOUDWATCH SETUP +# Description: Give permission for Lambda to write to CloudWatch logs +######################################################################## + +data "aws_iam_policy_document" "cw_document" { + statement { + actions = ["logs:CreateLogGroup"] + resources = [ + "arn:aws:logs:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:*" + ] + } + + statement { + actions = [ + "logs:CreateLogStream", + "logs:CreateLogGroup", + "logs:PutLogEvents" + ] + resources = [ + "arn:aws:logs:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:log-group:/aws/lambda/*" + ] + } +} + +######################################################################## +# POLICY WRITE & ATTACH +######################################################################## + # S3 WRITE POLICY resource "aws_iam_policy" "s3_write_policy" { policy = data.aws_iam_policy_document.s3_data_policy_doc.json @@ -70,16 +125,4 @@ resource "aws_iam_policy" "s3_write_policy" { resource "aws_iam_role_policy_attachment" "lambda_s3_policy_attachment" { role = aws_iam_role.lambda_role.name policy_arn = aws_iam_policy.s3_write_policy.arn -} - -######################################################################## -# LAMBDA SETUP -# Description: Allows Lambda permission to write to Cloudwatch logs -######################################################################## - - - -# Uses Iam policy document to assume role for lambda functions -resource "aws_iam_role" "lambda_role" { - assume_role_policy = data.aws_iam_policy_document.bentley_service_role.json } \ No newline at end of file -- cgit v1.2.3 From cdb4577b5ad7ae1f708797de6bbf17e289bfac14 Mon Sep 17 00:00:00 2001 From: T-Aji Date: Tue, 13 Aug 2024 15:32:33 +0100 Subject: feat/ add logging & split task into 3 helper functions --- src/extract_lambda.py | 140 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 109 insertions(+), 31 deletions(-) diff --git a/src/extract_lambda.py b/src/extract_lambda.py index 8317ef8..11ea5d1 100644 --- a/src/extract_lambda.py +++ b/src/extract_lambda.py @@ -1,54 +1,132 @@ -from pg8000.native import Connection, Error, DatabaseError, InterfaceError +from pg8000.native import Connection, DatabaseError, InterfaceError from dotenv import load_dotenv import os import boto3 import csv from botocore.exceptions import ClientError +import logging +import json +logger = logging.getLogger() +logger.setLevel(logging.INFO) load_dotenv() -def lambda_handler(event, context): - client = boto3.client('s3') -# temporary credentials for dev- will not have access when uploaded +database = os.getenv('database') +user = os.getenv('user') +password = os.getenv('password') +host = os.getenv('host') +port = os.getenv('port') + +def lambda_handler(event, context): + """This lambda function connects to the Totesys database, lists the contents of the ingestion bucket, + and converts all tables to CSV and if any of those tables do not exist in, or are different to the ones in s3, it uploads them + it uses 3 helper functions to achieve these 3 functionalities + """ + try: + db = connect_to_database() + existing_files = list_existing_s3_files() + any_changes = process_and_upload_tables(db, existing_files) + + if not any_changes: + logger.info("No changes detected in the database.") + return { + 'statusCode': 200, + 'body': json.dumps('No changes detected, no CSV files were uploaded.') + } + else: + return { + 'statusCode': 200, + 'body': json.dumps('CSV files processed and uploaded successfully.') + } + + except Exception as e: + logger.error(f'Error: {e}') + return { + 'statusCode': 500, + 'body': json.dumps('Internal server error.') + } - database = os.getenv('database') - user = os.getenv('user') - password = os.getenv('password') - host = os.getenv('host') - port = os.getenv('port') + finally: + + if db: + db.close() +def connect_to_database(): try: - db = Connection( - database=database, - user=user, - password=password, - host=host, - port=port + return Connection( + database=database, + user=user, + password=password, + host=host, + port=port ) except DatabaseError as e: - print(e) + logger.error(f'Database error: {e}') + raise except InterfaceError as i: - print(i) - #replace prints with upload to cloudwatch logs + logger.error(f'Interface error: {i}') + raise + + +def list_existing_s3_files(): + """Creates a dictionary and populates it with the + results of listing the contents of the s3 bucket, then + returns the populated dictionary + """ + client = boto3.client('s3') + existing_files = {} + + try: + response = client.list_objects_v2(Bucket=ingestion_bucket) + + if 'Contents' in response: + for obj in response['Contents']: + s3_key = obj['Key'] + try: + file_obj = client.get_object(Bucket=ingestion_bucket, Key=s3_key) + file_content = file_obj['Body'].read().decode('utf-8') + existing_files[s3_key] = file_content + except ClientError as e: + logger.error(f'Error retrieving S3 object {s3_key}: {e}') + + except ClientError as e: + logger.error(f'Error listing S3 objects: {e}') + + return existing_files + + + +def process_and_upload_tables(db, existing_files): + """Creates a list of the tables from a database query and + then selects everything from each table in individual queries + it then writes each table to CSV files and compares with the item + in the existing_files dictionary with the same name. If it finds sny changes + to files, or new tables/files it uploads them to the s3 bucket + """ + client = boto3.client('s3') tables = db.run("SELECT table_name FROM information_schema.tables WHERE table_schema='public' AND table_type='BASE TABLE';") + for table in tables: table_name = table[0] rows = db.run(f"SELECT * FROM {table_name};") - # this saves the csv files to the repo root before writing to s3, this is unnecessary. how will the lambda behave when it attempts to save files? - with open(f"{table_name}.csv", "w", newline='') as file: + + + csv_file_path = f"/tmp/{table_name}.csv" + with open(csv_file_path, "w", newline='') as file: writer = csv.writer(file) - writer.writerow([desc["name"] for desc in db.columns(f"SELECT * FROM {table_name};")]) + column_names = [desc["name"] for desc in db.columns(f"SELECT * FROM {table_name};")] + writer.writerow(column_names) writer.writerows(rows) - try: - client.upload_file(file, Bucket='ingestion-bucket', Object_name=table_name) - - except ClientError as e: - print(e) - #replace print with upload to cloudwatch logs - - if db: - db.close() + + s3_key = f"{table_name}/latest.csv" + new_csv_content = open(csv_file_path, "r").read() + - \ No newline at end of file + if s3_key not in existing_files or existing_files[s3_key] != new_csv_content: + try: + client.upload_file(csv_file_path, ingestion_bucket, s3_key) + logger.info(f"Uploaded {s3_key} to S3.") + except ClientError as e: + logger.error(f'Error uploading to S3: {e}') \ No newline at end of file -- cgit v1.2.3 From bb1665fd08d8abf10930875272bdc2b7f8a4c681 Mon Sep 17 00:00:00 2001 From: Ang Bel Date: Tue, 13 Aug 2024 16:25:06 +0100 Subject: rds.tf file with some placehodlers. Additional set up is required --- terraform/lambda.tf | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++ terraform/rds.tf | 78 +++++++++++++++++++++++++++++++++++++++++++++++++++++ terraform/vars.tf | 10 +++++++ 3 files changed, 162 insertions(+) create mode 100644 terraform/lambda.tf create mode 100644 terraform/rds.tf diff --git a/terraform/lambda.tf b/terraform/lambda.tf new file mode 100644 index 0000000..09d6697 --- /dev/null +++ b/terraform/lambda.tf @@ -0,0 +1,74 @@ +### EXTRACT LAMBDA SET UP +data "archive_file" "extract_lambda_zip" { + type = "zip" + source_file = "${path.module}/../src/extract_lambda.py" + output_path = "${path.module}/../extract_function.zip" +} + +resource "aws_lambda_function" "extract_lambda" { + function_name = "${var.extract_lambda_name}" + s3_bucket = aws_s3_bucket.lambda_bucket.bucket + s3_key = "extract_lambda/extract_function.zip" + role = aws_iam_role.PLACEHOLDER_extract_lambda_role.arn # << lambda role placehodler + handler = "extract_lambda.lambda_handler" # << check that the function is called lambda handler + runtime = "python3.11" + environment { + variables = { + output = aws_s3_bucket.extract_bucket.bucket + } + } +} + +resource "aws_lambda_permission" "allow_to_write_to_s3_extract_bucket" { + action = "lambda:InvokeFunction" + function_name = aws_lambda_function.extract_lambda.function_name + principal = "s3.amazonaws.com" + source_arn = aws_s3_bucket.extract_bucket.arn +} + + +### TRANSFORM LAMBDA SET UP +data "archive_file" "transform_lambda_zip" { + type = "zip" + source_file = "${path.module}/../src/transform_lambda.py" + output_path = "${path.module}/../transform_function.zip" +} + +resource "aws_lambda_function" "transform_lambda" { + function_name = "${var.transform_lambda_name}" + s3_bucket = aws_s3_bucket.lambda_bucket.bucket + s3_key = "transform_lambda/transform_function.zip" + role = aws_iam_role.PLACEHOLDER_transform_lambda_role.arn # << lambda role placehodler + handler = "transform_lambda.lambda_handler" # << check that the function is called lambda handler + runtime = "python3.11" + environment { + variables = { + output = aws_s3_bucket.transform_bucket.bucket + } + } +} + +resource "aws_lambda_permission" "allow_to_write_to_s3_transform_bucket" { + action = "lambda:InvokeFunction" + function_name = aws_lambda_function.transform_lambda.function_name + principal = "s3.amazonaws.com" + source_arn = aws_s3_bucket.transform_bucket.arn +} + + +### LOAD LAMBDA SET UP +data "archive_file" "load_lambda_zip" { + type = "zip" + source_file = "${path.module}/../src/load_lambda.py" + output_path = "${path.module}/../load_function.zip" +} + +resource "aws_lambda_function" "load_lambda" { + function_name = "${var.load_lambda_name}" + s3_bucket = aws_s3_bucket.lambda_bucket.bucket + s3_key = "load_lambda/load_function.zip" + role = aws_iam_role.PLACEHOLDER_load_lambda_role.arn # << lambda role placehodler + handler = "load_lambda.lambda_handler" # << check that the function is called lambda handler + runtime = "python3.11" +} + diff --git a/terraform/rds.tf b/terraform/rds.tf new file mode 100644 index 0000000..4b25c5f --- /dev/null +++ b/terraform/rds.tf @@ -0,0 +1,78 @@ +data "aws_availability_zones" "available" {} + +module "vpc" { + source = "terraform-aws-modules/vpc/aws" + version = "2.77.0" + + name = "${var.project_name}" + cidr = "10.0.0.0/16" + azs = data.aws_availability_zones.available.names + public_subnets = ["10.0.4.0/24", "10.0.5.0/24", "10.0.6.0/24"] + enable_dns_hostnames = true + enable_dns_support = true +} + +resource "aws_db_subnet_group" "Terrific-Totes-sub-gr" { + name = "TT-db-subnet" + subnet_ids = module.vpc.public_subnets + + tags = { + Name = "${var.project_name}" + } +} + +resource "aws_security_group" "rds" { + name = "${var.project_name}-rds" + vpc_id = module.vpc.vpc_id + + ingress { + from_port = 5432 + to_port = 5432 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + egress { + from_port = 5432 + to_port = 5432 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + tags = { + Name = "${var.project_name}-rds" + } +} + +resource "aws_db_parameter_group" "Terrific-Totes-param-gr" { + name = "TT-db-param" + family = "postgres14" + + parameter { + name = "log_connections" + value = "1" + } +} + +resource "aws_db_instance" "Terrific-Totes-rds" { + db_name = "${var.project_name}" + instance_class = "db.t3.micro" + allocated_storage = 5 + engine = "postgres" + engine_version = "14.1" + username = "user credentials for the root user" # we could use .env here + password = "user password for the root user" # we could use .env here + ### alternatively to providing username nad password we can specify: +# resource "aws_kms_key" "example_key" { +# description = "Example KMS Key" +# } +# within the resource: +# manage_master_user_password = true +# master_user_secret_kms_key_id = aws_kms_key.example.key_id +# } + db_subnet_group_name = aws_db_subnet_group.Terrific-Totes-sub-gr.name + vpc_security_group_ids = [aws_security_group.rds.id] + parameter_group_name = aws_db_parameter_group.Terrific-Totes-param-gr.name + publicly_accessible = false + skip_final_snapshot = true +} \ No newline at end of file diff --git a/terraform/vars.tf b/terraform/vars.tf index fa84222..350c2c6 100644 --- a/terraform/vars.tf +++ b/terraform/vars.tf @@ -23,6 +23,16 @@ variable "transform_lambda_name" { default = "transform-lambda" } +variable "load_lambda_name" { + type = string + default = "load-lambda" +} + +variable "project_name" { + type = string + default = "Terrific-Totes" +} + data "aws_caller_identity" "current" {} data "aws_region" "current" {} \ No newline at end of file -- cgit v1.2.3 From 6c7914a9d33fbaa962cf1f083c2ee79ace62f401 Mon Sep 17 00:00:00 2001 From: Ang Bel Date: Tue, 13 Aug 2024 16:33:56 +0100 Subject: restore load_lambda script --- src/load_lambda.py | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/load_lambda.py b/src/load_lambda.py index e69de29..5c6718c 100644 --- a/src/load_lambda.py +++ b/src/load_lambda.py @@ -0,0 +1,52 @@ +### Example taken from https://medium.com/@pranay1001090/how-to-load-data-from-amazon-s3-csv-parquet-to-aws-rds-using-python-3dc51dd2186e + +### THIS IS AN EXAMPLE CODE WE CAN PICK FROM, NONE OF THIS HAS BEEN CUSTOMISED YET + +import boto3 +import pandas as pd +import pyarrow.parquet as pq +from io import BytesIO +from sqlalchemy import create_engine + +# AWS credentials and region +aws_access_key = '' +aws_secret_key = '' +region_name = '' + +# S3 bucket and file details +bucket_name = '' +file_prefix = '' +s3_client = boto3.client('s3', aws_access_key_id=aws_access_key, aws_secret_access_key=aws_secret_key, region_name=region_name) + +# RDS connection details +database_name = '' +table_name = '' +rds_host = '' +rds_port = '' +rds_user = '' +rds_password = '' +# Function to load Parquet files into a Pandas DataFrame +def load_parquet_data(s3_bucket, s3_prefix): + file_objects = s3_client.list_objects_v2(Bucket=s3_bucket, Prefix=s3_prefix)['Contents'] + dfs = [] + for file_object in file_objects: + file_key = file_object['Key'] + file_obj = s3_client.get_object(Bucket=s3_bucket, Key=file_key) + parquet_file = pq.ParquetFile(BytesIO(file_obj['Body'].read())) + df = parquet_file.read().to_pandas() + dfs.append(df) + return pd.concat(dfs) + +# Load Parquet data from S3 into a Pandas DataFrame +df = load_parquet_data(bucket_name, file_prefix) +# Connect to RDS +conn_str = f'mysql+pymysql://{rds_user}:{rds_password}@{rds_host}:{rds_port}/{database_name}' +engine = create_engine(conn_str) + +# Write the DataFrame to RDS +df.to_sql(table_name, con=engine, if_exists='replace', index=False) + +# Closing the connection +engine.dispose() + +print('Data loaded successfully!') \ No newline at end of file -- cgit v1.2.3 From 68a0b4740e1aab2c507547ab985c7c1dc436d9c9 Mon Sep 17 00:00:00 2001 From: lian-manonog Date: Tue, 13 Aug 2024 17:16:12 +0100 Subject: wip: running terraform apply to continue fixing terraform infrastructure --- .gitignore | 5 +++++ terraform/events.tf | 4 ++-- terraform/iam.tf | 64 ++++++++++++++++++++++++++++------------------------- terraform/lambda.tf | 13 +++++------ terraform/s3.tf | 63 ++++++++++++++++++++++++++++++---------------------- 5 files changed, 84 insertions(+), 65 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5861f48 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*.tfstate +*.tfstate.* +*.tfvars +*.tfvars.json +.terraform.tfstate.lock.info \ No newline at end of file diff --git a/terraform/events.tf b/terraform/events.tf index 0196dc3..7a6b0ad 100644 --- a/terraform/events.tf +++ b/terraform/events.tf @@ -50,7 +50,7 @@ resource "aws_s3_bucket_notification" "extract_bucket_notification" { ###### -resource "aws_lambda_permission" "allow_s3_transfrom_bucket" { +resource "aws_lambda_permission" "allow_s3_transform_bucket" { statement_id = "AllowS3InvokeLambdaTransform" action = "lambda:InvokeFunction" function_name = aws_lambda_function.transform_lambda.function_name #replaced lambda name placeholder @@ -67,5 +67,5 @@ resource "aws_s3_bucket_notification" "transform_bucket_notification" { lambda_function_arn = aws_lambda_function.transform_lambda.arn #replaced lambda name placeholder } - depends_on = [aws_lambda_permission.allow_s3_transform] + depends_on = [aws_lambda_permission.allow_s3_transform_bucket] } \ No newline at end of file diff --git a/terraform/iam.tf b/terraform/iam.tf index bb8d932..f34d58a 100644 --- a/terraform/iam.tf +++ b/terraform/iam.tf @@ -4,7 +4,7 @@ ######################################################################## # DEFINE MULTI-SERVICE ROLE (lambda, s3, cloudwatch, events) -resource "aws_iam_role" "bentley_multi_service_role" { +resource "aws_iam_role" "multi_service_role" { name = "multi_service_role" assume_role_policy = jsonencode({ @@ -16,7 +16,7 @@ resource "aws_iam_role" "bentley_multi_service_role" { Principal = { Service = [ "lambda.amazonaws.com", - "states.amazonaws.com", + "cloudwatch.amazonaws.com", "events.amazonaws.com", "s3.amazonaws.com" ] @@ -27,7 +27,6 @@ resource "aws_iam_role" "bentley_multi_service_role" { } - ######################################################################## # S3 SETUP # Description: allows allows retention/tagging/access control settings @@ -35,32 +34,23 @@ resource "aws_iam_role" "bentley_multi_service_role" { ######################################################################## # S3 DEFINE POLICY -resource "aws_iam_policy" "s3_access_policy" { - name = "s3_access_policy" - path = "/" - description = "IAM policy for S3 access" - - policy = jsonencode({ - Version = "2012-10-17" - Statement = [ - { - Effect = "Allow" - Action = [ - "s3:PutObject", - "s3:GetObject", - "s3:ListBucket" - ] - resources = [ - "${aws_s3_bucket.extract_bucket.arn}/*", - "${aws_s3_bucket.transform_bucket.arn}/*", - "${aws_s3_bucket.lambda_bucket.arn}/*" - ] - } - ] - } - ) +data "aws_iam_policy_document" "s3_data_policy_doc" { + statement { + actions = [ + "s3:PutObject", + "s3:PutObjectRetention", + "s3:PutObjectTagging", + "s3:PutObjectAcl" + ] + resources = [ + "${aws_s3_bucket.extract_bucket.arn}/*", + "${aws_s3_bucket.transform_bucket.arn}/*", + "${aws_s3_bucket.lambda_code_bucket.arn}/*", + ] + } } + ######################################################################## # LAMBDA SETUP # Description: Allows Lambda permission to write to Cloudwatch logs @@ -112,6 +102,11 @@ data "aws_iam_policy_document" "cw_document" { } } +resource "aws_iam_policy" "cw_policy" { + name = "cw_policy" + policy = data.aws_iam_policy_document.cw_document.json +} + ######################################################################## # POLICY WRITE & ATTACH ######################################################################## @@ -123,6 +118,15 @@ resource "aws_iam_policy" "s3_write_policy" { # S3 ATTACH POLICY resource "aws_iam_role_policy_attachment" "lambda_s3_policy_attachment" { - role = aws_iam_role.lambda_role.name - policy_arn = aws_iam_policy.s3_write_policy.arn -} \ No newline at end of file + for_each = toset([ + aws_iam_policy.s3_write_policy.arn, + aws_iam_policy.lambda_execution_policy.arn, + aws_iam_policy.cw_policy.arn + ]) + role = aws_iam_role.multi_service_role.name + policy_arn = each.value +} + +################ +# RDS POLICIES # +################ diff --git a/terraform/lambda.tf b/terraform/lambda.tf index 09d6697..bcbf394 100644 --- a/terraform/lambda.tf +++ b/terraform/lambda.tf @@ -7,9 +7,9 @@ data "archive_file" "extract_lambda_zip" { resource "aws_lambda_function" "extract_lambda" { function_name = "${var.extract_lambda_name}" - s3_bucket = aws_s3_bucket.lambda_bucket.bucket + s3_bucket = aws_s3_bucket.lambda_code_bucket.bucket s3_key = "extract_lambda/extract_function.zip" - role = aws_iam_role.PLACEHOLDER_extract_lambda_role.arn # << lambda role placehodler + role = aws_iam_role.multi_service_role.arn #<< lambda role placehodler handler = "extract_lambda.lambda_handler" # << check that the function is called lambda handler runtime = "python3.11" environment { @@ -36,9 +36,9 @@ data "archive_file" "transform_lambda_zip" { resource "aws_lambda_function" "transform_lambda" { function_name = "${var.transform_lambda_name}" - s3_bucket = aws_s3_bucket.lambda_bucket.bucket + s3_bucket = aws_s3_bucket.lambda_code_bucket.bucket s3_key = "transform_lambda/transform_function.zip" - role = aws_iam_role.PLACEHOLDER_transform_lambda_role.arn # << lambda role placehodler + role = aws_iam_role.multi_service_role.arn # << lambda role placehodler handler = "transform_lambda.lambda_handler" # << check that the function is called lambda handler runtime = "python3.11" environment { @@ -55,7 +55,6 @@ resource "aws_lambda_permission" "allow_to_write_to_s3_transform_bucket" { source_arn = aws_s3_bucket.transform_bucket.arn } - ### LOAD LAMBDA SET UP data "archive_file" "load_lambda_zip" { type = "zip" @@ -65,9 +64,9 @@ data "archive_file" "load_lambda_zip" { resource "aws_lambda_function" "load_lambda" { function_name = "${var.load_lambda_name}" - s3_bucket = aws_s3_bucket.lambda_bucket.bucket + s3_bucket = aws_s3_bucket.lambda_code_bucket.bucket s3_key = "load_lambda/load_function.zip" - role = aws_iam_role.PLACEHOLDER_load_lambda_role.arn # << lambda role placehodler + role = aws_iam_role.multi_service_role.arn # << lambda role placehodler handler = "load_lambda.lambda_handler" # << check that the function is called lambda handler runtime = "python3.11" } diff --git a/terraform/s3.tf b/terraform/s3.tf index 8cb65ef..8ab5622 100644 --- a/terraform/s3.tf +++ b/terraform/s3.tf @@ -1,40 +1,51 @@ ### EXTRACT BUCKET SET-UP resource "aws_s3_bucket" "extract_bucket" { - bucket = "${var.s3_extract_bucket_name}" + bucket_prefix = "${var.s3_extract_bucket_name}-" } -resource "aws_s3_object" "extract_lambda_code" { - bucket = aws_s3_bucket.s3_code_bucket_name.bucket - key = "${var.extract_lambda_name}/extract_function.zip" - source = "${path.module}/../extract_function.zip" -} # << can't figure out how this is being used but we seem to need it - -resource "aws_s3_bucket_notification" "extract_bucket_notification" { - bucket = aws_s3_bucket.extract_bucket.id - lambda_function { - lambda_function_arn = aws_lambda_function.extract_lambda.arn - events = ["s3:ObjectCreated:*"] - } - depends_on = [aws_lambda_permission.allow_to_write_to_s3_extract_bucket] -} # << is this the correct permission dependency? - +# resource "aws_s3_bucket_notification" "extract_bucket_notification" { +# bucket = aws_s3_bucket.extract_bucket.id +# lambda_function { +# lambda_function_arn = aws_lambda_function.extract_lambda.arn +# events = ["s3:ObjectCreated:*"] +# } +# depends_on = [aws_lambda_permission.allow_to_write_to_s3_extract_bucket] +# } # << is this the correct permission dependency? ### TRANSFORM BUCKET SET-UP resource "aws_s3_bucket" "transform_bucket" { - bucket = "${var.s3_transform_bucket_name}" + bucket_prefix = "${var.s3_transform_bucket_name}-" } +# resource "aws_s3_bucket_notification" "transform_bucket_notification" { +# bucket = aws_s3_bucket.transform_bucket.id +# lambda_function { +# lambda_function_arn = aws_lambda_function.transform_lambda.arn +# events = ["s3:ObjectCreated:*"] +# } +# depends_on = [aws_lambda_permission.allow_to_write_to_s3_transform_bucket] +# } # << is this the correct permission dependency? + + +### LAMBDA BUCKET +resource "aws_s3_bucket" "lambda_code_bucket" { + bucket_prefix = "${var.s3_code_bucket_name}-" +} + +resource "aws_s3_object" "extract_lambda_code" { + bucket = aws_s3_bucket.lambda_code_bucket.bucket + key = "${var.extract_lambda_name}/extract_function.zip" + source = "${path.module}/../extract_function.zip" +} # << can't figure out how this is being used but we seem to need it + resource "aws_s3_object" "transform_lambda_code" { - bucket = aws_s3_bucket.s3_code_bucket_name.bucket + bucket = aws_s3_bucket.lambda_code_bucket.bucket key = "${var.transform_lambda_name}/transform_function.zip" source = "${path.module}/../transform_function.zip" } # << can't figure out how this is being used but we seem to need it -resource "aws_s3_bucket_notification" "transform_bucket_notification" { - bucket = aws_s3_bucket.transform_bucket.id - lambda_function { - lambda_function_arn = aws_lambda_function.transform_lambda.arn - events = ["s3:ObjectCreated:*"] - } - depends_on = [aws_lambda_permission.allow_to_write_to_s3_transform_bucket] -} # << is this the correct permission dependency? +resource "aws_s3_object" "load_lambda_code" { + bucket = aws_s3_bucket.lambda_code_bucket.bucket + key = "${var.load_lambda_name}/load_function.zip" + source = "${path.module}/../load_function.zip" +} \ No newline at end of file -- cgit v1.2.3 From 4f0d6f287ae83d7cdc0df6988ab7b9de10912f16 Mon Sep 17 00:00:00 2001 From: T-Aji Date: Wed, 14 Aug 2024 12:25:57 +0100 Subject: feat/passing tests to helper function list_existing_s3_files --- .gitignore | 3 +++ src/extract_lambda.py | 12 ++++++----- tests/dummy.txt | 1 + tests/test_extract_lambda.py | 49 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 60 insertions(+), 5 deletions(-) create mode 100644 .gitignore create mode 100644 tests/dummy.txt create mode 100644 tests/test_extract_lambda.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..428f94e --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +venv +.env +__pycache__/ \ No newline at end of file diff --git a/src/extract_lambda.py b/src/extract_lambda.py index 11ea5d1..dc70590 100644 --- a/src/extract_lambda.py +++ b/src/extract_lambda.py @@ -18,6 +18,7 @@ password = os.getenv('password') host = os.getenv('host') port = os.getenv('port') + def lambda_handler(event, context): """This lambda function connects to the Totesys database, lists the contents of the ingestion bucket, and converts all tables to CSV and if any of those tables do not exist in, or are different to the ones in s3, it uploads them @@ -69,27 +70,28 @@ def connect_to_database(): raise - -def list_existing_s3_files(): +def list_existing_s3_files(bucket_name='extract_bucket', client=boto3.client('s3')): """Creates a dictionary and populates it with the results of listing the contents of the s3 bucket, then returns the populated dictionary """ - client = boto3.client('s3') + existing_files = {} try: - response = client.list_objects_v2(Bucket=ingestion_bucket) + response = client.list_objects_v2(Bucket='extract_bucket') if 'Contents' in response: for obj in response['Contents']: s3_key = obj['Key'] try: - file_obj = client.get_object(Bucket=ingestion_bucket, Key=s3_key) + file_obj = client.get_object(Bucket=bucket_name, Key=s3_key) file_content = file_obj['Body'].read().decode('utf-8') existing_files[s3_key] = file_content except ClientError as e: logger.error(f'Error retrieving S3 object {s3_key}: {e}') + else: + logger.error('The bucket is empty') except ClientError as e: logger.error(f'Error listing S3 objects: {e}') diff --git a/tests/dummy.txt b/tests/dummy.txt new file mode 100644 index 0000000..af27ff4 --- /dev/null +++ b/tests/dummy.txt @@ -0,0 +1 @@ +This is a test file. \ No newline at end of file diff --git a/tests/test_extract_lambda.py b/tests/test_extract_lambda.py new file mode 100644 index 0000000..472e93a --- /dev/null +++ b/tests/test_extract_lambda.py @@ -0,0 +1,49 @@ +import pytest +import boto3 +from moto import mock_aws +from src.extract_lambda import list_existing_s3_files #process_and_upload_tables +import os +import logging + + +@pytest.fixture(scope='class') +def aws_credentials(): + os.environ["AWS_ACCESS_KEY_ID"] = 'testing' + os.environ["AWS_SECRET_ACCESS_KEY"] = 'testing' + os.environ["AWS_SECURIT_TOKEN"] = 'testing' + os.environ["AWS_SESSION_TOKEN"] = 'testing' + os.environ["AWS_DEFAULT_REGION"]= 'eu-west-2' + +@pytest.fixture(scope='class') +def s3_client(aws_credentials): + with mock_aws(): + yield boto3.client('s3') + +class TestListExistings3Files(): + def test_error_if_no_bucket(self, s3_client, caplog): + + logger = logging.getLogger() + logger.info('Testing now.') + caplog.set_level(logging.ERROR) + list_existing_s3_files(client=s3_client) + assert 'Error listing S3 objects' in caplog.text + + def test_error_if_bucket_is_empty(self, s3_client, caplog): + + s3_client.create_bucket(Bucket='extract_bucket', + CreateBucketConfiguration={ + 'LocationConstraint': 'eu-west-2' + }) + list_existing_s3_files(client=s3_client) + assert 'The bucket is empty' in caplog.text + + def test_error_retrieving_object(self, s3_client, caplog): + s3_client.upload_file('tests/dummy.txt', 'extract_bucket', 'dummy.txt') + list_existing_s3_files(bucket_name='test_bucket', client=s3_client) + + assert 'Error retrieving S3 object ' in caplog.text + + def test_retrieves_file_content(self, s3_client, caplog): + result = list_existing_s3_files(client=s3_client) + + assert list(result.values()) == ['This is a test file.'] \ No newline at end of file -- cgit v1.2.3 From 0e3faa19dec148fe05f6b2f58fc0331230ec41b7 Mon Sep 17 00:00:00 2001 From: lian-manonog Date: Wed, 14 Aug 2024 12:34:42 +0100 Subject: delete events TBD --- events - TBD?.tf | 52 ---------------------------------------------------- 1 file changed, 52 deletions(-) delete mode 100644 events - TBD?.tf diff --git a/events - TBD?.tf b/events - TBD?.tf deleted file mode 100644 index 25fb35b..0000000 --- a/events - TBD?.tf +++ /dev/null @@ -1,52 +0,0 @@ -resource "aws_cloudwatch_event_rule" "lambda_trigger" { - name = "lambda-scheduled-trigger" - description = "Schedule to trigger the Lambda function" - schedule_expression = "rate(30 minutes)" - -# event_pattern = jsonencode({ -# detail-type = [ -# "AWS Console Sign In via CloudTrail" -# ] -# }) -} - - -resource "aws_cloudwatch_event_target" "lambda" { - rule = aws_cloudwatch_event_rule.lambda_trigger.name - target_id = "TargetFunctionV1" - arn = aws_lambda_function.my_lambda_function.arn -} - - - -resource "aws_lambda_permission" "allow_eventbridge" { - statement_id = "AllowExecutionFromEventBridge" - action = "lambda:InvokeFunction" - function_name = aws_lambda_function.my_lambda_function.function_name - principal = "events.amazonaws.com" - source_arn = aws_cloudwatch_event_rule.lambda_trigger.arn -} - - -# below is step function 1 -resource "aws_lambda_permission" "allow_s3_ingestion" { - statement_id = "AllowS3InvokeLambdaTransform" - action = "lambda:InvokeFunction" - function_name = aws_lambda_function.lambda_transform.function_name - principal = "s3.amazonaws.com" - source_arn = aws_s3_bucket.extract.arn -} - - -resource "aws_s3_bucket_notification" "extract_bucket_notification" { - bucket = aws_s3_bucket.extract.id - - lambda_function { - events = ["s3:ObjectCreated:*"] - lambda_function_arn = aws_lambda_function.lambda_transform.arn - } - - depends_on = [aws_lambda_permission.allow_s3_ingestion] -} - -# need to duplicate and replace "2" with "3" \ No newline at end of file -- cgit v1.2.3 From 9966fcff43cc5e748bc3eb406c270d63fa51ca61 Mon Sep 17 00:00:00 2001 From: lian-manonog Date: Wed, 14 Aug 2024 12:35:25 +0100 Subject: change the s3-key --- terraform/lambda.tf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/terraform/lambda.tf b/terraform/lambda.tf index bcbf394..a5da972 100644 --- a/terraform/lambda.tf +++ b/terraform/lambda.tf @@ -8,7 +8,7 @@ data "archive_file" "extract_lambda_zip" { resource "aws_lambda_function" "extract_lambda" { function_name = "${var.extract_lambda_name}" s3_bucket = aws_s3_bucket.lambda_code_bucket.bucket - s3_key = "extract_lambda/extract_function.zip" + s3_key = "extract-lambda/extract_function.zip" role = aws_iam_role.multi_service_role.arn #<< lambda role placehodler handler = "extract_lambda.lambda_handler" # << check that the function is called lambda handler runtime = "python3.11" @@ -37,7 +37,7 @@ data "archive_file" "transform_lambda_zip" { resource "aws_lambda_function" "transform_lambda" { function_name = "${var.transform_lambda_name}" s3_bucket = aws_s3_bucket.lambda_code_bucket.bucket - s3_key = "transform_lambda/transform_function.zip" + s3_key = "transform-lambda/transform_function.zip" role = aws_iam_role.multi_service_role.arn # << lambda role placehodler handler = "transform_lambda.lambda_handler" # << check that the function is called lambda handler runtime = "python3.11" @@ -65,7 +65,7 @@ data "archive_file" "load_lambda_zip" { resource "aws_lambda_function" "load_lambda" { function_name = "${var.load_lambda_name}" s3_bucket = aws_s3_bucket.lambda_code_bucket.bucket - s3_key = "load_lambda/load_function.zip" + s3_key = "load-lambda/load_function.zip" role = aws_iam_role.multi_service_role.arn # << lambda role placehodler handler = "load_lambda.lambda_handler" # << check that the function is called lambda handler runtime = "python3.11" -- cgit v1.2.3 From 6caff95389a6054f8fe1d1c9c23db9a251b6b355 Mon Sep 17 00:00:00 2001 From: lian-manonog Date: Wed, 14 Aug 2024 12:35:59 +0100 Subject: change the name of bucket in backend bucket --- terraform/main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terraform/main.tf b/terraform/main.tf index 3ca9a3d..2a048b9 100644 --- a/terraform/main.tf +++ b/terraform/main.tf @@ -6,7 +6,7 @@ terraform { } } backend "s3" { - bucket = "bentley-secrets" + bucket = "bentley-project-secrets" key = "bentley-project/terraform.tfstate" region = "eu-west-2" } -- cgit v1.2.3 From 4d52771ceee6841febc6179e0b9608fb35a792d9 Mon Sep 17 00:00:00 2001 From: lian-manonog Date: Wed, 14 Aug 2024 12:36:22 +0100 Subject: change the attachment/policies, individual --- terraform/iam.tf | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/terraform/iam.tf b/terraform/iam.tf index f34d58a..cf4902a 100644 --- a/terraform/iam.tf +++ b/terraform/iam.tf @@ -117,14 +117,29 @@ resource "aws_iam_policy" "s3_write_policy" { } # S3 ATTACH POLICY -resource "aws_iam_role_policy_attachment" "lambda_s3_policy_attachment" { - for_each = toset([ - aws_iam_policy.s3_write_policy.arn, - aws_iam_policy.lambda_execution_policy.arn, - aws_iam_policy.cw_policy.arn - ]) - role = aws_iam_role.multi_service_role.name - policy_arn = each.value +# resource "aws_iam_role_policy_attachment" "lambda_s3_policy_attachment" { +# for_each = toset([ +# aws_iam_policy.s3_write_policy.arn, +# aws_iam_policy.lambda_execution_policy.arn, +# aws_iam_policy.cw_policy.arn +# ]) +# role = aws_iam_role.multi_service_role.name +# policy_arn = each.value +# } + +resource "aws_iam_role_policy_attachment" "s3_attachment" { + role = aws_iam_role.multi_service_role.name + policy_arn = aws_iam_policy.s3_write_policy.arn +} + +resource "aws_iam_role_policy_attachment" "lambda_attachment" { + role = aws_iam_role.multi_service_role.name + policy_arn = aws_iam_policy.lambda_execution_policy.arn +} + +resource "aws_iam_role_policy_attachment" "cw_attachment" { + role = aws_iam_role.multi_service_role.name + policy_arn = aws_iam_policy.cw_policy.arn } ################ -- cgit v1.2.3 From f2c0429698edafccb0846a58e4020bf419e7e824 Mon Sep 17 00:00:00 2001 From: lian-manonog Date: Wed, 14 Aug 2024 12:37:31 +0100 Subject: style change --- terraform/events.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terraform/events.tf b/terraform/events.tf index 7a6b0ad..6744085 100644 --- a/terraform/events.tf +++ b/terraform/events.tf @@ -4,7 +4,7 @@ resource "aws_cloudwatch_event_rule" "lambda_trigger" { schedule_expression = "rate(30 minutes)" # event_pattern = jsonencode({ -# detail-type = [ +# detail-type = # "AWS Console Sign In via CloudTrail" # ] # }) -- cgit v1.2.3 From 46673b671bef834dc2e043e7845e8a5b8fee9d34 Mon Sep 17 00:00:00 2001 From: lian-manonog Date: Wed, 14 Aug 2024 12:38:08 +0100 Subject: update gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 5861f48..239c7e0 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ *.tfstate.* *.tfvars *.tfvars.json -.terraform.tfstate.lock.info \ No newline at end of file +.terraform.tfstate.lock.info +*.zip \ No newline at end of file -- cgit v1.2.3 From 101e1e24cb38b6a45661b723881e2b2d6dd2fb07 Mon Sep 17 00:00:00 2001 From: lian-manonog Date: Wed, 14 Aug 2024 14:35:05 +0100 Subject: wip: terraform debugging --- .gitignore | 5 ++++- src/load_lambda.py | 2 ++ src/transform_lambda.py | 2 ++ terraform/events.tf | 18 ++++++++++-------- terraform/s3.tf | 34 +++++++++++++++++----------------- 5 files changed, 35 insertions(+), 26 deletions(-) diff --git a/.gitignore b/.gitignore index 239c7e0..d759665 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,7 @@ *.tfvars *.tfvars.json .terraform.tfstate.lock.info -*.zip \ No newline at end of file +*.zip +.terraform/ +.terraform* +log* \ No newline at end of file diff --git a/src/load_lambda.py b/src/load_lambda.py index e69de29..6ee681f 100644 --- a/src/load_lambda.py +++ b/src/load_lambda.py @@ -0,0 +1,2 @@ +def lambda_handler(): + pass \ No newline at end of file diff --git a/src/transform_lambda.py b/src/transform_lambda.py index e69de29..6ee681f 100644 --- a/src/transform_lambda.py +++ b/src/transform_lambda.py @@ -0,0 +1,2 @@ +def lambda_handler(): + pass \ No newline at end of file diff --git a/terraform/events.tf b/terraform/events.tf index 6744085..9fd89e4 100644 --- a/terraform/events.tf +++ b/terraform/events.tf @@ -1,7 +1,17 @@ +resource "aws_cloudwatch_event_target" "extract_lambda_cw_event" { + rule = aws_cloudwatch_event_rule.lambda_trigger.name + target_id = "TargetFunctionV1" + arn = aws_lambda_function.extract_lambda.arn #replaced lambda name placeholder + force_destroy = true +} + resource "aws_cloudwatch_event_rule" "lambda_trigger" { name = "lambda-scheduled-trigger" description = "Schedule to trigger the Lambda function" schedule_expression = "rate(30 minutes)" + force_destroy = true + # depends_on = [ + # aws_cloudwatch_event_target.extract_lambda_cw_event] # event_pattern = jsonencode({ # detail-type = @@ -10,14 +20,6 @@ resource "aws_cloudwatch_event_rule" "lambda_trigger" { # }) } - -resource "aws_cloudwatch_event_target" "extract_lambda_cw_event" { - rule = aws_cloudwatch_event_rule.lambda_trigger.name - target_id = "TargetFunctionV1" - arn = aws_lambda_function.extract_lambda.arn #replaced lambda name placeholder -} - - resource "aws_lambda_permission" "allow_eventbridge" { statement_id = "AllowExecutionFromEventBridge" action = "lambda:InvokeFunction" diff --git a/terraform/s3.tf b/terraform/s3.tf index 8ab5622..4c06b8e 100644 --- a/terraform/s3.tf +++ b/terraform/s3.tf @@ -32,20 +32,20 @@ resource "aws_s3_bucket" "lambda_code_bucket" { bucket_prefix = "${var.s3_code_bucket_name}-" } -resource "aws_s3_object" "extract_lambda_code" { - bucket = aws_s3_bucket.lambda_code_bucket.bucket - key = "${var.extract_lambda_name}/extract_function.zip" - source = "${path.module}/../extract_function.zip" -} # << can't figure out how this is being used but we seem to need it - -resource "aws_s3_object" "transform_lambda_code" { - bucket = aws_s3_bucket.lambda_code_bucket.bucket - key = "${var.transform_lambda_name}/transform_function.zip" - source = "${path.module}/../transform_function.zip" -} # << can't figure out how this is being used but we seem to need it - -resource "aws_s3_object" "load_lambda_code" { - bucket = aws_s3_bucket.lambda_code_bucket.bucket - key = "${var.load_lambda_name}/load_function.zip" - source = "${path.module}/../load_function.zip" -} \ No newline at end of file +# resource "aws_s3_object" "extract_lambda_code" { +# bucket = aws_s3_bucket.lambda_code_bucket.bucket +# key = "${var.extract_lambda_name}/extract_function.zip" +# source = "${path.module}/../extract_function.zip" +# } # << can't figure out how this is being used but we seem to need it + +# resource "aws_s3_object" "transform_lambda_code" { +# bucket = aws_s3_bucket.lambda_code_bucket.bucket +# key = "${var.transform_lambda_name}/transform_function.zip" +# source = "${path.module}/../transform_function.zip" +# } # << can't figure out how this is being used but we seem to need it + +# resource "aws_s3_object" "load_lambda_code" { +# bucket = aws_s3_bucket.lambda_code_bucket.bucket +# key = "${var.load_lambda_name}/load_function.zip" +# source = "${path.module}/../load_function.zip" +# } \ No newline at end of file -- cgit v1.2.3 From 45e025ac0c4ae8c721cb0b875fd0abd67cc2bc07 Mon Sep 17 00:00:00 2001 From: T-Aji Date: Wed, 14 Aug 2024 15:53:11 +0100 Subject: test: passing test for function connect_to_database --- src/extract_lambda.py | 40 +++++++++++++++++++++++++--------------- tests/test_extract_lambda.py | 40 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 62 insertions(+), 18 deletions(-) diff --git a/src/extract_lambda.py b/src/extract_lambda.py index dc70590..6e94bba 100644 --- a/src/extract_lambda.py +++ b/src/extract_lambda.py @@ -1,6 +1,5 @@ from pg8000.native import Connection, DatabaseError, InterfaceError -from dotenv import load_dotenv -import os +from dotenv import dotenv_values import boto3 import csv from botocore.exceptions import ClientError @@ -9,16 +8,15 @@ import json logger = logging.getLogger() logger.setLevel(logging.INFO) -load_dotenv() - - -database = os.getenv('database') -user = os.getenv('user') -password = os.getenv('password') -host = os.getenv('host') -port = os.getenv('port') +class DBConnectionException(Exception): + """Wraps pg8000.native Error or DatabaseError.""" + def __init__(self, e): + """Initialise with provided error message.""" + self.message = str(e) + super().__init__(self.message) + def lambda_handler(event, context): """This lambda function connects to the Totesys database, lists the contents of the ingestion bucket, and converts all tables to CSV and if any of those tables do not exist in, or are different to the ones in s3, it uploads them @@ -53,8 +51,19 @@ def lambda_handler(event, context): if db: db.close() -def connect_to_database(): +def get_config(path: str = ".env") -> dict: + return dotenv_values(path) + + +def connect_to_database() -> Connection: try: + config = get_config() + host = config["host"] + port = config["port"] + user = config["user"] + password = config["password"] + database = config["database"] + return Connection( database=database, user=user, @@ -62,12 +71,13 @@ def connect_to_database(): host=host, port=port ) - except DatabaseError as e: - logger.error(f'Database error: {e}') - raise + # except DatabaseError as e: + # logger.error(f'Database error: {e}') + # raise except InterfaceError as i: logger.error(f'Interface error: {i}') - raise + raise DBConnectionException("Failed to connect to database") + def list_existing_s3_files(bucket_name='extract_bucket', client=boto3.client('s3')): diff --git a/tests/test_extract_lambda.py b/tests/test_extract_lambda.py index 472e93a..18c49fc 100644 --- a/tests/test_extract_lambda.py +++ b/tests/test_extract_lambda.py @@ -1,10 +1,24 @@ import pytest import boto3 from moto import mock_aws -from src.extract_lambda import list_existing_s3_files #process_and_upload_tables +from unittest.mock import patch +from unittest import TestCase +from src.extract_lambda import list_existing_s3_files, connect_to_database, DBConnectionException #process_and_upload_tables import os import logging +@pytest.fixture(scope='class') +def mock_config(): + env_vars = { + "host": "abc", + "port": "5432", + "user": "def", + "password": "password", + "database": "db", + } + with patch("src.extract_lambda.get_config", return_value=env_vars) as mock_config: + yield mock_config + @pytest.fixture(scope='class') def aws_credentials(): @@ -19,7 +33,7 @@ def s3_client(aws_credentials): with mock_aws(): yield boto3.client('s3') -class TestListExistings3Files(): +class TestListExistings3Files: def test_error_if_no_bucket(self, s3_client, caplog): logger = logging.getLogger() @@ -46,4 +60,24 @@ class TestListExistings3Files(): def test_retrieves_file_content(self, s3_client, caplog): result = list_existing_s3_files(client=s3_client) - assert list(result.values()) == ['This is a test file.'] \ No newline at end of file + assert list(result.values()) == ['This is a test file.'] + +class TestConnectToDatabase: + def test_connect_to_database(mock_conn, mock_config): + with patch("src.extract_lambda.Connection", autospec=True) as mock_conn: + connect_to_database() + mock_conn.assert_called_with( + host="abc", user="def", port="5432", password="password", database="db" + ) + + def test_database_error(self, mock_config): + with pytest.raises(DBConnectionException): + connect_to_database() + + def test_logs_interface_error(self, caplog): + logger = logging.getLogger() + logger.info('Testing now.') + caplog.set_level(logging.ERROR) + with pytest.raises(DBConnectionException): + connect_to_database() + assert 'Interface error' in caplog.text \ No newline at end of file -- cgit v1.2.3 From e95e9e59d2a36a2d700bfa199f6b5ece3c49233d Mon Sep 17 00:00:00 2001 From: Ellie Date: Wed, 14 Aug 2024 16:35:32 +0100 Subject: chage rate 30 seconds --- terraform/events.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terraform/events.tf b/terraform/events.tf index 9fd89e4..ef01feb 100644 --- a/terraform/events.tf +++ b/terraform/events.tf @@ -8,7 +8,7 @@ resource "aws_cloudwatch_event_target" "extract_lambda_cw_event" { resource "aws_cloudwatch_event_rule" "lambda_trigger" { name = "lambda-scheduled-trigger" description = "Schedule to trigger the Lambda function" - schedule_expression = "rate(30 minutes)" + schedule_expression = "rate(30 seconds)" force_destroy = true # depends_on = [ # aws_cloudwatch_event_target.extract_lambda_cw_event] -- cgit v1.2.3 From 911b2a4ba08e59f46a53b5252a044a5853796f78 Mon Sep 17 00:00:00 2001 From: Alex Schofield Date: Wed, 14 Aug 2024 16:38:36 +0100 Subject: test(tf): attempt to solve ResourceConflictException --- terraform/events.tf | 35 ++++++++--------- terraform/iam.tf | 60 +++++++++++++++++++++-------- terraform/lambda.tf | 109 +++++++++++++++++++++++++--------------------------- terraform/main.tf | 26 ++++++------- terraform/s3.tf | 43 ++------------------- terraform/vars.tf | 26 ++++++------- 6 files changed, 139 insertions(+), 160 deletions(-) diff --git a/terraform/events.tf b/terraform/events.tf index 9fd89e4..d2e2eb5 100644 --- a/terraform/events.tf +++ b/terraform/events.tf @@ -1,23 +1,18 @@ -resource "aws_cloudwatch_event_target" "extract_lambda_cw_event" { - rule = aws_cloudwatch_event_rule.lambda_trigger.name - target_id = "TargetFunctionV1" - arn = aws_lambda_function.extract_lambda.arn #replaced lambda name placeholder - force_destroy = true -} - resource "aws_cloudwatch_event_rule" "lambda_trigger" { name = "lambda-scheduled-trigger" description = "Schedule to trigger the Lambda function" schedule_expression = "rate(30 minutes)" - force_destroy = true - # depends_on = [ - # aws_cloudwatch_event_target.extract_lambda_cw_event] - -# event_pattern = jsonencode({ -# detail-type = -# "AWS Console Sign In via CloudTrail" -# ] -# }) + + lifecycle { + create_before_destroy = true + } +} + +resource "aws_cloudwatch_event_target" "extract_lambda_cw_event" { + rule = aws_cloudwatch_event_rule.lambda_trigger.name + target_id = "TargetFunctionV1" + arn = aws_lambda_function.extract_lambda.arn #replaced lambda name placeholder + depends_on = [aws_lambda_permission.allow_eventbridge] } resource "aws_lambda_permission" "allow_eventbridge" { @@ -25,7 +20,7 @@ resource "aws_lambda_permission" "allow_eventbridge" { action = "lambda:InvokeFunction" function_name = aws_lambda_function.extract_lambda.function_name #replaced lambda name placeholder principal = "events.amazonaws.com" - source_arn = aws_cloudwatch_event_rule.lambda_trigger.arn + source_arn = aws_cloudwatch_event_rule.lambda_trigger.arn } @@ -43,7 +38,7 @@ resource "aws_s3_bucket_notification" "extract_bucket_notification" { bucket = aws_s3_bucket.extract_bucket.id #replaced bucket name placeholder lambda_function { - events = ["s3:ObjectCreated:*"] + events = ["s3:ObjectCreated:*"] lambda_function_arn = aws_lambda_function.transform_lambda.arn #replaced lambda name placeholder } @@ -65,9 +60,9 @@ resource "aws_s3_bucket_notification" "transform_bucket_notification" { bucket = aws_s3_bucket.transform_bucket.id #replaced bucket name placeholder lambda_function { - events = ["s3:ObjectCreated:*"] + events = ["s3:ObjectCreated:*"] lambda_function_arn = aws_lambda_function.transform_lambda.arn #replaced lambda name placeholder } depends_on = [aws_lambda_permission.allow_s3_transform_bucket] -} \ No newline at end of file +} diff --git a/terraform/iam.tf b/terraform/iam.tf index cf4902a..6c6b4fc 100644 --- a/terraform/iam.tf +++ b/terraform/iam.tf @@ -16,9 +16,7 @@ resource "aws_iam_role" "multi_service_role" { Principal = { Service = [ "lambda.amazonaws.com", - "cloudwatch.amazonaws.com", - "events.amazonaws.com", - "s3.amazonaws.com" + "scheduler.amazonaws.com" ] } } @@ -57,22 +55,22 @@ data "aws_iam_policy_document" "s3_data_policy_doc" { ######################################################################## resource "aws_iam_policy" "lambda_execution_policy" { - name = "lambda_execution_policy" - path = "/" + name = "lambda_execution_policy" + path = "/" description = "IAM policy for Lambda execution" policy = jsonencode({ Version = "2012-10-17" Statement = [ - { + { Effect = "Allow" Action = [ "lambda:InvokeFunction", "lambda:GetFunction" ] Resource = "*" - } - ] + } + ] } ) } @@ -87,7 +85,7 @@ data "aws_iam_policy_document" "cw_document" { actions = ["logs:CreateLogGroup"] resources = [ "arn:aws:logs:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:*" - ] + ] } statement { @@ -95,15 +93,15 @@ data "aws_iam_policy_document" "cw_document" { "logs:CreateLogStream", "logs:CreateLogGroup", "logs:PutLogEvents" - ] - resources = [ - "arn:aws:logs:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:log-group:/aws/lambda/*" - ] + ] + resources = [ + "arn:aws:logs:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:log-group:/aws/lambda/*" + ] } } resource "aws_iam_policy" "cw_policy" { - name = "cw_policy" + name = "cw_policy" policy = data.aws_iam_policy_document.cw_document.json } @@ -128,20 +126,48 @@ resource "aws_iam_policy" "s3_write_policy" { # } resource "aws_iam_role_policy_attachment" "s3_attachment" { - role = aws_iam_role.multi_service_role.name + role = aws_iam_role.multi_service_role.name policy_arn = aws_iam_policy.s3_write_policy.arn } resource "aws_iam_role_policy_attachment" "lambda_attachment" { - role = aws_iam_role.multi_service_role.name + role = aws_iam_role.multi_service_role.name policy_arn = aws_iam_policy.lambda_execution_policy.arn } resource "aws_iam_role_policy_attachment" "cw_attachment" { - role = aws_iam_role.multi_service_role.name + role = aws_iam_role.multi_service_role.name policy_arn = aws_iam_policy.cw_policy.arn } ################ # RDS POLICIES # ################ + +################### +# EVENTS POLICIES # +################### + +data "aws_iam_policy_document" "cloudwatch_events_policy" { + statement { + actions = [ + "events:PutRule", + "events:PutTargets", + "events:RemoveTargets", + "events:DeleteRule", + "events:PutEvents" + ] + resources = ["*"] + effect = "Allow" + } +} + +resource "aws_iam_policy" "cloudwatch_events_policy" { + name = "cloudwatch_events_policy" + policy = data.aws_iam_policy_document.cloudwatch_events_policy.json +} + +resource "aws_iam_role_policy_attachment" "cloudwatch_events_attachment" { + role = aws_iam_role.multi_service_role.name + policy_arn = aws_iam_policy.cloudwatch_events_policy.arn +} diff --git a/terraform/lambda.tf b/terraform/lambda.tf index a5da972..fb0a666 100644 --- a/terraform/lambda.tf +++ b/terraform/lambda.tf @@ -1,73 +1,68 @@ -### EXTRACT LAMBDA SET UP -data "archive_file" "extract_lambda_zip" { - type = "zip" - source_file = "${path.module}/../src/extract_lambda.py" - output_path = "${path.module}/../extract_function.zip" +# Extract Lambda Function +resource "aws_s3_object" "extract_lambda_code" { + bucket = aws_s3_bucket.lambda_code_bucket.bucket + key = "${var.extract_lambda_name}/extract_function.zip" + source = "${path.module}/../extract_function.zip" + etag = filemd5("${path.module}/../extract_function.zip") } resource "aws_lambda_function" "extract_lambda" { - function_name = "${var.extract_lambda_name}" - s3_bucket = aws_s3_bucket.lambda_code_bucket.bucket - s3_key = "extract-lambda/extract_function.zip" - role = aws_iam_role.multi_service_role.arn #<< lambda role placehodler - handler = "extract_lambda.lambda_handler" # << check that the function is called lambda handler - runtime = "python3.11" - environment { - variables = { - output = aws_s3_bucket.extract_bucket.bucket - } - } -} + function_name = var.extract_lambda_name + s3_bucket = aws_s3_bucket.lambda_code_bucket.bucket + s3_key = aws_s3_object.extract_lambda_code.key + role = aws_iam_role.multi_service_role.arn + handler = "extract_lambda.extract" + runtime = "python3.11" -resource "aws_lambda_permission" "allow_to_write_to_s3_extract_bucket" { - action = "lambda:InvokeFunction" - function_name = aws_lambda_function.extract_lambda.function_name - principal = "s3.amazonaws.com" - source_arn = aws_s3_bucket.extract_bucket.arn -} + lifecycle { + create_before_destroy = true + } + depends_on = [aws_s3_object.extract_lambda_code] +} -### TRANSFORM LAMBDA SET UP -data "archive_file" "transform_lambda_zip" { - type = "zip" - source_file = "${path.module}/../src/transform_lambda.py" - output_path = "${path.module}/../transform_function.zip" +# Transform Lambda Function +resource "aws_s3_object" "transform_lambda_code" { + bucket = aws_s3_bucket.lambda_code_bucket.bucket + key = "${var.transform_lambda_name}/transform_function.zip" + source = "${path.module}/../transform_function.zip" + etag = filemd5("${path.module}/../transform_function.zip") } resource "aws_lambda_function" "transform_lambda" { - function_name = "${var.transform_lambda_name}" - s3_bucket = aws_s3_bucket.lambda_code_bucket.bucket - s3_key = "transform-lambda/transform_function.zip" - role = aws_iam_role.multi_service_role.arn # << lambda role placehodler - handler = "transform_lambda.lambda_handler" # << check that the function is called lambda handler - runtime = "python3.11" - environment { - variables = { - output = aws_s3_bucket.transform_bucket.bucket - } - } -} + function_name = var.transform_lambda_name + s3_bucket = aws_s3_bucket.lambda_code_bucket.bucket + s3_key = aws_s3_object.transform_lambda_code.key + role = aws_iam_role.multi_service_role.arn + handler = "transform_lambda.transform" + runtime = "python3.11" -resource "aws_lambda_permission" "allow_to_write_to_s3_transform_bucket" { - action = "lambda:InvokeFunction" - function_name = aws_lambda_function.transform_lambda.function_name - principal = "s3.amazonaws.com" - source_arn = aws_s3_bucket.transform_bucket.arn + lifecycle { + create_before_destroy = true + } + + depends_on = [aws_s3_object.transform_lambda_code] } -### LOAD LAMBDA SET UP -data "archive_file" "load_lambda_zip" { - type = "zip" - source_file = "${path.module}/../src/load_lambda.py" - output_path = "${path.module}/../load_function.zip" +# Load Lambda Function +resource "aws_s3_object" "load_lambda_code" { + bucket = aws_s3_bucket.lambda_code_bucket.bucket + key = "${var.load_lambda_name}/load_function.zip" + source = "${path.module}/../load_function.zip" + etag = filemd5("${path.module}/../load_function.zip") } resource "aws_lambda_function" "load_lambda" { - function_name = "${var.load_lambda_name}" - s3_bucket = aws_s3_bucket.lambda_code_bucket.bucket - s3_key = "load-lambda/load_function.zip" - role = aws_iam_role.multi_service_role.arn # << lambda role placehodler - handler = "load_lambda.lambda_handler" # << check that the function is called lambda handler - runtime = "python3.11" -} + function_name = var.load_lambda_name + s3_bucket = aws_s3_bucket.lambda_code_bucket.bucket + s3_key = aws_s3_object.load_lambda_code.key + role = aws_iam_role.multi_service_role.arn + handler = "load_lambda.load" + runtime = "python3.11" + lifecycle { + create_before_destroy = true + } + + depends_on = [aws_s3_object.load_lambda_code] +} diff --git a/terraform/main.tf b/terraform/main.tf index 2a048b9..3b06701 100644 --- a/terraform/main.tf +++ b/terraform/main.tf @@ -1,26 +1,26 @@ terraform { required_providers { aws = { - source = "hashicorp/aws" - version = "~>5.0" + source = "hashicorp/aws" + version = "~>5.0" } } backend "s3" { bucket = "bentley-project-secrets" - key = "bentley-project/terraform.tfstate" + key = "bentley-project/terraform.tfstate" region = "eu-west-2" } } provider "aws" { - region = "eu-west-2" - default_tags { - tags = { - ProjectName = "Terrific-Totes" - Team = "Team-Bentley" - Environment = "Dev" - GitHubRepo = "de-project-bentley" - ManagedBy = "Terraform" - } + region = "eu-west-2" + default_tags { + tags = { + ProjectName = "Terrific-Totes" + Team = "Team-Bentley" + Environment = "Dev" + GitHubRepo = "de-project-bentley" + ManagedBy = "Terraform" } -} \ No newline at end of file + } +} diff --git a/terraform/s3.tf b/terraform/s3.tf index 4c06b8e..d5cdee3 100644 --- a/terraform/s3.tf +++ b/terraform/s3.tf @@ -1,51 +1,14 @@ ### EXTRACT BUCKET SET-UP resource "aws_s3_bucket" "extract_bucket" { - bucket_prefix = "${var.s3_extract_bucket_name}-" + bucket_prefix = "${var.s3_extract_bucket_name}-" } -# resource "aws_s3_bucket_notification" "extract_bucket_notification" { -# bucket = aws_s3_bucket.extract_bucket.id -# lambda_function { -# lambda_function_arn = aws_lambda_function.extract_lambda.arn -# events = ["s3:ObjectCreated:*"] -# } -# depends_on = [aws_lambda_permission.allow_to_write_to_s3_extract_bucket] -# } # << is this the correct permission dependency? - ### TRANSFORM BUCKET SET-UP resource "aws_s3_bucket" "transform_bucket" { - bucket_prefix = "${var.s3_transform_bucket_name}-" + bucket_prefix = "${var.s3_transform_bucket_name}-" } -# resource "aws_s3_bucket_notification" "transform_bucket_notification" { -# bucket = aws_s3_bucket.transform_bucket.id -# lambda_function { -# lambda_function_arn = aws_lambda_function.transform_lambda.arn -# events = ["s3:ObjectCreated:*"] -# } -# depends_on = [aws_lambda_permission.allow_to_write_to_s3_transform_bucket] -# } # << is this the correct permission dependency? - - ### LAMBDA BUCKET resource "aws_s3_bucket" "lambda_code_bucket" { - bucket_prefix = "${var.s3_code_bucket_name}-" + bucket_prefix = "${var.s3_code_bucket_name}-" } - -# resource "aws_s3_object" "extract_lambda_code" { -# bucket = aws_s3_bucket.lambda_code_bucket.bucket -# key = "${var.extract_lambda_name}/extract_function.zip" -# source = "${path.module}/../extract_function.zip" -# } # << can't figure out how this is being used but we seem to need it - -# resource "aws_s3_object" "transform_lambda_code" { -# bucket = aws_s3_bucket.lambda_code_bucket.bucket -# key = "${var.transform_lambda_name}/transform_function.zip" -# source = "${path.module}/../transform_function.zip" -# } # << can't figure out how this is being used but we seem to need it - -# resource "aws_s3_object" "load_lambda_code" { -# bucket = aws_s3_bucket.lambda_code_bucket.bucket -# key = "${var.load_lambda_name}/load_function.zip" -# source = "${path.module}/../load_function.zip" -# } \ No newline at end of file diff --git a/terraform/vars.tf b/terraform/vars.tf index cc9348a..84824ce 100644 --- a/terraform/vars.tf +++ b/terraform/vars.tf @@ -1,33 +1,33 @@ variable "s3_extract_bucket_name" { - type = string - default = "extract-bucket" + type = string + default = "extract-bucket" } variable "s3_transform_bucket_name" { - type = string - default = "transform-bucket" + type = string + default = "transform-bucket" } variable "s3_code_bucket_name" { - type = string - default = "lambda-bucket" + type = string + default = "lambda-bucket" } variable "extract_lambda_name" { - type = string - default = "extract-lambda" + type = string + default = "extract-lambda" } variable "transform_lambda_name" { - type = string - default = "transform-lambda" + type = string + default = "transform-lambda" } variable "load_lambda_name" { - type = string - default = "load-lambda" + type = string + default = "load-lambda" } data "aws_caller_identity" "current" {} -data "aws_region" "current" {} \ No newline at end of file +data "aws_region" "current" {} -- cgit v1.2.3 From 43b7bca661a4c42acbfd6c27ff26fee9c25e911e Mon Sep 17 00:00:00 2001 From: Ellie Date: Wed, 14 Aug 2024 16:39:12 +0100 Subject: amend s3 handler to extract --- terraform/lambda.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terraform/lambda.tf b/terraform/lambda.tf index a5da972..c190ea8 100644 --- a/terraform/lambda.tf +++ b/terraform/lambda.tf @@ -10,7 +10,7 @@ resource "aws_lambda_function" "extract_lambda" { s3_bucket = aws_s3_bucket.lambda_code_bucket.bucket s3_key = "extract-lambda/extract_function.zip" role = aws_iam_role.multi_service_role.arn #<< lambda role placehodler - handler = "extract_lambda.lambda_handler" # << check that the function is called lambda handler + handler = "extract_lambda.extract" # << check that the function is called lambda handler runtime = "python3.11" environment { variables = { -- cgit v1.2.3 From 51eb46bbeb8fe5cb7b8750c37c776e8c9b4ae7f6 Mon Sep 17 00:00:00 2001 From: Ellie Date: Wed, 14 Aug 2024 16:41:02 +0100 Subject: uncomment s3 objects --- terraform/s3.tf | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/terraform/s3.tf b/terraform/s3.tf index 4c06b8e..8ab5622 100644 --- a/terraform/s3.tf +++ b/terraform/s3.tf @@ -32,20 +32,20 @@ resource "aws_s3_bucket" "lambda_code_bucket" { bucket_prefix = "${var.s3_code_bucket_name}-" } -# resource "aws_s3_object" "extract_lambda_code" { -# bucket = aws_s3_bucket.lambda_code_bucket.bucket -# key = "${var.extract_lambda_name}/extract_function.zip" -# source = "${path.module}/../extract_function.zip" -# } # << can't figure out how this is being used but we seem to need it - -# resource "aws_s3_object" "transform_lambda_code" { -# bucket = aws_s3_bucket.lambda_code_bucket.bucket -# key = "${var.transform_lambda_name}/transform_function.zip" -# source = "${path.module}/../transform_function.zip" -# } # << can't figure out how this is being used but we seem to need it - -# resource "aws_s3_object" "load_lambda_code" { -# bucket = aws_s3_bucket.lambda_code_bucket.bucket -# key = "${var.load_lambda_name}/load_function.zip" -# source = "${path.module}/../load_function.zip" -# } \ No newline at end of file +resource "aws_s3_object" "extract_lambda_code" { + bucket = aws_s3_bucket.lambda_code_bucket.bucket + key = "${var.extract_lambda_name}/extract_function.zip" + source = "${path.module}/../extract_function.zip" +} # << can't figure out how this is being used but we seem to need it + +resource "aws_s3_object" "transform_lambda_code" { + bucket = aws_s3_bucket.lambda_code_bucket.bucket + key = "${var.transform_lambda_name}/transform_function.zip" + source = "${path.module}/../transform_function.zip" +} # << can't figure out how this is being used but we seem to need it + +resource "aws_s3_object" "load_lambda_code" { + bucket = aws_s3_bucket.lambda_code_bucket.bucket + key = "${var.load_lambda_name}/load_function.zip" + source = "${path.module}/../load_function.zip" +} \ No newline at end of file -- cgit v1.2.3 From 848a86b7f3b9c5ce16cd774d19e3fa62ca8ffc68 Mon Sep 17 00:00:00 2001 From: T-Aji Date: Wed, 14 Aug 2024 18:14:01 +0100 Subject: test: mid-through test for process_and_upload_tables --- src/extract_lambda.py | 16 +++++++--------- tests/test_extract_lambda.py | 35 ++++++++++++++++++++++++++++++++--- 2 files changed, 39 insertions(+), 12 deletions(-) diff --git a/src/extract_lambda.py b/src/extract_lambda.py index 6e94bba..a70ecdd 100644 --- a/src/extract_lambda.py +++ b/src/extract_lambda.py @@ -5,6 +5,7 @@ import csv from botocore.exceptions import ClientError import logging import json +from datetime import datetime logger = logging.getLogger() logger.setLevel(logging.INFO) @@ -16,7 +17,7 @@ class DBConnectionException(Exception): """Initialise with provided error message.""" self.message = str(e) super().__init__(self.message) - + def lambda_handler(event, context): """This lambda function connects to the Totesys database, lists the contents of the ingestion bucket, and converts all tables to CSV and if any of those tables do not exist in, or are different to the ones in s3, it uploads them @@ -71,9 +72,6 @@ def connect_to_database() -> Connection: host=host, port=port ) - # except DatabaseError as e: - # logger.error(f'Database error: {e}') - # raise except InterfaceError as i: logger.error(f'Interface error: {i}') raise DBConnectionException("Failed to connect to database") @@ -110,14 +108,14 @@ def list_existing_s3_files(bucket_name='extract_bucket', client=boto3.client('s3 -def process_and_upload_tables(db, existing_files): +def process_and_upload_tables(db, existing_files, client=boto3.client('s3')): """Creates a list of the tables from a database query and then selects everything from each table in individual queries it then writes each table to CSV files and compares with the item - in the existing_files dictionary with the same name. If it finds sny changes + in the existing_files dictionary with the same name. If it finds any changes to files, or new tables/files it uploads them to the s3 bucket """ - client = boto3.client('s3') + tables = db.run("SELECT table_name FROM information_schema.tables WHERE table_schema='public' AND table_type='BASE TABLE';") for table in tables: @@ -132,13 +130,13 @@ def process_and_upload_tables(db, existing_files): writer.writerow(column_names) writer.writerows(rows) - s3_key = f"{table_name}/latest.csv" + s3_key = f"{table_name}/{datetime.today().year}/{datetime.today().month}/{datetime.today().day}/{table_name}_{datetime.now().strftime('%H:%M:%S')}.csv" new_csv_content = open(csv_file_path, "r").read() if s3_key not in existing_files or existing_files[s3_key] != new_csv_content: try: - client.upload_file(csv_file_path, ingestion_bucket, s3_key) + client.upload_file(csv_file_path, 'extract_bucket', s3_key) logger.info(f"Uploaded {s3_key} to S3.") except ClientError as e: logger.error(f'Error uploading to S3: {e}') \ No newline at end of file diff --git a/tests/test_extract_lambda.py b/tests/test_extract_lambda.py index 18c49fc..74d7e2c 100644 --- a/tests/test_extract_lambda.py +++ b/tests/test_extract_lambda.py @@ -3,7 +3,7 @@ import boto3 from moto import mock_aws from unittest.mock import patch from unittest import TestCase -from src.extract_lambda import list_existing_s3_files, connect_to_database, DBConnectionException #process_and_upload_tables +from src.extract_lambda import list_existing_s3_files, connect_to_database, DBConnectionException, process_and_upload_tables import os import logging @@ -33,7 +33,7 @@ def s3_client(aws_credentials): with mock_aws(): yield boto3.client('s3') -class TestListExistings3Files: +class TestListExistingS3Files: def test_error_if_no_bucket(self, s3_client, caplog): logger = logging.getLogger() @@ -80,4 +80,33 @@ class TestConnectToDatabase: caplog.set_level(logging.ERROR) with pytest.raises(DBConnectionException): connect_to_database() - assert 'Interface error' in caplog.text \ No newline at end of file + assert 'Interface error' in caplog.text + +class TestProcessAndUploadTables: + def test_error_process_and_upload_tables(mock_conn, mock_config, s3_client, caplog, mocker): + logger = logging.getLogger() + logger.info('Testing now.') + caplog.set_level(logging.ERROR) + + with patch("src.extract_lambda.Connection", autospec=True) as mock_conn: + mock_db = connect_to_database() + # need to add a table + s3_key = 'dummy/2024/8/14/dummy_16:46:30.txt' + mock_existing_files = mocker.Mock(return_value={s3_key: 'This is a test file.' }) + s3_client.create_bucket(Bucket='extract_bucket', + CreateBucketConfiguration={ + 'LocationConstraint': 'eu-west-2' + }) + s3_client.upload_file('tests/dummy.txt', 'extract_bucket', s3_key) + process_and_upload_tables(mock_db, mock_existing_files, client=s3_client) + + assert 'Error uploading to S3' in caplog.text + +#@pytest.mark.describe("Helpers") +# @pytest.mark.it("Query processor returns correctly formatted dict") +# def test_process_query(): +# with patch("src.api.helpers.get_db_connection") as mock_conn: +# mock_conn().run.side_effect = db_data +# mock_conn().columns = sample_headers +# result = process_query("test query") +# assert result == sample_result \ No newline at end of file -- cgit v1.2.3 From 5cdcbd64e9f4dba5f3ed8e8eb9f6e91e1adde0ba Mon Sep 17 00:00:00 2001 From: Alex Schofield Date: Wed, 14 Aug 2024 22:51:32 +0100 Subject: chore(gitignore): ignore .DS_Store --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index d759665..d1df545 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ *.zip .terraform/ .terraform* -log* \ No newline at end of file +log* +.DS_Store -- cgit v1.2.3 From 9ff947c167932bb9ff93f05c8adf2ffcd98b91cc Mon Sep 17 00:00:00 2001 From: Alex Schofield Date: Wed, 14 Aug 2024 22:52:33 +0100 Subject: infra(tf): simplify multi_service_role --- terraform/iam.tf | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/terraform/iam.tf b/terraform/iam.tf index cf4902a..20aeab3 100644 --- a/terraform/iam.tf +++ b/terraform/iam.tf @@ -16,9 +16,7 @@ resource "aws_iam_role" "multi_service_role" { Principal = { Service = [ "lambda.amazonaws.com", - "cloudwatch.amazonaws.com", - "events.amazonaws.com", - "s3.amazonaws.com" + "scheduler.amazonaws.com" ] } } @@ -57,22 +55,22 @@ data "aws_iam_policy_document" "s3_data_policy_doc" { ######################################################################## resource "aws_iam_policy" "lambda_execution_policy" { - name = "lambda_execution_policy" - path = "/" + name = "lambda_execution_policy" + path = "/" description = "IAM policy for Lambda execution" policy = jsonencode({ Version = "2012-10-17" Statement = [ - { + { Effect = "Allow" Action = [ "lambda:InvokeFunction", "lambda:GetFunction" ] Resource = "*" - } - ] + } + ] } ) } @@ -87,7 +85,7 @@ data "aws_iam_policy_document" "cw_document" { actions = ["logs:CreateLogGroup"] resources = [ "arn:aws:logs:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:*" - ] + ] } statement { @@ -95,15 +93,15 @@ data "aws_iam_policy_document" "cw_document" { "logs:CreateLogStream", "logs:CreateLogGroup", "logs:PutLogEvents" - ] - resources = [ - "arn:aws:logs:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:log-group:/aws/lambda/*" - ] + ] + resources = [ + "arn:aws:logs:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:log-group:/aws/lambda/*" + ] } } resource "aws_iam_policy" "cw_policy" { - name = "cw_policy" + name = "cw_policy" policy = data.aws_iam_policy_document.cw_document.json } @@ -128,17 +126,17 @@ resource "aws_iam_policy" "s3_write_policy" { # } resource "aws_iam_role_policy_attachment" "s3_attachment" { - role = aws_iam_role.multi_service_role.name + role = aws_iam_role.multi_service_role.name policy_arn = aws_iam_policy.s3_write_policy.arn } resource "aws_iam_role_policy_attachment" "lambda_attachment" { - role = aws_iam_role.multi_service_role.name + role = aws_iam_role.multi_service_role.name policy_arn = aws_iam_policy.lambda_execution_policy.arn } resource "aws_iam_role_policy_attachment" "cw_attachment" { - role = aws_iam_role.multi_service_role.name + role = aws_iam_role.multi_service_role.name policy_arn = aws_iam_policy.cw_policy.arn } -- cgit v1.2.3 From 3f89444cb09f0372b6a7621913944e372acd826c Mon Sep 17 00:00:00 2001 From: Alex Schofield Date: Wed, 14 Aug 2024 22:58:50 +0100 Subject: infra(tf): temporarily remove events policies --- terraform/iam.tf | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/terraform/iam.tf b/terraform/iam.tf index 6c6b4fc..20aeab3 100644 --- a/terraform/iam.tf +++ b/terraform/iam.tf @@ -143,31 +143,3 @@ resource "aws_iam_role_policy_attachment" "cw_attachment" { ################ # RDS POLICIES # ################ - -################### -# EVENTS POLICIES # -################### - -data "aws_iam_policy_document" "cloudwatch_events_policy" { - statement { - actions = [ - "events:PutRule", - "events:PutTargets", - "events:RemoveTargets", - "events:DeleteRule", - "events:PutEvents" - ] - resources = ["*"] - effect = "Allow" - } -} - -resource "aws_iam_policy" "cloudwatch_events_policy" { - name = "cloudwatch_events_policy" - policy = data.aws_iam_policy_document.cloudwatch_events_policy.json -} - -resource "aws_iam_role_policy_attachment" "cloudwatch_events_attachment" { - role = aws_iam_role.multi_service_role.name - policy_arn = aws_iam_policy.cloudwatch_events_policy.arn -} -- cgit v1.2.3 From 8b975ba92d10c8034bdb35fee19c42b6187ea24f Mon Sep 17 00:00:00 2001 From: Alex Schofield Date: Wed, 14 Aug 2024 23:03:14 +0100 Subject: chore(cleanup): remove commented out code --- terraform/iam.tf | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/terraform/iam.tf b/terraform/iam.tf index 20aeab3..195b07c 100644 --- a/terraform/iam.tf +++ b/terraform/iam.tf @@ -114,17 +114,6 @@ resource "aws_iam_policy" "s3_write_policy" { policy = data.aws_iam_policy_document.s3_data_policy_doc.json } -# S3 ATTACH POLICY -# resource "aws_iam_role_policy_attachment" "lambda_s3_policy_attachment" { -# for_each = toset([ -# aws_iam_policy.s3_write_policy.arn, -# aws_iam_policy.lambda_execution_policy.arn, -# aws_iam_policy.cw_policy.arn -# ]) -# role = aws_iam_role.multi_service_role.name -# policy_arn = each.value -# } - resource "aws_iam_role_policy_attachment" "s3_attachment" { role = aws_iam_role.multi_service_role.name policy_arn = aws_iam_policy.s3_write_policy.arn -- cgit v1.2.3 From 8ce75bcc1a89000fc0a9fea9b78ad31cd316ca06 Mon Sep 17 00:00:00 2001 From: Alex Schofield Date: Wed, 14 Aug 2024 23:04:32 +0100 Subject: chore(cleanup): remove unused rds section --- terraform/iam.tf | 3 --- 1 file changed, 3 deletions(-) diff --git a/terraform/iam.tf b/terraform/iam.tf index 195b07c..acb98f4 100644 --- a/terraform/iam.tf +++ b/terraform/iam.tf @@ -129,6 +129,3 @@ resource "aws_iam_role_policy_attachment" "cw_attachment" { policy_arn = aws_iam_policy.cw_policy.arn } -################ -# RDS POLICIES # -################ -- cgit v1.2.3 From c5f840e96b8c1696a6ed506e9260c4f1c26db10d Mon Sep 17 00:00:00 2001 From: Alex Schofield Date: Wed, 14 Aug 2024 23:05:26 +0100 Subject: feat(tf): add cloudwatch events iam policies --- terraform/iam.tf | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/terraform/iam.tf b/terraform/iam.tf index acb98f4..0e5fa6d 100644 --- a/terraform/iam.tf +++ b/terraform/iam.tf @@ -129,3 +129,30 @@ resource "aws_iam_role_policy_attachment" "cw_attachment" { policy_arn = aws_iam_policy.cw_policy.arn } +################### +# EVENTS POLICIES # +################### + +data "aws_iam_policy_document" "cloudwatch_events_policy" { + statement { + actions = [ + "events:PutRule", + "events:PutTargets", + "events:RemoveTargets", + "events:DeleteRule", + "events:PutEvents" + ] + resources = ["*"] + effect = "Allow" + } +} + +resource "aws_iam_policy" "cloudwatch_events_policy" { + name = "cloudwatch_events_policy" + policy = data.aws_iam_policy_document.cloudwatch_events_policy.json +} + +resource "aws_iam_role_policy_attachment" "cloudwatch_events_attachment" { + role = aws_iam_role.multi_service_role.name + policy_arn = aws_iam_policy.cloudwatch_events_policy.arn +} -- cgit v1.2.3 From d78db8a0f99c778b1e4b401c7cb4a094a5f2b103 Mon Sep 17 00:00:00 2001 From: Alex Schofield Date: Thu, 15 Aug 2024 00:24:41 +0100 Subject: fix(infra/tf): add unique id to permissions This was done to resolve issues with `ResourceConflictException` - add random_string resources for every lambda permission to add as suffixes - also add lifecycle options to force recreation whenever the random strings change --- terraform/events.tf | 45 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/terraform/events.tf b/terraform/events.tf index d2e2eb5..263141f 100644 --- a/terraform/events.tf +++ b/terraform/events.tf @@ -1,11 +1,25 @@ +resource "random_string" "eventbridge_suffix" { + length = 8 + special = false + upper = false +} + +resource "random_string" "s3_ingestion_suffix" { + length = 8 + special = false + upper = false +} + +resource "random_string" "s3_transform_suffix" { + length = 8 + special = false + upper = false +} + resource "aws_cloudwatch_event_rule" "lambda_trigger" { name = "lambda-scheduled-trigger" description = "Schedule to trigger the Lambda function" schedule_expression = "rate(30 minutes)" - - lifecycle { - create_before_destroy = true - } } resource "aws_cloudwatch_event_target" "extract_lambda_cw_event" { @@ -16,21 +30,28 @@ resource "aws_cloudwatch_event_target" "extract_lambda_cw_event" { } resource "aws_lambda_permission" "allow_eventbridge" { - statement_id = "AllowExecutionFromEventBridge" + statement_id = "AllowExecutionFromEventBridge${random_string.eventbridge_suffix.result}" action = "lambda:InvokeFunction" - function_name = aws_lambda_function.extract_lambda.function_name #replaced lambda name placeholder + function_name = aws_lambda_function.extract_lambda.function_name principal = "events.amazonaws.com" source_arn = aws_cloudwatch_event_rule.lambda_trigger.arn -} + lifecycle { + replace_triggered_by = [random_string.eventbridge_suffix] + } +} # below is step function 1 resource "aws_lambda_permission" "allow_s3_ingestion" { - statement_id = "AllowS3InvokeLambdaTransform" + statement_id = "AllowS3InvokeLambdaTransform${random_string.s3_ingestion_suffix.result}" action = "lambda:InvokeFunction" function_name = aws_lambda_function.transform_lambda.function_name #replaced lambda name placeholder principal = "s3.amazonaws.com" source_arn = aws_s3_bucket.extract_bucket.arn #replaced bucket name placeholder + + lifecycle { + replace_triggered_by = [random_string.s3_ingestion_suffix] + } } @@ -45,14 +66,16 @@ resource "aws_s3_bucket_notification" "extract_bucket_notification" { depends_on = [aws_lambda_permission.allow_s3_ingestion] } -###### - resource "aws_lambda_permission" "allow_s3_transform_bucket" { - statement_id = "AllowS3InvokeLambdaTransform" + statement_id = "AllowS3InvokeLambdaTransform${random_string.s3_transform_suffix.result}" action = "lambda:InvokeFunction" function_name = aws_lambda_function.transform_lambda.function_name #replaced lambda name placeholder principal = "s3.amazonaws.com" source_arn = aws_s3_bucket.transform_bucket.arn #replaced bucket name placeholder + + lifecycle { + replace_triggered_by = [random_string.s3_transform_suffix] + } } -- cgit v1.2.3 From 79d230f3c76609d32a3d8c553d64f37ce9fe6d09 Mon Sep 17 00:00:00 2001 From: Alex Schofield Date: Thu, 15 Aug 2024 10:15:31 +0100 Subject: fix(tf): create lambda zips before referencing --- terraform/lambda.tf | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/terraform/lambda.tf b/terraform/lambda.tf index fb0a666..72d1306 100644 --- a/terraform/lambda.tf +++ b/terraform/lambda.tf @@ -1,9 +1,14 @@ # Extract Lambda Function +data "archive_file" "extract_lambda_zip" { + type = "zip" + source_file = "${path.module}/../src/extract_lambda.py" + output_path = "${path.module}/../extract_function.zip" +} resource "aws_s3_object" "extract_lambda_code" { bucket = aws_s3_bucket.lambda_code_bucket.bucket key = "${var.extract_lambda_name}/extract_function.zip" - source = "${path.module}/../extract_function.zip" - etag = filemd5("${path.module}/../extract_function.zip") + source = data.archive_file.extract_lambda_zip.output_path + etag = filemd5(data.archive_file.extract_lambda_zip.output_path) } resource "aws_lambda_function" "extract_lambda" { @@ -22,11 +27,16 @@ resource "aws_lambda_function" "extract_lambda" { } # Transform Lambda Function +data "archive_file" "transform_lambda_zip" { + type = "zip" + source_file = "${path.module}/../src/transform_lambda.py" + output_path = "${path.module}/../transform_function.zip" +} resource "aws_s3_object" "transform_lambda_code" { bucket = aws_s3_bucket.lambda_code_bucket.bucket key = "${var.transform_lambda_name}/transform_function.zip" - source = "${path.module}/../transform_function.zip" - etag = filemd5("${path.module}/../transform_function.zip") + source = data.archive_file.transform_lambda_zip.output_path + etag = filemd5(data.archive_file.transform_lambda_zip.output_path) } resource "aws_lambda_function" "transform_lambda" { @@ -45,11 +55,16 @@ resource "aws_lambda_function" "transform_lambda" { } # Load Lambda Function +data "archive_file" "load_lambda_zip" { + type = "zip" + source_file = "${path.module}/../src/load_lambda.py" + output_path = "${path.module}/../load_function.zip" +} resource "aws_s3_object" "load_lambda_code" { bucket = aws_s3_bucket.lambda_code_bucket.bucket key = "${var.load_lambda_name}/load_function.zip" - source = "${path.module}/../load_function.zip" - etag = filemd5("${path.module}/../load_function.zip") + source = data.archive_file.load_lambda_zip.output_path + etag = filemd5(data.archive_file.load_lambda_zip.output_path) } resource "aws_lambda_function" "load_lambda" { -- cgit v1.2.3 From fe548561acc5e133e3bee4026aab85db2e511bcd Mon Sep 17 00:00:00 2001 From: lian-manonog Date: Thu, 15 Aug 2024 13:51:53 +0100 Subject: wip: secrets manager pushing to merge with extract_lambda --- .gitignore | 1 + src/extract_lambda.py | 1 + src/secrets_manager.py | 48 ++++++++++++++++++++++++++++++++++++++++++++ test/test_secrets_manager.py | 34 +++++++++++++++++++++++++++++++ 4 files changed, 84 insertions(+) create mode 100644 src/secrets_manager.py create mode 100644 test/test_secrets_manager.py diff --git a/.gitignore b/.gitignore index d1df545..d164c3f 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ .terraform* log* .DS_Store +venv \ No newline at end of file diff --git a/src/extract_lambda.py b/src/extract_lambda.py index 7d56c66..faa1d30 100644 --- a/src/extract_lambda.py +++ b/src/extract_lambda.py @@ -2,6 +2,7 @@ from pg8000.native import Connection, Error, DatabaseError, InterfaceError from dotenv import load_dotenv import os + load_dotenv() def extract(): diff --git a/src/secrets_manager.py b/src/secrets_manager.py new file mode 100644 index 0000000..c0fb61e --- /dev/null +++ b/src/secrets_manager.py @@ -0,0 +1,48 @@ +import boto3 +from botocore.exceptions import ClientError +import json + + +def sm_client(): + sm_client = boto3.client('secretsmanager') + yield sm_client + +def create_secret(sm_client, secret_name, cohort_id, user, password, host, database, port): + secret = { + "cohort_id": cohort_id, + "user": user, + "password": password, + "host": host, + "database": database, + "port": port + } + + response = sm_client.create_secret( + Name = secret_name, + SecretString = json.dumps(secret) + ) + + print(response) + return response + +def list_secret(sm_client): + response = sm_client.list_secrets() + secret_dict = response['SecretList'] + secret_names = [] + for items in secret_dict: + secret_names.append(items['Name']) + print(f'{len(secret_names)} secret(s) available') + for name in secret_names: + print(name) + return secret_names + +def retrieve_secrets(sm_client): + response = sm_client.get_secrets( + + ) + + + +#retrieve secret +#so lambda can access totesy db +#so lambda connect to the db and then retrieve the data \ No newline at end of file diff --git a/test/test_secrets_manager.py b/test/test_secrets_manager.py new file mode 100644 index 0000000..86533bc --- /dev/null +++ b/test/test_secrets_manager.py @@ -0,0 +1,34 @@ +from src.secrets_manager import sm_client, create_secret, list_secret +import boto3 +from moto import mock_aws +import json +import pytest +import os + +pytest.fixture(scope='class') +def mock_aws_credentials(): + """Mocked AWS Credentials for moto.""" + os.environ["AWS_ACCESS_KEY_ID"] = "testing" + os.environ["AWS_SECRET_ACCESS_KEY"] = "testing" + os.environ["AWS_SECURITY_TOKEN"] = "testing" + os.environ["AWS_SESSION_TOKEN"] = "testing" + os.environ["AWS_DEFAULT_REGION"] = "eu-west-2" + +@pytest.fixture(scope='class') +def mock_sm_client(mock_aws_credentials): + with mock_aws(): + yield boto3.client('secretsmanager') + + +def test_create_secret_stores_secrets(mock_sm_client): + cohort_id = "test_cohort_id" + user = "test_user_id" + password = "test_password" + host = "test_host" + database = "test_database" + port = "test_port" + + secret_name = "test_secret" + response = create_secret(mock_sm_client, secret_name, cohort_id, user, password, host, database, port) + + assert response['Name'] == secret_name \ No newline at end of file -- cgit v1.2.3