Skip to content

Commit

Permalink
merge: release 1.42.0
Browse files Browse the repository at this point in the history
release: 1.42.0
  • Loading branch information
mndeveci committed Nov 23, 2021
2 parents e214dac + 5d3ea53 commit db25683
Show file tree
Hide file tree
Showing 24 changed files with 735 additions and 1 deletion.
@@ -0,0 +1,30 @@
from parameterized import parameterized

from integration.helpers.base_test import BaseTest


class TestApiWithDisableExecuteApiEndpoint(BaseTest):
@parameterized.expand(
[
("combination/api_with_disable_execute_api_endpoint", True),
("combination/api_with_disable_execute_api_endpoint", False),
]
)
def test_end_point_configuration(self, file_name, disable_value):
parameters = [
{
"ParameterKey": "DisableExecuteApiEndpointValue",
"ParameterValue": "true" if disable_value else "false",
"UsePreviousValue": False,
"ResolvedValue": "string",
}
]

self.create_and_verify_stack(file_name, parameters)

rest_api_id = self.get_physical_id_by_type("AWS::ApiGateway::RestApi")
apigw_client = self.client_provider.api_client

response = apigw_client.get_rest_api(restApiId=rest_api_id)
api_result = response["disableExecuteApiEndpoint"]
self.assertEqual(api_result, disable_value)
@@ -0,0 +1,8 @@
[
{"LogicalResourceId": "RestApiGateway", "ResourceType": "AWS::ApiGateway::RestApi"},
{"LogicalResourceId": "RestApiGatewayDeployment", "ResourceType": "AWS::ApiGateway::Deployment"},
{"LogicalResourceId": "RestApiGatewayProdStage", "ResourceType": "AWS::ApiGateway::Stage"},
{"LogicalResourceId": "RestApiFunction", "ResourceType": "AWS::Lambda::Function"},
{"LogicalResourceId": "RestApiFunctionIamPermissionProd", "ResourceType": "AWS::Lambda::Permission"},
{"LogicalResourceId": "RestApiFunctionRole", "ResourceType": "AWS::IAM::Role"}
]
@@ -0,0 +1,39 @@
Parameters:
DisableExecuteApiEndpointValue:
Description: Variable to define if client can access default API endpoint.
Type: String
AllowedValues: [true, false]

Resources:
RestApiGateway:
Type: AWS::Serverless::Api
Properties:
StageName: Prod
DisableExecuteApiEndpoint:
Ref: DisableExecuteApiEndpointValue

