En este post vamos a crear la siguiente infraestructura utilizando algunas de las herramienta de IaC que tenemos a nuestra disposición en AWS.
Empecemos con la plantilla para CloudFormation:
AWSTemplateFormatVersion: '2010-09-09'
Parameters:
MetodoGET:
Type: String
Default: GET
MetodoPOST:
Type: String
Default: POST
ClientePath:
Type: String
Default: cliente
SucursalPath:
Type: String
Default: sucursal
Stage:
Type: String
Default: dev
RoleFullAccessDynamoDB:
Type: String
Default: arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess
RoleFullAccessCloudWatch:
Type: String
Default: arn:aws:iam::aws:policy/CloudWatchFullAccess
Resources:
RoleFuncionLambda:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- 'sts:AssumeRole'
Path: /
ManagedPolicyArns:
- !Ref RoleFullAccessDynamoDB
- !Ref RoleFullAccessCloudWatch
APITienda:
Type: AWS::ApiGateway::RestApi
Properties:
Name: APITienda
EndpointConfiguration:
Types:
- REGIONAL
RecursoCliente:
Type: AWS::ApiGateway::Resource
Properties:
RestApiId: !Ref APITienda
PathPart: !Ref ClientePath
ParentId: !GetAtt APITienda.RootResourceId
RecursoSucursal:
Type: AWS::ApiGateway::Resource
Properties:
RestApiId: !Ref APITienda
PathPart: !Ref SucursalPath
ParentId: !GetAtt APITienda.RootResourceId
MetodoObtenerClientes:
Type: AWS::ApiGateway::Method
Properties:
AuthorizationType: NONE
HttpMethod: !Ref MetodoGET
Integration:
IntegrationHttpMethod: POST
Type: AWS_PROXY
Uri: !Sub
- arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${lambdaArn}/invocations
- lambdaArn: !GetAtt ObtenerClienteLambda.Arn
ResourceId: !GetAtt RecursoCliente.ResourceId
RestApiId: !Ref APITienda
MetodoRegistrarCliente:
Type: AWS::ApiGateway::Method
Properties:
AuthorizationType: NONE
HttpMethod: !Ref MetodoPOST
Integration:
IntegrationHttpMethod: POST
Type: AWS_PROXY
Uri: !Sub
- arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${lambdaArn}/invocations
- lambdaArn: !GetAtt RegistrarClienteLambda.Arn
ResourceId: !GetAtt RecursoCliente.ResourceId
RestApiId: !Ref APITienda
MetodoObtenerSucursales:
Type: AWS::ApiGateway::Method
Properties:
AuthorizationType: NONE
HttpMethod: !Ref MetodoGET
Integration:
IntegrationHttpMethod: POST
Type: AWS_PROXY
Uri: !Sub
- arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${lambdaArn}/invocations
- lambdaArn: !GetAtt ObtenerSucursalesLambda.Arn
ResourceId: !GetAtt RecursoSucursal.ResourceId
RestApiId: !Ref APITienda
Despliegue:
Type: AWS::ApiGateway::Deployment
DependsOn:
- MetodoObtenerClientes
- MetodoRegistrarCliente
- MetodoObtenerSucursales
Properties:
RestApiId: !Ref APITienda
StageName: !Ref Stage
RegistrarClienteLambda:
Type: AWS::Lambda::Function
Properties:
Description: "Agrega clientes a la base de datos"
FunctionName: "RegistarCliente"
Handler: index.lambda_handler
MemorySize: 128
Runtime: python3.8
Timeout: 3
Code:
ZipFile: |
import json
import boto3
from boto3.dynamodb.conditions import Key
dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table("Tiendas")
def lambda_handler(event, context):
data = json.loads(event["body"])
idCliente= data["id"]
nombre = data["nombre"]
apellido = data["apellido"]
table.put_item(Item={
"Id": "Cliente",
"Filtro":"Id#"+idCliente,
"Nombre":nombre,
"Apellido":apellido
})
return {
'statusCode': 200,
'body': json.dumps(idCliente)
}
Role: !GetAtt RoleFuncionLambda.Arn
ObtenerClienteLambda:
Type: AWS::Lambda::Function
Properties:
Description: "Obtiene los clientes"
FunctionName: "ObtenerCliente"
Handler: index.lambda_handler
MemorySize: 128
Runtime: python3.8
Timeout: 3
Code:
ZipFile: |
import json
import boto3
from boto3.dynamodb.conditions import Key
dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table("Tiendas")
def lambda_handler(event, context):
id = event["queryStringParameters"]["id"]
cliente = table.query(KeyConditionExpression=Key('Id').eq("Cliente") & Key('Filtro').eq('Id#'+id),
ProjectionExpression="Id,Filtro,Nombre,Apellido")
return {
'statusCode': 200,
'body': json.dumps(cliente)
}
Role: !GetAtt RoleFuncionLambda.Arn
ObtenerSucursalesLambda:
Type: AWS::Lambda::Function
Properties:
Description: "Obtiene las sucursales"
FunctionName: "ObtenerSucursales"
Handler: index.lambda_handler
MemorySize: 128
Runtime: python3.8
Timeout: 3
Code:
ZipFile: |
import json
import boto3
from boto3.dynamodb.conditions import Key
dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table("Tiendas")
def lambda_handler(event, context):
sucursales = table.query(KeyConditionExpression=Key('Id').eq("Sucursal"),
ProjectionExpression="Id,Filtro,Direccion")
return {
'statusCode': 200,
'body': json.dumps(sucursales)
}
Role: !GetAtt RoleFuncionLambda.Arn
PermisoObtenerClientes:
Type: AWS::Lambda::Permission
Properties:
Action: "lambda:InvokeFunction"
FunctionName: !GetAtt ObtenerClienteLambda.Arn
Principal: "apigateway.amazonaws.com"
SourceArn: !Sub "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${APITienda}/*/${MetodoGET}/${ClientePath}"
PermisoRegistrarCliente:
Type: AWS::Lambda::Permission
Properties:
Action: "lambda:InvokeFunction"
FunctionName: !Ref RegistrarClienteLambda
Principal: "apigateway.amazonaws.com"
SourceArn: !Sub "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${APITienda}/*/${MetodoPOST}/${ClientePath}"
PermisoObtenerSucursales:
Type: AWS::Lambda::Permission
Properties:
Action: "lambda:InvokeFunction"
FunctionName: !Ref ObtenerSucursalesLambda
Principal: "apigateway.amazonaws.com"
SourceArn: !Sub "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${APITienda}/*/${MetodoGET}/${SucursalPath}"
Tabla:
Type: AWS::DynamoDB::Table
Properties:
TableName: Tiendas
BillingMode: PROVISIONED
ProvisionedThroughput:
ReadCapacityUnits: "5"
WriteCapacityUnits: "5"
AttributeDefinitions:
-
AttributeName: "Id"
AttributeType: "S"
-
AttributeName: "Filtro"
AttributeType: "S"
KeySchema:
-
AttributeName: "Id"
KeyType: "HASH"
-
AttributeName: "Filtro"
KeyType: "RANGE"
Outputs:
ApiEndPoint:
Description: "EndPoint Api"
Value: !Sub "https://${APITienda}.execute-api.${AWS::Region}.amazonaws.com/${Stage}/"
ClienteEndPoint:
Description: "EndPoint Metodos Cliente"
Value: !Sub "https://${APITienda}.execute-api.${AWS::Region}.amazonaws.com/${Stage}/${ClientePath}"
SucursalEndPoint:
Description: "EndPoint Metodo Sucursal"
Value: !Sub "https://${APITienda}.execute-api.${AWS::Region}.amazonaws.com/${Stage}/${SucursalPath}"
Las etiquetas que utilizamos para crear los recursos son las siguientes:
Para poder desplegar nuestra infraestructura debemos asegurarnos de tener instalada la CLI (Instalar CLI) en nuestro equipo y haber configurado nuestras credenciales(comando aws configure).
Vamos a validar que nuestra plantilla este construida de manera correcta. Ubicados en la carpeta donde se encuentra la plantilla vamos a ejecutar el siguiente comando:
aws cloudformation validate-template --template-body file://apirest.yml
Si la plantilla esta bien construida el comando nos va a retornar la lista de parámetros que tiene nuestra plantilla.
Una vez sabemos que nuestra plantilla es correcta vamos a desplegarla utilizando el siguiente comando
aws cloudformation deploy --template-file apirest.yml --stack-name apiresIaC --capabilities CAPABILITY_IAM
que recibe como parámetros el nombre de la plantilla y el nombre del stack que vamos a crear. Adicionalmente le enviamos el parámetro --capabilities CAPABILITY_IAM ya que vamos hacer uso de IAM.
Una vez que ejecutamos el comando vamos a ver lo siguiente:
Lo que nos indica que nuestros recursos fueron desplegados con exito. Podemos revisar todo el proceso y el resultado del despliegue de la infraestructura desde la consola de AWS. Para este buscamos el servicio CloudFormation y damos click en Stacks, donde vamos a ver el stack que creamos con el comando deploy.
Si damos click en el nombre del stack creado vamos a poder ver todo el detalle de los eventos del proceso de creación, los recursos creados, los parámetros, las salidas, el template y los cambios que se han realizado sobre la plantilla. Damos click en Outputs para ver las rutas de los servicios desplegados.
Para probar nuestros servicios podemos utilizar PostMan o en mi caso estoy utilizando la extension REST Client para Visual Studio Code. Puedo probar los servicios de la siguiente manera:
POST https://5d24uhg9db.execute-api.us-east-1.amazonaws.com/dev/cliente HTTP/1.1
content-type: application/json
{
"id": "1",
"nombre": "Lisa",
"apellido": "Simpson"
}
###
GET https://5d24uhg9db.execute-api.us-east-1.amazonaws.com/dev/cliente?id=1 HTTP/1.1
content-type: application/json
###
GET https://5d24uhg9db.execute-api.us-east-1.amazonaws.com/dev/sucursal HTTP/1.1
content-type: application/json
Con esto finalizamos el despliegue y las pruebas de nuestro API Rest creado con CloudFormation.
Ahora vamos a crear la misma infraestructura utilizando SAM(Serverless Application Model). SAM nos permite desplegar aplicaciones sin servidor en AWS a diferencia de CloudFormation que nos permite desplegar todo tipo de recursos.
Las etiquetas que utilizamos para crear los recursos son las siguientes:
La plantilla que utilizamos es la siguiente:
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Parameters:
Stage:
Type: String
Default: dev
RoleFullAccessDynamoDB:
Type: String
Default: arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess
RoleFullAccessCloudWatch:
Type: String
Default: arn:aws:iam::aws:policy/CloudWatchFullAccess
ClientePath:
Type: String
Default: cliente
SucursalPath:
Type: String
Default: sucursal
Resources:
RoleFuncionLambda:
Type: 'AWS::IAM::Role'
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- 'sts:AssumeRole'
Path: /
ManagedPolicyArns:
- !Ref RoleFullAccessDynamoDB
- !Ref RoleFullAccessCloudWatch
APITienda:
Type: AWS::Serverless::Api
Properties:
Name: APITienda
StageName: !Ref Stage
RegistrarClienteLambda:
Type: AWS::Serverless::Function
Properties:
InlineCode: |
import json
import boto3
from boto3.dynamodb.conditions import Key
dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table("Tiendas")
def lambda_handler(event, context):
data = json.loads(event["body"])
idCliente= data["id"]
nombre = data["nombre"]
apellido = data["apellido"]
table.put_item(Item={
"Id": "Cliente",
"Filtro":"Id#"+idCliente,
"Nombre":nombre,
"Apellido":apellido
})
return {
'statusCode': 200,
'body': json.dumps(idCliente)
}
Handler: index.lambda_handler
Runtime: python3.7
PackageType: Zip
FunctionName: RegistrarCliente
Role: !GetAtt RoleFuncionLambda.Arn
Events:
RegistrarCliente:
Type: Api
Properties:
RestApiId: !Ref APITienda
Path: /cliente
Method: POST
ObtenerClienteLambda:
Type: AWS::Serverless::Function
Properties:
InlineCode: |
import json
import boto3
from boto3.dynamodb.conditions import Key
dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table("Tiendas")
def lambda_handler(event, context):
id = event["queryStringParameters"]["id"]
cliente = table.query(KeyConditionExpression=Key('Id').eq("Cliente") & Key('Filtro').eq('Id#'+id),
ProjectionExpression="Id,Filtro,Nombre,Apellido")
return {
'statusCode': 200,
'body': json.dumps(cliente)
}
Handler: index.lambda_handler
Runtime: python3.7
PackageType: Zip
FunctionName: ObtenerCliente
Role: !GetAtt RoleFuncionLambda.Arn
Events:
ObtenerClientes:
Type: Api
Properties:
RestApiId: !Ref APITienda
Path: /cliente
Method: GET
SucursalesLambda:
Type: AWS::Serverless::Function
Properties:
InlineCode: |
import json
import boto3
from boto3.dynamodb.conditions import Key
dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table("Tiendas")
def lambda_handler(event, context):
sucursales = table.query(KeyConditionExpression=Key('Id').eq("Sucursal"),
ProjectionExpression="Id,Filtro,Direccion")
print(sucursales["Items"])
return {
'statusCode': 200,
'body': json.dumps(sucursales)
}
Handler: index.lambda_handler
Runtime: python3.7
PackageType: Zip
FunctionName: ObtenerSucursales
Role: !GetAtt RoleFuncionLambda.Arn
Events:
ObtenerSucursales:
Type: Api
Properties:
RestApiId: !Ref APITienda
Path: /sucursal
Method: GET
Tabla:
Type: AWS::DynamoDB::Table
Properties:
TableName: Tiendas
BillingMode: PROVISIONED
ProvisionedThroughput:
ReadCapacityUnits: "5"
WriteCapacityUnits: "5"
AttributeDefinitions:
-
AttributeName: "Id"
AttributeType: "S"
-
AttributeName: "Filtro"
AttributeType: "S"
KeySchema:
-
AttributeName: "Id"
KeyType: "HASH"
-
AttributeName: "Filtro"
KeyType: "RANGE"
Outputs:
ApiEndPoint:
Description: "EndPoint Api"
Value: !Sub "https://${APITienda}.execute-api.${AWS::Region}.amazonaws.com/${Stage}/"
ClienteEndPoint:
Description: "EndPoint Metodos Cliente"
Value: !Sub "https://${APITienda}.execute-api.${AWS::Region}.amazonaws.com/${Stage}/${ClientePath}"
SucursalEndPoint:
Description: "EndPoint Metodo Sucursal"
Value: !Sub "https://${APITienda}.execute-api.${AWS::Region}.amazonaws.com/${Stage}/${SucursalPath}"
Algo que diferencia a las plantillas SAM de las plantillas de CloudFormation es la siguiente linea que identifica la plantilla como SAM:
Transform: AWS::Serverless-2016-10-31
SAM utiliza algunas etiquetas diferentes para crear los recursos como:
AWS::Serverless::Api : Nos sirve para crear el API Gateway.
AWS::Serverless::Function: Nos sirve para crear la función Lambda y la integración con API Gateway (los recursos, los métodos, el stage y el despliegue).
Como podemos observar utilizar SAM para desplegar aplicaciones sin servidor es mucho mas practico que utilizar CloudFormation.
Teniendo nuestra plantilla debemos desplegarla en AWS. Primero debemos tener instalada la SAM CLI en nuestro equipo.
Una vez instalada SAM CLI vamos a validar que nuestra plantilla este construida correctamente con el siguiente comando.
sam validate --template apirestsam.yml
Como vemos que nuestra plantilla esta correcta vamos a desplegarla con el siguiente comando:
sam deploy --template-file apirestsam.yml --stack-name apirestSAMIaC --capabilities CAPABILITY_IAM
Al ejecutar el comando vamos a ver en detalle como se están creando nuestros recursos. Van a ver algo similar a esto en su terminal:
Esto nos indica que nuestra API Rest fue desplegado con éxito.
Como lo hicimos en el despliegue de la plantilla de CloudFormation podemos ir a la consola de AWS y verificar que los recursos se hayan creado.
En este post podemos observar que se pueden utilizar diferentes herramientas para cumplir un mismo objetivo, sin embargo debemos buscar siempre la mas adecuada para cada caso, que en este es SAM, ya que la infraestructura a desplegar es sin servidor.
Como ejercicio pueden implementar la función Lambda para ingresar las sucursales e implementar un método de autenticación para validar el acceso a las métodos del API Gateway.
Me pueden encontrar en:
Top comments (0)