본문 바로가기
카테고리 없음

api-gateway-lambda-dynamodb 테라폼 스크립트

by 16비트 2023. 6. 30.

초기 코드

vi main.tf

provider "aws" {
  profile = "default"
  region  = "ap-northeast-2"
}
resource "aws_dynamodb_table" "product_table" {
  name         = "PRODUCT"
  billing_mode = "PAY_PER_REQUEST"
  hash_key     = "product_id"
  attribute {
    name = "product_id"
    type = "S"
  }
  attribute {
    name = "category"
    type = "S"
  }
  attribute {
    name = "product_rating"
    type = "N"
  }
  global_secondary_index {
    name            = "ProductCategoryRatingIndex"
    hash_key        = "category"
    range_key       = "product_rating"
    projection_type = "ALL"
  }
}


resource "aws_api_gateway_rest_api" "product_apigw" {
  name        = "product_apigw"
  description = "Product API Gateway"
  endpoint_configuration {
    types = ["REGIONAL"]
  }
}
resource "aws_api_gateway_resource" "product" {
  rest_api_id = aws_api_gateway_rest_api.product_apigw.id
  parent_id   = aws_api_gateway_rest_api.product_apigw.root_resource_id
  path_part   = "product"
}
resource "aws_api_gateway_method" "createproduct" {
  rest_api_id   = aws_api_gateway_rest_api.product_apigw.id
  resource_id   = aws_api_gateway_resource.product.id
  http_method   = "POST"
  authorization = "NONE"
}




resource "aws_iam_role" "ProductLambdaRole" {
  name               = "ProductLambdaRole"
  assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": "sts:AssumeRole",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      },
      "Effect": "Allow",
      "Sid": ""
    }
  ]
}
EOF
}
data "template_file" "productlambdapolicy" {
  template = "${file("${path.module}/policy.json")}"
}
resource "aws_iam_policy" "ProductLambdaPolicy" {
  name        = "ProductLambdaPolicy"
  path        = "/"
  description = "IAM policy for Product lambda functions"
  policy      = data.template_file.productlambdapolicy.rendered
}
resource "aws_iam_role_policy_attachment" "ProductLambdaRolePolicy" {
  role       = aws_iam_role.ProductLambdaRole.name
  policy_arn = aws_iam_policy.ProductLambdaPolicy.arn
}




resource "aws_lambda_function" "CreateProductHandler" {
  function_name = "CreateProductHandler"
  filename = "../lambda/product_lambda.zip"
  handler = "createproduct.lambda_handler"
  runtime = "python3.8"
  environment {
    variables = {
      REGION        = "ap-northeast-2"
      PRODUCT_TABLE = aws_dynamodb_table.product_table.name
   }
  }
  source_code_hash = filebase64sha256("../lambda/product_lambda.zip")
  role = aws_iam_role.ProductLambdaRole.arn
  timeout     = "5"
  memory_size = "128"
}




resource "aws_api_gateway_integration" "createproduct-lambda" {
rest_api_id = aws_api_gateway_rest_api.product_apigw.id
  resource_id = aws_api_gateway_method.createproduct.resource_id
  http_method = aws_api_gateway_method.createproduct.http_method
  integration_http_method = "POST"
  type                    = "AWS_PROXY"
uri = aws_lambda_function.CreateProductHandler.invoke_arn
}
resource "aws_lambda_permission" "apigw-CreateProductHandler" {
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.CreateProductHandler.function_name
  principal     = "apigateway.amazonaws.com"
  source_arn = "${aws_api_gateway_rest_api.product_apigw.execution_arn}/*/POST/product"
}
resource "aws_api_gateway_deployment" "productapistageprod" {
  depends_on = [
    aws_api_gateway_integration.createproduct-lambda
  ]
  rest_api_id = aws_api_gateway_rest_api.product_apigw.id
  stage_name  = "prod"
}

 

 

vi policy.json

{
    "Version": "2012-10-17",
    "Statement": [
      {
          "Effect": "Allow",
          "Action": [
              "logs:CreateLogStream",
              "logs:CreateLogGroup",
              "logs:PutLogEvents"
          ],
          "Resource": "arn:aws:logs:*:*:*"
      },
      {
          "Effect": "Allow",
          "Action": [
              "dynamodb:PutItem"
          ],
          "Resource": "arn:aws:dynamodb:*:*:table/PRODUCT"
      }
    ]
}
~

 

 

vi createproduct.py

zip ./product_lambda.zip *.py

