Initial commit

This commit is contained in:
2026-05-14 14:07:04 -03:00
commit e0bc5d784b
34 changed files with 7496 additions and 0 deletions

View File

@@ -0,0 +1,35 @@
config:
aws:region: us-east-1
project_name: labels-valvula-bloco-funcao
lambda-api:
- name: labels-valvula-bloco-funcao
network_config:
is_private: true
vpc_id: vpc-098bd05c4ef524627
private_subnet_ids: #using API VPC Endpoint in 1 subnet is cheaper than using it in 2 or more
- subnet-0c8b5a9233eff22b4
# - subnet-00adc4773686d8c1b
timeout: 900
memory: 2048
ecr:
repo_name: rekognition-valvulas-funcao
tag: latest
#env_vars:
provisioned_concurrency: 0
api_gateway:
use_api_gw: true
communication_type: REST #REST # WEBSOCKET | HTTP | REST #TODO implement all
type: REGIONAL # PRIVATE | REGIONAL | EDGE
authorization: NONE # | AWS_IAM | ...
allow_inbound_any: true
allow_inbound_cidrs:
- 3.14.44.224/32 # IP VPN DNX
create_and_allow_vpce: true # only used for PRIVATE api type
stage_name: dev
routes:
- method: POST
path: /execute
iam:
managed_policies: []
#custom_policies:

View File

@@ -0,0 +1,11 @@
name: lambda-api
runtime:
name: python
options:
toolchain: pip
virtualenv: venv
description: A Python program to deploy a serverless application on AWS
config:
pulumi:tags:
value:
pulumi:template: serverless-aws-python

View File

@@ -0,0 +1,110 @@
import json
import pulumi
import pulumi_aws as aws
import pulumi_aws_apigateway as apigateway
import api_gw
config = pulumi.Config()
aws_config = pulumi.Config("aws")
aws_region = aws_config.require("region")
account_id = aws.get_caller_identity().account_id
def create_lambda_role(lambda_name, iam_config=None):
"""Create IAM role for Lambda with configurable policies"""
# Base managed policies
managed_policies = [aws.iam.ManagedPolicy.AWS_LAMBDA_BASIC_EXECUTION_ROLE,
aws.iam.ManagedPolicy.AWS_LAMBDA_VPC_ACCESS_EXECUTION_ROLE]
if iam_config and "managed_policies" in iam_config:
managed_policies.extend(iam_config["managed_policies"])
role = aws.iam.Role(f"role-{lambda_name}",
assume_role_policy=json.dumps({
"Version": "2012-10-17",
"Statement": [{
"Action": "sts:AssumeRole",
"Effect": "Allow",
"Principal": {"Service": "lambda.amazonaws.com"},
}],
}),
managed_policy_arns=managed_policies
)
# Create custom inline policies from YAML config
if iam_config and "custom_policies" in iam_config:
for policy in iam_config["custom_policies"]:
aws.iam.RolePolicy(f"{lambda_name}-{policy['name']}",
role=role.id,
policy=json.dumps({
"Version": "2012-10-17",
"Statement": [{
"Effect": policy["effect"],
"Action": policy["actions"],
"Resource": policy["resources"]
}]
})
)
return role
lambda_api_configs = config.require_object("lambda-api")
for l_api in lambda_api_configs:
ecr_repo = aws.ecr.get_repository(name=l_api["ecr"]["repo_name"])
ecr_image = aws.ecr.get_image(repository_name=l_api["ecr"]["repo_name"], image_tag=l_api["ecr"]["tag"])
lambda_name = l_api["name"]
if not l_api["network_config"]["is_private"]:
raise "Not implemented yet: Public lambda function"
else:
lambda_sg = aws.ec2.SecurityGroup(f"lambda-sg-{lambda_name}",
description=f"SG for Lambda {lambda_name}",
egress=[{
"cidr_blocks": ["0.0.0.0/0"],
"from_port": 0,
"protocol": "-1",
"to_port": 0,
}],
name=lambda_name,
vpc_id=l_api["network_config"]["vpc_id"],
)
# print(pulumi.Output.all(ecr_repo.repository_url, ecr_image.image_digest).apply(lambda args: f'{args[0]}@{args[1]}'))
# Create role for this specific Lambda
role = create_lambda_role(lambda_name, l_api.get("iam"))
if "env_vars" in l_api:
variables={k:v for k,v in l_api["env_vars"].items()}
else:
variables={}
# Define the Lambda function, replacing <IMAGE_URI> with your actual image URI
fn = aws.lambda_.Function(f"{lambda_name}",
package_type="Image",
# image_uri=ecr_repo.repository_url.apply(lambda url: f"{url}:latest"), # Assuming 'latest' tag
image_uri=pulumi.Output.all(ecr_repo.repository_url, ecr_image.image_digest).apply(lambda args: f'{args[0]}@{args[1]}'),
role=role.arn,
timeout=l_api["timeout"],
memory_size=l_api["memory"],
environment={
"variables": variables
},
vpc_config=dict(
ipv6_allowed_for_dual_stack = False,
subnet_ids = l_api["network_config"]["private_subnet_ids"],
security_group_ids=[lambda_sg.id]
),
publish=l_api["provisioned_concurrency"]>0, #necessary for provisioned concurrency
)
if l_api["provisioned_concurrency"]>0:
lambda_concurrency = aws.lambda_.ProvisionedConcurrencyConfig(l_api["name"],
provisioned_concurrent_executions=l_api["provisioned_concurrency"],
function_name=fn.name,
qualifier=fn.version
)
api_config = l_api["api_gateway"]
if api_config["use_api_gw"]:
if api_config["communication_type"]=="HTTP":
api_gw.create_api_gatewayv2(api_config, l_api, fn, aws_region, config.require('project_name'))
else:
api_gw.create_api_gateway(api_config, l_api, fn, account_id, aws_region, config.require('project_name'))

