Project Translate: The Translate API (Part 4)

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 configure
aws configure
aws configure

Enter fullscreen mode Exit fullscreen mode

Next, we initialize Terraform and apply the configuration:

terraform init
terraform apply
terraform init
terraform apply
terraform 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!

原文链接:Project Translate: The Translate API (Part 4)

© 版权声明
THE END
喜欢就支持一下吧
点赞13 分享
Not afraid of people blocking, I'm afraid their surrender.
不怕万人阻挡,只怕自己投降
评论 抢沙发

请登录后发表评论

    暂无评论内容