Skip to content

Commit 8010796

Browse files
qingchmmndeveciCoshUSmoelasmar
authored
Release/v1.36.0 (#2037)
* 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]> * 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]> * chore: bump version to 1.36.0 (#2014) Co-authored-by: Mehmet Nuri Deveci <[email protected]> Co-authored-by: Cosh_ <[email protected]> Co-authored-by: Mohamed Elasmar <[email protected]>
1 parent c5d0ed2 commit 8010796

Some content is hidden

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

42 files changed

+3454
-1572
lines changed

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/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: 54 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,9 @@
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+
LOG.setLevel(logging.INFO)
30+
2731
_CORS_WILDCARD = "'*'"
2832
CorsProperties = namedtuple(
2933
"_CorsProperties", ["AllowMethods", "AllowHeaders", "AllowOrigin", "MaxAge", "AllowCredentials"]
@@ -52,12 +56,20 @@
5256
GatewayResponseProperties = ["ResponseParameters", "ResponseTemplates", "StatusCode"]
5357

5458

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

71+
72+
class ApiGenerator(object):
6173
def __init__(
6274
self,
6375
logical_id,
@@ -69,6 +81,7 @@ def __init__(
6981
definition_uri,
7082
name,
7183
stage_name,
84+
shared_api_usage_plan,
7285
tags=None,
7386
endpoint_configuration=None,
7487
method_settings=None,
@@ -134,6 +147,7 @@ def __init__(
134147
self.models = models
135148
self.domain = domain
136149
self.description = description
150+
self.shared_api_usage_plan = shared_api_usage_plan
137151

138152
def _construct_rest_api(self):
139153
"""Constructs and returns the ApiGateway RestApi.
@@ -617,7 +631,11 @@ def _construct_usage_plan(self, rest_api_stage=None):
617631
# create usage plan for this api only
618632
elif usage_plan_properties.get("CreateUsagePlan") == "PER_API":
619633
usage_plan_logical_id = self.logical_id + "UsagePlan"
620-
usage_plan = ApiGatewayUsagePlan(logical_id=usage_plan_logical_id, depends_on=[self.logical_id])
634+
usage_plan = ApiGatewayUsagePlan(
635+
logical_id=usage_plan_logical_id,
636+
depends_on=[self.logical_id],
637+
attributes=self.passthrough_resource_attributes,
638+
)
621639
api_stages = list()
622640
api_stage = dict()
623641
api_stage["ApiId"] = ref(self.logical_id)
@@ -630,18 +648,21 @@ def _construct_usage_plan(self, rest_api_stage=None):
630648

631649
# create a usage plan for all the Apis
632650
elif create_usage_plan == "SHARED":
651+
LOG.info("Creating SHARED usage plan for all the Apis")
633652
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)
653+
if self.logical_id not in self.shared_api_usage_plan.depends_on_shared:
654+
self.shared_api_usage_plan.depends_on_shared.append(self.logical_id)
636655
usage_plan = ApiGatewayUsagePlan(
637-
logical_id=usage_plan_logical_id, depends_on=ApiGenerator.depends_on_shared
656+
logical_id=usage_plan_logical_id,
657+
depends_on=self.shared_api_usage_plan.depends_on_shared,
658+
attributes=self.passthrough_resource_attributes,
638659
)
639660
api_stage = dict()
640661
api_stage["ApiId"] = ref(self.logical_id)
641662
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
663+
if api_stage not in self.shared_api_usage_plan.api_stages_shared:
664+
self.shared_api_usage_plan.api_stages_shared.append(api_stage)
665+
usage_plan.ApiStages = self.shared_api_usage_plan.api_stages_shared
645666

646667
api_key = self._construct_api_key(usage_plan_logical_id, create_usage_plan, rest_api_stage)
647668
usage_plan_key = self._construct_usage_plan_key(usage_plan_logical_id, create_usage_plan, api_key)
@@ -667,20 +688,30 @@ def _construct_api_key(self, usage_plan_logical_id, create_usage_plan, rest_api_
667688
"""
668689
if create_usage_plan == "SHARED":
669690
# create an api key resource for all the apis
691+
LOG.info("Creating api key resource for all the Apis from SHARED usage plan")
670692
api_key_logical_id = "ServerlessApiKey"
671-
api_key = ApiGatewayApiKey(logical_id=api_key_logical_id, depends_on=[usage_plan_logical_id])
693+
api_key = ApiGatewayApiKey(
694+
logical_id=api_key_logical_id,
695+
depends_on=[usage_plan_logical_id],
696+
attributes=self.passthrough_resource_attributes,
697+
)
672698
api_key.Enabled = True
673699
stage_key = dict()
674700
stage_key["RestApiId"] = ref(self.logical_id)
675701
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
702+
if stage_key not in self.shared_api_usage_plan.stage_keys_shared:
703+
self.shared_api_usage_plan.stage_keys_shared.append(stage_key)
704+
api_key.StageKeys = self.shared_api_usage_plan.stage_keys_shared
679705
# for create_usage_plan = "PER_API"
680706
else:
681707
# create an api key resource for this api
682708
api_key_logical_id = self.logical_id + "ApiKey"
683-
api_key = ApiGatewayApiKey(logical_id=api_key_logical_id, depends_on=[usage_plan_logical_id])
709+
api_key = ApiGatewayApiKey(
710+
logical_id=api_key_logical_id,
711+
depends_on=[usage_plan_logical_id],
712+
attributes=self.passthrough_resource_attributes,
713+
)
714+
# api_key = ApiGatewayApiKey(logical_id=api_key_logical_id, depends_on=[usage_plan_logical_id])
684715
api_key.Enabled = True
685716
stage_keys = list()
686717
stage_key = dict()
@@ -705,7 +736,12 @@ def _construct_usage_plan_key(self, usage_plan_logical_id, create_usage_plan, ap
705736
# create a mapping between api key and the usage plan
706737
usage_plan_key_logical_id = self.logical_id + "UsagePlanKey"
707738

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

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

samtranslator/model/eventsources/pull.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,9 @@ def to_cloudformation(self, **kwargs):
6060

6161
resources = []
6262

63-
lambda_eventsourcemapping = LambdaEventSourceMapping(self.logical_id)
63+
lambda_eventsourcemapping = LambdaEventSourceMapping(
64+
self.logical_id, attributes=function.get_passthrough_resource_attributes()
65+
)
6466
resources.append(lambda_eventsourcemapping)
6567

6668
try:
@@ -122,9 +124,6 @@ def to_cloudformation(self, **kwargs):
122124
)
123125
lambda_eventsourcemapping.DestinationConfig = self.DestinationConfig
124126

125-
if "Condition" in function.resource_attributes:
126-
lambda_eventsourcemapping.set_resource_attribute("Condition", function.resource_attributes["Condition"])
127-
128127
if "role" in kwargs:
129128
self._link_policy(kwargs["role"], destination_config_policy)
130129

0 commit comments

Comments
 (0)