View File

@@ -0,0 +1,407 @@
import pulumi
import pulumi_aws as aws
import json
# import time
def create_api_gatewayv2(api_config, l_api, fn, aws_region, project_name):
# Create VPC endpoint for PRIVATE API Gateway
# if api_config["type"] == "PRIVATE":
# vpce = aws.ec2.VpcEndpoint(f"vpce-{l_api['name']}",
# vpc_id=l_api["network_config"]["vpc_id"],
# service_name=f"com.amazonaws.{aws_region}.execute-api",
# subnet_ids=l_api["network_config"]["private_subnet_ids"],
# private_dns_enabled=True,
# vpc_endpoint_type="Interface"
# )
# HTTP API apigwv2
# Create API Gateway V2 HTTP API
api = aws.apigatewayv2.Api(f"api-{l_api['name']}",
name=l_api['name'],
protocol_type="HTTP",
)
sg_vpc_link = aws.ec2.SecurityGroup(f"secgroup-{l_api['name']}",
vpc_id=l_api["network_config"]["vpc_id"],
ingress=[
aws.ec2.SecurityGroupIngressArgs(
protocol="tcp",
from_port=0,
to_port=0,
cidr_blocks=["3.14.44.224/32"]
)
],
egress=[
aws.ec2.SecurityGroupEgressArgs(
protocol="-1",
from_port=0,
to_port=0,
cidr_blocks=["0.0.0.0/0"]
)
]
)
# Create a VPC Link
vpc_link = aws.apigatewayv2.VpcLink(
f"VpcLink-{project_name}",
subnet_ids=l_api["network_config"]["private_subnet_ids"],
security_group_ids=sg_vpc_link,
)
# Add IAM resource policy to restrict access by VPC (for PRIVATE type)
# if api_config["type"] == "PRIVATE":
# api_policy = aws.apigatewayv2.ApiPolicy(f"policy-{l_api['name']}",
# api_id=api.id,
# policy=pulumi.Output.all(api.arn, l_api["network_config"]["vpc_id"]).apply(
# lambda args: json.dumps({
# "Version": "2012-10-17",
# "Statement": [{
# "Effect": "Allow",
# "Principal": "*",
# "Action": "execute-api:Invoke",
# "Resource": f"{args[0]}/*",
# "Condition": {
# "StringEquals": {
# "aws:sourceVpc": args[1]
# }
# }
# }]
# })
# )
# )
integration_get = None
integration_post = None
# Create Lambda integrations
for route in api_config["routes"]:
if route["method"] == "GET" and not integration_get:
integration_get = aws.apigatewayv2.Integration(f"integration-{l_api['name']}-{route['path'].replace('/', '-')}",
api_id=api.id,
integration_type="AWS_PROXY",
integration_method="GET",
integration_uri=fn.invoke_arn,
# connection_type="INTERNET",
payload_format_version="2.0",
# connection_id=vpc_link.id
)
elif route["method"] == "POST" and not integration_post:
integration_post = aws.apigatewayv2.Integration(f"integration-{l_api['name']}-{route['path'].replace('/', '-')}",
api_id=api.id,
integration_type="AWS_PROXY",
integration_uri=fn.invoke_arn,
integration_method="POST",
payload_format_version="2.0"
)
# Create routes dynamically from config
routes = []
for route in api_config["routes"]:
if route['method'] == "GET":
integration = integration_get
elif route['method'] == "POST":
integration = integration_post
r = aws.apigatewayv2.Route(
f"route-{l_api['name']}-{route['method']}-{route['path'].replace('/', '-')}",
api_id=api.id,
route_key=f"{route['method']} {route['path']}",
target=integration.id.apply(lambda id: f"integrations/{id}")
)
routes.append(r)
# Lambda permission for API Gateway
permission = aws.lambda_.Permission(
f"permission-{l_api['name']}",
action="lambda:InvokeFunction",
function=fn.name,
principal="apigateway.amazonaws.com",
source_arn=api.execution_arn.apply(lambda arn: f"{arn}/*/*")
)
# Create stage
stage = aws.apigatewayv2.Stage(f"stage-{l_api['name']}",
api_id=api.id,
name="$default",
auto_deploy=True
)
# Export the API URL
pulumi.export(f"{l_api['name']}-url", api.api_endpoint)
def create_api_gateway(api_config, l_api, fn, account_id, aws_region, project_name):
vpce_id = None
# Create API Gateway
if api_config["type"] == "PRIVATE":
if api_config["create_and_allow_vpce"]:
# Cria uma nova Security Group para o VPC Endpoint
vpc_endpoint_sg = aws.ec2.SecurityGroup(f"api-gateway-vpce-sg-{project_name}",
vpc_id=l_api["network_config"]["vpc_id"],
description=f"Security Group for API Gateway VPC Endpoint - {project_name}",
ingress=[
aws.ec2.SecurityGroupIngressArgs(
protocol="tcp",
from_port=0,
to_port=0,
cidr_blocks=["0.0.0.0/0"],
description="Allow HTTPS traffic to API Gateway Endpoint"
),
],
egress=[
# Permite todo o tráfego de saída. Pode ser restringido se necessário.
aws.ec2.SecurityGroupEgressArgs(
protocol="-1", # "-1" significa todos os protocolos
from_port=0,
to_port=0,
cidr_blocks=["0.0.0.0/0"],
),
]
)
# Create VPC endpoint for PRIVATE API Gateway
vpce = aws.ec2.VpcEndpoint(f"vpce-{l_api['name']}",
vpc_id=l_api["network_config"]["vpc_id"],
service_name=f"com.amazonaws.{aws_region}.execute-api",
subnet_ids=l_api["network_config"]["private_subnet_ids"],
private_dns_enabled=False,
vpc_endpoint_type="Interface",
security_group_ids=[vpc_endpoint_sg]
)
vpce_id = vpce.id
# api = aws.apigateway.RestApi(f"api-{l_api["name"]}",
# description=l_api["name"],
# fail_on_warnings=False,
# put_rest_api_mode='merge',
# endpoint_configuration={
# "types": api_config["type"],
# "vpc_endpoint_ids": [vpce.id] if api_config["type"] == "PRIVATE" and api_config["create_and_allow_vpce"] else None
# }
# )
api = aws.apigateway.RestApi(f"api-{l_api["name"]}",
description=l_api["name"],
put_rest_api_mode='merge' if api_config["type"] == "PRIVATE" else 'overwrite',
fail_on_warnings=False,
endpoint_configuration={
"types": api_config["type"],
"vpc_endpoint_ids": [vpce_id] if api_config["type"] == "PRIVATE" and api_config["create_and_allow_vpce"] else None
}
)
# Build policy statements
policy_outputs = []
if api_config["allow_inbound_any"]:
policy_outputs.append(
pulumi.Output.all(aws_region, account_id, api.id, vpce_id).apply(
lambda args: {
"Effect": "Allow",
"Principal": "*",
"Action": "execute-api:Invoke",
"Resource": f"arn:aws:execute-api:{args[0]}:{args[1]}:{args[2]}/*"
}
)
)
else:
if api_config["type"] == "PRIVATE" and api_config["create_and_allow_vpce"]:
policy_outputs.append(
pulumi.Output.all(aws_region, account_id, api.id, vpce_id).apply(
lambda args: {
"Effect": "Allow",
"Principal": "*",
"Action": "execute-api:Invoke",
"Resource": f"arn:aws:execute-api:{args[0]}:{args[1]}:{args[2]}/*",
"Condition": {
"StringEquals": {
"aws:sourceVpce": args[3]
}
}
}
)
)
if api_config.get("allow_inbound_cidrs"):
policy_outputs.append(
pulumi.Output.all(aws_region, account_id, api.id).apply(
lambda args: {
"Effect": "Allow",
"Principal": "*",
"Action": "execute-api:Invoke",
"Resource": f"arn:aws:execute-api:{args[0]}:{args[1]}:{args[2]}/*",
"Condition": {
"IpAddress": {
"aws:SourceIp": api_config.get("allow_inbound_cidrs", [])
}
}
}
)
)
if len(policy_outputs) > 0:
# Resource policy for private API
resource_policy = aws.apigateway.RestApiPolicy(f"policy-{l_api['name']}",
rest_api_id=api.id,
policy=pulumi.Output.all(*policy_outputs).apply(
lambda statements: json.dumps({
"Version": "2012-10-17",
"Statement": statements
})
)
)
# Create resources and methods dynamically
resources = {}
api_dependencies = []
# routes = []
for route in api_config["routes"]:
path = route["path"].strip("/")
path_parts = path.split("/")
# Build nested resources
parent_id = api.root_resource_id
resource_path = ""
for part in path_parts:
resource_path += f"/{part}"
resource_key = resource_path
if resource_key not in resources:
resources[resource_key] = aws.apigateway.Resource(
f"resource-{l_api['name']}-{part}",
rest_api=api.id,
parent_id=parent_id,
path_part=part
)
parent_id = resources[resource_key].id
# Create method for this route
method = aws.apigateway.Method(
f"method-{l_api['name']}-{route['method']}-{path.replace('/', '-')}",
rest_api=api.id,
resource_id=parent_id,
http_method=route["method"],
authorization=api_config["authorization"]
)
# Create integration
integration = aws.apigateway.Integration(
f"integration-{l_api['name']}-{route['method']}-{path.replace('/', '-')}",
rest_api=api.id,
resource_id=parent_id,
http_method=method.http_method,
integration_http_method="POST", # for Lambda integration, it's always POST
type="AWS_PROXY",
uri=fn.invoke_arn
)
method_response = aws.apigateway.MethodResponse(f"methodResponse-{path.replace('/', '-')}",
rest_api=api.id,
resource_id=parent_id,
http_method=method.http_method,
status_code="200",
response_models={"application/json": "Empty"}
)
integration_response = aws.apigateway.IntegrationResponse(f"integrationResponse-{path.replace('/', '-')}",
rest_api=api.id,
resource_id=parent_id,
http_method=method.http_method,
status_code="200",
selection_pattern="",
response_templates={"application/json": ""},
opts=pulumi.ResourceOptions(depends_on=[integration])
)
# # Lambda permission for API Gateway
# permission = aws.lambda_.Permission(
# f"permission-{l_api['name']}-{route['method']}-{path.replace('/', '-')}",
# action="lambda:InvokeFunction",
# function=fn.name,
# principal="apigateway.amazonaws.com",
# source_arn=api.execution_arn.apply(
# lambda arn, m=route['method'], r=resource_path: f"{arn}/*/{m}{r}"
# )
# )
api_dependencies.append(method)
api_dependencies.append(integration)
api_dependencies.append(method_response)
api_dependencies.append(integration_response)
# Lambda permission for API Gateway
permission = aws.lambda_.Permission(
f"permission-{l_api['name']}-general",
action="lambda:InvokeFunction",
function=fn.name,
principal="apigateway.amazonaws.com",
source_arn=api.execution_arn.apply(
lambda arn: f"{arn}/*/*"
)
)
api_dependencies.append(permission)
# method = getattr(apigateway.Method, route["method"])
# routes.append(apigateway.RouteArgs(
# path=route["path"],
# method=method,
# event_handler=fn
# ))
# api = apigateway.RestAPI("api",
# routes=routes,
# # type=api_config["type"],
# # put_rest_api_mode="merge" if api_config["type"] == "PRIVATE" else "overwrite"
# )
# # Create a VPC link to integrate API Gateway with the VPC
# vpc_link = aws.apigateway.VpcLink(f"VpcLink-{l_api['name']}",
# name=f"VpcLink-{l_api['name']}",
# target_arn=l_api["network_config"]["vpc_id"],
# tags={
# "Name": f'VpcLink-{l_api["name"]}',
# })
# Create a deployment for the API Gateway
deployment = aws.apigateway.Deployment(f"deployment-{l_api['name']}",
rest_api=api.id,
# triggers={"redeployment": str(int(time.time()))},
opts=pulumi.ResourceOptions(depends_on=list(resources.values())+api_dependencies)
)
# Create a stage
stage = aws.apigateway.Stage(f"stage-{l_api['name']}",
deployment=deployment.id,
rest_api=api.id,
stage_name=api_config["stage_name"],
opts=pulumi.ResourceOptions(depends_on=[deployment])
)
if False: #use_api_key: #TODO
api_key = aws.apigateway.ApiKey(api_gateway_config["name"],
name=api_gateway_config["name"],
description=api_gateway_config["description"],
enabled=True
)
# API Key to Stage
usage_plan = aws.apigateway.UsagePlan("api-usage-plan",
name=api_gateway_config["usage_plan_name"],
description="Usage plan for API Gateway associated with API Key",
api_stages=[aws.apigateway.UsagePlanApiStageArgs(
api_id=api.id,
stage=deployment.stage_name
)]
)
# API Key to Usage Plan
aws.apigateway.UsagePlanKey("api-key-usage-plan-association",
key_id=api_key.id,
key_type="API_KEY",
usage_plan_id=usage_plan.id
)
# Export the stage URL
pulumi.export(f"{l_api['name']}-url", stage.invoke_url)

View File

@@ -0,0 +1,4 @@
pulumi>=3.0.0,<4.0.0
pulumi-aws>=7.0.0,<8.0.0
pulumi-aws-apigateway>=3.0.0,<4.0.0
pulumi-awsx>=3.0.0,<4.0.0