One of the major improvements is streamlining API Gateway definition resources. Since single API definition contains plenty of resource, many things need to be repeated. Macro handles dedicated structure that keeps entre API definition in single structure and extracts it into separate resources also making some tedious tasks automated or at least simplified.
Since REST API definition is combining many other resources it is separated into new template section RestApis - it is
a hash with API logical identifier (key in your template) as a key and API properties as each entry:
RestApis:
MyApi:
Authorizers:
# authorizers definitions (see below)
Models:
# models definitions (see below)
Resources:
# API structure definition (see below)
# all other properties will be mapped directly to API resources, so you can do:
Name: "my-public-api"
Each REST API also defines AWS::ApiGateway::Deployment resource.
Defining any API using RestApis block also creates AWS::ApiGateway::Account and logging role for it.
Note: On each level any property not mentioned directly here is passed to underlying resource, so in case of any changes in native CloudFormation resources can be directly used in the macro.
Authorizers:
SomeAuthorizerId:
Type: "TOKEN"
# this is not `!Sub`, this is an API gateway stage variable reference
Lambda: "${stageVariables.AuthorizerLambda}"
IdentitySource: "method.request.header.Authorization"
AuthorizerResultTtlInSeconds: 3600
This is a completely ordinary authorizer definition except that it is automatically scoped within API. You also don't need to define its name.
Note: For Lambda property see “Simplifications” section.
Models:
AccountInfo:
Schema:
type: "object"
properties:
accountName:
type: string
required:
- "accountName"
additionalProperties: false
Again, definition of model is just a native AWS::ApiGateway::Model just automatically scoped to current API. Content
type, schema type and model title are, however, optional. You can see “Simplifications” section for details.
The biggest simplification comes from converting excessive definitions of resource resources (well… resources of resources?) into structure definition - a little like Open API directly in CloudFormation template:
# keep in mind - this is `Resources` section of API not a top-level template section
Resources:
/accounts:
/{accountId}:
"@GET":
# integration method - see below
"@DELETE":
# integration method - see below
/keys:
"@GET":
# integration method - see below
There is literally no resource definition needed, as it is extracted directly from the tree-ish structure. It is just a container for integration methods definitions.
"@PUT":
Authorizer: "TokenAuthorizer"
RequestValidator: "BODY_AND_PARAMETERS"
RequestModels:
application/json: !Ref "RestApi:V1:Model:TenantInfo"
RequestParameters:
method.request.header.Authorization: true
method.request.path.clientTenantId: true
method.request.path.apiKey: true
Integration:
Lambda: "${stageVariables.TenantKeySavingTarget}"
Credentials: !GetAtt "ApiGatewayRole.Arn"
RequestTemplates:
application/json: |
{
"tenantId": "$context.authorizer.tenantId",
"clientTenantId": "$input.params('clientTenantId')",
"apiKey": "$input.params('apiKey')",
"tenantInfo": $input.json('$')
}
IntegrationResponses:
"202":
ResponseParameters:
method.response.header.Content-Type: "'application/atom+xml'"
"404":
SelectionPattern: ".*Tag not found.*"
PassthroughBehavior: "NEVER"
AWS::ApiGateway::Method definition with some changes.@PUT its set to PUT.Authorizer (instead of AuthorizerId) is just an identifier from Authorizers definitions - macro converts it to
proper reference. AuthorizerType is also automatically assigned based on referred authorizer type.RequestValidator (instead of RequestValidatorId) is just one of the three values: BODY_ONLY,
PARAMETERS_ONLY, orBODY_AND_PARAMETERS`. Underlying validators are managed on-demand by macro.MethodResponses and Integration.IntegrationResponses can be defined as maps, where keys become
StatusCode of defined responses (you can still use native notation of CloudFormation template).MethodResponses is defined directly in the method it is built based on Integration.IntegrationResponses by
leaving StatusCode and ResponseParameters (if present) replaced by boolean flags instead of any values.Note: Mapping keys in CloudFormation templates must be strings, even if it's 202 it must be wrapped within quotes!
You can refer to any resource defined by an API using specific notation (macro replaces them in Ref, Fn::Sub and
Fn::GetAtt):
RestApi:MyApi - returns API reference;RestApi:MyApi:Deployment - current API reference deployment;RestApi:MyApi:Authorizer:AuthorizerIdentifier - authorizer reference;RestApi:MyApi:Validator:ValidatorIdentifier - validator reference (it can be BODY_ONLY, PARAMETERS_ONLY or
BODY_AND_PARAMETERS);RestApi:MyApi:Model:ModelIdentifier - model reference;RestApi:MyApi:Resource:ResourcePath - resource reference;RestApi:MyApi:Method:MethodAndResourcePath - integration method reference.In case of Resource and Method references you need to use %param% instead of {param} path placeholder as that
would collide with Fn::Sub calls.
Examples of references:
!Ref "RestApi:MyApi:Authorizer:TokenAuthorizer"!GetAtt "RestApi:MyApi.RootResourceId"!Sub "${RestApi:MyApi:Resource:/%accountId%}"Type defaults to AWS and IntegrationHttpMethod defaults to POST.Lambda property instead of Uri and AuthorizerUri
respectively - for that you can define just a Lambda function name (can be a reference or even stage variable).application/json, it's $schema to http://json-schema.org/draft-04/schema# and
title to its template identifier.Name of validator is not required - it defaults to its template identifier.
RestApis:
# ApiGatewayV1
V1:
Name: "MyTestApi"
Authorizers:
TokenAuthorizer:
Type: "TOKEN"
Lambda: "${stageVariables.AuthorizerLambda}"
IdentitySource: "method.request.header.Authorization"
AuthorizerResultTtlInSeconds: 3600
Models:
AccountInfo:
Schema:
type: "object"
properties:
accountName:
type: string
required:
- "accountName"
additionalProperties: false
Resources:
/accounts:
/{accountId}:
"@DELETE":
Authorizer: "TokenAuthorizer"
RequestValidator: "PARAMETERS_ONLY"
RequestParameters:
method.request.header.Authorization: true
method.request.path.accountId: true
Integration:
Lambda: "${stageVariables.AccountDeletionTarget}"
Credentials: !GetAtt "ApiGatewayRole.Arn"
RequestTemplates:
application/json: |
{
"tenantId": "$context.authorizer.tenantId",
"accountId": "$input.params('accountId')"
}
IntegrationResponses:
"202": {}
PassthroughBehavior: "NEVER"
/keys:
/{apiKey}:
"@PUT":
Authorizer: "TokenAuthorizer"
RequestValidator: "BODY_AND_PARAMETERS"
RequestModels:
application/json: !Ref "RestApi:V1:Model:AccountInfo"
RequestParameters:
method.request.header.Authorization: true
method.request.path.accountId: true
method.request.path.apiKey: true
Integration:
Lambda: "${stageVariables.AccountKeySavingTarget}"
Credentials: !GetAtt "ApiGatewayRole.Arn"
RequestTemplates:
application/json: |
{
"tenantId": "$context.authorizer.tenantId",
"accountId": "$input.params('accountId')",
"apiKey": "$input.params('apiKey')",
"accountInfo": $input.json('$')
}
IntegrationResponses:
"202": {}
PassthroughBehavior: "NEVER"
Outputs:
RestApiId:
Description: "API Gateway ID."
Value: !Ref "RestApi:V1"