import logging
import boto3
import json
import os
session = boto3.Session(region_name=os.environ['REGION'])
dynamodb_client = session.client('dynamodb')
def lambda_handler(event, context):
    try:
        print("event ->" + str(event))
        payload = json.loads(event["body"])
        print("payload ->" + str(payload))
        dynamodb_response = dynamodb_client.put_item(
            TableName=os.environ["PRODUCT_TABLE"],
            Item={
                "product_id": {
                    "S": payload["productId"]
                },
                "category": {
                    "S": payload["category"]
                },
                "product_rating": {
                    "N": str(payload["productRating"])
                },
                "product_name": {
                    "S": payload["productName"]
                },
                "product_price": {
                    "N": str(payload["productPrice"])
                },
                "product_description": {
                    "S": payload["productDescription"]
                }
            }
        )
        print(dynamodb_response)
        return {
            'statusCode': 201,
           'body': '{"status":"Product created"}'
        }
    except Exception as e:
        logging.error(e)
        return {
            'statusCode': 500,
           'body': '{"status":"Server error"}'
        }

 

 

curl -X POST "https://zdqpdl1wmd.execute-api.ap-northeast-2.amazonaws.com/prod/product" -H 'Content-Type: application/json' -d'
{
  "productId": "1",
  "category": "Category",
  "productName": "<<Product Name>>",
  "productPrice": 12,
  "productDescription": "Product description",
  "productRating": 2
}
'

 

 

 

 

 

 

 

최종 코드

vi main.tf

route53 zone_id 수정 필요

provider "aws" {
  profile = "default"
  region  = "ap-northeast-2"
}
resource "aws_dynamodb_table" "product_table" {
  name         = "UserTable"
  billing_mode = "PAY_PER_REQUEST"
  hash_key     = "user_id"
  attribute {
    name = "user_id"
    type = "S"
  }
#  attribute {
#    name = "category"
#    type = "S"
#  }
#  attribute {
#    name = "product_rating"
#    type = "N"
#  }
#  global_secondary_index {
#    name            = "ProductCategoryRatingIndex"
#    hash_key        = "category"
#    range_key       = "product_rating"
#    projection_type = "ALL"
#  }
}


resource "aws_api_gateway_rest_api" "product_apigw" {
  name        = "product_apigw"
  description = "Product API Gateway"
  endpoint_configuration {
    types = ["REGIONAL"]
  }
}
#resource "aws_api_gateway_resource" "product" {
#  rest_api_id = aws_api_gateway_rest_api.product_apigw.id
#  parent_id   = aws_api_gateway_rest_api.product_apigw.root_resource_id
#  path_part   = "product"
#}
resource "aws_api_gateway_method" "createproduct" {
  rest_api_id   = aws_api_gateway_rest_api.product_apigw.id
  resource_id   = aws_api_gateway_rest_api.product_apigw.root_resource_id
  http_method   = "POST"
  authorization = "NONE"
}




resource "aws_api_gateway_method" "test_options" {
  rest_api_id             = aws_api_gateway_rest_api.product_apigw.id
  resource_id             = aws_api_gateway_rest_api.product_apigw.root_resource_id
  http_method   = "OPTIONS"
  authorization = "NONE"
}


resource "aws_api_gateway_method_response" "test_options_200" {
  rest_api_id             = aws_api_gateway_rest_api.product_apigw.id
  resource_id             = aws_api_gateway_rest_api.product_apigw.root_resource_id
  http_method = aws_api_gateway_method.test_options.http_method
  status_code = "200"

  response_models = {
    "application/json" = "Empty"
  }

  response_parameters = {
    "method.response.header.Access-Control-Allow-Headers" = true,
    "method.response.header.Access-Control-Allow-Methods" = true,
    "method.response.header.Access-Control-Allow-Origin"  = true
  }
}

resource "aws_api_gateway_integration_response" "test_options" {
  rest_api_id             = aws_api_gateway_rest_api.product_apigw.id
  resource_id             = aws_api_gateway_rest_api.product_apigw.root_resource_id

  http_method = aws_api_gateway_method.test_options.http_method
  status_code = aws_api_gateway_method_response.test_options_200.status_code

  response_parameters = {
    "method.response.header.Access-Control-Allow-Headers" = "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'",
    "method.response.header.Access-Control-Allow-Methods" = "'GET,OPTIONS,POST,PUT'",
    "method.response.header.Access-Control-Allow-Origin"  = "'*'"
  }
}

