Hey developers! For the last post in the series, we’ll provision the infrastructure on AWS and deploy our API. Let’s dive in! You can check out my GitHub for the complete code.
Configure Terraform
First, we’ll define the Terraform version and set up the backend to store the state in an S3 bucket. Since Terraform doesn’t create the backend bucket automatically, you’ll need to provision it beforehand. Variables cannot be used in the backend configuration so the values to be hardcoded but feel free to change them.
<span>#infrastructure/versions.tf</span><span>terraform</span> <span>{</span><span>required_providers</span> <span>{</span><span>aws</span> <span>=</span> <span>{</span><span>source</span> <span>=</span> <span>"hashicorp/aws"</span><span>version</span> <span>=</span> <span>"~>5.0"</span><span>}</span><span>}</span><span>}</span><span>#infrastructure/versions.tf</span> <span>terraform</span> <span>{</span> <span>required_providers</span> <span>{</span> <span>aws</span> <span>=</span> <span>{</span> <span>source</span> <span>=</span> <span>"hashicorp/aws"</span> <span>version</span> <span>=</span> <span>"~>5.0"</span> <span>}</span> <span>}</span> <span>}</span>#infrastructure/versions.tf terraform { required_providers { aws = { source = "hashicorp/aws" version = "~>5.0" } } }
Enter fullscreen mode Exit fullscreen mode
<span>#infrastructure/terraform.tf</span><span>terraform</span> <span>{</span><span>backend</span> <span>"s3"</span> <span>{</span><span>bucket</span> <span>=</span> <span>"translate-state-bucket"</span><span>region</span> <span>=</span> <span>"us-east-1"</span><span>key</span> <span>=</span> <span>"terraform.tfstate"</span><span>}</span><span>}</span><span>#infrastructure/terraform.tf</span> <span>terraform</span> <span>{</span> <span>backend</span> <span>"s3"</span> <span>{</span> <span>bucket</span> <span>=</span> <span>"translate-state-bucket"</span> <span>region</span> <span>=</span> <span>"us-east-1"</span> <span>key</span> <span>=</span> <span>"terraform.tfstate"</span> <span>}</span> <span>}</span>#infrastructure/terraform.tf terraform { backend "s3" { bucket = "translate-state-bucket" region = "us-east-1" key = "terraform.tfstate" } }
Enter fullscreen mode Exit fullscreen mode
We’ll also create a few local variables
<span>#infrastructure/locals.tf</span><span>locals</span> <span>{</span><span>tags</span> <span>=</span> <span>{</span><span>Terraform</span> <span>=</span> <span>true</span><span>Application</span> <span>=</span> <span>"translate"</span><span>}</span><span>}</span><span>#infrastructure/locals.tf</span> <span>locals</span> <span>{</span> <span>tags</span> <span>=</span> <span>{</span> <span>Terraform</span> <span>=</span> <span>true</span> <span>Application</span> <span>=</span> <span>"translate"</span> <span>}</span> <span>}</span>#infrastructure/locals.tf locals { tags = { Terraform = true Application = "translate" } }
Enter fullscreen mode Exit fullscreen mode
DynamoDB Table
Now we’ll define the configuration to provision the DynamoDB table. DynamoDB tables require a name
, a hash_key
,which should also specified as an attribute, and a billing_mode
.
<span>resource</span> <span>"aws_dynamodb_table"</span> <span>"this"</span> <span>{</span><span>name</span> <span>=</span> <span>"translate-table"</span><span>hash_key</span> <span>=</span> <span>"id"</span><span>billing_mode</span> <span>=</span> <span>"PAY_PER_REQUEST"</span><span>attribute</span> <span>{</span><span>name</span> <span>=</span> <span>"id"</span><span>type</span> <span>=</span> <span>"S"</span> <span>#string</span><span>}</span><span>tags</span> <span>=</span> <span>local</span><span>.</span><span>tags</span><span>}</span><span>resource</span> <span>"aws_dynamodb_table"</span> <span>"this"</span> <span>{</span> <span>name</span> <span>=</span> <span>"translate-table"</span> <span>hash_key</span> <span>=</span> <span>"id"</span> <span>billing_mode</span> <span>=</span> <span>"PAY_PER_REQUEST"</span> <span>attribute</span> <span>{</span> <span>name</span> <span>=</span> <span>"id"</span> <span>type</span> <span>=</span> <span>"S"</span> <span>#string</span> <span>}</span> <span>tags</span> <span>=</span> <span>local</span><span>.</span><span>tags</span> <span>}</span>resource "aws_dynamodb_table" "this" { name = "translate-table" hash_key = "id" billing_mode = "PAY_PER_REQUEST" attribute { name = "id" type = "S" #string } tags = local.tags }
Enter fullscreen mode Exit fullscreen mode
S3 Bucket
Next, we’ll provision the output S3 bucket by specifying a name using either the bucket
or bucket_prefix
field. To manage storage efficiently, we’ll add a lifecycle configuration that automatically deletes files after one day. Lifecycle configurations require the bucket name and at least one rule to define the retention policy.
<span>resource</span> <span>"aws_s3_bucket"</span> <span>"this"</span> <span>{</span><span>bucket_prefix</span> <span>=</span> <span>"translate-output-"</span><span>tags</span> <span>=</span> <span>local</span><span>.</span><span>tags</span><span>}</span><span>resource</span> <span>"aws_s3_bucket_lifecycle_configuration"</span> <span>"this"</span> <span>{</span> <span># A lifecycle hook to delete files after 1 day</span><span>bucket</span> <span>=</span> <span>aws_s3_bucket</span><span>.</span><span>this</span><span>.</span><span>id</span><span>rule</span> <span>{</span><span>id</span> <span>=</span> <span>"rule-1"</span><span>filter</span> <span>{}</span><span>expiration</span> <span>{</span><span>days</span> <span>=</span> <span>1</span><span>}</span><span>status</span> <span>=</span> <span>"Enabled"</span><span>}</span><span>}</span><span>resource</span> <span>"aws_s3_bucket"</span> <span>"this"</span> <span>{</span> <span>bucket_prefix</span> <span>=</span> <span>"translate-output-"</span> <span>tags</span> <span>=</span> <span>local</span><span>.</span><span>tags</span> <span>}</span> <span>resource</span> <span>"aws_s3_bucket_lifecycle_configuration"</span> <span>"this"</span> <span>{</span> <span># A lifecycle hook to delete files after 1 day</span> <span>bucket</span> <span>=</span> <span>aws_s3_bucket</span><span>.</span><span>this</span><span>.</span><span>id</span> <span>rule</span> <span>{</span> <span>id</span> <span>=</span> <span>"rule-1"</span> <span>filter</span> <span>{}</span> <span>expiration</span> <span>{</span> <span>days</span> <span>=</span> <span>1</span> <span>}</span> <span>status</span> <span>=</span> <span>"Enabled"</span> <span>}</span> <span>}</span>resource "aws_s3_bucket" "this" { bucket_prefix = "translate-output-" tags = local.tags } resource "aws_s3_bucket_lifecycle_configuration" "this" { # A lifecycle hook to delete files after 1 day bucket = aws_s3_bucket.this.id rule { id = "rule-1" filter {} expiration { days = 1 } status = "Enabled" } }
Enter fullscreen mode Exit fullscreen mode
Lambda functions
We’ll start by assigning the AWSLambdaBasicExecutionRole
, which grants essential permissions like creating CloudWatch logs.
To package the Python scripts for the Translate Text and Translate File endpoints, we’ll use the archive_file
data resource. Since the Translate File API has dependencies, we’ll install them beforehand using a null_resource
before packaging.
We’ll also create an IAM role for the Lambda functions and attach the necessary policies. Finally, we’ll define the Lambda functions using the packaged files, set the handler to main.handler
, and configure the required environment variables.
<span>data</span> <span>"aws_iam_policy"</span> <span>"this"</span> <span>{</span><span>name</span> <span>=</span> <span>"AWSLambdaBasicExecutionRole"</span><span>}</span><span>data</span> <span>"archive_file"</span> <span>"translate"</span> <span>{</span><span>type</span> <span>=</span> <span>"zip"</span><span>source_dir</span> <span>=</span> <span>"${path.module}/../api/translate"</span><span>output_path</span> <span>=</span> <span>"${path.module}/files/translate.zip"</span><span>}</span><span>data</span> <span>"archive_file"</span> <span>"translate-file"</span> <span>{</span><span>type</span> <span>=</span> <span>"zip"</span><span>source_dir</span> <span>=</span> <span>"${path.module}/files/translate_file"</span><span>output_path</span> <span>=</span> <span>"${path.module}/files/translate-file.zip"</span><span>depends_on</span> <span>=</span> <span>[</span> <span>null_resource</span><span>.</span><span>this</span> <span>]</span><span>}</span><span>resource</span> <span>"null_resource"</span> <span>"this"</span> <span>{</span><span>provisioner</span> <span>"local-exec"</span> <span>{</span><span>command</span> <span>=</span> <span><<</span><span>EOT</span><span> rm -rf ${path.module}/files/translate_file cp -r ${path.module}/../api/translate_file ${path.module}/files/translate_file cd ${path.module}/files/translate_file pip install -r requirements.txt -t ./ </span><span> EOT </span> <span>}</span><span>triggers</span> <span>=</span> <span>{</span><span>always_run</span> <span>=</span> <span>timestamp</span><span>()</span><span>}</span><span>}</span><span>data</span> <span>"aws_iam_policy_document"</span> <span>"assumeRole"</span> <span>{</span><span>statement</span> <span>{</span><span>effect</span> <span>=</span> <span>"Allow"</span><span>actions</span> <span>=</span> <span>[</span><span>"sts:AssumeRole"</span><span>]</span><span>principals</span> <span>{</span><span>identifiers</span> <span>=</span> <span>[</span><span>"lambda.amazonaws.com"</span><span>]</span><span>type</span> <span>=</span> <span>"Service"</span><span>}</span><span>}</span><span>}</span><span>data</span> <span>"aws_iam_policy_document"</span> <span>"this"</span> <span>{</span><span>statement</span> <span>{</span><span>effect</span> <span>=</span> <span>"Allow"</span><span>actions</span> <span>=</span> <span>[</span><span>"translate:TranslateText"</span><span>,</span><span>"translate:TranslateDocument"</span><span>,</span><span>"comprehend:DetectDominantLanguage"</span><span>]</span><span>resources</span> <span>=</span> <span>[</span><span>"*"</span><span>]</span><span>}</span><span>statement</span> <span>{</span><span>effect</span> <span>=</span> <span>"Allow"</span><span>actions</span> <span>=</span> <span>[</span><span>"dynamodb:*"</span><span>]</span><span>resources</span> <span>=</span> <span>[</span><span>aws_dynamodb_table</span><span>.</span><span>this</span><span>.</span><span>arn</span><span>,</span><span>"${aws_dynamodb_table.this.arn}/*"</span><span>]</span><span>}</span><span>statement</span> <span>{</span><span>effect</span> <span>=</span> <span>"Allow"</span><span>actions</span> <span>=</span> <span>[</span><span>"s3:*"</span><span>]</span><span>resources</span> <span>=</span> <span>[</span><span>aws_s3_bucket</span><span>.</span><span>this</span><span>.</span><span>arn</span><span>,</span><span>"${aws_s3_bucket.this.arn}/*"</span><span>]</span><span>}</span><span>}</span><span>resource</span> <span>"aws_iam_role"</span> <span>"this"</span> <span>{</span><span>name_prefix</span> <span>=</span> <span>"translate-app-role"</span><span>assume_role_policy</span> <span>=</span> <span>data</span><span>.</span><span>aws_iam_policy_document</span><span>.</span><span>assumeRole</span><span>.</span><span>json</span><span>tags</span> <span>=</span> <span>local</span><span>.</span><span>tags</span><span>}</span><span>resource</span> <span>"aws_iam_role_policy"</span> <span>"this"</span> <span>{</span><span>role</span> <span>=</span> <span>aws_iam_role</span><span>.</span><span>this</span><span>.</span><span>name</span><span>policy</span> <span>=</span> <span>data</span><span>.</span><span>aws_iam_policy_document</span><span>.</span><span>this</span><span>.</span><span>json</span><span>}</span><span>resource</span> <span>"aws_iam_role_policy_attachment"</span> <span>"this"</span> <span>{</span><span>role</span> <span>=</span> <span>aws_iam_role</span><span>.</span><span>this</span><span>.</span><span>name</span><span>policy_arn</span> <span>=</span> <span>data</span><span>.</span><span>aws_iam_policy</span><span>.</span><span>this</span><span>.</span><span>arn</span><span>}</span><span>resource</span> <span>"aws_lambda_function"</span> <span>"translate"</span> <span>{</span><span>function_name</span> <span>=</span> <span>"translate-app-translate-lambda"</span><span>description</span> <span>=</span> <span>"Lambda function for the /translate endpoint"</span><span>role</span> <span>=</span> <span>aws_iam_role</span><span>.</span><span>this</span><span>.</span><span>arn</span><span>filename</span> <span>=</span> <span>data</span><span>.</span><span>archive_file</span><span>.</span><span>translate</span><span>.</span><span>output_path</span><span>handler</span> <span>=</span> <span>"main.handler"</span><span>source_code_hash</span> <span>=</span> <span>data</span><span>.</span><span>archive_file</span><span>.</span><span>translate</span><span>.</span><span>output_base64sha256</span><span>runtime</span> <span>=</span> <span>"python3.10"</span><span>environment</span> <span>{</span><span>variables</span> <span>=</span> <span>{</span><span>DYNAMODB_TABLE</span> <span>=</span> <span>aws_dynamodb_table</span><span>.</span><span>this</span><span>.</span><span>name</span><span>}</span><span>}</span><span>tags</span> <span>=</span> <span>local</span><span>.</span><span>tags</span><span>}</span><span>resource</span> <span>"aws_lambda_function"</span> <span>"translate-file"</span> <span>{</span><span>function_name</span> <span>=</span> <span>"translate-app-translate-file-lambda"</span><span>description</span> <span>=</span> <span>"Lambda function for the /translate/file endpoint"</span><span>role</span> <span>=</span> <span>aws_iam_role</span><span>.</span><span>this</span><span>.</span><span>arn</span><span>filename</span> <span>=</span> <span>data</span><span>.</span><span>archive_file</span><span>.</span><span>translate-file</span><span>.</span><span>output_path</span><span>handler</span> <span>=</span> <span>"main.handler"</span><span>source_code_hash</span> <span>=</span> <span>data</span><span>.</span><span>archive_file</span><span>.</span><span>translate-file</span><span>.</span><span>output_base64sha256</span><span>runtime</span> <span>=</span> <span>"python3.10"</span><span>environment</span> <span>{</span><span>variables</span> <span>=</span> <span>{</span><span>DYNAMODB_TABLE</span> <span>=</span> <span>aws_dynamodb_table</span><span>.</span><span>this</span><span>.</span><span>name</span><span>S3_BUCKET</span> <span>=</span> <span>aws_s3_bucket</span><span>.</span><span>this</span><span>.</span><span>bucket</span><span>}</span><span>}</span><span>tags</span> <span>=</span> <span>local</span><span>.</span><span>tags</span><span>}</span><span>data</span> <span>"aws_iam_policy"</span> <span>"this"</span> <span>{</span> <span>name</span> <span>=</span> <span>"AWSLambdaBasicExecutionRole"</span> <span>}</span> <span>data</span> <span>"archive_file"</span> <span>"translate"</span> <span>{</span> <span>type</span> <span>=</span> <span>"zip"</span> <span>source_dir</span> <span>=</span> <span>"${path.module}/../api/translate"</span> <span>output_path</span> <span>=</span> <span>"${path.module}/files/translate.zip"</span> <span>}</span> <span>data</span> <span>"archive_file"</span> <span>"translate-file"</span> <span>{</span> <span>type</span> <span>=</span> <span>"zip"</span> <span>source_dir</span> <span>=</span> <span>"${path.module}/files/translate_file"</span> <span>output_path</span> <span>=</span> <span>"${path.module}/files/translate-file.zip"</span> <span>depends_on</span> <span>=</span> <span>[</span> <span>null_resource</span><span>.</span><span>this</span> <span>]</span> <span>}</span> <span>resource</span> <span>"null_resource"</span> <span>"this"</span> <span>{</span> <span>provisioner</span> <span>"local-exec"</span> <span>{</span> <span>command</span> <span>=</span> <span><<</span><span>EOT</span><span> rm -rf ${path.module}/files/translate_file cp -r ${path.module}/../api/translate_file ${path.module}/files/translate_file cd ${path.module}/files/translate_file pip install -r requirements.txt -t ./ </span><span> EOT </span> <span>}</span> <span>triggers</span> <span>=</span> <span>{</span> <span>always_run</span> <span>=</span> <span>timestamp</span><span>()</span> <span>}</span> <span>}</span> <span>data</span> <span>"aws_iam_policy_document"</span> <span>"assumeRole"</span> <span>{</span> <span>statement</span> <span>{</span> <span>effect</span> <span>=</span> <span>"Allow"</span> <span>actions</span> <span>=</span> <span>[</span><span>"sts:AssumeRole"</span><span>]</span> <span>principals</span> <span>{</span> <span>identifiers</span> <span>=</span> <span>[</span><span>"lambda.amazonaws.com"</span><span>]</span> <span>type</span> <span>=</span> <span>"Service"</span> <span>}</span> <span>}</span> <span>}</span> <span>data</span> <span>"aws_iam_policy_document"</span> <span>"this"</span> <span>{</span> <span>statement</span> <span>{</span> <span>effect</span> <span>=</span> <span>"Allow"</span> <span>actions</span> <span>=</span> <span>[</span><span>"translate:TranslateText"</span><span>,</span> <span>"translate:TranslateDocument"</span><span>,</span> <span>"comprehend:DetectDominantLanguage"</span> <span>]</span> <span>resources</span> <span>=</span> <span>[</span><span>"*"</span><span>]</span> <span>}</span> <span>statement</span> <span>{</span> <span>effect</span> <span>=</span> <span>"Allow"</span> <span>actions</span> <span>=</span> <span>[</span><span>"dynamodb:*"</span><span>]</span> <span>resources</span> <span>=</span> <span>[</span> <span>aws_dynamodb_table</span><span>.</span><span>this</span><span>.</span><span>arn</span><span>,</span> <span>"${aws_dynamodb_table.this.arn}/*"</span> <span>]</span> <span>}</span> <span>statement</span> <span>{</span> <span>effect</span> <span>=</span> <span>"Allow"</span> <span>actions</span> <span>=</span> <span>[</span><span>"s3:*"</span><span>]</span> <span>resources</span> <span>=</span> <span>[</span> <span>aws_s3_bucket</span><span>.</span><span>this</span><span>.</span><span>arn</span><span>,</span> <span>"${aws_s3_bucket.this.arn}/*"</span> <span>]</span> <span>}</span> <span>}</span> <span>resource</span> <span>"aws_iam_role"</span> <span>"this"</span> <span>{</span> <span>name_prefix</span> <span>=</span> <span>"translate-app-role"</span> <span>assume_role_policy</span> <span>=</span> <span>data</span><span>.</span><span>aws_iam_policy_document</span><span>.</span><span>assumeRole</span><span>.</span><span>json</span> <span>tags</span> <span>=</span> <span>local</span><span>.</span><span>tags</span> <span>}</span> <span>resource</span> <span>"aws_iam_role_policy"</span> <span>"this"</span> <span>{</span> <span>role</span> <span>=</span> <span>aws_iam_role</span><span>.</span><span>this</span><span>.</span><span>name</span> <span>policy</span> <span>=</span> <span>data</span><span>.</span><span>aws_iam_policy_document</span><span>.</span><span>this</span><span>.</span><span>json</span> <span>}</span> <span>resource</span> <span>"aws_iam_role_policy_attachment"</span> <span>"this"</span> <span>{</span> <span>role</span> <span>=</span> <span>aws_iam_role</span><span>.</span><span>this</span><span>.</span><span>name</span> <span>policy_arn</span> <span>=</span> <span>data</span><span>.</span><span>aws_iam_policy</span><span>.</span><span>this</span><span>.</span><span>arn</span> <span>}</span> <span>resource</span> <span>"aws_lambda_function"</span> <span>"translate"</span> <span>{</span> <span>function_name</span> <span>=</span> <span>"translate-app-translate-lambda"</span> <span>description</span> <span>=</span> <span>"Lambda function for the /translate endpoint"</span> <span>role</span> <span>=</span> <span>aws_iam_role</span><span>.</span><span>this</span><span>.</span><span>arn</span> <span>filename</span> <span>=</span> <span>data</span><span>.</span><span>archive_file</span><span>.</span><span>translate</span><span>.</span><span>output_path</span> <span>handler</span> <span>=</span> <span>"main.handler"</span> <span>source_code_hash</span> <span>=</span> <span>data</span><span>.</span><span>archive_file</span><span>.</span><span>translate</span><span>.</span><span>output_base64sha256</span> <span>runtime</span> <span>=</span> <span>"python3.10"</span> <span>environment</span> <span>{</span> <span>variables</span> <span>=</span> <span>{</span> <span>DYNAMODB_TABLE</span> <span>=</span> <span>aws_dynamodb_table</span><span>.</span><span>this</span><span>.</span><span>name</span> <span>}</span> <span>}</span> <span>tags</span> <span>=</span> <span>local</span><span>.</span><span>tags</span> <span>}</span> <span>resource</span> <span>"aws_lambda_function"</span> <span>"translate-file"</span> <span>{</span> <span>function_name</span> <span>=</span> <span>"translate-app-translate-file-lambda"</span> <span>description</span> <span>=</span> <span>"Lambda function for the /translate/file endpoint"</span> <span>role</span> <span>=</span> <span>aws_iam_role</span><span>.</span><span>this</span><span>.</span><span>arn</span> <span>filename</span> <span>=</span> <span>data</span><span>.</span><span>archive_file</span><span>.</span><span>translate-file</span><span>.</span><span>output_path</span> <span>handler</span> <span>=</span> <span>"main.handler"</span> <span>source_code_hash</span> <span>=</span> <span>data</span><span>.</span><span>archive_file</span><span>.</span><span>translate-file</span><span>.</span><span>output_base64sha256</span> <span>runtime</span> <span>=</span> <span>"python3.10"</span> <span>environment</span> <span>{</span> <span>variables</span> <span>=</span> <span>{</span> <span>DYNAMODB_TABLE</span> <span>=</span> <span>aws_dynamodb_table</span><span>.</span><span>this</span><span>.</span><span>name</span> <span>S3_BUCKET</span> <span>=</span> <span>aws_s3_bucket</span><span>.</span><span>this</span><span>.</span><span>bucket</span> <span>}</span> <span>}</span> <span>tags</span> <span>=</span> <span>local</span><span>.</span><span>tags</span> <span>}</span>data "aws_iam_policy" "this" { name = "AWSLambdaBasicExecutionRole" } data "archive_file" "translate" { type = "zip" source_dir = "${path.module}/../api/translate" output_path = "${path.module}/files/translate.zip" } data "archive_file" "translate-file" { type = "zip" source_dir = "${path.module}/files/translate_file" output_path = "${path.module}/files/translate-file.zip" depends_on = [ null_resource.this ] } resource "null_resource" "this" { provisioner "local-exec" { command = <<EOT rm -rf ${path.module}/files/translate_file cp -r ${path.module}/../api/translate_file ${path.module}/files/translate_file cd ${path.module}/files/translate_file pip install -r requirements.txt -t ./ EOT } triggers = { always_run = timestamp() } } data "aws_iam_policy_document" "assumeRole" { statement { effect = "Allow" actions = ["sts:AssumeRole"] principals { identifiers = ["lambda.amazonaws.com"] type = "Service" } } } data "aws_iam_policy_document" "this" { statement { effect = "Allow" actions = ["translate:TranslateText", "translate:TranslateDocument", "comprehend:DetectDominantLanguage" ] resources = ["*"] } statement { effect = "Allow" actions = ["dynamodb:*"] resources = [ aws_dynamodb_table.this.arn, "${aws_dynamodb_table.this.arn}/*" ] } statement { effect = "Allow" actions = ["s3:*"] resources = [ aws_s3_bucket.this.arn, "${aws_s3_bucket.this.arn}/*" ] } } resource "aws_iam_role" "this" { name_prefix = "translate-app-role" assume_role_policy = data.aws_iam_policy_document.assumeRole.json tags = local.tags } resource "aws_iam_role_policy" "this" { role = aws_iam_role.this.name policy = data.aws_iam_policy_document.this.json } resource "aws_iam_role_policy_attachment" "this" { role = aws_iam_role.this.name policy_arn = data.aws_iam_policy.this.arn } resource "aws_lambda_function" "translate" { function_name = "translate-app-translate-lambda" description = "Lambda function for the /translate endpoint" role = aws_iam_role.this.arn filename = data.archive_file.translate.output_path handler = "main.handler" source_code_hash = data.archive_file.translate.output_base64sha256 runtime = "python3.10" environment { variables = { DYNAMODB_TABLE = aws_dynamodb_table.this.name } } tags = local.tags } resource "aws_lambda_function" "translate-file" { function_name = "translate-app-translate-file-lambda" description = "Lambda function for the /translate/file endpoint" role = aws_iam_role.this.arn filename = data.archive_file.translate-file.output_path handler = "main.handler" source_code_hash = data.archive_file.translate-file.output_base64sha256 runtime = "python3.10" environment { variables = { DYNAMODB_TABLE = aws_dynamodb_table.this.name S3_BUCKET = aws_s3_bucket.this.bucket } } tags = local.tags }
Enter fullscreen mode Exit fullscreen mode
API Gateway
Finally, we’ll define the API Gateway API. To handle file uploads properly, we’ll include multipart/form-data
in the binary_media_types
configuration, ensuring that multipart requests are base64-encoded before being sent to the Lambda function.
Additionally, we’ll configure the API resources as deployment triggers, so any changes to their properties automatically trigger a new deployment.
<span>data</span> <span>"aws_caller_identity"</span> <span>"this"</span> <span>{}</span><span>data</span> <span>"aws_region"</span> <span>"this"</span> <span>{}</span><span>resource</span> <span>"aws_api_gateway_rest_api"</span> <span>"this"</span> <span>{</span><span>name</span> <span>=</span> <span>"translate-app-api"</span><span>description</span> <span>=</span> <span>"The translate app API"</span><span>binary_media_types</span> <span>=</span> <span>[</span><span>"multipart/form-data"</span><span>]</span><span>tags</span> <span>=</span> <span>local</span><span>.</span><span>tags</span><span>}</span><span>resource</span> <span>"aws_api_gateway_resource"</span> <span>"translate"</span> <span>{</span><span>rest_api_id</span> <span>=</span> <span>aws_api_gateway_rest_api</span><span>.</span><span>this</span><span>.</span><span>id</span><span>parent_id</span> <span>=</span> <span>aws_api_gateway_rest_api</span><span>.</span><span>this</span><span>.</span><span>root_resource_id</span><span>path_part</span> <span>=</span> <span>"translate"</span><span>}</span><span>resource</span> <span>"aws_api_gateway_method"</span> <span>"translate"</span> <span>{</span><span>rest_api_id</span> <span>=</span> <span>aws_api_gateway_rest_api</span><span>.</span><span>this</span><span>.</span><span>id</span><span>resource_id</span> <span>=</span> <span>aws_api_gateway_resource</span><span>.</span><span>translate</span><span>.</span><span>id</span><span>http_method</span> <span>=</span> <span>"POST"</span><span>authorization</span> <span>=</span> <span>"NONE"</span><span>}</span><span>resource</span> <span>"aws_api_gateway_integration"</span> <span>"translate"</span> <span>{</span><span>rest_api_id</span> <span>=</span> <span>aws_api_gateway_rest_api</span><span>.</span><span>this</span><span>.</span><span>id</span><span>resource_id</span> <span>=</span> <span>aws_api_gateway_resource</span><span>.</span><span>translate</span><span>.</span><span>id</span><span>http_method</span> <span>=</span> <span>aws_api_gateway_method</span><span>.</span><span>translate</span><span>.</span><span>http_method</span><span>integration_http_method</span> <span>=</span> <span>"POST"</span><span>type</span> <span>=</span> <span>"AWS_PROXY"</span><span>uri</span> <span>=</span> <span>aws_lambda_function</span><span>.</span><span>translate</span><span>.</span><span>invoke_arn</span><span>}</span><span>resource</span> <span>"aws_api_gateway_resource"</span> <span>"translate-file"</span> <span>{</span><span>rest_api_id</span> <span>=</span> <span>aws_api_gateway_rest_api</span><span>.</span><span>this</span><span>.</span><span>id</span><span>parent_id</span> <span>=</span> <span>aws_api_gateway_resource</span><span>.</span><span>translate</span><span>.</span><span>id</span><span>path_part</span> <span>=</span> <span>"file"</span><span>}</span><span>resource</span> <span>"aws_api_gateway_method"</span> <span>"translate-file"</span> <span>{</span><span>rest_api_id</span> <span>=</span> <span>aws_api_gateway_rest_api</span><span>.</span><span>this</span><span>.</span><span>id</span><span>resource_id</span> <span>=</span> <span>aws_api_gateway_resource</span><span>.</span><span>translate-file</span><span>.</span><span>id</span><span>http_method</span> <span>=</span> <span>"POST"</span><span>authorization</span> <span>=</span> <span>"NONE"</span><span>}</span><span>resource</span> <span>"aws_api_gateway_integration"</span> <span>"translate-file"</span> <span>{</span><span>rest_api_id</span> <span>=</span> <span>aws_api_gateway_rest_api</span><span>.</span><span>this</span><span>.</span><span>id</span><span>resource_id</span> <span>=</span> <span>aws_api_gateway_resource</span><span>.</span><span>translate-file</span><span>.</span><span>id</span><span>http_method</span> <span>=</span> <span>aws_api_gateway_method</span><span>.</span><span>translate-file</span><span>.</span><span>http_method</span><span>content_handling</span> <span>=</span> <span>"CONVERT_TO_TEXT"</span><span>integration_http_method</span> <span>=</span> <span>"POST"</span><span>type</span> <span>=</span> <span>"AWS_PROXY"</span><span>uri</span> <span>=</span> <span>aws_lambda_function</span><span>.</span><span>translate-file</span><span>.</span><span>invoke_arn</span><span>}</span><span>resource</span> <span>"aws_api_gateway_deployment"</span> <span>"this"</span> <span>{</span><span>rest_api_id</span> <span>=</span> <span>aws_api_gateway_rest_api</span><span>.</span><span>this</span><span>.</span><span>id</span><span>triggers</span> <span>=</span> <span>{</span><span>redeployment</span> <span>=</span> <span>sha1</span><span>(</span><span>jsonencode</span><span>([</span><span>aws_api_gateway_rest_api</span><span>.</span><span>this</span><span>,</span><span>aws_api_gateway_resource</span><span>.</span><span>translate</span><span>,</span><span>aws_api_gateway_method</span><span>.</span><span>translate</span><span>,</span><span>aws_api_gateway_integration</span><span>.</span><span>translate</span><span>,</span><span>aws_api_gateway_resource</span><span>.</span><span>translate-file</span><span>,</span><span>aws_api_gateway_method</span><span>.</span><span>translate-file</span><span>,</span><span>aws_api_gateway_integration</span><span>.</span><span>translate-file</span><span>,</span><span>]))</span><span>}</span><span>lifecycle</span> <span>{</span><span>create_before_destroy</span> <span>=</span> <span>true</span><span>}</span><span>}</span><span>resource</span> <span>"aws_api_gateway_stage"</span> <span>"this"</span> <span>{</span><span>deployment_id</span> <span>=</span> <span>aws_api_gateway_deployment</span><span>.</span><span>this</span><span>.</span><span>id</span><span>rest_api_id</span> <span>=</span> <span>aws_api_gateway_rest_api</span><span>.</span><span>this</span><span>.</span><span>id</span><span>stage_name</span> <span>=</span> <span>"prod"</span><span>tags</span> <span>=</span> <span>local</span><span>.</span><span>tags</span><span>}</span><span>resource</span> <span>"aws_lambda_permission"</span> <span>"translate"</span> <span>{</span><span>action</span> <span>=</span> <span>"lambda:InvokeFunction"</span><span>function_name</span> <span>=</span> <span>aws_lambda_function</span><span>.</span><span>translate</span><span>.</span><span>function_name</span><span>principal</span> <span>=</span> <span>"apigateway.amazonaws.com"</span><span>source_arn</span> <span>=</span> <span>"arn:aws:execute-api:${data.aws_region.this.name}:${data.aws_caller_identity.this.account_id}:${aws_api_gateway_rest_api.this.id}/*/${aws_api_gateway_method.translate.http_method}${aws_api_gateway_resource.translate.path}"</span><span>}</span><span>resource</span> <span>"aws_lambda_permission"</span> <span>"translate-file"</span> <span>{</span><span>action</span> <span>=</span> <span>"lambda:InvokeFunction"</span><span>function_name</span> <span>=</span> <span>aws_lambda_function</span><span>.</span><span>translate-file</span><span>.</span><span>function_name</span><span>principal</span> <span>=</span> <span>"apigateway.amazonaws.com"</span><span>source_arn</span> <span>=</span> <span>"arn:aws:execute-api:${data.aws_region.this.name}:${data.aws_caller_identity.this.account_id}:${aws_api_gateway_rest_api.this.id}/*/${aws_api_gateway_method.translate-file.http_method}${aws_api_gateway_resource.translate-file.path}"</span><span>}</span><span>data</span> <span>"aws_caller_identity"</span> <span>"this"</span> <span>{}</span> <span>data</span> <span>"aws_region"</span> <span>"this"</span> <span>{}</span> <span>resource</span> <span>"aws_api_gateway_rest_api"</span> <span>"this"</span> <span>{</span> <span>name</span> <span>=</span> <span>"translate-app-api"</span> <span>description</span> <span>=</span> <span>"The translate app API"</span> <span>binary_media_types</span> <span>=</span> <span>[</span><span>"multipart/form-data"</span><span>]</span> <span>tags</span> <span>=</span> <span>local</span><span>.</span><span>tags</span> <span>}</span> <span>resource</span> <span>"aws_api_gateway_resource"</span> <span>"translate"</span> <span>{</span> <span>rest_api_id</span> <span>=</span> <span>aws_api_gateway_rest_api</span><span>.</span><span>this</span><span>.</span><span>id</span> <span>parent_id</span> <span>=</span> <span>aws_api_gateway_rest_api</span><span>.</span><span>this</span><span>.</span><span>root_resource_id</span> <span>path_part</span> <span>=</span> <span>"translate"</span> <span>}</span> <span>resource</span> <span>"aws_api_gateway_method"</span> <span>"translate"</span> <span>{</span> <span>rest_api_id</span> <span>=</span> <span>aws_api_gateway_rest_api</span><span>.</span><span>this</span><span>.</span><span>id</span> <span>resource_id</span> <span>=</span> <span>aws_api_gateway_resource</span><span>.</span><span>translate</span><span>.</span><span>id</span> <span>http_method</span> <span>=</span> <span>"POST"</span> <span>authorization</span> <span>=</span> <span>"NONE"</span> <span>}</span> <span>resource</span> <span>"aws_api_gateway_integration"</span> <span>"translate"</span> <span>{</span> <span>rest_api_id</span> <span>=</span> <span>aws_api_gateway_rest_api</span><span>.</span><span>this</span><span>.</span><span>id</span> <span>resource_id</span> <span>=</span> <span>aws_api_gateway_resource</span><span>.</span><span>translate</span><span>.</span><span>id</span> <span>http_method</span> <span>=</span> <span>aws_api_gateway_method</span><span>.</span><span>translate</span><span>.</span><span>http_method</span> <span>integration_http_method</span> <span>=</span> <span>"POST"</span> <span>type</span> <span>=</span> <span>"AWS_PROXY"</span> <span>uri</span> <span>=</span> <span>aws_lambda_function</span><span>.</span><span>translate</span><span>.</span><span>invoke_arn</span> <span>}</span> <span>resource</span> <span>"aws_api_gateway_resource"</span> <span>"translate-file"</span> <span>{</span> <span>rest_api_id</span> <span>=</span> <span>aws_api_gateway_rest_api</span><span>.</span><span>this</span><span>.</span><span>id</span> <span>parent_id</span> <span>=</span> <span>aws_api_gateway_resource</span><span>.</span><span>translate</span><span>.</span><span>id</span> <span>path_part</span> <span>=</span> <span>"file"</span> <span>}</span> <span>resource</span> <span>"aws_api_gateway_method"</span> <span>"translate-file"</span> <span>{</span> <span>rest_api_id</span> <span>=</span> <span>aws_api_gateway_rest_api</span><span>.</span><span>this</span><span>.</span><span>id</span> <span>resource_id</span> <span>=</span> <span>aws_api_gateway_resource</span><span>.</span><span>translate-file</span><span>.</span><span>id</span> <span>http_method</span> <span>=</span> <span>"POST"</span> <span>authorization</span> <span>=</span> <span>"NONE"</span> <span>}</span> <span>resource</span> <span>"aws_api_gateway_integration"</span> <span>"translate-file"</span> <span>{</span> <span>rest_api_id</span> <span>=</span> <span>aws_api_gateway_rest_api</span><span>.</span><span>this</span><span>.</span><span>id</span> <span>resource_id</span> <span>=</span> <span>aws_api_gateway_resource</span><span>.</span><span>translate-file</span><span>.</span><span>id</span> <span>http_method</span> <span>=</span> <span>aws_api_gateway_method</span><span>.</span><span>translate-file</span><span>.</span><span>http_method</span> <span>content_handling</span> <span>=</span> <span>"CONVERT_TO_TEXT"</span> <span>integration_http_method</span> <span>=</span> <span>"POST"</span> <span>type</span> <span>=</span> <span>"AWS_PROXY"</span> <span>uri</span> <span>=</span> <span>aws_lambda_function</span><span>.</span><span>translate-file</span><span>.</span><span>invoke_arn</span> <span>}</span> <span>resource</span> <span>"aws_api_gateway_deployment"</span> <span>"this"</span> <span>{</span> <span>rest_api_id</span> <span>=</span> <span>aws_api_gateway_rest_api</span><span>.</span><span>this</span><span>.</span><span>id</span> <span>triggers</span> <span>=</span> <span>{</span> <span>redeployment</span> <span>=</span> <span>sha1</span><span>(</span><span>jsonencode</span><span>([</span> <span>aws_api_gateway_rest_api</span><span>.</span><span>this</span><span>,</span> <span>aws_api_gateway_resource</span><span>.</span><span>translate</span><span>,</span> <span>aws_api_gateway_method</span><span>.</span><span>translate</span><span>,</span> <span>aws_api_gateway_integration</span><span>.</span><span>translate</span><span>,</span> <span>aws_api_gateway_resource</span><span>.</span><span>translate-file</span><span>,</span> <span>aws_api_gateway_method</span><span>.</span><span>translate-file</span><span>,</span> <span>aws_api_gateway_integration</span><span>.</span><span>translate-file</span><span>,</span> <span>]))</span> <span>}</span> <span>lifecycle</span> <span>{</span> <span>create_before_destroy</span> <span>=</span> <span>true</span> <span>}</span> <span>}</span> <span>resource</span> <span>"aws_api_gateway_stage"</span> <span>"this"</span> <span>{</span> <span>deployment_id</span> <span>=</span> <span>aws_api_gateway_deployment</span><span>.</span><span>this</span><span>.</span><span>id</span> <span>rest_api_id</span> <span>=</span> <span>aws_api_gateway_rest_api</span><span>.</span><span>this</span><span>.</span><span>id</span> <span>stage_name</span> <span>=</span> <span>"prod"</span> <span>tags</span> <span>=</span> <span>local</span><span>.</span><span>tags</span> <span>}</span> <span>resource</span> <span>"aws_lambda_permission"</span> <span>"translate"</span> <span>{</span> <span>action</span> <span>=</span> <span>"lambda:InvokeFunction"</span> <span>function_name</span> <span>=</span> <span>aws_lambda_function</span><span>.</span><span>translate</span><span>.</span><span>function_name</span> <span>principal</span> <span>=</span> <span>"apigateway.amazonaws.com"</span> <span>source_arn</span> <span>=</span> <span>"arn:aws:execute-api:${data.aws_region.this.name}:${data.aws_caller_identity.this.account_id}:${aws_api_gateway_rest_api.this.id}/*/${aws_api_gateway_method.translate.http_method}${aws_api_gateway_resource.translate.path}"</span> <span>}</span> <span>resource</span> <span>"aws_lambda_permission"</span> <span>"translate-file"</span> <span>{</span> <span>action</span> <span>=</span> <span>"lambda:InvokeFunction"</span> <span>function_name</span> <span>=</span> <span>aws_lambda_function</span><span>.</span><span>translate-file</span><span>.</span><span>function_name</span> <span>principal</span> <span>=</span> <span>"apigateway.amazonaws.com"</span> <span>source_arn</span> <span>=</span> <span>"arn:aws:execute-api:${data.aws_region.this.name}:${data.aws_caller_identity.this.account_id}:${aws_api_gateway_rest_api.this.id}/*/${aws_api_gateway_method.translate-file.http_method}${aws_api_gateway_resource.translate-file.path}"</span> <span>}</span>data "aws_caller_identity" "this" {} data "aws_region" "this" {} resource "aws_api_gateway_rest_api" "this" { name = "translate-app-api" description = "The translate app API" binary_media_types = ["multipart/form-data"] tags = local.tags } resource "aws_api_gateway_resource" "translate" { rest_api_id = aws_api_gateway_rest_api.this.id parent_id = aws_api_gateway_rest_api.this.root_resource_id path_part = "translate" } resource "aws_api_gateway_method" "translate" { rest_api_id = aws_api_gateway_rest_api.this.id resource_id = aws_api_gateway_resource.translate.id http_method = "POST" authorization = "NONE" } resource "aws_api_gateway_integration" "translate" { rest_api_id = aws_api_gateway_rest_api.this.id resource_id = aws_api_gateway_resource.translate.id http_method = aws_api_gateway_method.translate.http_method integration_http_method = "POST" type = "AWS_PROXY" uri = aws_lambda_function.translate.invoke_arn } resource "aws_api_gateway_resource" "translate-file" { rest_api_id = aws_api_gateway_rest_api.this.id parent_id = aws_api_gateway_resource.translate.id path_part = "file" } resource "aws_api_gateway_method" "translate-file" { rest_api_id = aws_api_gateway_rest_api.this.id resource_id = aws_api_gateway_resource.translate-file.id http_method = "POST" authorization = "NONE" } resource "aws_api_gateway_integration" "translate-file" { rest_api_id = aws_api_gateway_rest_api.this.id resource_id = aws_api_gateway_resource.translate-file.id http_method = aws_api_gateway_method.translate-file.http_method content_handling = "CONVERT_TO_TEXT" integration_http_method = "POST" type = "AWS_PROXY" uri = aws_lambda_function.translate-file.invoke_arn } resource "aws_api_gateway_deployment" "this" { rest_api_id = aws_api_gateway_rest_api.this.id triggers = { redeployment = sha1(jsonencode([ aws_api_gateway_rest_api.this, aws_api_gateway_resource.translate, aws_api_gateway_method.translate, aws_api_gateway_integration.translate, aws_api_gateway_resource.translate-file, aws_api_gateway_method.translate-file, aws_api_gateway_integration.translate-file, ])) } lifecycle { create_before_destroy = true } } resource "aws_api_gateway_stage" "this" { deployment_id = aws_api_gateway_deployment.this.id rest_api_id = aws_api_gateway_rest_api.this.id stage_name = "prod" tags = local.tags } resource "aws_lambda_permission" "translate" { action = "lambda:InvokeFunction" function_name = aws_lambda_function.translate.function_name principal = "apigateway.amazonaws.com" source_arn = "arn:aws:execute-api:${data.aws_region.this.name}:${data.aws_caller_identity.this.account_id}:${aws_api_gateway_rest_api.this.id}/*/${aws_api_gateway_method.translate.http_method}${aws_api_gateway_resource.translate.path}" } resource "aws_lambda_permission" "translate-file" { action = "lambda:InvokeFunction" function_name = aws_lambda_function.translate-file.function_name principal = "apigateway.amazonaws.com" source_arn = "arn:aws:execute-api:${data.aws_region.this.name}:${data.aws_caller_identity.this.account_id}:${aws_api_gateway_rest_api.this.id}/*/${aws_api_gateway_method.translate-file.http_method}${aws_api_gateway_resource.translate-file.path}" }
Enter fullscreen mode Exit fullscreen mode
We’ll also define a few outputs.
<span>#infrastructure/outputs.tf</span><span>output</span> <span>api_url</span> <span>{</span><span>value</span> <span>=</span> <span>aws_api_gateway_stage</span><span>.</span><span>this</span><span>.</span><span>invoke_url</span><span>}</span><span>#infrastructure/outputs.tf</span> <span>output</span> <span>api_url</span> <span>{</span> <span>value</span> <span>=</span> <span>aws_api_gateway_stage</span><span>.</span><span>this</span><span>.</span><span>invoke_url</span> <span>}</span>#infrastructure/outputs.tf output api_url { value = aws_api_gateway_stage.this.invoke_url }
Enter fullscreen mode Exit fullscreen mode
To provision the infrastructure, we first configure the AWS CLI with our credentials:
aws configureaws configureaws configure
Enter fullscreen mode Exit fullscreen mode
Next, we initialize Terraform and apply the configuration:
terraform initterraform applyterraform init terraform applyterraform init terraform apply
Enter fullscreen mode Exit fullscreen mode
After successful provisioning, we can access the API via the output url.
To wrap up this series, we’ve walked through writing python code for lambda functions and provisioning a complete infrastructure using Terraform and AWS.
If you’ve followed along, you should now have an API that can translate text and files running on AWS. But this is just the beginning! There’s always more to explore—whether it’s optimizing performance, integrating monitoring tools, or adding a CI/CD pipeline.
I’d love to hear your thoughts! Drop a comment below if you have questions, insights, or ideas for future topics. Thanks for reading, and happy coding!
暂无评论内容