RestApiFunction:
Type: AWS::Serverless::Function
Properties:
InlineCode: |
exports.handler = async (event) => {
const response = {
statusCode: 200,
body: JSON.stringify('Hello from Lambda!'),
};
return response;
};
Handler: index.handler
Runtime: nodejs12.x
Events:
Iam:
Type: Api
Properties:
RestApiId: !Ref RestApiGateway
Method: GET
Path: /
Outputs:
ApiUrl:
Description: "API endpoint URL for Prod environment"
Value:
Fn::Sub: 'https://${RestApiGateway}.execute-api.${AWS::Region}.${AWS::URLSuffix}/Prod/'
2 changes: 1 addition & 1 deletion samtranslator/__init__.py
@@ -1 +1 @@
__version__ = "1.41.0"
__version__ = "1.42.0"
24 changes: 24 additions & 0 deletions samtranslator/model/api/api_generator.py
Expand Up @@ -170,6 +170,7 @@ def __init__(
method_settings=None,
binary_media=None,
minimum_compression_size=None,
disable_execute_api_endpoint=None,
cors=None,
auth=None,
gateway_responses=None,
Expand Down Expand Up @@ -218,6 +219,7 @@ def __init__(
self.method_settings = method_settings
self.binary_media = binary_media
self.minimum_compression_size = minimum_compression_size
self.disable_execute_api_endpoint = disable_execute_api_endpoint
self.cors = cors
self.auth = auth
self.gateway_responses = gateway_responses
Expand Down Expand Up @@ -290,8 +292,27 @@ def _construct_rest_api(self):
if self.mode:
rest_api.Mode = self.mode

if self.disable_execute_api_endpoint is not None:
self._add_endpoint_extension()

return rest_api

def _add_endpoint_extension(self):
"""Add disableExecuteApiEndpoint if it is set in SAM
Note:
If neither DefinitionUri nor DefinitionBody are specified,
SAM will generate a openapi definition body based on template configuration.
https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-api.html#sam-api-definitionbody
For this reason, we always put DisableExecuteApiEndpoint into openapi object irrespective of origin of DefinitionBody.
"""
if self.disable_execute_api_endpoint and not self.definition_body:
raise InvalidResourceException(
self.logical_id, "DisableExecuteApiEndpoint works only within 'DefinitionBody' property."
)
editor = SwaggerEditor(self.definition_body)
editor.add_disable_execute_api_endpoint_extension(self.disable_execute_api_endpoint)
self.definition_body = editor.swagger

def _construct_body_s3_dict(self):
"""Constructs the RestApi's `BodyS3Location property`_, from the SAM Api's DefinitionUri property.
Expand Down Expand Up @@ -444,6 +465,9 @@ def _construct_api_domain(self, rest_api):
if self.domain.get("SecurityPolicy", None):
domain.SecurityPolicy = self.domain["SecurityPolicy"]

if self.domain.get("OwnershipVerificationCertificateArn", None):
domain.OwnershipVerificationCertificateArn = self.domain["OwnershipVerificationCertificateArn"]

# Create BasepathMappings
if self.domain.get("BasePath") and isinstance(self.domain.get("BasePath"), string_types):
basepaths = [self.domain.get("BasePath")]
Expand Down
6 changes: 6 additions & 0 deletions samtranslator/model/api/http_api_generator.py
Expand Up @@ -253,6 +253,12 @@ def _construct_api_domain(self, http_api):
"EndpointConfiguration for Custom Domains must be one of {}.".format(["REGIONAL"]),
)
domain_config["EndpointType"] = endpoint

if self.domain.get("OwnershipVerificationCertificateArn", None):
domain_config["OwnershipVerificationCertificateArn"] = self.domain.get(
"OwnershipVerificationCertificateArn"
)

domain_config["CertificateArn"] = self.domain.get("CertificateArn")
if self.domain.get("SecurityPolicy", None):
domain_config["SecurityPolicy"] = self.domain.get("SecurityPolicy")
Expand Down
1 change: 1 addition & 0 deletions samtranslator/model/apigateway.py
Expand Up @@ -169,6 +169,7 @@ class ApiGatewayDomainName(Resource):
"MutualTlsAuthentication": PropertyType(False, is_type(dict)),
"SecurityPolicy": PropertyType(False, is_str()),
"CertificateArn": PropertyType(False, is_str()),
"OwnershipVerificationCertificateArn": PropertyType(False, is_str()),
}


Expand Down
20 changes: 20 additions & 0 deletions samtranslator/model/eventsources/pull.py
Expand Up @@ -21,6 +21,9 @@ class PullEventSource(ResourceMacro):
:cvar str policy_arn: The ARN of the AWS managed role policy corresponding to this pull event source
"""

# Event types that support `FilterCriteria`, stored as a list to keep the alphabetical order
RESOURCE_TYPES_WITH_EVENT_FILTERING = ["DynamoDB", "Kinesis", "SQS"]

resource_type = None
requires_stream_queue_broker = True
property_types = {
Expand All @@ -43,6 +46,7 @@ class PullEventSource(ResourceMacro):
"TumblingWindowInSeconds": PropertyType(False, is_type(int)),
"FunctionResponseTypes": PropertyType(False, is_type(list)),
"KafkaBootstrapServers": PropertyType(False, is_type(list)),
"FilterCriteria": PropertyType(False, is_type(dict)),
}

def get_policy_arn(self):
Expand Down Expand Up @@ -102,6 +106,8 @@ def to_cloudformation(self, **kwargs):
lambda_eventsourcemapping.SourceAccessConfigurations = self.SourceAccessConfigurations
lambda_eventsourcemapping.TumblingWindowInSeconds = self.TumblingWindowInSeconds
lambda_eventsourcemapping.FunctionResponseTypes = self.FunctionResponseTypes
lambda_eventsourcemapping.FilterCriteria = self.FilterCriteria
self._validate_filter_criteria()

if self.KafkaBootstrapServers:
lambda_eventsourcemapping.SelfManagedEventSource = {
Expand Down Expand Up @@ -169,6 +175,20 @@ def _link_policy(self, role, destination_config_policy=None):
if not destination_config_policy.get("PolicyDocument") in [d["PolicyDocument"] for d in role.Policies]:
role.Policies.append(destination_config_policy)

def _validate_filter_criteria(self):
if not self.FilterCriteria or is_intrinsic(self.FilterCriteria):
return
if self.resource_type not in self.RESOURCE_TYPES_WITH_EVENT_FILTERING:
raise InvalidEventException(
self.relative_id,
"FilterCriteria is only available for {} events.".format(
", ".join(self.RESOURCE_TYPES_WITH_EVENT_FILTERING)
),
)
# FilterCriteria is either empty or only has "Filters"
if list(self.FilterCriteria.keys()) not in [[], ["Filters"]]:
raise InvalidEventException(self.relative_id, "FilterCriteria field has a wrong format")


class Kinesis(PullEventSource):
"""Kinesis event source."""
Expand Down
1 change: 1 addition & 0 deletions samtranslator/model/lambda_.py
Expand Up @@ -79,6 +79,7 @@ class LambdaEventSourceMapping(Resource):
"TumblingWindowInSeconds": PropertyType(False, is_type(int)),
"FunctionResponseTypes": PropertyType(False, is_type(list)),
"SelfManagedEventSource": PropertyType(False, is_type(dict)),
"FilterCriteria": PropertyType(False, is_type(dict)),
}

runtime_attrs = {"name": lambda self: ref(self.logical_id)}
Expand Down
2 changes: 2 additions & 0 deletions samtranslator/model/sam_resources.py
Expand Up @@ -878,6 +878,7 @@ class SamApi(SamResourceMacro):
"Domain": PropertyType(False, is_type(dict)),
"Description": PropertyType(False, is_str()),
"Mode": PropertyType(False, is_str()),
"DisableExecuteApiEndpoint": PropertyType(False, is_type(bool)),
}

referable_properties = {
Expand Down Expand Up @@ -925,6 +926,7 @@ def to_cloudformation(self, **kwargs):
method_settings=self.MethodSettings,
binary_media=self.BinaryMediaTypes,
minimum_compression_size=self.MinimumCompressionSize,
disable_execute_api_endpoint=self.DisableExecuteApiEndpoint,
cors=self.Cors,
auth=self.Auth,
gateway_responses=self.GatewayResponses,
Expand Down
14 changes: 14 additions & 0 deletions samtranslator/swagger/swagger.py
Expand Up @@ -25,6 +25,7 @@ class SwaggerEditor(object):
_X_ANY_METHOD = "x-amazon-apigateway-any-method"
_X_APIGW_REQUEST_VALIDATORS = "x-amazon-apigateway-request-validators"
_X_APIGW_REQUEST_VALIDATOR = "x-amazon-apigateway-request-validator"
_X_ENDPOINT_CONFIG = "x-amazon-apigateway-endpoint-configuration"
_CACHE_KEY_PARAMETERS = "cacheKeyParameters"
# https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
_ALL_HTTP_METHODS = ["OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE", "PATCH"]
Expand Down Expand Up @@ -111,6 +112,19 @@ def get_method_contents(self, method):
return method[self._CONDITIONAL_IF][1:]
return [method]

def add_disable_execute_api_endpoint_extension(self, disable_execute_api_endpoint):
"""Add endpoint configuration to _X_APIGW_ENDPOINT_CONFIG in open api definition as extension
Following this guide:
https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-swagger-extensions-endpoint-configuration.html
:param boolean disable_execute_api_endpoint: Specifies whether clients can invoke your API by using the default execute-api endpoint.
"""
if not self._doc.get(self._X_ENDPOINT_CONFIG):
self._doc[self._X_ENDPOINT_CONFIG] = {}

DISABLE_EXECUTE_API_ENDPOINT = "disableExecuteApiEndpoint"
set_disable_api_endpoint = {DISABLE_EXECUTE_API_ENDPOINT: disable_execute_api_endpoint}
self._doc[self._X_ENDPOINT_CONFIG].update(set_disable_api_endpoint)

def has_integration(self, path, method):
"""
Checks if an API Gateway integration is already present at the given path/method
Expand Down
12 changes: 12 additions & 0 deletions samtranslator/validator/sam_schema/definitions/api.json
Expand Up @@ -98,6 +98,12 @@
"intrinsic"
]
},
"DisableExecuteApiEndpoint": {
"type": [
"boolean",
"intrinsic"
]
},
"Domain": {
"$ref": "#definitions/AWS::Serverless::Api.DomainConfiguration"
},
Expand Down Expand Up @@ -443,6 +449,12 @@
"string",
"intrinsic"
]
},
"OwnershipVerificationCertificateArn": {
"type": [
"string",
"intrinsic"
]
}
},
"required": [
Expand Down
56 changes: 56 additions & 0 deletions tests/translator/input/error_event_filtering.yaml
@@ -0,0 +1,56 @@
Resources:
WrongFilterName:
Type: AWS::Serverless::Function
Properties:
CodeUri: s3://sam-demo-bucket/filtered_events.zip
Handler: index.handler
Runtime: nodejs16.x
Events:
DynamoDBStreamEvent:
Type: DynamoDB
Properties:
Stream: !GetAtt DynamoDBTable.StreamArn
StartingPosition: TRIM_HORIZON
FilterCriteria:
FiltersToUse:
- Pattern: '{"name": "value"}'

NotSupportedPullEvent:
Type: AWS::Serverless::Function
Properties:
CodeUri: s3://sam-demo-bucket/filtered_events.zip
Handler: index.handler
Runtime: nodejs16.x
Events:
KafkaEvent:
Type: MSK
Properties:
StartingPosition: LATEST
Stream: arn:aws:kafka:us-east-1:012345678012:cluster/clusterName/abcdefab-1234-abcd-5678-cdef0123ab01-2
Topics:
- MyTopic
FilterCriteria:
Filters:
- Pattern: '{"name": "value"}'


NotSupportedPushEvent:
Type: AWS::Serverless::Function
Properties:
CodeUri: s3://sam-demo-bucket/filtered_events.zip
Handler: index.handler
Runtime: nodejs16.x
Events:
SNSEvent:
Type: SNS
Properties:
Topic: !GetAtt MySnsTopic.Arn
FilterCriteria:
Filters:
- Pattern: '{"name": "value"}'

DynamoDBTable:
Type: AWS::DynamoDB::Table

MySnsTopic:
Type: AWS::SNS::Topic
49 changes: 49 additions & 0 deletions tests/translator/input/function_with_event_filtering.yaml
@@ -0,0 +1,49 @@
Resources:
FilteredEventsFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: s3://sam-demo-bucket/filtered_events.zip
Handler: index.handler
Runtime: nodejs16.x
Events:
KinesisStream:
Type: Kinesis
Properties:
Stream: !GetAtt KinesisStream.Arn
StartingPosition: LATEST
FilterCriteria:
Filters:
- Pattern: '{"name": "value"}'
- Pattern: '{"name2": "value2"}'
DynamoDBStreamEvent:
Type: DynamoDB
Properties:
Stream: !GetAtt DynamoDBTable.StreamArn
StartingPosition: TRIM_HORIZON
FilterCriteria:
Filters:
- Pattern: '{
"dynamodb": {
"NewImage": {
"value": { "S": ["test"] }
}
}
}'
MySqsQueue:
Type: SQS
Properties:
Queue: !GetAtt MySqsQueue.Arn
FilterCriteria:
Filters:
- Pattern: '{"name": "value"}'

KinesisStream:
Type: AWS::Kinesis::Stream
Properties:
ShardCount: 1

DynamoDBTable:
Type: AWS::DynamoDB::Table

MySqsQueue:
Type: AWS::SQS::Queue

0 comments on commit db25683

Please sign in to comment.