resource "aws_api_gateway_integration" "test_options_mock" {
  rest_api_id             = aws_api_gateway_rest_api.product_apigw.id
  resource_id             = aws_api_gateway_rest_api.product_apigw.root_resource_id
  http_method = aws_api_gateway_method.test_options.http_method
  type        = "MOCK"

  request_templates = {
    "application/json" = <<EOF
{
  "statusCode": 200
}
EOF
  }
}







#resource "aws_api_gateway_integration" "createproduct-lambda" {
#rest_api_id = aws_api_gateway_rest_api.product_apigw.id
#  resource_id = aws_api_gateway_method.createproduct.resource_id
#  http_method = aws_api_gateway_method.createproduct.http_method
#  integration_http_method = "POST"
#  type                    = "AWS"
#uri = aws_lambda_function.CreateProductHandler.invoke_arn
#}



resource "aws_api_gateway_integration" "createproduct-lambda" {
  rest_api_id             = aws_api_gateway_rest_api.product_apigw.id
  resource_id             = aws_api_gateway_rest_api.product_apigw.root_resource_id
  http_method             = aws_api_gateway_method.createproduct.http_method
  integration_http_method = "POST" #  Lambda function can only be invoked via POST
  type                    = "AWS"
  uri                     = aws_lambda_function.CreateProductHandler.invoke_arn
  content_handling        = "CONVERT_TO_TEXT"
}

resource "aws_api_gateway_method_response" "lambda" {
  rest_api_id = aws_api_gateway_rest_api.product_apigw.id
  resource_id = aws_api_gateway_rest_api.product_apigw.root_resource_id
  http_method = aws_api_gateway_method.createproduct.http_method
  status_code = "200"

  response_models = {
    "application/json" = "Empty"
  }
}

resource "aws_api_gateway_integration_response" "lambda" {
  rest_api_id = aws_api_gateway_rest_api.product_apigw.id
  resource_id = aws_api_gateway_rest_api.product_apigw.root_resource_id
  http_method = aws_api_gateway_method.createproduct.http_method
  status_code = aws_api_gateway_method_response.lambda.status_code
  depends_on  = [aws_api_gateway_integration.createproduct-lambda]
}




resource "aws_api_gateway_deployment" "lambda" {
  rest_api_id = aws_api_gateway_rest_api.product_apigw.id

  triggers = {
    redeployment = sha1(jsonencode([
      aws_api_gateway_method.createproduct.id,
      aws_api_gateway_integration.createproduct-lambda.id,
      aws_api_gateway_method_response.lambda,
      aws_api_gateway_integration_response.lambda,
    ]))
  }

  lifecycle {
    create_before_destroy = true
  }
}

resource "aws_api_gateway_stage" "lambda" {
  deployment_id = aws_api_gateway_deployment.lambda.id
  rest_api_id   = aws_api_gateway_rest_api.product_apigw.id
  stage_name    = "run" # Any Name you wish
}



output "deployment_invoke_url" {
  description = "Deployment invoke url"
  value       = aws_api_gateway_stage.lambda.invoke_url
}








resource "aws_iam_role" "ProductLambdaRole" {
  name               = "ProductLambdaRole"
  assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": "sts:AssumeRole",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      },
      "Effect": "Allow",
      "Sid": ""
    }
  ]
}
EOF
}
data "template_file" "productlambdapolicy" {
  template = "${file("${path.module}/policy.json")}"
}
resource "aws_iam_policy" "ProductLambdaPolicy" {
  name        = "ProductLambdaPolicy"
  path        = "/"
  description = "IAM policy for Product lambda functions"
  policy      = data.template_file.productlambdapolicy.rendered
}
resource "aws_iam_role_policy_attachment" "ProductLambdaRolePolicy" {
  role       = aws_iam_role.ProductLambdaRole.name
  policy_arn = aws_iam_policy.ProductLambdaPolicy.arn
}




resource "aws_lambda_function" "CreateProductHandler" {
  function_name = "CreateProductHandler"
  filename = "./product_lambda.zip"
  handler = "createproduct.lambda_handler"
  runtime = "python3.8"
  environment {
    variables = {
      REGION        = "ap-northeast-2"
      PRODUCT_TABLE = aws_dynamodb_table.product_table.name
   }
  }
  source_code_hash = filebase64sha256("./product_lambda.zip")
  role = aws_iam_role.ProductLambdaRole.arn
  timeout     = "5"
  memory_size = "128"
}




