Skip to content

Commit 0fbcafa

Browse files
qingchmyan12125HarshCasperPranav016CoshUS
authored
Release/v1.36.0 (#2022)
* chore: don't install integration tests (#1964) * Remove unnecessary use of comprehension (#1805) * fix: Grammatical error in README.md (#1965) * fix: Added SAR Support Check (#1972) * Added SAR Support Check * Added docstring and Removed Instance Initialization for Class Method * update pyyaml version to get the security update (#1974) * Issue 1508 remove check requiring identity to be required if ReauthorizeEvery equals zero (#1577) * remove check requiring identity to be required Check removed to avoid must specify Identity with at least one of Headers, QueryStrings, StageVariables, or Context. error. This is allowed to be removed from aws console. * set identity to empty dictionary Revert back removal of code section and set identity to empty dictionary instead when function_payload_type is "REQUEST" and no identity defined. * use the correct identity variable fix issue catched by unit test. * Update apigateway.py just set the identity to None * undo change. * remove extra spaces * remove some more spaces * Update test_translator.py remove from test case error_api_invalid_auth as this should be valid. * make the Lambda Authorizer is optional if the authorization caching is not enabled (reference https://docs.aws.amazon.com/apigateway/api-reference/resource/authorizer/#identitySource) * add unit testing to cover the InvalidResourceException in case if the identity values are not exist, and not cached * black reformat Co-authored-by: Mohamed Elasmar <[email protected]> * fix the request parameter parsing, the value can contain dots (#1975) * fix the request parameter parsing, the value can contain dots * fix the unit test for python 2.7 * use built in split, instead of concatenating the string * refactor: Optimize shared API usage plan handling (#1973) * fix: use instance variables for generating shared api usage plan * add extra log statements * fix: Added SAR Support Check (#1972) * Added SAR Support Check * Added docstring and Removed Instance Initialization for Class Method * set log level explicitly * update pyyaml version to get the security update (#1974) * fix: use instance variables for generating shared api usage plan * add extra log statements * set log level explicitly * black formatting * black formatting Co-authored-by: Cosh_ <[email protected]> Co-authored-by: Mohamed Elasmar <[email protected]> * Documentation: fix incorrect header (#1979) Fixed the incorrectly formatted header for HTTP API section * fix: mutable default values in method definitions (#1997) * fix: remove explicit logging level set in single module (#1998) * fix: Crash when using an invalid method in open api (#2001) When customers use auth and define an invalid method in the open api definition, SAM would return a 'server error'. This was actually due to SAM attempting to get the method from the path. If the method was not a supported method and non-lowercase, SAM would attempt to fetch the lower case method and crash with a KeyError. This PR addresses that by checking for the valid methods supported. Co-authored-by: Jacob Fuss <[email protected]> * feat: Resource level attributes support (#2008) * Fix for invalid MQ event source managed policy * Fix for invalid managed policy for MQ, included support for new MQ event source property, updated test cases * Black reformatting * Test case changes * Changed policy name * Modified test cases with new policy name * Added resource attributes and unit tests * Resource attributes initial work * Passthrough attributes for some resources, updated some tests * Resolve merge conflicts * Fixed a typo * Modified implicit api plugin for resource attributes support * Partial update of the tests * Partially updated test cases, black reformatted * Partially updated test templates * Partially updated test templates * Partially updated test templates * Added event bridge support for passthrough resource attributes * Partially updated test templates (up to function with amq kms) * Partially updated test templates (up to sns) * Partially updated test templates (all the ones left) * Prevented passthrough resource attributes from changing layer version hashes * Added test to verify resource passthrough precedence for implicit api * Modified tests related to lambda layer to revert the hash changes, keeping the hash the same with resource attributes added * fix: mutable default values in method definitions (#1997) * fix: remove explicit logging level set in single module (#1998) * run automated tests for resource level attribute support * Skipping metadata in layer hashing * Refactored the classes for TestTranslatorEndToEnd and TestResourceLevelAttributes to share the same parent class * Added new translator tests for version and layer resources * Added new unit tests * Removed after transform resource plugin * Black reformatting * Refactoring implicit api plugin support for DeletionPolicy and UpdateReplacePolicy * Refactoring to improve code quality * Added simple documentation * Black reformatting * Added input template that was missing * Refactoring: use sets instead of lists for implicit api plugin * Changing import to be compatible with py2.7 * Changing test deployment hashes to their actual values Co-authored-by: Mehmet Nuri Deveci <[email protected]> * fix: Fail when Intrinsics are in SourceVPC list instead of IntrinsicsSourceVPC (#1999) * chore: bump version to 1.36.0 (#2014) * Revert "fix: Crash when using an invalid method in open api (#2001)" (#2021) This reverts commit d57b132. Co-authored-by: Chih-Hsuan Yen <[email protected]> Co-authored-by: Harsh Mishra <[email protected]> Co-authored-by: Pranav <[email protected]> Co-authored-by: Cosh_ <[email protected]> Co-authored-by: Mohamed Elasmar <[email protected]> Co-authored-by: daftster <[email protected]> Co-authored-by: Mohamed Elasmar <[email protected]> Co-authored-by: Mehmet Nuri Deveci <[email protected]> Co-authored-by: Ben <[email protected]> Co-authored-by: Jacob Fuss <[email protected]> Co-authored-by: Jacob Fuss <[email protected]>
1 parent c5d0ed2 commit 0fbcafa

File tree

72 files changed

+4108
-1648
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

72 files changed

+4108
-1648
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ environment that lets you locally build, test, debug, and deploy applications de
5252

5353
## What is this Github repository? 💻
5454

55-
This GitHub repository contains the SAM Specification, the Python code that translates SAM templates into AWS CloudFormation stacks and lots of examples applications.
55+
This GitHub repository contains the SAM Specification, the Python code that translates SAM templates into AWS CloudFormation stacks and lots of example applications.
5656
In the words of SAM developers:
5757

5858
> SAM Translator is the Python code that deploys SAM templates via AWS CloudFormation. Source code is high quality (95% unit test coverage),

docs/internals/generated_resources.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ AWS::Lambda::Permission MyFunction\ **ThumbnailApi**\ Permission\ **P
129129
NOTE: ``ServerlessRestApi*`` resources are generated one per stack.
130130

131131
HTTP API
132-
^^^
132+
^^^^
133133
This is called an "Implicit HTTP API". There can be many functions in the template that define these APIs. Behind the
134134
scenes, SAM will collect all implicit HTTP APIs from all Functions in the template, generate an OpenApi doc, and create an
135135
implicit ``AWS::Serverless::HttpApi`` using this OpenApi. This API defaults to a StageName called "$default" that cannot be

requirements/dev.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ flake8~=3.8.4
33
tox~=3.20.1
44
pytest-cov~=2.10.1
55
pylint>=1.7.2,<2.0
6-
pyyaml~=5.3.1
6+
pyyaml~=5.4
77

88
# Test requirements
99
pytest~=6.1.1; python_version >= '3.6'

samtranslator/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "1.35.0"
1+
__version__ = "1.36.0"

samtranslator/intrinsics/resolver.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99

1010
class IntrinsicsResolver(object):
11-
def __init__(self, parameters, supported_intrinsics=DEFAULT_SUPPORTED_INTRINSICS):
11+
def __init__(self, parameters, supported_intrinsics=None):
1212
"""
1313
Instantiate the resolver
1414
:param dict parameters: Map of parameter names to their values
@@ -17,6 +17,8 @@ def __init__(self, parameters, supported_intrinsics=DEFAULT_SUPPORTED_INTRINSICS
1717
:raises TypeError: If parameters or the supported_intrinsics arguments are invalid
1818
"""
1919

20+
if supported_intrinsics is None:
21+
supported_intrinsics = DEFAULT_SUPPORTED_INTRINSICS
2022
if parameters is None or not isinstance(parameters, dict):
2123
raise InvalidDocumentException(
2224
[InvalidTemplateException("'Mappings' or 'Parameters' is either null or not a valid dictionary.")]

samtranslator/model/__init__.py

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,11 @@ class Resource(object):
4343
property_types = None
4444
_keywords = ["logical_id", "relative_id", "depends_on", "resource_attributes"]
4545

46-
_supported_resource_attributes = ["DeletionPolicy", "UpdatePolicy", "Condition"]
46+
# For attributes in this list, they will be passed into the translated template for the same resource itself.
47+
_supported_resource_attributes = ["DeletionPolicy", "UpdatePolicy", "Condition", "UpdateReplacePolicy", "Metadata"]
48+
# For attributes in this list, they will be passed into the translated template for the same resource,
49+
# as well as all the auto-generated resources that are created from this resource.
50+
_pass_through_attributes = ["Condition", "DeletionPolicy", "UpdateReplacePolicy"]
4751

4852
# Runtime attributes that can be qureied resource. They are CloudFormation attributes like ARN, Name etc that
4953
# will be resolvable at runtime. This map will be implemented by sub-classes to express list of attributes they
@@ -76,6 +80,22 @@ def __init__(self, logical_id, relative_id=None, depends_on=None, attributes=Non
7680
for attr, value in attributes.items():
7781
self.set_resource_attribute(attr, value)
7882

83+
@classmethod
84+
def get_supported_resource_attributes(cls):
85+
"""
86+
A getter method for the supported resource attributes
87+
returns: a tuple that contains the name of all supported resource attributes
88+
"""
89+
return tuple(cls._supported_resource_attributes)
90+
91+
@classmethod
92+
def get_pass_through_attributes(cls):
93+
"""
94+
A getter method for the resource attributes to be passed to auto-generated resources
95+
returns: a tuple that contains the name of all pass through attributes
96+
"""
97+
return tuple(cls._pass_through_attributes)
98+
7999
@classmethod
80100
def from_dict(cls, logical_id, resource_dict, relative_id=None, sam_plugins=None):
81101
"""Constructs a Resource object with the given logical id, based on the given resource dict. The resource dict
@@ -318,9 +338,10 @@ def get_passthrough_resource_attributes(self):
318338
319339
:return: Dictionary of resource attributes.
320340
"""
321-
attributes = None
322-
if "Condition" in self.resource_attributes:
323-
attributes = {"Condition": self.resource_attributes["Condition"]}
341+
attributes = {}
342+
for resource_attribute in self.get_pass_through_attributes():
343+
if resource_attribute in self.resource_attributes:
344+
attributes[resource_attribute] = self.resource_attributes.get(resource_attribute)
324345
return attributes
325346

326347

samtranslator/model/api/api_generator.py

Lines changed: 53 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import logging
12
from collections import namedtuple
23
from six import string_types
34
from samtranslator.model.intrinsics import ref, fnGetAtt
@@ -24,6 +25,8 @@
2425
from samtranslator.translator.arn_generator import ArnGenerator
2526
from samtranslator.model.tags.resource_tagging import get_tag_list
2627

28+
LOG = logging.getLogger(__name__)
29+
2730
_CORS_WILDCARD = "'*'"
2831
CorsProperties = namedtuple(
2932
"_CorsProperties", ["AllowMethods", "AllowHeaders", "AllowOrigin", "MaxAge", "AllowCredentials"]
@@ -52,12 +55,20 @@
5255
GatewayResponseProperties = ["ResponseParameters", "ResponseTemplates", "StatusCode"]
5356

5457

55-
class ApiGenerator(object):
56-
usage_plan_shared = False
57-
stage_keys_shared = list()
58-
api_stages_shared = list()
59-
depends_on_shared = list()
58+
class SharedApiUsagePlan(object):
59+
"""
60+
Collects API information from different API resources in the same template,
61+
so that these information can be used in the shared usage plan
62+
"""
63+
64+
def __init__(self):
65+
self.usage_plan_shared = False
66+
self.stage_keys_shared = list()
67+
self.api_stages_shared = list()
68+
self.depends_on_shared = list()
6069

70+
71+
class ApiGenerator(object):
6172
def __init__(
6273
self,
6374
logical_id,
@@ -69,6 +80,7 @@ def __init__(
6980
definition_uri,
7081
name,
7182
stage_name,
83+
shared_api_usage_plan,
7284
tags=None,
7385
endpoint_configuration=None,
7486
method_settings=None,
@@ -134,6 +146,7 @@ def __init__(
134146
self.models = models
135147
self.domain = domain
136148
self.description = description
149+
self.shared_api_usage_plan = shared_api_usage_plan
137150

138151
def _construct_rest_api(self):
139152
"""Constructs and returns the ApiGateway RestApi.
@@ -617,7 +630,11 @@ def _construct_usage_plan(self, rest_api_stage=None):
617630
# create usage plan for this api only
618631
elif usage_plan_properties.get("CreateUsagePlan") == "PER_API":
619632
usage_plan_logical_id = self.logical_id + "UsagePlan"
620-
usage_plan = ApiGatewayUsagePlan(logical_id=usage_plan_logical_id, depends_on=[self.logical_id])
633+
usage_plan = ApiGatewayUsagePlan(
634+
logical_id=usage_plan_logical_id,
635+
depends_on=[self.logical_id],
636+
attributes=self.passthrough_resource_attributes,
637+
)
621638
api_stages = list()
622639
api_stage = dict()
623640
api_stage["ApiId"] = ref(self.logical_id)
@@ -630,18 +647,21 @@ def _construct_usage_plan(self, rest_api_stage=None):
630647

631648
# create a usage plan for all the Apis
632649
elif create_usage_plan == "SHARED":
650+
LOG.info("Creating SHARED usage plan for all the Apis")
633651
usage_plan_logical_id = "ServerlessUsagePlan"
634-
if self.logical_id not in ApiGenerator.depends_on_shared:
635-
ApiGenerator.depends_on_shared.append(self.logical_id)
652+
if self.logical_id not in self.shared_api_usage_plan.depends_on_shared:
653+
self.shared_api_usage_plan.depends_on_shared.append(self.logical_id)
636654
usage_plan = ApiGatewayUsagePlan(
637-
logical_id=usage_plan_logical_id, depends_on=ApiGenerator.depends_on_shared
655+
logical_id=usage_plan_logical_id,
656+
depends_on=self.shared_api_usage_plan.depends_on_shared,
657+
attributes=self.passthrough_resource_attributes,
638658
)
639659
api_stage = dict()
640660
api_stage["ApiId"] = ref(self.logical_id)
641661
api_stage["Stage"] = ref(rest_api_stage.logical_id)
642-
if api_stage not in ApiGenerator.api_stages_shared:
643-
ApiGenerator.api_stages_shared.append(api_stage)
644-
usage_plan.ApiStages = ApiGenerator.api_stages_shared
662+
if api_stage not in self.shared_api_usage_plan.api_stages_shared:
663+
self.shared_api_usage_plan.api_stages_shared.append(api_stage)
664+
usage_plan.ApiStages = self.shared_api_usage_plan.api_stages_shared
645665

646666
api_key = self._construct_api_key(usage_plan_logical_id, create_usage_plan, rest_api_stage)
647667
usage_plan_key = self._construct_usage_plan_key(usage_plan_logical_id, create_usage_plan, api_key)
@@ -667,20 +687,30 @@ def _construct_api_key(self, usage_plan_logical_id, create_usage_plan, rest_api_
667687
"""
668688
if create_usage_plan == "SHARED":
669689
# create an api key resource for all the apis
690+
LOG.info("Creating api key resource for all the Apis from SHARED usage plan")
670691
api_key_logical_id = "ServerlessApiKey"
671-
api_key = ApiGatewayApiKey(logical_id=api_key_logical_id, depends_on=[usage_plan_logical_id])
692+
api_key = ApiGatewayApiKey(
693+
logical_id=api_key_logical_id,
694+
depends_on=[usage_plan_logical_id],
695+
attributes=self.passthrough_resource_attributes,
696+
)
672697
api_key.Enabled = True
673698
stage_key = dict()
674699
stage_key["RestApiId"] = ref(self.logical_id)
675700
stage_key["StageName"] = ref(rest_api_stage.logical_id)
676-
if stage_key not in ApiGenerator.stage_keys_shared:
677-
ApiGenerator.stage_keys_shared.append(stage_key)
678-
api_key.StageKeys = ApiGenerator.stage_keys_shared
701+
if stage_key not in self.shared_api_usage_plan.stage_keys_shared:
702+
self.shared_api_usage_plan.stage_keys_shared.append(stage_key)
703+
api_key.StageKeys = self.shared_api_usage_plan.stage_keys_shared
679704
# for create_usage_plan = "PER_API"
680705
else:
681706
# create an api key resource for this api
682707
api_key_logical_id = self.logical_id + "ApiKey"
683-
api_key = ApiGatewayApiKey(logical_id=api_key_logical_id, depends_on=[usage_plan_logical_id])
708+
api_key = ApiGatewayApiKey(
709+
logical_id=api_key_logical_id,
710+
depends_on=[usage_plan_logical_id],
711+
attributes=self.passthrough_resource_attributes,
712+
)
713+
# api_key = ApiGatewayApiKey(logical_id=api_key_logical_id, depends_on=[usage_plan_logical_id])
684714
api_key.Enabled = True
685715
stage_keys = list()
686716
stage_key = dict()
@@ -705,7 +735,12 @@ def _construct_usage_plan_key(self, usage_plan_logical_id, create_usage_plan, ap
705735
# create a mapping between api key and the usage plan
706736
usage_plan_key_logical_id = self.logical_id + "UsagePlanKey"
707737

708-
usage_plan_key = ApiGatewayUsagePlanKey(logical_id=usage_plan_key_logical_id, depends_on=[api_key.logical_id])
738+
usage_plan_key = ApiGatewayUsagePlanKey(
739+
logical_id=usage_plan_key_logical_id,
740+
depends_on=[api_key.logical_id],
741+
attributes=self.passthrough_resource_attributes,
742+
)
743+
# usage_plan_key = ApiGatewayUsagePlanKey(logical_id=usage_plan_key_logical_id, depends_on=[api_key.logical_id])
709744
usage_plan_key.KeyId = ref(api_key.logical_id)
710745
usage_plan_key.KeyType = "API_KEY"
711746
usage_plan_key.UsagePlanId = ref(usage_plan_logical_id)

samtranslator/model/apigateway.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -231,8 +231,10 @@ def __init__(
231231
function_payload_type=None,
232232
function_invoke_role=None,
233233
is_aws_iam_authorizer=False,
234-
authorization_scopes=[],
234+
authorization_scopes=None,
235235
):
236+
if authorization_scopes is None:
237+
authorization_scopes = []
236238
if function_payload_type not in ApiGatewayAuthorizer._VALID_FUNCTION_PAYLOAD_TYPES:
237239
raise InvalidResourceException(
238240
api_logical_id,
@@ -267,8 +269,9 @@ def _is_missing_identity_source(self, identity):
267269
query_strings = identity.get("QueryStrings")
268270
stage_variables = identity.get("StageVariables")
269271
context = identity.get("Context")
272+
ttl = identity.get("ReauthorizeEvery")
270273

271-
if not headers and not query_strings and not stage_variables and not context:
274+
if (ttl is None or int(ttl) > 0) and not headers and not query_strings and not stage_variables and not context:
272275
return True
273276

274277
return False
@@ -311,7 +314,9 @@ def generate_swagger(self):
311314
swagger[APIGATEWAY_AUTHORIZER_KEY]["authorizerCredentials"] = function_invoke_role
312315

313316
if self._get_function_payload_type() == "REQUEST":
314-
swagger[APIGATEWAY_AUTHORIZER_KEY]["identitySource"] = self._get_identity_source()
317+
identity_source = self._get_identity_source()
318+
if identity_source:
319+
swagger[APIGATEWAY_AUTHORIZER_KEY]["identitySource"] = self._get_identity_source()
315320

316321
# Authorizer Validation Expression is only allowed on COGNITO_USER_POOLS and LAMBDA_TOKEN
317322
is_lambda_token_authorizer = authorizer_type == "LAMBDA" and self._get_function_payload_type() == "TOKEN"

samtranslator/model/eventbridge_utils.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@
44

55
class EventBridgeRuleUtils:
66
@staticmethod
7-
def create_dead_letter_queue_with_policy(rule_logical_id, rule_arn, queue_logical_id=None):
7+
def create_dead_letter_queue_with_policy(rule_logical_id, rule_arn, queue_logical_id=None, attributes=None):
88
resources = []
99

10-
queue = SQSQueue(queue_logical_id or rule_logical_id + "Queue")
10+
queue = SQSQueue(queue_logical_id or rule_logical_id + "Queue", attributes=attributes)
1111
dlq_queue_arn = queue.get_runtime_attr("arn")
1212
dlq_queue_url = queue.get_runtime_attr("queue_url")
1313

1414
# grant necessary permission to Eventbridge Rule resource for sending messages to dead-letter queue
15-
policy = SQSQueuePolicy(rule_logical_id + "QueuePolicy")
15+
policy = SQSQueuePolicy(rule_logical_id + "QueuePolicy", attributes=attributes)
1616
policy.PolicyDocument = SQSQueuePolicies.eventbridge_dlq_send_message_resource_based_policy(
1717
rule_arn, dlq_queue_arn
1818
)
@@ -41,14 +41,14 @@ def validate_dlq_config(source_logical_id, dead_letter_config):
4141
raise InvalidEventException(source_logical_id, "No 'Arn' or 'Type' property provided for DeadLetterConfig")
4242

4343
@staticmethod
44-
def get_dlq_queue_arn_and_resources(cw_event_source, source_arn):
44+
def get_dlq_queue_arn_and_resources(cw_event_source, source_arn, attributes):
4545
"""returns dlq queue arn and dlq_resources, assuming cw_event_source.DeadLetterConfig has been validated"""
4646
dlq_queue_arn = cw_event_source.DeadLetterConfig.get("Arn")
4747
if dlq_queue_arn is not None:
4848
return dlq_queue_arn, []
4949
queue_logical_id = cw_event_source.DeadLetterConfig.get("QueueLogicalId")
5050
dlq_resources = EventBridgeRuleUtils.create_dead_letter_queue_with_policy(
51-
cw_event_source.logical_id, source_arn, queue_logical_id
51+
cw_event_source.logical_id, source_arn, queue_logical_id, attributes
5252
)
5353
dlq_queue_arn = dlq_resources[0].get_runtime_attr("arn")
5454
return dlq_queue_arn, dlq_resources

samtranslator/model/eventsources/cloudwatchlogs.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,13 @@ def get_source_arn(self):
4343
)
4444

4545
def get_subscription_filter(self, function, permission):
46-
subscription_filter = SubscriptionFilter(self.logical_id, depends_on=[permission.logical_id])
46+
subscription_filter = SubscriptionFilter(
47+
self.logical_id,
48+
depends_on=[permission.logical_id],
49+
attributes=function.get_passthrough_resource_attributes(),
50+
)
4751
subscription_filter.LogGroupName = self.LogGroupName
4852
subscription_filter.FilterPattern = self.FilterPattern
4953
subscription_filter.DestinationArn = function.get_runtime_attr("arn")
50-
if "Condition" in function.resource_attributes:
51-
subscription_filter.set_resource_attribute("Condition", function.resource_attributes["Condition"])
5254

5355
return subscription_filter

0 commit comments

Comments
 (0)