Решил я заняться развертыванием AWS Lambda Function и API Gateway через Terraform.
Вводные были следующие:
- API Gateway создается на основе OpenAPI-шаблона, который вызывает Lambda Functions. Соответственно, в шаблоне должен быть указан Lambda invocation url (lambda типа как prerequisite);
 - Lambda Function должна запускаться только из API Gateway – для разрешения (permission) в качестве source_arn необходимо указать API Gateway execution arn (API gateway prerequisite).
 
Сначала я создавал lambda function с помощью штатных ресурсов AWS-provider: aws_lambda_function, aws_lambda_permission и archive_file. Проблем с закольцовкой не было: Terraform умело разруливал порядок создания ресурсов.
Но потом лямбда функций стало больше, и я решил использовать принцип DRY (don’t repeat yourself). В Terraform эту роль играют модули.
Я начал использовать публичный модуль для создания лямбд отсюда – и меня догнали Terraform Cycle Error на этапе terraform validate.
Отрицание
Не может такого быть, повторял я и варьировал различные варианты кода.
Неужели придется отказаться от модуля в случае создания таких функций???
Гнев
Пока я изучал модуль, обнаружил в нем входную переменную putin_khuylo, содержащую по умолчанию значение true.
Торг
Внимательное изучение выходных значений модуля подсказало наличие выходного значения function_arn_static, которые должны помочь избежать зацикливания.
НО НЕТ!
Депрессия
Был еще один вариант:
- выполнить команду terraform -grath -show-cycles;
 - полученный текстовый вывод скормить graphviz либо graphviz онлайн.
 
Увы, установить утилиту я не мог, а онлайн-утилита вылетала по памяти.
В теории этот путь поможет в разрезании цикла, показав ресурсы, участвующие в цикле.
Принятие
С горя я решил задать вопрос ChatGPT: как избежать зацикливания в данном случае.
Внезапно, мэтр программирования IaC Terraform посоветовал ресурс aws_lambda_function создавать в модуле, а aws_lambda_permission оставить в корневом файле.
Да-да, это то самое чувство, когда соседский мальчик умнее тебя 🙁
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49  | 
						provider "aws" {   region = "us-east-1" } locals {   list1 = {     lambda1 = {       name = "lambda1"       path = "../lambda1"     }     lambda2 = {       name = "lambda1"       path = "../lambda1"      }    }   list2 = {     lambda1 = aws_apigatewayv2_api.example_api.execution_arn     lambda2 = aws_apigatewayv2_api.example_api.execution_arn   } } module "lambda" {   source   = "../lambda"   for_each = local.list1   name     = each.value.name   ... } data "template_file" "example_api" {   template = "${file("example_api.tpl")}"   vars = {     lambda_invocation_url = module.lambda.lambda_function_invoke_url   } } resource "aws_apigatewayv2_api" "example_api" {   name          = "example_api"   protocol_type = "HTTP"   body          = data.template_file.example_api.rendered } resource "aws_lambda_permission" "example_permission" {   for_each      = local.list2   statement_id  = "AllowAPIGatewayInvoke"   action        = "lambda:InvokeFunction"   function_name = module.lambda.lambda_function_arn   principal     = "apigateway.amazonaws.com"   source_arn    = each.value }  | 
					
| 
					 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44  | 
						provider "aws" {   region = "us-east-1" } resource "aws_lambda_function" "example_lambda" {   filename         = "lambda_function.zip"   function_name    = "example_lambda"   role             = aws_iam_role.lambda_exec.arn   handler          = "lambda_function.handler"   runtime          = "nodejs14.x"   source_code_hash = filebase64sha256("lambda_function.zip")   environment {     variables = {       EXAMPLE_VAR = "example_value"     }   } } resource "aws_iam_role" "lambda_exec" {   name = "lambda_exec_role"   assume_role_policy = jsonencode({     Version = "2012-10-17"     Statement = [       {         Action = "sts:AssumeRole"         Effect = "Allow"         Principal = {           Service = "lambda.amazonaws.com"         }       }     ]   }) } resource "aws_iam_role_policy_attachment" "lambda_exec_policy" {   policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"   role       = aws_iam_role.lambda_exec.name } output "lambda_function_arn" {   value = aws_lambda_function.example_lambda.invoke_arn }  | 
					
Так как функций было несколько, то их входные значения для модуля (имя, путь и т.д.) генерировались в разделе locals. Для использования цикла for_each в ресурсе aws_lambda_permissions потребовалось хранить source_arn в отдельной локальной переменной. УРА, terraform validate пройден.
Правда, на этапе terraform plan выяснилось, что модуль требует для своей работы Python и иногда падает, если в runtime вашей функции указана странная версия Python’а, и в Runner для CI/CD что-то пошло не так 🙂
На этом я решил психануть закопать стюардессу написать свой модуль создания лямбда функции без Питона и Путина.
Цей Lambda модуль може працювати без Пітона, якщо хочеться…