#resource "aws_api_gateway_integration" "createproduct-lambda" {
#rest_api_id = aws_api_gateway_rest_api.product_apigw.id
#  resource_id = aws_api_gateway_method.createproduct.resource_id
#  http_method = aws_api_gateway_method.createproduct.http_method
#  integration_http_method = "POST"
#  type                    = "AWS"
#uri = aws_lambda_function.CreateProductHandler.invoke_arn
#}



resource "aws_lambda_permission" "apigw-CreateProductHandler" {
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.CreateProductHandler.function_name
  principal     = "apigateway.amazonaws.com"
  source_arn = "${aws_api_gateway_rest_api.product_apigw.execution_arn}/*"
}




#resource "aws_api_gateway_deployment" "productapistageprod" {
#  depends_on = [
#    aws_api_gateway_integration.createproduct-lambda
#  ]
#  rest_api_id = aws_api_gateway_rest_api.product_apigw.id
#  stage_name  = "prod"
#}

 

vi policy.json

{
    "Version": "2012-10-17",
    "Statement": [
      {
          "Effect": "Allow",
          "Action": [
              "logs:CreateLogStream",
              "logs:CreateLogGroup",
              "logs:PutLogEvents"
          ],
          "Resource": "arn:aws:logs:*:*:*"
      },
      {
          "Effect": "Allow",
          "Action": [
              "dynamodb:*"
          ],
          "Resource": [ 
              "*"
          ]
      }
    ]
}
~

 

 

 

zip ./product_lambda.zip *.py

# import the json utility package since we will be working with a JSON object
import json
# import the AWS SDK (for Python the package name is boto3)
import boto3
# import two packages to help us with dates and date formatting
from time import gmtime, strftime
import uuid


PARTITION_KEY = 'user_id'


    
def lambda_handler(event, context):
    # AWS SDK의 DynamoDB 클라이언트를 생성합니다.
    user_id=uuid.uuid4()
    now = strftime("%a, %d %b %Y %H:%M:%S +0000", gmtime())
    dynamodb = boto3.client('dynamodb')
#    user_id = event['user_id']
    name = event['Name']
    phone = event['phone']
    email = event['email']
    try:
        # PutItem 작업에 필요한 매개변수를 구성합니다.
        params = {
            'TableName': 'UserTable',  # 테이블 이름을 적절히 변경하세요.
            'Item': {
                'user_id': {'S': str(user_id)},  # 테이블의 키 필드와 값을 적절히 변경하세요.
                'Name': {'S': name},
                'Phone': {'S': phone},
                'Email': {'S': email},
                'Time': {'S': now},
                # 추가적인 속성 및 값을 필요에 따라 정의할 수 있습니다.
            }
        }

        # PutItem 작업을 수행합니다.
        response = dynamodb.put_item(**params)
        print('PutItem 성공:', response)

        return {
            'statusCode': 200,
            'body': 'PutItem 작업이 성공적으로 완료되었습니다.'
        }
    except Exception as e:
        print('PutItem 실패:', e)
        return {
            'statusCode': 500,
            'body': 'PutItem 작업이 실패했습니다.'
        }

 

S3 

iam identifier ID에 맞게 수정 필요

provider "aws" {
  alias = "virginia"
  region = "us-east-1"
  default_tags {
    tags = {
      project = "serverless-demo",
      type    = "web",
      iac     = "terraform"
    }
  }
}


######### S3 ##########

resource "aws_s3_bucket" "website_bucket" {
  bucket = "my-unique-bucket-name-peach1102"
}

#resource "aws_s3_object" "website_bucket" {
#  bucket       = aws_s3_bucket.website_bucket.id
#  key          = "index.html"
#  source       = "index.html"
#  content_type = "text/html"
#}



### 버킷 정책




resource "aws_s3_bucket_ownership_controls" "example" {
  bucket = aws_s3_bucket.website_bucket.id
  rule {
    object_ownership = "BucketOwnerPreferred"
  }
}

resource "aws_s3_bucket_public_access_block" "website_bucket" {
  bucket = aws_s3_bucket.website_bucket.id
  block_public_acls       = false
  block_public_policy     = false
  ignore_public_acls      = false
  restrict_public_buckets = false
}

resource "aws_s3_bucket_acl" "example" {
  depends_on = [
    aws_s3_bucket_ownership_controls.example,
    aws_s3_bucket_public_access_block.website_bucket,
  ]

  bucket = aws_s3_bucket.website_bucket.id
  acl    = "private"
}




