mirror of
https://github.com/Sea-Haven-Industries/apm-wo-analysis.git
synced 2026-09-30 08:53:15 +00:00
228 lines
6.8 KiB
Terraform
228 lines
6.8 KiB
Terraform
|
|
# Terraform owns the function skeletons (role, runtime, memory, environment).
|
||
|
|
# Code is owned by .github/workflows/deploy.yaml, which uploads
|
||
|
|
# functions/<name>/<sha>.zip and calls update-function-code. The lifecycle
|
||
|
|
# block is the seam: an app deploy is not drift, and a Terraform apply never
|
||
|
|
# rolls the code back to the bootstrap stub.
|
||
|
|
|
||
|
|
data "aws_iam_policy_document" "lambda_assume" {
|
||
|
|
statement {
|
||
|
|
effect = "Allow"
|
||
|
|
actions = ["sts:AssumeRole"]
|
||
|
|
|
||
|
|
principals {
|
||
|
|
type = "Service"
|
||
|
|
identifiers = ["lambda.amazonaws.com"]
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
locals {
|
||
|
|
lambda_identity = {
|
||
|
|
classifier = [
|
||
|
|
{
|
||
|
|
sid = "ReadRaw"
|
||
|
|
actions = ["s3:GetObject"]
|
||
|
|
resources = ["${aws_s3_bucket.exports.arn}/raw/*"]
|
||
|
|
},
|
||
|
|
{
|
||
|
|
sid = "WriteAnalytics"
|
||
|
|
actions = ["s3:GetObject", "s3:PutObject", "s3:DeleteObject", "s3:AbortMultipartUpload"]
|
||
|
|
resources = ["${aws_s3_bucket.exports.arn}/analytics/*"]
|
||
|
|
},
|
||
|
|
{
|
||
|
|
sid = "WriteMeta"
|
||
|
|
actions = ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"]
|
||
|
|
resources = ["${aws_s3_bucket.exports.arn}/meta/*"]
|
||
|
|
},
|
||
|
|
{
|
||
|
|
sid = "ListExports"
|
||
|
|
actions = ["s3:ListBucket"]
|
||
|
|
resources = [aws_s3_bucket.exports.arn]
|
||
|
|
},
|
||
|
|
{
|
||
|
|
sid = "AnthropicSecret"
|
||
|
|
actions = ["secretsmanager:GetSecretValue"]
|
||
|
|
resources = ["arn:aws:secretsmanager:${var.aws_region}:${local.account_id}:secret:apm-wo-analysis/anthropic-api-key*"]
|
||
|
|
},
|
||
|
|
{
|
||
|
|
sid = "InvokeSlackPost"
|
||
|
|
actions = ["lambda:InvokeFunction"]
|
||
|
|
resources = ["arn:aws:lambda:${var.aws_region}:${local.account_id}:function:apm-wo-analysis-slack-post"]
|
||
|
|
},
|
||
|
|
{
|
||
|
|
sid = "DlqSend"
|
||
|
|
actions = ["sqs:SendMessage"]
|
||
|
|
resources = [aws_sqs_queue.classifier_dlq.arn]
|
||
|
|
},
|
||
|
|
]
|
||
|
|
slack_post = [
|
||
|
|
{
|
||
|
|
sid = "ReadMeta"
|
||
|
|
actions = ["s3:GetObject"]
|
||
|
|
resources = ["${aws_s3_bucket.exports.arn}/meta/*"]
|
||
|
|
},
|
||
|
|
{
|
||
|
|
sid = "ListExports"
|
||
|
|
actions = ["s3:ListBucket"]
|
||
|
|
resources = [aws_s3_bucket.exports.arn]
|
||
|
|
},
|
||
|
|
{
|
||
|
|
sid = "SlackSecret"
|
||
|
|
actions = ["secretsmanager:GetSecretValue"]
|
||
|
|
resources = ["arn:aws:secretsmanager:${var.aws_region}:${local.account_id}:secret:apm-wo-analysis/slack-credentials*"]
|
||
|
|
},
|
||
|
|
{
|
||
|
|
sid = "DashboardParam"
|
||
|
|
actions = ["ssm:GetParameter"]
|
||
|
|
resources = ["arn:aws:ssm:${var.aws_region}:${local.account_id}:parameter${local.grafana_url_param}"]
|
||
|
|
},
|
||
|
|
]
|
||
|
|
slack_interactions = [
|
||
|
|
{
|
||
|
|
sid = "ReadMeta"
|
||
|
|
actions = ["s3:GetObject"]
|
||
|
|
resources = ["${aws_s3_bucket.exports.arn}/meta/*"]
|
||
|
|
},
|
||
|
|
{
|
||
|
|
sid = "ListExports"
|
||
|
|
actions = ["s3:ListBucket"]
|
||
|
|
resources = [aws_s3_bucket.exports.arn]
|
||
|
|
},
|
||
|
|
{
|
||
|
|
sid = "SlackSecret"
|
||
|
|
actions = ["secretsmanager:GetSecretValue"]
|
||
|
|
resources = ["arn:aws:secretsmanager:${var.aws_region}:${local.account_id}:secret:apm-wo-analysis/slack-credentials*"]
|
||
|
|
},
|
||
|
|
{
|
||
|
|
sid = "DashboardParam"
|
||
|
|
actions = ["ssm:GetParameter"]
|
||
|
|
resources = ["arn:aws:ssm:${var.aws_region}:${local.account_id}:parameter${local.grafana_url_param}"]
|
||
|
|
},
|
||
|
|
]
|
||
|
|
}
|
||
|
|
|
||
|
|
lambda_env = {
|
||
|
|
classifier = {
|
||
|
|
APM_HAIKU_FALLBACK = "on"
|
||
|
|
SLACK_POST_FUNCTION_NAME = local.functions.slack_post.function_name
|
||
|
|
}
|
||
|
|
slack_post = {
|
||
|
|
SLACK_SECRET_NAME = "apm-wo-analysis/slack-credentials"
|
||
|
|
DASHBOARD_URL_PARAM = local.grafana_url_param
|
||
|
|
ANALYTICS_BUCKET = aws_s3_bucket.exports.bucket
|
||
|
|
}
|
||
|
|
slack_interactions = {
|
||
|
|
SLACK_SECRET_NAME = "apm-wo-analysis/slack-credentials"
|
||
|
|
DASHBOARD_URL_PARAM = local.grafana_url_param
|
||
|
|
ANALYTICS_BUCKET = aws_s3_bucket.exports.bucket
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
resource "aws_iam_role" "lambda" {
|
||
|
|
for_each = local.functions
|
||
|
|
|
||
|
|
name = each.value.role_name
|
||
|
|
path = "/tf-managed/"
|
||
|
|
description = "Lambda execution role for ${each.value.function_name}"
|
||
|
|
assume_role_policy = data.aws_iam_policy_document.lambda_assume.json
|
||
|
|
permissions_boundary = aws_iam_policy.exec_boundary.arn
|
||
|
|
}
|
||
|
|
|
||
|
|
data "aws_iam_policy_document" "lambda" {
|
||
|
|
for_each = local.functions
|
||
|
|
|
||
|
|
dynamic "statement" {
|
||
|
|
for_each = local.lambda_identity[each.key]
|
||
|
|
|
||
|
|
content {
|
||
|
|
sid = statement.value.sid
|
||
|
|
effect = "Allow"
|
||
|
|
actions = statement.value.actions
|
||
|
|
resources = statement.value.resources
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
resource "aws_iam_role_policy" "lambda" {
|
||
|
|
for_each = local.functions
|
||
|
|
|
||
|
|
name = each.key
|
||
|
|
role = aws_iam_role.lambda[each.key].id
|
||
|
|
policy = data.aws_iam_policy_document.lambda[each.key].json
|
||
|
|
}
|
||
|
|
|
||
|
|
resource "aws_iam_role_policy_attachment" "lambda_basic" {
|
||
|
|
for_each = local.functions
|
||
|
|
|
||
|
|
role = aws_iam_role.lambda[each.key].name
|
||
|
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
|
||
|
|
}
|
||
|
|
|
||
|
|
resource "aws_lambda_function" "this" {
|
||
|
|
for_each = local.functions
|
||
|
|
|
||
|
|
function_name = each.value.function_name
|
||
|
|
role = aws_iam_role.lambda[each.key].arn
|
||
|
|
handler = each.value.handler
|
||
|
|
runtime = "python3.12"
|
||
|
|
architectures = ["arm64"]
|
||
|
|
memory_size = each.value.memory_size
|
||
|
|
timeout = each.value.timeout
|
||
|
|
layers = each.value.layers
|
||
|
|
|
||
|
|
s3_bucket = aws_s3_bucket.artifacts.id
|
||
|
|
s3_key = aws_s3_object.bootstrap_stub.key
|
||
|
|
source_code_hash = data.archive_file.bootstrap_stub.output_base64sha256
|
||
|
|
|
||
|
|
environment {
|
||
|
|
variables = local.lambda_env[each.key]
|
||
|
|
}
|
||
|
|
|
||
|
|
dynamic "dead_letter_config" {
|
||
|
|
for_each = each.key == "classifier" ? [aws_sqs_queue.classifier_dlq.arn] : []
|
||
|
|
|
||
|
|
content {
|
||
|
|
target_arn = dead_letter_config.value
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
lifecycle {
|
||
|
|
ignore_changes = [filename, s3_bucket, s3_key, s3_object_version, source_code_hash]
|
||
|
|
}
|
||
|
|
|
||
|
|
depends_on = [
|
||
|
|
aws_cloudwatch_log_group.lambda,
|
||
|
|
aws_iam_role_policy.lambda,
|
||
|
|
aws_iam_role_policy_attachment.lambda_basic,
|
||
|
|
]
|
||
|
|
}
|
||
|
|
|
||
|
|
resource "aws_lambda_permission" "s3_classifier" {
|
||
|
|
statement_id = "AllowS3InvokeClassifier"
|
||
|
|
action = "lambda:InvokeFunction"
|
||
|
|
function_name = aws_lambda_function.this["classifier"].function_name
|
||
|
|
principal = "s3.amazonaws.com"
|
||
|
|
source_arn = aws_s3_bucket.exports.arn
|
||
|
|
}
|
||
|
|
|
||
|
|
resource "aws_s3_bucket_notification" "raw_exports" {
|
||
|
|
bucket = aws_s3_bucket.exports.id
|
||
|
|
|
||
|
|
lambda_function {
|
||
|
|
lambda_function_arn = aws_lambda_function.this["classifier"].arn
|
||
|
|
events = ["s3:ObjectCreated:*"]
|
||
|
|
filter_prefix = "raw/"
|
||
|
|
filter_suffix = ".xlsx"
|
||
|
|
}
|
||
|
|
|
||
|
|
lambda_function {
|
||
|
|
lambda_function_arn = aws_lambda_function.this["classifier"].arn
|
||
|
|
events = ["s3:ObjectCreated:*"]
|
||
|
|
filter_prefix = "raw/"
|
||
|
|
filter_suffix = ".csv"
|
||
|
|
}
|
||
|
|
|
||
|
|
depends_on = [aws_lambda_permission.s3_classifier]
|
||
|
|
}
|