resource "aws_s3_bucket_policy" "allow_access_from_another_account" {
  bucket = aws_s3_bucket.website_bucket.id
  policy = data.aws_iam_policy_document.allow_access_from_another_account.json
}

data "aws_iam_policy_document" "allow_access_from_another_account" {
  statement {
    principals {
      type        = "AWS"
      identifiers = ["606203148720"]
    }

    actions = [
      "s3:GetObject",
      "s3:ListBucket",
    ]

    resources = [
      aws_s3_bucket.website_bucket.arn,
      "${aws_s3_bucket.website_bucket.arn}/*",
    ]
  }
}

resource "aws_s3_bucket_website_configuration" "website_bucket" {
  bucket = aws_s3_bucket.website_bucket.id
  index_document {
    suffix = "index.html"
  }
}

 

Cloudfront

######## cloudfront######

resource "aws_cloudfront_origin_access_identity" "example" {
  comment = "Some comment"
}


resource "aws_cloudfront_distribution" "cdn_static_site" {
  enabled             = true
  is_ipv6_enabled     = true
  default_root_object = "index.html"
  comment             = "my cloudfront in front of the s3 bucket"

  origin {
    domain_name              = aws_s3_bucket.website_bucket.bucket_regional_domain_name
    origin_id                = "my-s3-origin"
    origin_access_control_id = aws_cloudfront_origin_access_control.default.id
  }

  default_cache_behavior {
    min_ttl                = 0
    default_ttl            = 0
    max_ttl                = 0
    viewer_protocol_policy = "redirect-to-https"

    allowed_methods  = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
    cached_methods   = ["GET", "HEAD"]
    target_origin_id = "my-s3-origin"

    forwarded_values {
      query_string = false
      cookies {
        forward = "none"
      }
    }
  }

  restrictions {
    geo_restriction {
      locations        = []
      restriction_type = "none"
    }
  }

  aliases = ["blog4.changhoon.shop"]

  viewer_certificate {
    cloudfront_default_certificate = true
    acm_certificate_arn      = aws_acm_certificate.acm_certificate.arn
    ssl_support_method       = "sni-only"
    minimum_protocol_version = "TLSv1.2_2021"
  }
}

resource "aws_cloudfront_origin_access_control" "default" {
  name                              = "cloudfront OAC"
  description                       = "description of OAC"
  origin_access_control_origin_type = "s3"
  signing_behavior                  = "always"
  signing_protocol                  = "sigv4"
}

output "cloudfront_url" {
  value = aws_cloudfront_distribution.cdn_static_site.domain_name
}

 

ACM+Route53

Z0209195I0PT3MTRG9LY   zone_id 변경 필요

######### ACM, Route53




# request public certificates from the amazon certificate manager.
resource "aws_acm_certificate" "acm_certificate" {
  domain_name               = "*.changhoon.shop"
#  subject_alternative_names = ["changhoon.shop"]
  validation_method         = "DNS"
  provider = aws.virginia
  lifecycle {
    create_before_destroy = true
  }
}

# get details about a route 53 hosted zone
data "aws_route53_zone" "route53_zone" {
  zone_id      = "Z065245838U84TYWCZQFH"
#  name         = "changhoon.shop"
  private_zone = false
}

# create a record set in route 53 for domain validatation
resource "aws_route53_record" "route53_record" {
  for_each = {
    for dvo in aws_acm_certificate.acm_certificate.domain_validation_options : dvo.domain_name => {
      name   = dvo.resource_record_name
      record = dvo.resource_record_value
      type   = dvo.resource_record_type
    }
  }

  allow_overwrite = true
  name            = each.value.name
  records         = [each.value.record]
  ttl             = 60
  type            = each.value.type
  zone_id         = data.aws_route53_zone.route53_zone.zone_id
}


# validate acm certificates
resource "aws_acm_certificate_validation" "acm_certificate_validation" {
  provider = aws.virginia
  certificate_arn         = aws_acm_certificate.acm_certificate.arn
  validation_record_fqdns = [for record in aws_route53_record.route53_record : record.fqdn]
}

resource "aws_route53_record" "blog" {
  zone_id = data.aws_route53_zone.route53_zone.zone_id
  name    = "blog4.changhoon.shop"
  type    = "A"

  alias {
    name                   = aws_cloudfront_distribution.cdn_static_site.domain_name
    zone_id                = aws_cloudfront_distribution.cdn_static_site.hosted_zone_id
    evaluate_target_health = false
  }
}

댓글