diff --git a/README.md b/README.md index 09e8c5638..c2d6f6b70 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ environment that lets you locally build, test, debug, and deploy applications de ## What is this Github repository? 💻 -This GitHub repository contains the SAM Specification, the Python code that translates SAM templates into AWS CloudFormation stacks and lots of examples applications. +This GitHub repository contains the SAM Specification, the Python code that translates SAM templates into AWS CloudFormation stacks and lots of example applications. In the words of SAM developers: > SAM Translator is the Python code that deploys SAM templates via AWS CloudFormation. Source code is high quality (95% unit test coverage), diff --git a/docs/internals/generated_resources.rst b/docs/internals/generated_resources.rst index 8b2760d61..ae35e2249 100644 --- a/docs/internals/generated_resources.rst +++ b/docs/internals/generated_resources.rst @@ -129,7 +129,7 @@ AWS::Lambda::Permission MyFunction\ **ThumbnailApi**\ Permission\ **P NOTE: ``ServerlessRestApi*`` resources are generated one per stack. HTTP API -^^^ +^^^^ This is called an "Implicit HTTP API". There can be many functions in the template that define these APIs. Behind the scenes, SAM will collect all implicit HTTP APIs from all Functions in the template, generate an OpenApi doc, and create an implicit ``AWS::Serverless::HttpApi`` using this OpenApi. This API defaults to a StageName called "$default" that cannot be diff --git a/requirements/dev.txt b/requirements/dev.txt index be3851301..193b157bb 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -3,7 +3,7 @@ flake8~=3.8.4 tox~=3.20.1 pytest-cov~=2.10.1 pylint>=1.7.2,<2.0 -pyyaml~=5.3.1 +pyyaml~=5.4 # Test requirements pytest~=6.1.1; python_version >= '3.6' diff --git a/samtranslator/__init__.py b/samtranslator/__init__.py index cf51ab459..4a1dc255b 100644 --- a/samtranslator/__init__.py +++ b/samtranslator/__init__.py @@ -1 +1 @@ -__version__ = "1.35.0" +__version__ = "1.36.0" diff --git a/samtranslator/intrinsics/resolver.py b/samtranslator/intrinsics/resolver.py index 519f856fe..af79704a9 100644 --- a/samtranslator/intrinsics/resolver.py +++ b/samtranslator/intrinsics/resolver.py @@ -8,7 +8,7 @@ class IntrinsicsResolver(object): - def __init__(self, parameters, supported_intrinsics=DEFAULT_SUPPORTED_INTRINSICS): + def __init__(self, parameters, supported_intrinsics=None): """ Instantiate the resolver :param dict parameters: Map of parameter names to their values @@ -17,6 +17,8 @@ def __init__(self, parameters, supported_intrinsics=DEFAULT_SUPPORTED_INTRINSICS :raises TypeError: If parameters or the supported_intrinsics arguments are invalid """ + if supported_intrinsics is None: + supported_intrinsics = DEFAULT_SUPPORTED_INTRINSICS if parameters is None or not isinstance(parameters, dict): raise InvalidDocumentException( [InvalidTemplateException("'Mappings' or 'Parameters' is either null or not a valid dictionary.")] diff --git a/samtranslator/model/__init__.py b/samtranslator/model/__init__.py index 150645589..d535fb142 100644 --- a/samtranslator/model/__init__.py +++ b/samtranslator/model/__init__.py @@ -43,7 +43,11 @@ class Resource(object): property_types = None _keywords = ["logical_id", "relative_id", "depends_on", "resource_attributes"] - _supported_resource_attributes = ["DeletionPolicy", "UpdatePolicy", "Condition"] + # For attributes in this list, they will be passed into the translated template for the same resource itself. + _supported_resource_attributes = ["DeletionPolicy", "UpdatePolicy", "Condition", "UpdateReplacePolicy", "Metadata"] + # For attributes in this list, they will be passed into the translated template for the same resource, + # as well as all the auto-generated resources that are created from this resource. + _pass_through_attributes = ["Condition", "DeletionPolicy", "UpdateReplacePolicy"] # Runtime attributes that can be qureied resource. They are CloudFormation attributes like ARN, Name etc that # 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 for attr, value in attributes.items(): self.set_resource_attribute(attr, value) + @classmethod + def get_supported_resource_attributes(cls): + """ + A getter method for the supported resource attributes + returns: a tuple that contains the name of all supported resource attributes + """ + return tuple(cls._supported_resource_attributes) + + @classmethod + def get_pass_through_attributes(cls): + """ + A getter method for the resource attributes to be passed to auto-generated resources + returns: a tuple that contains the name of all pass through attributes + """ + return tuple(cls._pass_through_attributes) + @classmethod def from_dict(cls, logical_id, resource_dict, relative_id=None, sam_plugins=None): """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): :return: Dictionary of resource attributes. """ - attributes = None - if "Condition" in self.resource_attributes: - attributes = {"Condition": self.resource_attributes["Condition"]} + attributes = {} + for resource_attribute in self.get_pass_through_attributes(): + if resource_attribute in self.resource_attributes: + attributes[resource_attribute] = self.resource_attributes.get(resource_attribute) return attributes diff --git a/samtranslator/model/api/api_generator.py b/samtranslator/model/api/api_generator.py index 4ae7c9df6..eef1d3d27 100644 --- a/samtranslator/model/api/api_generator.py +++ b/samtranslator/model/api/api_generator.py @@ -1,3 +1,4 @@ +import logging from collections import namedtuple from six import string_types from samtranslator.model.intrinsics import ref, fnGetAtt @@ -24,6 +25,8 @@ from samtranslator.translator.arn_generator import ArnGenerator from samtranslator.model.tags.resource_tagging import get_tag_list +LOG = logging.getLogger(__name__) + _CORS_WILDCARD = "'*'" CorsProperties = namedtuple( "_CorsProperties", ["AllowMethods", "AllowHeaders", "AllowOrigin", "MaxAge", "AllowCredentials"] @@ -52,12 +55,20 @@ GatewayResponseProperties = ["ResponseParameters", "ResponseTemplates", "StatusCode"] -class ApiGenerator(object): - usage_plan_shared = False - stage_keys_shared = list() - api_stages_shared = list() - depends_on_shared = list() +class SharedApiUsagePlan(object): + """ + Collects API information from different API resources in the same template, + so that these information can be used in the shared usage plan + """ + + def __init__(self): + self.usage_plan_shared = False + self.stage_keys_shared = list() + self.api_stages_shared = list() + self.depends_on_shared = list() + +class ApiGenerator(object): def __init__( self, logical_id, @@ -69,6 +80,7 @@ def __init__( definition_uri, name, stage_name, + shared_api_usage_plan, tags=None, endpoint_configuration=None, method_settings=None, @@ -134,6 +146,7 @@ def __init__( self.models = models self.domain = domain self.description = description + self.shared_api_usage_plan = shared_api_usage_plan def _construct_rest_api(self): """Constructs and returns the ApiGateway RestApi. @@ -617,7 +630,11 @@ def _construct_usage_plan(self, rest_api_stage=None): # create usage plan for this api only elif usage_plan_properties.get("CreateUsagePlan") == "PER_API": usage_plan_logical_id = self.logical_id + "UsagePlan" - usage_plan = ApiGatewayUsagePlan(logical_id=usage_plan_logical_id, depends_on=[self.logical_id]) + usage_plan = ApiGatewayUsagePlan( + logical_id=usage_plan_logical_id, + depends_on=[self.logical_id], + attributes=self.passthrough_resource_attributes, + ) api_stages = list() api_stage = dict() api_stage["ApiId"] = ref(self.logical_id) @@ -630,18 +647,21 @@ def _construct_usage_plan(self, rest_api_stage=None): # create a usage plan for all the Apis elif create_usage_plan == "SHARED": + LOG.info("Creating SHARED usage plan for all the Apis") usage_plan_logical_id = "ServerlessUsagePlan" - if self.logical_id not in ApiGenerator.depends_on_shared: - ApiGenerator.depends_on_shared.append(self.logical_id) + if self.logical_id not in self.shared_api_usage_plan.depends_on_shared: + self.shared_api_usage_plan.depends_on_shared.append(self.logical_id) usage_plan = ApiGatewayUsagePlan( - logical_id=usage_plan_logical_id, depends_on=ApiGenerator.depends_on_shared + logical_id=usage_plan_logical_id, + depends_on=self.shared_api_usage_plan.depends_on_shared, + attributes=self.passthrough_resource_attributes, ) api_stage = dict() api_stage["ApiId"] = ref(self.logical_id) api_stage["Stage"] = ref(rest_api_stage.logical_id) - if api_stage not in ApiGenerator.api_stages_shared: - ApiGenerator.api_stages_shared.append(api_stage) - usage_plan.ApiStages = ApiGenerator.api_stages_shared + if api_stage not in self.shared_api_usage_plan.api_stages_shared: + self.shared_api_usage_plan.api_stages_shared.append(api_stage) + usage_plan.ApiStages = self.shared_api_usage_plan.api_stages_shared api_key = self._construct_api_key(usage_plan_logical_id, create_usage_plan, rest_api_stage) 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_ """ if create_usage_plan == "SHARED": # create an api key resource for all the apis + LOG.info("Creating api key resource for all the Apis from SHARED usage plan") api_key_logical_id = "ServerlessApiKey" - api_key = ApiGatewayApiKey(logical_id=api_key_logical_id, depends_on=[usage_plan_logical_id]) + api_key = ApiGatewayApiKey( + logical_id=api_key_logical_id, + depends_on=[usage_plan_logical_id], + attributes=self.passthrough_resource_attributes, + ) api_key.Enabled = True stage_key = dict() stage_key["RestApiId"] = ref(self.logical_id) stage_key["StageName"] = ref(rest_api_stage.logical_id) - if stage_key not in ApiGenerator.stage_keys_shared: - ApiGenerator.stage_keys_shared.append(stage_key) - api_key.StageKeys = ApiGenerator.stage_keys_shared + if stage_key not in self.shared_api_usage_plan.stage_keys_shared: + self.shared_api_usage_plan.stage_keys_shared.append(stage_key) + api_key.StageKeys = self.shared_api_usage_plan.stage_keys_shared # for create_usage_plan = "PER_API" else: # create an api key resource for this api api_key_logical_id = self.logical_id + "ApiKey" - api_key = ApiGatewayApiKey(logical_id=api_key_logical_id, depends_on=[usage_plan_logical_id]) + api_key = ApiGatewayApiKey( + logical_id=api_key_logical_id, + depends_on=[usage_plan_logical_id], + attributes=self.passthrough_resource_attributes, + ) + # api_key = ApiGatewayApiKey(logical_id=api_key_logical_id, depends_on=[usage_plan_logical_id]) api_key.Enabled = True stage_keys = list() stage_key = dict() @@ -705,7 +735,12 @@ def _construct_usage_plan_key(self, usage_plan_logical_id, create_usage_plan, ap # create a mapping between api key and the usage plan usage_plan_key_logical_id = self.logical_id + "UsagePlanKey" - usage_plan_key = ApiGatewayUsagePlanKey(logical_id=usage_plan_key_logical_id, depends_on=[api_key.logical_id]) + usage_plan_key = ApiGatewayUsagePlanKey( + logical_id=usage_plan_key_logical_id, + depends_on=[api_key.logical_id], + attributes=self.passthrough_resource_attributes, + ) + # usage_plan_key = ApiGatewayUsagePlanKey(logical_id=usage_plan_key_logical_id, depends_on=[api_key.logical_id]) usage_plan_key.KeyId = ref(api_key.logical_id) usage_plan_key.KeyType = "API_KEY" usage_plan_key.UsagePlanId = ref(usage_plan_logical_id) diff --git a/samtranslator/model/apigateway.py b/samtranslator/model/apigateway.py index 649f277c6..0e768918f 100644 --- a/samtranslator/model/apigateway.py +++ b/samtranslator/model/apigateway.py @@ -231,8 +231,10 @@ def __init__( function_payload_type=None, function_invoke_role=None, is_aws_iam_authorizer=False, - authorization_scopes=[], + authorization_scopes=None, ): + if authorization_scopes is None: + authorization_scopes = [] if function_payload_type not in ApiGatewayAuthorizer._VALID_FUNCTION_PAYLOAD_TYPES: raise InvalidResourceException( api_logical_id, @@ -267,8 +269,9 @@ def _is_missing_identity_source(self, identity): query_strings = identity.get("QueryStrings") stage_variables = identity.get("StageVariables") context = identity.get("Context") + ttl = identity.get("ReauthorizeEvery") - if not headers and not query_strings and not stage_variables and not context: + if (ttl is None or int(ttl) > 0) and not headers and not query_strings and not stage_variables and not context: return True return False @@ -311,7 +314,9 @@ def generate_swagger(self): swagger[APIGATEWAY_AUTHORIZER_KEY]["authorizerCredentials"] = function_invoke_role if self._get_function_payload_type() == "REQUEST": - swagger[APIGATEWAY_AUTHORIZER_KEY]["identitySource"] = self._get_identity_source() + identity_source = self._get_identity_source() + if identity_source: + swagger[APIGATEWAY_AUTHORIZER_KEY]["identitySource"] = self._get_identity_source() # Authorizer Validation Expression is only allowed on COGNITO_USER_POOLS and LAMBDA_TOKEN is_lambda_token_authorizer = authorizer_type == "LAMBDA" and self._get_function_payload_type() == "TOKEN" diff --git a/samtranslator/model/eventbridge_utils.py b/samtranslator/model/eventbridge_utils.py index 39bf40745..bb407cd84 100644 --- a/samtranslator/model/eventbridge_utils.py +++ b/samtranslator/model/eventbridge_utils.py @@ -4,15 +4,15 @@ class EventBridgeRuleUtils: @staticmethod - def create_dead_letter_queue_with_policy(rule_logical_id, rule_arn, queue_logical_id=None): + def create_dead_letter_queue_with_policy(rule_logical_id, rule_arn, queue_logical_id=None, attributes=None): resources = [] - queue = SQSQueue(queue_logical_id or rule_logical_id + "Queue") + queue = SQSQueue(queue_logical_id or rule_logical_id + "Queue", attributes=attributes) dlq_queue_arn = queue.get_runtime_attr("arn") dlq_queue_url = queue.get_runtime_attr("queue_url") # grant necessary permission to Eventbridge Rule resource for sending messages to dead-letter queue - policy = SQSQueuePolicy(rule_logical_id + "QueuePolicy") + policy = SQSQueuePolicy(rule_logical_id + "QueuePolicy", attributes=attributes) policy.PolicyDocument = SQSQueuePolicies.eventbridge_dlq_send_message_resource_based_policy( rule_arn, dlq_queue_arn ) @@ -41,14 +41,14 @@ def validate_dlq_config(source_logical_id, dead_letter_config): raise InvalidEventException(source_logical_id, "No 'Arn' or 'Type' property provided for DeadLetterConfig") @staticmethod - def get_dlq_queue_arn_and_resources(cw_event_source, source_arn): + def get_dlq_queue_arn_and_resources(cw_event_source, source_arn, attributes): """returns dlq queue arn and dlq_resources, assuming cw_event_source.DeadLetterConfig has been validated""" dlq_queue_arn = cw_event_source.DeadLetterConfig.get("Arn") if dlq_queue_arn is not None: return dlq_queue_arn, [] queue_logical_id = cw_event_source.DeadLetterConfig.get("QueueLogicalId") dlq_resources = EventBridgeRuleUtils.create_dead_letter_queue_with_policy( - cw_event_source.logical_id, source_arn, queue_logical_id + cw_event_source.logical_id, source_arn, queue_logical_id, attributes ) dlq_queue_arn = dlq_resources[0].get_runtime_attr("arn") return dlq_queue_arn, dlq_resources diff --git a/samtranslator/model/eventsources/cloudwatchlogs.py b/samtranslator/model/eventsources/cloudwatchlogs.py index 8adc32439..5cce4d0aa 100644 --- a/samtranslator/model/eventsources/cloudwatchlogs.py +++ b/samtranslator/model/eventsources/cloudwatchlogs.py @@ -43,11 +43,13 @@ def get_source_arn(self): ) def get_subscription_filter(self, function, permission): - subscription_filter = SubscriptionFilter(self.logical_id, depends_on=[permission.logical_id]) + subscription_filter = SubscriptionFilter( + self.logical_id, + depends_on=[permission.logical_id], + attributes=function.get_passthrough_resource_attributes(), + ) subscription_filter.LogGroupName = self.LogGroupName subscription_filter.FilterPattern = self.FilterPattern subscription_filter.DestinationArn = function.get_runtime_attr("arn") - if "Condition" in function.resource_attributes: - subscription_filter.set_resource_attribute("Condition", function.resource_attributes["Condition"]) return subscription_filter diff --git a/samtranslator/model/eventsources/pull.py b/samtranslator/model/eventsources/pull.py index 747968bb9..106700d63 100644 --- a/samtranslator/model/eventsources/pull.py +++ b/samtranslator/model/eventsources/pull.py @@ -60,7 +60,9 @@ def to_cloudformation(self, **kwargs): resources = [] - lambda_eventsourcemapping = LambdaEventSourceMapping(self.logical_id) + lambda_eventsourcemapping = LambdaEventSourceMapping( + self.logical_id, attributes=function.get_passthrough_resource_attributes() + ) resources.append(lambda_eventsourcemapping) try: @@ -122,9 +124,6 @@ def to_cloudformation(self, **kwargs): ) lambda_eventsourcemapping.DestinationConfig = self.DestinationConfig - if "Condition" in function.resource_attributes: - lambda_eventsourcemapping.set_resource_attribute("Condition", function.resource_attributes["Condition"]) - if "role" in kwargs: self._link_policy(kwargs["role"], destination_config_policy) diff --git a/samtranslator/model/eventsources/push.py b/samtranslator/model/eventsources/push.py index d134f1940..39792c211 100644 --- a/samtranslator/model/eventsources/push.py +++ b/samtranslator/model/eventsources/push.py @@ -113,7 +113,8 @@ def to_cloudformation(self, **kwargs): resources = [] - events_rule = EventsRule(self.logical_id) + passthrough_resource_attributes = function.get_passthrough_resource_attributes() + events_rule = EventsRule(self.logical_id, attributes=passthrough_resource_attributes) resources.append(events_rule) events_rule.ScheduleExpression = self.Schedule @@ -126,13 +127,13 @@ def to_cloudformation(self, **kwargs): dlq_queue_arn = None if self.DeadLetterConfig is not None: EventBridgeRuleUtils.validate_dlq_config(self.logical_id, self.DeadLetterConfig) - dlq_queue_arn, dlq_resources = EventBridgeRuleUtils.get_dlq_queue_arn_and_resources(self, source_arn) + dlq_queue_arn, dlq_resources = EventBridgeRuleUtils.get_dlq_queue_arn_and_resources( + self, source_arn, passthrough_resource_attributes + ) resources.extend(dlq_resources) events_rule.Targets = [self._construct_target(function, dlq_queue_arn)] - if CONDITION in function.resource_attributes: - events_rule.set_resource_attribute(CONDITION, function.resource_attributes[CONDITION]) resources.append(self._construct_permission(function, source_arn=source_arn)) return resources @@ -186,7 +187,8 @@ def to_cloudformation(self, **kwargs): resources = [] - events_rule = EventsRule(self.logical_id) + passthrough_resource_attributes = function.get_passthrough_resource_attributes() + events_rule = EventsRule(self.logical_id, attributes=passthrough_resource_attributes) events_rule.EventBusName = self.EventBusName events_rule.EventPattern = self.Pattern source_arn = events_rule.get_runtime_attr("arn") @@ -194,12 +196,12 @@ def to_cloudformation(self, **kwargs): dlq_queue_arn = None if self.DeadLetterConfig is not None: EventBridgeRuleUtils.validate_dlq_config(self.logical_id, self.DeadLetterConfig) - dlq_queue_arn, dlq_resources = EventBridgeRuleUtils.get_dlq_queue_arn_and_resources(self, source_arn) + dlq_queue_arn, dlq_resources = EventBridgeRuleUtils.get_dlq_queue_arn_and_resources( + self, source_arn, passthrough_resource_attributes + ) resources.extend(dlq_resources) events_rule.Targets = [self._construct_target(function, dlq_queue_arn)] - if CONDITION in function.resource_attributes: - events_rule.set_resource_attribute(CONDITION, function.resource_attributes[CONDITION]) resources.append(events_rule) resources.append(self._construct_permission(function, source_arn=source_arn)) @@ -427,20 +429,20 @@ def to_cloudformation(self, **kwargs): self.Topic, self.Region, self.FilterPolicy, - function.resource_attributes, + function, ) return [self._construct_permission(function, source_arn=self.Topic), subscription] # SNS -> SQS(Create New) -> Lambda if isinstance(self.SqsSubscription, bool): resources = [] - queue = self._inject_sqs_queue() + queue = self._inject_sqs_queue(function) queue_arn = queue.get_runtime_attr("arn") queue_url = queue.get_runtime_attr("queue_url") - queue_policy = self._inject_sqs_queue_policy(self.Topic, queue_arn, queue_url, function.resource_attributes) + queue_policy = self._inject_sqs_queue_policy(self.Topic, queue_arn, queue_url, function) subscription = self._inject_subscription( - "sqs", queue_arn, self.Topic, self.Region, self.FilterPolicy, function.resource_attributes + "sqs", queue_arn, self.Topic, self.Region, self.FilterPolicy, function ) event_source = self._inject_sqs_event_source_mapping(function, role, queue_arn) @@ -462,11 +464,9 @@ def to_cloudformation(self, **kwargs): enabled = self.SqsSubscription.get("Enabled", None) queue_policy = self._inject_sqs_queue_policy( - self.Topic, queue_arn, queue_url, function.resource_attributes, queue_policy_logical_id - ) - subscription = self._inject_subscription( - "sqs", queue_arn, self.Topic, self.Region, self.FilterPolicy, function.resource_attributes + self.Topic, queue_arn, queue_url, function, queue_policy_logical_id ) + subscription = self._inject_subscription("sqs", queue_arn, self.Topic, self.Region, self.FilterPolicy, function) event_source = self._inject_sqs_event_source_mapping(function, role, queue_arn, batch_size, enabled) resources = resources + event_source @@ -474,35 +474,36 @@ def to_cloudformation(self, **kwargs): resources.append(subscription) return resources - def _inject_subscription(self, protocol, endpoint, topic, region, filterPolicy, resource_attributes): - subscription = SNSSubscription(self.logical_id) + def _inject_subscription(self, protocol, endpoint, topic, region, filterPolicy, function): + subscription = SNSSubscription(self.logical_id, attributes=function.get_passthrough_resource_attributes()) subscription.Protocol = protocol subscription.Endpoint = endpoint subscription.TopicArn = topic + if region is not None: subscription.Region = region - if CONDITION in resource_attributes: - subscription.set_resource_attribute(CONDITION, resource_attributes[CONDITION]) if filterPolicy is not None: subscription.FilterPolicy = filterPolicy return subscription - def _inject_sqs_queue(self): - return SQSQueue(self.logical_id + "Queue") + def _inject_sqs_queue(self, function): + return SQSQueue(self.logical_id + "Queue", attributes=function.get_passthrough_resource_attributes()) def _inject_sqs_event_source_mapping(self, function, role, queue_arn, batch_size=None, enabled=None): - event_source = SQS(self.logical_id + "EventSourceMapping") + event_source = SQS( + self.logical_id + "EventSourceMapping", attributes=function.get_passthrough_resource_attributes() + ) event_source.Queue = queue_arn event_source.BatchSize = batch_size or 10 event_source.Enabled = enabled or True return event_source.to_cloudformation(function=function, role=role) - def _inject_sqs_queue_policy(self, topic_arn, queue_arn, queue_url, resource_attributes, logical_id=None): - policy = SQSQueuePolicy(logical_id or self.logical_id + "QueuePolicy") - if CONDITION in resource_attributes: - policy.set_resource_attribute(CONDITION, resource_attributes[CONDITION]) + def _inject_sqs_queue_policy(self, topic_arn, queue_arn, queue_url, function, logical_id=None): + policy = SQSQueuePolicy( + logical_id or self.logical_id + "QueuePolicy", attributes=function.get_passthrough_resource_attributes() + ) policy.PolicyDocument = SQSQueuePolicies.sns_topic_send_message_role_policy(topic_arn, queue_arn) policy.Queues = [queue_url] @@ -895,7 +896,7 @@ def to_cloudformation(self, **kwargs): return resources def _construct_iot_rule(self, function): - rule = IotTopicRule(self.logical_id) + rule = IotTopicRule(self.logical_id, attributes=function.get_passthrough_resource_attributes()) payload = { "Sql": self.Sql, @@ -907,8 +908,6 @@ def _construct_iot_rule(self, function): payload["AwsIotSqlVersion"] = self.AwsIotSqlVersion rule.TopicRulePayload = payload - if CONDITION in function.resource_attributes: - rule.set_resource_attribute(CONDITION, function.resource_attributes[CONDITION]) return rule @@ -953,11 +952,17 @@ def to_cloudformation(self, **kwargs): resources = [] source_arn = fnGetAtt(userpool_id, "Arn") - resources.append( - self._construct_permission(function, source_arn=source_arn, prefix=function.logical_id + "Cognito") + lambda_permission = self._construct_permission( + function, source_arn=source_arn, prefix=function.logical_id + "Cognito" ) + for attribute, value in function.get_passthrough_resource_attributes().items(): + lambda_permission.set_resource_attribute(attribute, value) + resources.append(lambda_permission) self._inject_lambda_config(function, userpool) + userpool_resource = CognitoUserPool.from_dict(userpool_id, userpool) + for attribute, value in function.get_passthrough_resource_attributes().items(): + userpool_resource.set_resource_attribute(attribute, value) resources.append(CognitoUserPool.from_dict(userpool_id, userpool)) return resources diff --git a/samtranslator/model/intrinsics.py b/samtranslator/model/intrinsics.py index c81e40d00..e95a5d6d0 100644 --- a/samtranslator/model/intrinsics.py +++ b/samtranslator/model/intrinsics.py @@ -24,7 +24,9 @@ def fnAnd(argument_list): return {"Fn::And": argument_list} -def make_conditional(condition, true_data, false_data={"Ref": "AWS::NoValue"}): +def make_conditional(condition, true_data, false_data=None): + if false_data is None: + false_data = {"Ref": "AWS::NoValue"} return {"Fn::If": [condition, true_data, false_data]} diff --git a/samtranslator/model/sam_resources.py b/samtranslator/model/sam_resources.py index a3a9a6d17..b0d179c2f 100644 --- a/samtranslator/model/sam_resources.py +++ b/samtranslator/model/sam_resources.py @@ -1,5 +1,6 @@ """ SAM macro definitions """ from six import string_types +import copy import samtranslator.model.eventsources import samtranslator.model.eventsources.pull @@ -279,13 +280,17 @@ def _validate_and_inject_resource(self, dest_config, event, logical_id, conditio ) if dest_config.get("Destination") is None or property_condition is not None: combined_condition = self._make_and_conditions( - self.get_passthrough_resource_attributes(), property_condition, conditions + self.get_passthrough_resource_attributes().get("Condition"), property_condition, conditions ) if dest_config.get("Type") in auto_inject_list: if dest_config.get("Type") == "SQS": - resource = SQSQueue(resource_logical_id + "Queue") + resource = SQSQueue( + resource_logical_id + "Queue", attributes=self.get_passthrough_resource_attributes() + ) if dest_config.get("Type") == "SNS": - resource = SNSTopic(resource_logical_id + "Topic") + resource = SNSTopic( + resource_logical_id + "Topic", attributes=self.get_passthrough_resource_attributes() + ) if combined_condition: resource.set_resource_attribute("Condition", combined_condition) if property_condition: @@ -313,12 +318,10 @@ def _make_and_conditions(self, resource_condition, property_condition, condition return property_condition if property_condition is None: - return resource_condition["Condition"] + return resource_condition - and_condition = make_and_condition([resource_condition, {"Condition": property_condition}]) - condition_name = self._make_gen_condition_name( - resource_condition.get("Condition") + "AND" + property_condition, self.logical_id - ) + and_condition = make_and_condition([{"Condition": resource_condition}, {"Condition": property_condition}]) + condition_name = self._make_gen_condition_name(resource_condition + "AND" + property_condition, self.logical_id) conditions[condition_name] = and_condition return condition_name @@ -732,7 +735,8 @@ def _construct_version(self, function, intrinsics_resolver, code_sha256=None): attributes = self.get_passthrough_resource_attributes() if attributes is None: attributes = {} - attributes["DeletionPolicy"] = "Retain" + if "DeletionPolicy" not in attributes: + attributes["DeletionPolicy"] = "Retain" lambda_version = LambdaVersion(logical_id=logical_id, attributes=attributes) lambda_version.FunctionName = function.get_runtime_attr("name") @@ -857,6 +861,7 @@ def to_cloudformation(self, **kwargs): self.Domain = intrinsics_resolver.resolve_parameter_refs(self.Domain) self.Auth = intrinsics_resolver.resolve_parameter_refs(self.Auth) redeploy_restapi_parameters = kwargs.get("redeploy_restapi_parameters") + shared_api_usage_plan = kwargs.get("shared_api_usage_plan") api_generator = ApiGenerator( self.logical_id, @@ -868,6 +873,7 @@ def to_cloudformation(self, **kwargs): self.DefinitionUri, self.Name, self.StageName, + shared_api_usage_plan, tags=self.Tags, endpoint_configuration=self.EndpointConfiguration, method_settings=self.MethodSettings, @@ -1160,15 +1166,29 @@ def _construct_lambda_layer(self, intrinsics_resolver): intrinsics_resolver, self.RetentionPolicy, "RetentionPolicy" ) + # If nothing defined, this will be set to Retain retention_policy_value = self._get_retention_policy_value() attributes = self.get_passthrough_resource_attributes() if attributes is None: attributes = {} - attributes["DeletionPolicy"] = retention_policy_value + if "DeletionPolicy" not in attributes: + attributes["DeletionPolicy"] = self.RETAIN + if retention_policy_value is not None: + attributes["DeletionPolicy"] = retention_policy_value old_logical_id = self.logical_id - new_logical_id = logical_id_generator.LogicalIdGenerator(old_logical_id, self.to_dict()).gen() + + # This is to prevent the passthrough resource attributes to be included for hashing + hash_dict = copy.deepcopy(self.to_dict()) + if "DeletionPolicy" in hash_dict.get(old_logical_id): + del hash_dict[old_logical_id]["DeletionPolicy"] + if "UpdateReplacePolicy" in hash_dict.get(old_logical_id): + del hash_dict[old_logical_id]["UpdateReplacePolicy"] + if "Metadata" in hash_dict.get(old_logical_id): + del hash_dict[old_logical_id]["Metadata"] + + new_logical_id = logical_id_generator.LogicalIdGenerator(old_logical_id, hash_dict).gen() self.logical_id = new_logical_id lambda_layer = LambdaLayerVersion(self.logical_id, depends_on=self.depends_on, attributes=attributes) @@ -1200,7 +1220,9 @@ def _get_retention_policy_value(self): :return: value for the DeletionPolicy attribute. """ - if self.RetentionPolicy is None or self.RetentionPolicy.lower() == self.RETAIN.lower(): + if self.RetentionPolicy is None: + return None + elif self.RetentionPolicy.lower() == self.RETAIN.lower(): return self.RETAIN elif self.RetentionPolicy.lower() == self.DELETE.lower(): return self.DELETE diff --git a/samtranslator/model/stepfunctions/events.py b/samtranslator/model/stepfunctions/events.py index 88fe60cfe..a50054528 100644 --- a/samtranslator/model/stepfunctions/events.py +++ b/samtranslator/model/stepfunctions/events.py @@ -97,7 +97,8 @@ def to_cloudformation(self, resource, **kwargs): permissions_boundary = kwargs.get("permissions_boundary") - events_rule = EventsRule(self.logical_id) + passthrough_resource_attributes = resource.get_passthrough_resource_attributes() + events_rule = EventsRule(self.logical_id, attributes=passthrough_resource_attributes) resources.append(events_rule) events_rule.ScheduleExpression = self.Schedule @@ -105,8 +106,6 @@ def to_cloudformation(self, resource, **kwargs): events_rule.State = "ENABLED" if self.Enabled else "DISABLED" events_rule.Name = self.Name events_rule.Description = self.Description - if CONDITION in resource.resource_attributes: - events_rule.set_resource_attribute(CONDITION, resource.resource_attributes[CONDITION]) role = self._construct_role(resource, permissions_boundary) resources.append(role) @@ -115,7 +114,9 @@ def to_cloudformation(self, resource, **kwargs): dlq_queue_arn = None if self.DeadLetterConfig is not None: EventBridgeRuleUtils.validate_dlq_config(self.logical_id, self.DeadLetterConfig) - dlq_queue_arn, dlq_resources = EventBridgeRuleUtils.get_dlq_queue_arn_and_resources(self, source_arn) + dlq_queue_arn, dlq_resources = EventBridgeRuleUtils.get_dlq_queue_arn_and_resources( + self, source_arn, passthrough_resource_attributes + ) resources.extend(dlq_resources) events_rule.Targets = [self._construct_target(resource, role, dlq_queue_arn)] @@ -170,11 +171,10 @@ def to_cloudformation(self, resource, **kwargs): permissions_boundary = kwargs.get("permissions_boundary") - events_rule = EventsRule(self.logical_id) + passthrough_resource_attributes = resource.get_passthrough_resource_attributes() + events_rule = EventsRule(self.logical_id, attributes=passthrough_resource_attributes) events_rule.EventBusName = self.EventBusName events_rule.EventPattern = self.Pattern - if CONDITION in resource.resource_attributes: - events_rule.set_resource_attribute(CONDITION, resource.resource_attributes[CONDITION]) resources.append(events_rule) @@ -185,7 +185,9 @@ def to_cloudformation(self, resource, **kwargs): dlq_queue_arn = None if self.DeadLetterConfig is not None: EventBridgeRuleUtils.validate_dlq_config(self.logical_id, self.DeadLetterConfig) - dlq_queue_arn, dlq_resources = EventBridgeRuleUtils.get_dlq_queue_arn_and_resources(self, source_arn) + dlq_queue_arn, dlq_resources = EventBridgeRuleUtils.get_dlq_queue_arn_and_resources( + self, source_arn, passthrough_resource_attributes + ) resources.extend(dlq_resources) events_rule.Targets = [self._construct_target(resource, role, dlq_queue_arn)] diff --git a/samtranslator/model/stepfunctions/generators.py b/samtranslator/model/stepfunctions/generators.py index 8e1797c7d..f8431c189 100644 --- a/samtranslator/model/stepfunctions/generators.py +++ b/samtranslator/model/stepfunctions/generators.py @@ -286,7 +286,7 @@ def _replace_dynamic_values_with_substitutions(self, input): location[path[-1]] = sub_key return substitution_map - def _get_paths_to_intrinsics(self, input, path=[]): + def _get_paths_to_intrinsics(self, input, path=None): """ Returns all paths to dynamic values within a dictionary @@ -294,6 +294,8 @@ def _get_paths_to_intrinsics(self, input, path=[]): :param path: Optional list to keep track of the path to the input dictionary :returns list: List of keys that defines the path to a dynamic value within the input dictionary """ + if path is None: + path = [] dynamic_value_paths = [] if isinstance(input, dict): iterator = input.items() diff --git a/samtranslator/open_api/open_api.py b/samtranslator/open_api/open_api.py index fc0f77a26..af6b47e49 100644 --- a/samtranslator/open_api/open_api.py +++ b/samtranslator/open_api/open_api.py @@ -391,7 +391,7 @@ def add_auth_to_method(self, path, method_name, auth, api): if method_authorizer: self._set_method_authorizer(path, method_name, method_authorizer, authorizers, authorization_scopes) - def _set_method_authorizer(self, path, method_name, authorizer_name, authorizers, authorization_scopes=[]): + def _set_method_authorizer(self, path, method_name, authorizer_name, authorizers, authorization_scopes=None): """ Adds the authorizer_name to the security block for each method on this path. This is used to configure the authorizer for individual functions. @@ -402,6 +402,8 @@ def _set_method_authorizer(self, path, method_name, authorizer_name, authorizers authorizers param. :param list authorization_scopes: list of strings that are the auth scopes for this method """ + if authorization_scopes is None: + authorization_scopes = [] normalized_method_name = self._normalize_method_name(method_name) # It is possible that the method could have two definitions in a Fn::If block. for method_definition in self.get_method_contents(self.get_path(path)[normalized_method_name]): diff --git a/samtranslator/plugins/api/implicit_api_plugin.py b/samtranslator/plugins/api/implicit_api_plugin.py index 2ae336103..abf440b08 100644 --- a/samtranslator/plugins/api/implicit_api_plugin.py +++ b/samtranslator/plugins/api/implicit_api_plugin.py @@ -39,6 +39,8 @@ def __init__(self, name): # dict containing condition (or None) for each resource path+method for all APIs. dict format: # {api_id: {path: {method: condition_name_or_None}}} self.api_conditions = {} + self.api_deletion_policies = {} + self.api_update_replace_policies = {} self._setup_api_properties() def _setup_api_properties(self): @@ -75,16 +77,22 @@ def on_before_transform_template(self, template_dict): api_events = self._get_api_events(resource) condition = resource.condition + deletion_policy = resource.deletion_policy + update_replace_policy = resource.update_replace_policy if len(api_events) == 0: continue try: - self._process_api_events(resource, api_events, template, condition) + self._process_api_events( + resource, api_events, template, condition, deletion_policy, update_replace_policy + ) except InvalidEventException as ex: errors.append(InvalidResourceException(logicalId, ex.message)) self._maybe_add_condition_to_implicit_api(template_dict) + self._maybe_add_deletion_policy_to_implicit_api(template_dict) + self._maybe_add_update_replace_policy_to_implicit_api(template_dict) self._maybe_add_conditions_to_implicit_api_paths(template) self._maybe_remove_implicit_api(template) @@ -121,7 +129,9 @@ def _get_api_events(self, resource): return api_events - def _process_api_events(self, resource, api_events, template, condition=None): + def _process_api_events( + self, resource, api_events, template, condition=None, deletion_policy=None, update_replace_policy=None + ): """ Actually process given API events. Iteratively adds the APIs to Swagger JSON in the respective Serverless::Api resource from the template @@ -247,6 +257,75 @@ def _maybe_add_condition_to_implicit_api(self, template_dict): template_dict, self.implicit_api_condition, all_resource_method_conditions ) + def _maybe_add_deletion_policy_to_implicit_api(self, template_dict): + """ + Decides whether to add a deletion policy to the implicit api resource. + :param dict template_dict: SAM template dictionary + """ + # Short-circuit if template doesn't have any functions with implicit API events + if not self.api_deletion_policies.get(self.implicit_api_logical_id, {}): + return + + # Add a deletion policy to the API resource if its resources contains DeletionPolicy. + implicit_api_deletion_policies = self.api_deletion_policies.get(self.implicit_api_logical_id) + at_least_one_resource_method = len(implicit_api_deletion_policies) > 0 + one_resource_method_contains_deletion_policy = False + contains_retain = False + contains_delete = False + # If multiple functions with multiple different policies reference the Implicit Api, + # we set DeletionPolicy to Retain if Retain is present in one of the functions, + # else Delete if Delete is present + for iterated_policy in implicit_api_deletion_policies: + if iterated_policy: + one_resource_method_contains_deletion_policy = True + if iterated_policy == "Retain": + contains_retain = True + if iterated_policy == "Delete": + contains_delete = True + if at_least_one_resource_method and one_resource_method_contains_deletion_policy: + implicit_api_resource = template_dict.get("Resources").get(self.implicit_api_logical_id) + if contains_retain: + implicit_api_resource["DeletionPolicy"] = "Retain" + elif contains_delete: + implicit_api_resource["DeletionPolicy"] = "Delete" + + def _maybe_add_update_replace_policy_to_implicit_api(self, template_dict): + """ + Decides whether to add an update replace policy to the implicit api resource. + :param dict template_dict: SAM template dictionary + """ + # Short-circuit if template doesn't have any functions with implicit API events + if not self.api_update_replace_policies.get(self.implicit_api_logical_id, {}): + return + + # Add a update replace policy to the API resource if its resources contains UpdateReplacePolicy. + implicit_api_update_replace_policies = self.api_update_replace_policies.get(self.implicit_api_logical_id) + at_least_one_resource_method = len(implicit_api_update_replace_policies) > 0 + one_resource_method_contains_update_replace_policy = False + contains_retain = False + contains_snapshot = False + contains_delete = False + # If multiple functions with multiple different policies reference the Implicit Api, + # we set UpdateReplacePolicy to Retain if Retain is present in one of the functions, + # Snapshot if Snapshot is present, else Delete if Delete is present + for iterated_policy in implicit_api_update_replace_policies: + if iterated_policy: + one_resource_method_contains_update_replace_policy = True + if iterated_policy == "Retain": + contains_retain = True + if iterated_policy == "Snapshot": + contains_snapshot = True + if iterated_policy == "Delete": + contains_delete = True + if at_least_one_resource_method and one_resource_method_contains_update_replace_policy: + implicit_api_resource = template_dict.get("Resources").get(self.implicit_api_logical_id) + if contains_retain: + implicit_api_resource["UpdateReplacePolicy"] = "Retain" + elif contains_snapshot: + implicit_api_resource["UpdateReplacePolicy"] = "Snapshot" + elif contains_delete: + implicit_api_resource["UpdateReplacePolicy"] = "Delete" + def _add_combined_condition_to_template(self, template_dict, condition_name, conditions_to_combine): """ Add top-level template condition that combines the given list of conditions. diff --git a/samtranslator/plugins/api/implicit_http_api_plugin.py b/samtranslator/plugins/api/implicit_http_api_plugin.py index 83c078ea1..372742632 100644 --- a/samtranslator/plugins/api/implicit_http_api_plugin.py +++ b/samtranslator/plugins/api/implicit_http_api_plugin.py @@ -43,7 +43,9 @@ def _setup_api_properties(self): self.api_id_property = "ApiId" self.editor = OpenApiEditor - def _process_api_events(self, function, api_events, template, condition=None): + def _process_api_events( + self, function, api_events, template, condition=None, deletion_policy=None, update_replace_policy=None + ): """ Actually process given HTTP API events. Iteratively adds the APIs to OpenApi JSON in the respective AWS::Serverless::HttpApi resource from the template @@ -89,11 +91,15 @@ def _process_api_events(self, function, api_events, template, condition=None): if isinstance(api_id, dict): raise InvalidEventException(logicalId, "Api Event must reference an Api in the same template.") - api_dict = self.api_conditions.setdefault(api_id, {}) - method_conditions = api_dict.setdefault(path, {}) + api_dict_condition = self.api_conditions.setdefault(api_id, {}) + method_conditions = api_dict_condition.setdefault(path, {}) + method_conditions[method] = condition - if condition: - method_conditions[method] = condition + api_dict_deletion = self.api_deletion_policies.setdefault(api_id, set()) + api_dict_deletion.add(deletion_policy) + + api_dict_update_replace = self.api_update_replace_policies.setdefault(api_id, set()) + api_dict_update_replace.add(update_replace_policy) self._add_api_to_swagger(logicalId, event_properties, template) if "RouteSettings" in event_properties: diff --git a/samtranslator/plugins/api/implicit_rest_api_plugin.py b/samtranslator/plugins/api/implicit_rest_api_plugin.py index 92ecf8d86..3e94309f1 100644 --- a/samtranslator/plugins/api/implicit_rest_api_plugin.py +++ b/samtranslator/plugins/api/implicit_rest_api_plugin.py @@ -46,7 +46,9 @@ def _setup_api_properties(self): self.api_id_property = "RestApiId" self.editor = SwaggerEditor - def _process_api_events(self, function, api_events, template, condition=None): + def _process_api_events( + self, function, api_events, template, condition=None, deletion_policy=None, update_replace_policy=None + ): """ Actually process given API events. Iteratively adds the APIs to Swagger JSON in the respective Serverless::Api resource from the template @@ -87,10 +89,16 @@ def _process_api_events(self, function, api_events, template, condition=None): if isinstance(api_id, dict): raise InvalidEventException(logicalId, "Api Event must reference an Api in the same template.") - api_dict = self.api_conditions.setdefault(api_id, {}) - method_conditions = api_dict.setdefault(path, {}) + api_dict_condition = self.api_conditions.setdefault(api_id, {}) + method_conditions = api_dict_condition.setdefault(path, {}) method_conditions[method] = condition + api_dict_deletion = self.api_deletion_policies.setdefault(api_id, set()) + api_dict_deletion.add(deletion_policy) + + api_dict_update_replace = self.api_update_replace_policies.setdefault(api_id, set()) + api_dict_update_replace.add(update_replace_policy) + self._add_api_to_swagger(logicalId, event_properties, template) api_events[logicalId] = event diff --git a/samtranslator/plugins/application/serverless_app_plugin.py b/samtranslator/plugins/application/serverless_app_plugin.py index 2f4d9b0e4..05741c5df 100644 --- a/samtranslator/plugins/application/serverless_app_plugin.py +++ b/samtranslator/plugins/application/serverless_app_plugin.py @@ -12,6 +12,7 @@ from samtranslator.public.sdk.template import SamTemplate from samtranslator.intrinsics.resolver import IntrinsicsResolver from samtranslator.intrinsics.actions import FindInMapAction +from samtranslator.region_configuration import RegionConfiguration LOG = logging.getLogger(__name__) @@ -41,7 +42,7 @@ class ServerlessAppPlugin(BasePlugin): LOCATION_KEY = "Location" TEMPLATE_URL_KEY = "TemplateUrl" - def __init__(self, sar_client=None, wait_for_template_active_status=False, validate_only=False, parameters={}): + def __init__(self, sar_client=None, wait_for_template_active_status=False, validate_only=False, parameters=None): """ Initialize the plugin. @@ -51,6 +52,8 @@ def __init__(self, sar_client=None, wait_for_template_active_status=False, valid :param bool validate_only: Flag to only validate application access (uses get_application API instead) """ super(ServerlessAppPlugin, self).__init__(ServerlessAppPlugin.__name__) + if parameters is None: + parameters = {} self._applications = {} self._in_progress_templates = [] self._sar_client = sar_client @@ -104,6 +107,10 @@ def on_before_transform_template(self, template_dict): if key not in self._applications: try: + if not RegionConfiguration.is_sar_supported(): + raise InvalidResourceException( + logical_id, "Serverless Application Repository is not available in this region." + ) # Lazy initialization of the client- create it when it is needed if not self._sar_client: self._sar_client = boto3.client("serverlessrepo") diff --git a/samtranslator/region_configuration.py b/samtranslator/region_configuration.py index 712d4faf9..c7c6b55e8 100644 --- a/samtranslator/region_configuration.py +++ b/samtranslator/region_configuration.py @@ -1,3 +1,5 @@ +import boto3 + from .translator.arn_generator import ArnGenerator @@ -22,3 +24,15 @@ def is_apigw_edge_configuration_supported(cls): "aws-iso-b", "aws-cn", ] + + @classmethod + def is_sar_supported(cls): + """ + SAR is not supported in af-south-1 at the moment. + https://aws.amazon.com/about-aws/global-infrastructure/regional-product-services/ + + :return: True, if SAR is supported in current region. + """ + return boto3.Session().region_name not in [ + "af-south-1", + ] diff --git a/samtranslator/sdk/resource.py b/samtranslator/sdk/resource.py index d430feaa6..94b310281 100644 --- a/samtranslator/sdk/resource.py +++ b/samtranslator/sdk/resource.py @@ -23,6 +23,8 @@ def __init__(self, resource_dict): self.resource_dict = resource_dict self.type = resource_dict.get("Type") self.condition = resource_dict.get("Condition", None) + self.deletion_policy = resource_dict.get("DeletionPolicy", None) + self.update_replace_policy = resource_dict.get("UpdateReplacePolicy", None) # Properties is *not* required. Ex: SimpleTable resource has no required properties self.properties = resource_dict.get("Properties", {}) @@ -41,6 +43,20 @@ def valid(self): if not is_str()(self.condition, should_raise=False): raise InvalidDocumentException([InvalidTemplateException("Every Condition member must be a string.")]) + if self.deletion_policy: + + if not is_str()(self.deletion_policy, should_raise=False): + raise InvalidDocumentException( + [InvalidTemplateException("Every DeletionPolicy member must be a string.")] + ) + + if self.update_replace_policy: + + if not is_str()(self.update_replace_policy, should_raise=False): + raise InvalidDocumentException( + [InvalidTemplateException("Every UpdateReplacePolicy member must be a string.")] + ) + return SamResourceType.has_value(self.type) def to_dict(self): diff --git a/samtranslator/swagger/swagger.py b/samtranslator/swagger/swagger.py index 85b131855..53ac00bee 100644 --- a/samtranslator/swagger/swagger.py +++ b/samtranslator/swagger/swagger.py @@ -698,7 +698,7 @@ def add_auth_to_method(self, path, method_name, auth, api): if method_apikey_required is not None: self._set_method_apikey_handling(path, method_name, method_apikey_required) - def _set_method_authorizer(self, path, method_name, authorizer_name, authorizers={}, method_scopes=None): + def _set_method_authorizer(self, path, method_name, authorizer_name, authorizers=None, method_scopes=None): """ Adds the authorizer_name to the security block for each method on this path. This is used to configure the authorizer for individual functions. @@ -708,6 +708,8 @@ def _set_method_authorizer(self, path, method_name, authorizer_name, authorizers :param string authorizer_name: Name of the authorizer to use. Must be a key in the authorizers param. """ + if authorizers is None: + authorizers = {} normalized_method_name = self._normalize_method_name(method_name) # It is possible that the method could have two definitions in a Fn::If block. for method_definition in self.get_method_contents(self.get_path(path)[normalized_method_name]): @@ -877,6 +879,8 @@ def add_resource_policy(self, resource_policy, path, api_id, stage): ip_range_blacklist = resource_policy.get("IpRangeBlacklist") source_vpc_whitelist = resource_policy.get("SourceVpcWhitelist") source_vpc_blacklist = resource_policy.get("SourceVpcBlacklist") + + # Intrinsic's supported in these properties source_vpc_intrinsic_whitelist = resource_policy.get("IntrinsicVpcWhitelist") source_vpce_intrinsic_whitelist = resource_policy.get("IntrinsicVpceWhitelist") source_vpc_intrinsic_blacklist = resource_policy.get("IntrinsicVpcBlacklist") @@ -898,31 +902,38 @@ def add_resource_policy(self, resource_policy, path, api_id, stage): resource_list = self._get_method_path_uri_list(path, api_id, stage) self._add_ip_resource_policy_for_method(ip_range_blacklist, "IpAddress", resource_list) - if ( - (source_vpc_blacklist is not None) - or (source_vpc_intrinsic_blacklist is not None) - or (source_vpce_intrinsic_blacklist is not None) - ): - blacklist_dict = { - "StringEndpointList": source_vpc_blacklist, - "IntrinsicVpcList": source_vpc_intrinsic_blacklist, - "IntrinsicVpceList": source_vpce_intrinsic_blacklist, - } - resource_list = self._get_method_path_uri_list(path, api_id, stage) - self._add_vpc_resource_policy_for_method(blacklist_dict, "StringEquals", resource_list) + if not SwaggerEditor._validate_list_property_is_resolved(source_vpc_blacklist): + raise InvalidDocumentException( + [ + InvalidTemplateException( + "SourceVpcBlacklist must be a list of strings. Use IntrinsicVpcBlacklist instead for values that use Intrinsic Functions" + ) + ] + ) - if ( - (source_vpc_whitelist is not None) - or (source_vpc_intrinsic_whitelist is not None) - or (source_vpce_intrinsic_whitelist is not None) - ): - whitelist_dict = { - "StringEndpointList": source_vpc_whitelist, - "IntrinsicVpcList": source_vpc_intrinsic_whitelist, - "IntrinsicVpceList": source_vpce_intrinsic_whitelist, - } - resource_list = self._get_method_path_uri_list(path, api_id, stage) - self._add_vpc_resource_policy_for_method(whitelist_dict, "StringNotEquals", resource_list) + blacklist_dict = { + "StringEndpointList": source_vpc_blacklist, + "IntrinsicVpcList": source_vpc_intrinsic_blacklist, + "IntrinsicVpceList": source_vpce_intrinsic_blacklist, + } + resource_list = self._get_method_path_uri_list(path, api_id, stage) + self._add_vpc_resource_policy_for_method(blacklist_dict, "StringEquals", resource_list) + + if not SwaggerEditor._validate_list_property_is_resolved(source_vpc_whitelist): + raise InvalidDocumentException( + [ + InvalidTemplateException( + "SourceVpcWhitelist must be a list of strings. Use IntrinsicVpcWhitelist instead for values that use Intrinsic Functions" + ) + ] + ) + + whitelist_dict = { + "StringEndpointList": source_vpc_whitelist, + "IntrinsicVpcList": source_vpc_intrinsic_whitelist, + "IntrinsicVpceList": source_vpce_intrinsic_whitelist, + } + self._add_vpc_resource_policy_for_method(whitelist_dict, "StringNotEquals", resource_list) self._doc[self._X_APIGW_POLICY] = self.resource_policy @@ -1134,7 +1145,8 @@ def add_request_parameters_to_method(self, path, method_name, request_parameters parameter_name = request_parameter["Name"] location_name = parameter_name.replace("method.request.", "") - location, name = location_name.split(".") + + location, name = location_name.split(".", 1) if location == "querystring": location = "query" @@ -1252,3 +1264,17 @@ def safe_compare_regex_with_string(regex, data): def get_path_without_trailing_slash(path): # convert greedy paths to such as {greedy+}, {proxy+} to "*" return re.sub(r"{([a-zA-Z0-9._-]+|[a-zA-Z0-9._-]+\+|proxy\+)}", "*", path) + + @staticmethod + def _validate_list_property_is_resolved(property_list): + """ + Validate if the values of a Property List are all of type string + + :param property_list: Value of a Property List + :return bool: True if the property_list is all of type string otherwise False + """ + + if property_list is not None and not all(isinstance(x, string_types) for x in property_list): + return False + + return True diff --git a/samtranslator/translator/arn_generator.py b/samtranslator/translator/arn_generator.py index 9394a6c0a..897661e8d 100644 --- a/samtranslator/translator/arn_generator.py +++ b/samtranslator/translator/arn_generator.py @@ -6,7 +6,7 @@ class NoRegionFound(Exception): class ArnGenerator(object): - class_boto_session = None + BOTO_SESSION_REGION_NAME = None @classmethod def generate_arn(cls, partition, service, resource, include_account_id=True): @@ -50,10 +50,10 @@ def get_partition_name(cls, region=None): # Use Boto3 to get the region where code is running. This uses Boto's regular region resolution # mechanism, starting from AWS_DEFAULT_REGION environment variable. - if ArnGenerator.class_boto_session is None: + if ArnGenerator.BOTO_SESSION_REGION_NAME is None: region = boto3.session.Session().region_name else: - region = ArnGenerator.class_boto_session.region_name + region = ArnGenerator.BOTO_SESSION_REGION_NAME # If region is still None, then we could not find the region. This will only happen # in the local context. When this is deployed, we will be able to find the region like diff --git a/samtranslator/translator/translator.py b/samtranslator/translator/translator.py index d4ca78068..c7d7c415b 100644 --- a/samtranslator/translator/translator.py +++ b/samtranslator/translator/translator.py @@ -6,6 +6,7 @@ FeatureToggleDefaultConfigProvider, ) from samtranslator.model import ResourceTypeResolver, sam_resources +from samtranslator.model.api.api_generator import SharedApiUsagePlan from samtranslator.translator.verify_logical_id import verify_unique_logical_id from samtranslator.model.preferences.deployment_preference_collection import DeploymentPreferenceCollection from samtranslator.model.exceptions import ( @@ -44,7 +45,8 @@ def __init__(self, managed_policy_map, sam_parser, plugins=None, boto_session=No self.feature_toggle = None self.boto_session = boto_session - ArnGenerator.class_boto_session = self.boto_session + if self.boto_session: + ArnGenerator.BOTO_SESSION_REGION_NAME = self.boto_session.region_name def _get_function_names(self, resource_dict, intrinsics_resolver): """ @@ -111,6 +113,7 @@ def translate(self, sam_template, parameter_values, feature_toggle=None): ) deployment_preference_collection = DeploymentPreferenceCollection() supported_resource_refs = SupportedResourceReferences() + shared_api_usage_plan = SharedApiUsagePlan() document_errors = [] changed_logical_ids = {} for logical_id, resource_dict in self._get_resources_to_iterate(sam_template, macro_resolver): @@ -130,6 +133,7 @@ def translate(self, sam_template, parameter_values, feature_toggle=None): resource_dict, intrinsics_resolver ) kwargs["redeploy_restapi_parameters"] = self.redeploy_restapi_parameters + kwargs["shared_api_usage_plan"] = shared_api_usage_plan translated = macro.to_cloudformation(**kwargs) supported_resource_refs = macro.get_resource_references(translated, supported_resource_refs) @@ -223,7 +227,7 @@ def _get_resources_to_iterate(self, sam_template, macro_resolver): return functions + statemachines + apis + others -def prepare_plugins(plugins, parameters={}): +def prepare_plugins(plugins, parameters=None): """ Creates & returns a plugins object with the given list of plugins installed. In addition to the given plugins, we will also install a few "required" plugins that are necessary to provide complete support for SAM template spec. @@ -233,6 +237,8 @@ def prepare_plugins(plugins, parameters={}): :return samtranslator.plugins.SamPlugins: Instance of `SamPlugins` """ + if parameters is None: + parameters = {} required_plugins = [ DefaultDefinitionBodyPlugin(), make_implicit_rest_api_plugin(), diff --git a/samtranslator/validator/sam_schema/schema.json b/samtranslator/validator/sam_schema/schema.json index 72df7539e..4cf7f1f5c 100644 --- a/samtranslator/validator/sam_schema/schema.json +++ b/samtranslator/validator/sam_schema/schema.json @@ -6,11 +6,12 @@ "additionalProperties": false, "properties": { "DeletionPolicy": { - "enum": [ - "Delete", - "Retain", - "Snapshot" - ], + "type": "string" + }, + "UpdateReplacePolicy": { + "type": "string" + }, + "Condition": { "type": "string" }, "DependsOn": { @@ -132,11 +133,12 @@ "additionalProperties": false, "properties": { "DeletionPolicy": { - "enum": [ - "Delete", - "Retain", - "Snapshot" - ], + "type": "string" + }, + "UpdateReplacePolicy": { + "type": "string" + }, + "Condition": { "type": "string" }, "DependsOn": { @@ -838,11 +840,12 @@ "additionalProperties": false, "properties": { "DeletionPolicy": { - "enum": [ - "Delete", - "Retain", - "Snapshot" - ], + "type": "string" + }, + "UpdateReplacePolicy": { + "type": "string" + }, + "Condition": { "type": "string" }, "DependsOn": { diff --git a/setup.py b/setup.py index 38af9df29..8ca3f5605 100755 --- a/setup.py +++ b/setup.py @@ -58,7 +58,9 @@ def read_requirements(req="base.txt"): url="https://github.com/awslabs/serverless-application-model", license="Apache License 2.0", # Exclude all but the code folders - packages=find_packages(exclude=("tests", "tests.*", "docs", "examples", "versions")), + packages=find_packages( + exclude=("tests", "tests.*", "integration", "integration.*", "docs", "examples", "versions") + ), install_requires=read_requirements("base.txt"), include_package_data=True, extras_require={"dev": read_requirements("dev.txt")}, diff --git a/tests/intrinsics/test_resolver.py b/tests/intrinsics/test_resolver.py index 2f62b510f..946307b87 100644 --- a/tests/intrinsics/test_resolver.py +++ b/tests/intrinsics/test_resolver.py @@ -192,11 +192,6 @@ class SomeAction(Action): with self.assertRaises(TypeError): IntrinsicsResolver({}, supported_intrinsics) - def test_configure_supported_intrinsics_must_error_for_none_input(self): - - with self.assertRaises(TypeError): - IntrinsicsResolver({}, None) - def test_configure_supported_intrinsics_must_error_for_non_dict_input(self): with self.assertRaises(TypeError): diff --git a/tests/model/test_api.py b/tests/model/test_api.py index 627bda3d5..8bc871ee7 100644 --- a/tests/model/test_api.py +++ b/tests/model/test_api.py @@ -17,3 +17,18 @@ def test_create_authorizer_fails_with_string_authorization_scopes(self): auth = ApiGatewayAuthorizer( api_logical_id="logicalId", name="authName", authorization_scopes="invalid_scope" ) + + def test_create_authorizer_fails_with_missing_identity_values_and_not_cached(self): + with pytest.raises(InvalidResourceException): + auth = ApiGatewayAuthorizer( + api_logical_id="logicalId", + name="authName", + identity={"ReauthorizeEvery": 10}, + function_payload_type="REQUEST", + ) + + def test_create_authorizer_fails_with_empty_identity(self): + with pytest.raises(InvalidResourceException): + auth = ApiGatewayAuthorizer( + api_logical_id="logicalId", name="authName", identity={}, function_payload_type="REQUEST" + ) diff --git a/tests/model/test_sam_resources.py b/tests/model/test_sam_resources.py index e84c5b1ce..f37d82b05 100644 --- a/tests/model/test_sam_resources.py +++ b/tests/model/test_sam_resources.py @@ -332,3 +332,11 @@ def test_with_description_not_defined_in_definition_body(self): resources = sam_http_api.to_cloudformation(**self.kwargs) http_api = [x for x in resources if isinstance(x, ApiGatewayV2HttpApi)] self.assertEqual(http_api[0].Body.get("info", {}).get("description"), "new description") + + +class TestPassthroughResourceAttributes(TestCase): + def test_with_passthrough_resource_attributes(self): + expected = {"DeletionPolicy": "Delete", "UpdateReplacePolicy": "Retain", "Condition": "C1"} + function = SamFunction("foo", attributes=expected) + attributes = function.get_passthrough_resource_attributes() + self.assertEqual(attributes, expected) diff --git a/tests/openapi/test_openapi.py b/tests/openapi/test_openapi.py index f29cb4d47..99197de9c 100644 --- a/tests/openapi/test_openapi.py +++ b/tests/openapi/test_openapi.py @@ -292,7 +292,7 @@ def setUp(self): def test_must_iterate_on_paths(self): expected = {"/foo", "/bar", "/baz"} - actual = set([path for path in self.editor.iter_on_path()]) + actual = set(list(self.editor.iter_on_path())) self.assertEqual(expected, actual) diff --git a/tests/plugins/api/test_implicit_api_plugin.py b/tests/plugins/api/test_implicit_api_plugin.py index 3181e11bb..1ad927212 100644 --- a/tests/plugins/api/test_implicit_api_plugin.py +++ b/tests/plugins/api/test_implicit_api_plugin.py @@ -96,9 +96,9 @@ def test_must_process_functions(self, SamTemplateMock): self.plugin._get_api_events.assert_has_calls([call(function1), call(function2), call(function3)]) self.plugin._process_api_events.assert_has_calls( [ - call(function1, ["event1", "event2"], sam_template, None), - call(function2, ["event1", "event2"], sam_template, None), - call(function3, ["event1", "event2"], sam_template, None), + call(function1, ["event1", "event2"], sam_template, None, None, None), + call(function2, ["event1", "event2"], sam_template, None, None, None), + call(function3, ["event1", "event2"], sam_template, None, None, None), ] ) @@ -133,9 +133,9 @@ def test_must_process_state_machines(self, SamTemplateMock): self.plugin._get_api_events.assert_has_calls([call(statemachine1), call(statemachine2), call(statemachine3)]) self.plugin._process_api_events.assert_has_calls( [ - call(statemachine1, ["event1", "event2"], sam_template, None), - call(statemachine2, ["event1", "event2"], sam_template, None), - call(statemachine3, ["event1", "event2"], sam_template, None), + call(statemachine1, ["event1", "event2"], sam_template, None, None, None), + call(statemachine2, ["event1", "event2"], sam_template, None, None, None), + call(statemachine3, ["event1", "event2"], sam_template, None, None, None), ] ) @@ -813,3 +813,38 @@ def test_must_restore_if_existing_resource_present(self): # Must restore original resource template.set.assert_called_with(IMPLICIT_API_LOGICAL_ID, resource) + + +class TestImplicitApiPlugin_generate_resource_attributes(TestCase): + def setUp(self): + self.plugin = ImplicitRestApiPlugin() + self.plugin.api_conditions = {} + + def test_maybe_add_condition(self): + template_dict = {"Resources": {"ServerlessRestApi": {"Type": "AWS::Serverless::Api"}}} + self.plugin.api_conditions = {"ServerlessRestApi": {"/{proxy+}": {"any": "C1"}}} + self.plugin._maybe_add_condition_to_implicit_api(template_dict) + print(template_dict) + self.assertEqual( + template_dict, {"Resources": {"ServerlessRestApi": {"Type": "AWS::Serverless::Api", "Condition": "C1"}}} + ) + + def test_maybe_add_deletion_policies(self): + template_dict = {"Resources": {"ServerlessRestApi": {"Type": "AWS::Serverless::Api"}}} + self.plugin.api_deletion_policies = {"ServerlessRestApi": {"Delete", "Retain"}} + self.plugin._maybe_add_deletion_policy_to_implicit_api(template_dict) + print(template_dict) + self.assertEqual( + template_dict, + {"Resources": {"ServerlessRestApi": {"Type": "AWS::Serverless::Api", "DeletionPolicy": "Retain"}}}, + ) + + def test_maybe_add_update_replace_policies(self): + template_dict = {"Resources": {"ServerlessRestApi": {"Type": "AWS::Serverless::Api"}}} + self.plugin.api_update_replace_policies = {"ServerlessRestApi": {"Snapshot", "Retain"}} + self.plugin._maybe_add_update_replace_policy_to_implicit_api(template_dict) + print(template_dict) + self.assertEqual( + template_dict, + {"Resources": {"ServerlessRestApi": {"Type": "AWS::Serverless::Api", "UpdateReplacePolicy": "Retain"}}}, + ) diff --git a/tests/swagger/test_swagger.py b/tests/swagger/test_swagger.py index 615fabec3..f237aa103 100644 --- a/tests/swagger/test_swagger.py +++ b/tests/swagger/test_swagger.py @@ -302,7 +302,7 @@ def setUp(self): def test_must_iterate_on_paths(self): expected = {"/foo", "/bar", "/baz"} - actual = set([path for path in self.editor.iter_on_path()]) + actual = set(list(self.editor.iter_on_path())) self.assertEqual(expected, actual) @@ -1181,6 +1181,18 @@ def test_must_add_vpc_allow_string_only(self): self.assertEqual(deep_sort_lists(expected), deep_sort_lists(self.editor.swagger[_X_POLICY])) + @parameterized.expand( + [ + param("SourceVpcWhitelist"), + param("SourceVpcBlacklist"), + ] + ) + def test_must_fail_when_vpc_whitelist_is_non_string(self, resource_policy_key): + resource_policy = {resource_policy_key: [{"sub": "somevalue"}]} + + with self.assertRaises(InvalidDocumentException): + self.editor.add_resource_policy(resource_policy, "/foo", "123", "prod") + def test_must_add_vpc_deny_string_only(self): resourcePolicy = { diff --git a/tests/test_model.py b/tests/test_model.py index 84da811b8..9783be7b5 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -107,6 +107,15 @@ def test_to_dict(self): dict_with_attributes = { "id": {"Type": "foo", "Properties": {}, "UpdatePolicy": "update", "DeletionPolicy": {"foo": "bar"}} } + dict_with_attributes2 = { + "id": { + "Type": "foo", + "Properties": {}, + "UpdateReplacePolicy": "update", + "Metadata": {"foo": "bar"}, + "Condition": "con", + } + } r = self.MyResource("id") self.assertEqual(r.to_dict(), empty_resource_dict) @@ -114,6 +123,11 @@ def test_to_dict(self): r = self.MyResource("id", attributes={"UpdatePolicy": "update", "DeletionPolicy": {"foo": "bar"}}) self.assertEqual(r.to_dict(), dict_with_attributes) + r = self.MyResource( + "id", attributes={"UpdateReplacePolicy": "update", "Metadata": {"foo": "bar"}, "Condition": "con"} + ) + self.assertEqual(r.to_dict(), dict_with_attributes2) + def test_invalid_attr(self): with pytest.raises(KeyError) as ex: @@ -140,6 +154,9 @@ def test_from_dict(self): "Properties": {}, "UpdatePolicy": "update", "DeletionPolicy": [1, 2, 3], + "UpdateReplacePolicy": "update", + "Metadata": {"foo": "bar"}, + "Condition": "con", } r = self.MyResource.from_dict("id", resource_dict=no_attribute) @@ -148,6 +165,9 @@ def test_from_dict(self): r = self.MyResource.from_dict("id", resource_dict=all_supported_attributes) self.assertEqual(r.get_resource_attribute("DeletionPolicy"), [1, 2, 3]) self.assertEqual(r.get_resource_attribute("UpdatePolicy"), "update") + self.assertEqual(r.get_resource_attribute("UpdateReplacePolicy"), "update") + self.assertEqual(r.get_resource_attribute("Metadata"), {"foo": "bar"}) + self.assertEqual(r.get_resource_attribute("Condition"), "con") class TestResourceRuntimeAttributes(TestCase): diff --git a/tests/translator/input/api_with_auth_all_minimum.yaml b/tests/translator/input/api_with_auth_all_minimum.yaml index f6eda0af2..b89479219 100644 --- a/tests/translator/input/api_with_auth_all_minimum.yaml +++ b/tests/translator/input/api_with_auth_all_minimum.yaml @@ -32,6 +32,20 @@ Resources: Identity: Headers: - Authorization1 + + MyApiWithNotCachedLambdaRequestAuth: + Type: "AWS::Serverless::Api" + Properties: + StageName: Prod + Auth: + DefaultAuthorizer: MyLambdaRequestAuth + Authorizers: + MyLambdaRequestAuth: + FunctionPayloadType: REQUEST + FunctionArn: !GetAtt MyAuthFn.Arn + Identity: + ReauthorizeEvery: 0 + MyAuthFn: Type: AWS::Serverless::Function Properties: @@ -63,6 +77,12 @@ Resources: RestApiId: !Ref MyApiWithLambdaRequestAuth Method: get Path: /lambda-request + LambdaNotCachedRequest: + Type: Api + Properties: + RestApiId: !Ref MyApiWithNotCachedLambdaRequestAuth + Method: get + Path: /not-cached-lambda-request MyUserPool: Type: AWS::Cognito::UserPool Properties: diff --git a/tests/translator/input/api_with_swagger_authorizer_none.yaml b/tests/translator/input/api_with_swagger_authorizer_none.yaml new file mode 100644 index 000000000..eb0ae32be --- /dev/null +++ b/tests/translator/input/api_with_swagger_authorizer_none.yaml @@ -0,0 +1,117 @@ +Resources: + MyApiWithCognitoAuth: + Type: "AWS::Serverless::Api" + Properties: + StageName: Prod + Auth: + Authorizers: + MyCognitoAuth: + UserPoolArn: !GetAtt MyUserPool.Arn + DefaultAuthorizer: MyCognitoAuth + + MyApiWithLambdaTokenAuth: + Type: "AWS::Serverless::Api" + Properties: + StageName: Prod + Auth: + Authorizers: + MyLambdaTokenAuth: + FunctionArn: !GetAtt MyAuthFn.Arn + DefaultAuthorizer: MyLambdaTokenAuth + + MyApiWithLambdaRequestAuth: + Type: "AWS::Serverless::Api" + Properties: + StageName: Prod + DefinitionBody: + swagger: 2.0 + info: + version: '1.0' + title: !Ref AWS::StackName + schemes: + - https + paths: + "/lambda-request": + get: + x-amazon-apigateway-integration: + httpMethod: POST + type: aws_proxy + uri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations + passthroughBehavior: when_no_match + responses: {} + Auth: + Authorizers: + MyLambdaRequestAuth: + FunctionPayloadType: REQUEST + FunctionArn: !GetAtt MyAuthFn.Arn + Identity: + Headers: + - Authorization1 + DefaultAuthorizer: MyLambdaRequestAuth + + MyAuthFn: + Type: AWS::Serverless::Function + Properties: + InlineCode: | + exports.handler = async (event) => { + return { + statusCode: 200, + body: JSON.stringify(event), + headers: {} + } + } + Handler: index.handler + Runtime: nodejs8.10 + + MyFn: + Type: AWS::Serverless::Function + Properties: + InlineCode: | + exports.handler = async (event) => { + return { + statusCode: 200, + body: JSON.stringify(event), + headers: {} + } + } + Handler: index.handler + Runtime: nodejs8.10 + Events: + Cognito: + Type: Api + Properties: + RestApiId: !Ref MyApiWithCognitoAuth + Method: get + Auth: + Authorizer: NONE + Path: /cognito + LambdaToken: + Type: Api + Properties: + RestApiId: !Ref MyApiWithLambdaTokenAuth + Method: get + Auth: + Authorizer: NONE + Path: /lambda-token + LambdaRequest: + Type: Api + Properties: + RestApiId: !Ref MyApiWithLambdaRequestAuth + Auth: + Authorizer: NONE + Method: get + Path: /lambda-request + + MyUserPool: + Type: AWS::Cognito::UserPool + Properties: + UserPoolName: UserPoolName + Policies: + PasswordPolicy: + MinimumLength: 8 + UsernameAttributes: + - email + Schema: + - AttributeDataType: String + Name: email + Required: false \ No newline at end of file diff --git a/tests/translator/input/error_api_invalid_source_vpc_blacklist.yaml b/tests/translator/input/error_api_invalid_source_vpc_blacklist.yaml new file mode 100644 index 000000000..27eadd1fe --- /dev/null +++ b/tests/translator/input/error_api_invalid_source_vpc_blacklist.yaml @@ -0,0 +1,31 @@ +Globals: + Api: + Auth: + ResourcePolicy: + SourceVpcBlacklist: [{"Ref":"SomeParameter"}] + +Parameters: + SomeParameter: + Type: String + Default: param + +Resources: + MyFunction: + 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: + Api: + Type: Api + Properties: + Method: Put + Path: /get \ No newline at end of file diff --git a/tests/translator/input/error_api_invalid_source_vpc_whitelist.yaml b/tests/translator/input/error_api_invalid_source_vpc_whitelist.yaml new file mode 100644 index 000000000..99ba49f2d --- /dev/null +++ b/tests/translator/input/error_api_invalid_source_vpc_whitelist.yaml @@ -0,0 +1,31 @@ +Globals: + Api: + Auth: + ResourcePolicy: + SourceVpcWhitelist: [{"Ref":"SomeParameter"}] + +Parameters: + SomeParameter: + Type: String + Default: param + +Resources: + MyFunction: + 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: + Api: + Type: Api + Properties: + Method: Put + Path: /get \ No newline at end of file diff --git a/tests/translator/input/function_with_request_parameters.yaml b/tests/translator/input/function_with_request_parameters.yaml index e77a7c4a8..2875457c5 100644 --- a/tests/translator/input/function_with_request_parameters.yaml +++ b/tests/translator/input/function_with_request_parameters.yaml @@ -38,3 +38,4 @@ Resources: RequestParameters: - method.request.querystring.type - method.request.path.id + - method.request.querystring.full.type diff --git a/tests/translator/input/implicit_api_deletion_policy_precedence.yaml b/tests/translator/input/implicit_api_deletion_policy_precedence.yaml new file mode 100644 index 000000000..643b9ac47 --- /dev/null +++ b/tests/translator/input/implicit_api_deletion_policy_precedence.yaml @@ -0,0 +1,32 @@ +Resources: + RestApiFunction: + Type: AWS::Serverless::Function + DeletionPolicy: Delete + UpdateReplacePolicy: Retain + Properties: + CodeUri: s3://sam-demo-bucket/todo_list.zip + Handler: index.restapi + Runtime: nodejs12.x + Policies: AmazonDynamoDBFullAccess + Events: + GetHtml: + Type: Api + Properties: + Path: /{proxy+} + Method: any + + GetHtmlFunction: + Type: AWS::Serverless::Function + DeletionPolicy: Retain + UpdateReplacePolicy: Retain + Properties: + CodeUri: s3://sam-demo-bucket/todo_list.zip + Handler: index.gethtml + Runtime: nodejs12.x + Policies: AmazonDynamoDBReadOnlyAccess + Events: + GetHtml: + Type: Api + Properties: + Path: /{proxy++} + Method: any diff --git a/tests/translator/input/layer_deletion_policy_precedence.yaml b/tests/translator/input/layer_deletion_policy_precedence.yaml new file mode 100644 index 000000000..a967ed621 --- /dev/null +++ b/tests/translator/input/layer_deletion_policy_precedence.yaml @@ -0,0 +1,18 @@ +Resources: + MinimalLayer: + Type: 'AWS::Serverless::LayerVersion' + DeletionPolicy: Delete + Properties: + ContentUri: s3://sam-demo-bucket/layer.zip + RetentionPolicy: Retain + + MinimalLayer2: + Type: 'AWS::Serverless::LayerVersion' + DeletionPolicy: Delete + Properties: + ContentUri: s3://sam-demo-bucket/layer.zip + + MinimalLayer3: + Type: 'AWS::Serverless::LayerVersion' + Properties: + ContentUri: s3://sam-demo-bucket/layer.zip \ No newline at end of file diff --git a/tests/translator/input/version_deletion_policy_precedence.yaml b/tests/translator/input/version_deletion_policy_precedence.yaml new file mode 100644 index 000000000..bf868f9a6 --- /dev/null +++ b/tests/translator/input/version_deletion_policy_precedence.yaml @@ -0,0 +1,19 @@ +Resources: + MinimalFunction: + Type: 'AWS::Serverless::Function' + Properties: + CodeUri: s3://sam-demo-bucket/hello.zip + Handler: hello.handler + Runtime: python2.7 + AutoPublishAlias: live + VersionDescription: sam-testing + + MinimalFunction2: + Type: 'AWS::Serverless::Function' + DeletionPolicy: Delete + Properties: + CodeUri: s3://sam-demo-bucket/hello.zip + Handler: hello.handler + Runtime: python2.7 + AutoPublishAlias: live + VersionDescription: sam-testing \ No newline at end of file diff --git a/tests/translator/output/api_with_auth_all_minimum.json b/tests/translator/output/api_with_auth_all_minimum.json index 640d74f16..4a4dbe937 100644 --- a/tests/translator/output/api_with_auth_all_minimum.json +++ b/tests/translator/output/api_with_auth_all_minimum.json @@ -63,7 +63,19 @@ }, "StageName": "Prod" } - }, + }, + "MyApiWithNotCachedLambdaRequestAuthProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiWithNotCachedLambdaRequestAuthDeployment444f67cd7c" + }, + "RestApiId": { + "Ref": "MyApiWithNotCachedLambdaRequestAuth" + }, + "StageName": "Prod" + } + }, "MyApiWithLambdaTokenAuthMyLambdaTokenAuthAuthorizerPermission": { "Type": "AWS::Lambda::Permission", "Properties": { @@ -205,7 +217,30 @@ ] } } - }, + }, + "MyApiWithNotCachedLambdaRequestAuthMyLambdaRequestAuthAuthorizerPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/authorizers/*", + { + "__ApiId__": { + "Ref": "MyApiWithNotCachedLambdaRequestAuth" + } + } + ] + } + } + }, "MyFnLambdaTokenPermissionProd": { "Type": "AWS::Lambda::Permission", "Properties": { @@ -236,7 +271,17 @@ "Description": "RestApi deployment id: 6e52add211cda52ae10a7cc0e0afcf4afc682f9f", "StageName": "Stage" } - }, + }, + "MyApiWithNotCachedLambdaRequestAuthDeployment444f67cd7c": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApiWithNotCachedLambdaRequestAuth" + }, + "Description": "RestApi deployment id: 444f67cd7c6475a698a0101480ba99b498325e90", + "StageName": "Stage" + } + }, "MyFnLambdaRequestPermissionProd": { "Type": "AWS::Lambda::Permission", "Properties": { @@ -257,7 +302,28 @@ ] } } - }, + }, + "MyFnLambdaNotCachedRequestPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/not-cached-lambda-request", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithNotCachedLambdaRequestAuth" + } + } + ] + } + } + }, "MyApiWithLambdaTokenAuth": { "Type": "AWS::ApiGateway::RestApi", "Properties": { @@ -468,6 +534,64 @@ } } } + }, + "MyApiWithNotCachedLambdaRequestAuth": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/not-cached-lambda-request": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" + } + }, + "security": [ + { + "MyLambdaRequestAuth": [] + } + ], + "responses": {} + } + } + }, + "swagger": "2.0", + "securityDefinitions": { + "MyLambdaRequestAuth": { + "in": "header", + "type": "apiKey", + "name": "Unused", + "x-amazon-apigateway-authorizer": { + "type": "request", + "authorizerUri": { + "Fn::Sub": [ + "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + { + "__FunctionArn__": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + } + } + ] + }, + "authorizerResultTtlInSeconds": 0 + }, + "x-amazon-apigateway-authtype": "custom" + } + } + } + } } } } diff --git a/tests/translator/output/api_with_swagger_authorizer_none.json b/tests/translator/output/api_with_swagger_authorizer_none.json index d9b372b88..be6d32c56 100644 --- a/tests/translator/output/api_with_swagger_authorizer_none.json +++ b/tests/translator/output/api_with_swagger_authorizer_none.json @@ -1,475 +1,475 @@ { - "Resources": { - "MyFnCognitoPermissionProd": { - "Type": "AWS::Lambda::Permission", - "Properties": { - "Action": "lambda:InvokeFunction", - "Principal": "apigateway.amazonaws.com", - "FunctionName": { - "Ref": "MyFn" - }, - "SourceArn": { - "Fn::Sub": [ - "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/cognito", - { - "__Stage__": "*", - "__ApiId__": { - "Ref": "MyApiWithCognitoAuth" + "Resources": { + "MyFnCognitoPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/cognito", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithCognitoAuth" + } } - } - ] + ] + } } - } - }, - "MyApiWithCognitoAuth": { - "Type": "AWS::ApiGateway::RestApi", - "Properties": { - "Body": { - "info": { - "version": "1.0", - "title": { - "Ref": "AWS::StackName" - } - }, - "paths": { - "/cognito": { - "get": { - "x-amazon-apigateway-integration": { - "httpMethod": "POST", - "type": "aws_proxy", - "uri": { - "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" - } + }, + "MyApiWithCognitoAuth": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/cognito": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" + } + }, + "security": [ + { + "NONE": [] + } + ], + "responses": {} + } + } + }, + "swagger": "2.0", + "securityDefinitions": { + "MyCognitoAuth": { + "in": "header", + "type": "apiKey", + "name": "Authorization", + "x-amazon-apigateway-authorizer": { + "providerARNs": [ + { + "Fn::GetAtt": [ + "MyUserPool", + "Arn" + ] + } + ], + "type": "cognito_user_pools" }, - "security": [ - { - "NONE": [] - } - ], - "responses": {} + "x-amazon-apigateway-authtype": "cognito_user_pools" } } + } + } + }, + "MyApiWithLambdaRequestAuthMyLambdaRequestAuthAuthorizerPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] }, - "swagger": "2.0", - "securityDefinitions": { - "MyCognitoAuth": { - "in": "header", - "type": "apiKey", - "name": "Authorization", - "x-amazon-apigateway-authorizer": { - "providerARNs": [ - { - "Fn::GetAtt": [ - "MyUserPool", - "Arn" - ] - } - ], - "type": "cognito_user_pools" - }, - "x-amazon-apigateway-authtype": "cognito_user_pools" - } + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/authorizers/*", + { + "__ApiId__": { + "Ref": "MyApiWithLambdaRequestAuth" + } + } + ] } } - } - }, - "MyApiWithLambdaRequestAuthMyLambdaRequestAuthAuthorizerPermission": { - "Type": "AWS::Lambda::Permission", - "Properties": { - "Action": "lambda:InvokeFunction", - "Principal": "apigateway.amazonaws.com", - "FunctionName": { - "Fn::GetAtt": [ - "MyAuthFn", - "Arn" - ] - }, - "SourceArn": { - "Fn::Sub": [ - "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/authorizers/*", - { - "__ApiId__": { - "Ref": "MyApiWithLambdaRequestAuth" + }, + "MyApiWithLambdaRequestAuthProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiWithLambdaRequestAuthDeploymentfeb40d0e71" + }, + "RestApiId": { + "Ref": "MyApiWithLambdaRequestAuth" + }, + "StageName": "Prod" + } + }, + "MyFnLambdaTokenPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/lambda-token", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithLambdaTokenAuth" + } } - } - ] + ] + } } - } - }, - "MyApiWithLambdaRequestAuthProdStage": { - "Type": "AWS::ApiGateway::Stage", - "Properties": { - "DeploymentId": { - "Ref": "MyApiWithLambdaRequestAuthDeploymentfeb40d0e71" - }, - "RestApiId": { - "Ref": "MyApiWithLambdaRequestAuth" - }, - "StageName": "Prod" - } - }, - "MyFnLambdaTokenPermissionProd": { - "Type": "AWS::Lambda::Permission", - "Properties": { - "Action": "lambda:InvokeFunction", - "Principal": "apigateway.amazonaws.com", - "FunctionName": { - "Ref": "MyFn" - }, - "SourceArn": { - "Fn::Sub": [ - "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/lambda-token", - { - "__Stage__": "*", - "__ApiId__": { - "Ref": "MyApiWithLambdaTokenAuth" + }, + "MyApiWithLambdaTokenAuthMyLambdaTokenAuthAuthorizerPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/authorizers/*", + { + "__ApiId__": { + "Ref": "MyApiWithLambdaTokenAuth" + } + } + ] + } + } + }, + "MyFnLambdaRequestPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/lambda-request", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithLambdaRequestAuth" + } + } + ] + } + } + }, + "MyApiWithLambdaTokenAuth": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/lambda-token": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" + } + }, + "security": [ + { + "NONE": [] + } + ], + "responses": {} + } + } + }, + "swagger": "2.0", + "securityDefinitions": { + "MyLambdaTokenAuth": { + "in": "header", + "type": "apiKey", + "name": "Authorization", + "x-amazon-apigateway-authorizer": { + "type": "token", + "authorizerUri": { + "Fn::Sub": [ + "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + { + "__FunctionArn__": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + } + } + ] + } + }, + "x-amazon-apigateway-authtype": "custom" } } - ] + } } - } - }, - "MyApiWithLambdaTokenAuthMyLambdaTokenAuthAuthorizerPermission": { - "Type": "AWS::Lambda::Permission", - "Properties": { - "Action": "lambda:InvokeFunction", - "Principal": "apigateway.amazonaws.com", - "FunctionName": { - "Fn::GetAtt": [ - "MyAuthFn", - "Arn" - ] - }, - "SourceArn": { - "Fn::Sub": [ - "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/authorizers/*", + }, + "MyApiWithLambdaTokenAuthProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiWithLambdaTokenAuthDeployment4644d735d8" + }, + "RestApiId": { + "Ref": "MyApiWithLambdaTokenAuth" + }, + "StageName": "Prod" + } + }, + "MyApiWithLambdaRequestAuthDeploymentfeb40d0e71": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApiWithLambdaRequestAuth" + }, + "Description": "RestApi deployment id: feb40d0e712dce07ba2392d6bb86eff0c5b22b7b", + "StageName": "Stage" + } + }, + "MyUserPool": { + "Type": "AWS::Cognito::UserPool", + "Properties": { + "UsernameAttributes": [ + "email" + ], + "UserPoolName": "UserPoolName", + "Policies": { + "PasswordPolicy": { + "MinimumLength": 8 + } + }, + "Schema": [ { - "__ApiId__": { - "Ref": "MyApiWithLambdaTokenAuth" - } + "AttributeDataType": "String", + "Required": false, + "Name": "email" } ] } - } - }, - "MyFnLambdaRequestPermissionProd": { - "Type": "AWS::Lambda::Permission", - "Properties": { - "Action": "lambda:InvokeFunction", - "Principal": "apigateway.amazonaws.com", - "FunctionName": { - "Ref": "MyFn" - }, - "SourceArn": { - "Fn::Sub": [ - "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/lambda-request", + }, + "MyAuthFn": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.handler", + "Code": { + "ZipFile": "exports.handler = async (event) => {\n return {\n statusCode: 200,\n body: JSON.stringify(event),\n headers: {}\n }\n}\n" + }, + "Role": { + "Fn::GetAtt": [ + "MyAuthFnRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ { - "__Stage__": "*", - "__ApiId__": { - "Ref": "MyApiWithLambdaRequestAuth" - } + "Value": "SAM", + "Key": "lambda:createdBy" } ] } - } - }, - "MyApiWithLambdaTokenAuth": { - "Type": "AWS::ApiGateway::RestApi", - "Properties": { - "Body": { - "info": { - "version": "1.0", - "title": { - "Ref": "AWS::StackName" - } - }, - "paths": { - "/lambda-token": { - "get": { - "x-amazon-apigateway-integration": { - "httpMethod": "POST", - "type": "aws_proxy", - "uri": { - "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" - } - }, - "security": [ - { - "NONE": [] - } - ], - "responses": {} + }, + "MyApiWithLambdaRequestAuth": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" } - } - }, - "swagger": "2.0", - "securityDefinitions": { - "MyLambdaTokenAuth": { - "in": "header", - "type": "apiKey", - "name": "Authorization", - "x-amazon-apigateway-authorizer": { - "type": "token", - "authorizerUri": { - "Fn::Sub": [ - "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + }, + "paths": { + "/lambda-request": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "passthroughBehavior": "when_no_match", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" + } + }, + "security": [ { - "__FunctionArn__": { - "Fn::GetAtt": [ - "MyAuthFn", - "Arn" - ] - } + "NONE": [] } - ] + ], + "responses": {} } - }, - "x-amazon-apigateway-authtype": "custom" - } - } - } - } - }, - "MyApiWithLambdaTokenAuthProdStage": { - "Type": "AWS::ApiGateway::Stage", - "Properties": { - "DeploymentId": { - "Ref": "MyApiWithLambdaTokenAuthDeployment4644d735d8" - }, - "RestApiId": { - "Ref": "MyApiWithLambdaTokenAuth" - }, - "StageName": "Prod" - } - }, - "MyApiWithLambdaRequestAuthDeploymentfeb40d0e71": { - "Type": "AWS::ApiGateway::Deployment", - "Properties": { - "RestApiId": { - "Ref": "MyApiWithLambdaRequestAuth" - }, - "Description": "RestApi deployment id: feb40d0e712dce07ba2392d6bb86eff0c5b22b7b", - "StageName": "Stage" - } - }, - "MyUserPool": { - "Type": "AWS::Cognito::UserPool", - "Properties": { - "UsernameAttributes": [ - "email" - ], - "UserPoolName": "UserPoolName", - "Policies": { - "PasswordPolicy": { - "MinimumLength": 8 - } - }, - "Schema": [ - { - "AttributeDataType": "String", - "Required": false, - "Name": "email" - } - ] - } - }, - "MyAuthFn": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Handler": "index.handler", - "Code": { - "ZipFile": "exports.handler = async (event) => {\n return {\n statusCode: 200,\n body: JSON.stringify(event),\n headers: {}\n }\n}\n" - }, - "Role": { - "Fn::GetAtt": [ - "MyAuthFnRole", - "Arn" - ] - }, - "Runtime": "nodejs12.x", - "Tags": [ - { - "Value": "SAM", - "Key": "lambda:createdBy" - } - ] - } - }, - "MyApiWithLambdaRequestAuth": { - "Type": "AWS::ApiGateway::RestApi", - "Properties": { - "Body": { - "info": { - "version": "1.0", - "title": { - "Ref": "AWS::StackName" - } - }, - "paths": { - "/lambda-request": { - "get": { - "x-amazon-apigateway-integration": { - "httpMethod": "POST", - "passthroughBehavior": "when_no_match", - "type": "aws_proxy", - "uri": { - "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" + } + }, + "swagger": 2.0, + "schemes": [ + "https" + ], + "securityDefinitions": { + "MyLambdaRequestAuth": { + "in": "header", + "type": "apiKey", + "name": "Unused", + "x-amazon-apigateway-authorizer": { + "type": "request", + "identitySource": "method.request.header.Authorization1", + "authorizerUri": { + "Fn::Sub": [ + "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + { + "__FunctionArn__": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + } + } + ] } }, - "security": [ - { - "NONE": [] - } - ], - "responses": {} + "x-amazon-apigateway-authtype": "custom" } } + } + } + }, + "MyApiWithLambdaTokenAuthDeployment4644d735d8": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApiWithLambdaTokenAuth" }, - "swagger": 2.0, - "schemes": [ - "https" + "Description": "RestApi deployment id: 4644d735d869a70806f7145ca725b1c8cb248fb7", + "StageName": "Stage" + } + }, + "MyAuthFnRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" ], - "securityDefinitions": { - "MyLambdaRequestAuth": { - "in": "header", - "type": "apiKey", - "name": "Unused", - "x-amazon-apigateway-authorizer": { - "type": "request", - "identitySource": "method.request.header.Authorization1", - "authorizerUri": { - "Fn::Sub": [ - "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", - { - "__FunctionArn__": { - "Fn::GetAtt": [ - "MyAuthFn", - "Arn" - ] - } - } + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" ] } - }, - "x-amazon-apigateway-authtype": "custom" - } + } + ] } } - } - }, - "MyApiWithLambdaTokenAuthDeployment4644d735d8": { - "Type": "AWS::ApiGateway::Deployment", - "Properties": { - "RestApiId": { - "Ref": "MyApiWithLambdaTokenAuth" - }, - "Description": "RestApi deployment id: 4644d735d869a70806f7145ca725b1c8cb248fb7", - "StageName": "Stage" - } - }, - "MyAuthFnRole": { - "Type": "AWS::IAM::Role", - "Properties": { - "ManagedPolicyArns": [ - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - ], - "Tags": [ - { - "Value": "SAM", - "Key": "lambda:createdBy" - } - ], - "AssumeRolePolicyDocument": { - "Version": "2012-10-17", - "Statement": [ + }, + "MyApiWithCognitoAuthDeploymentf67b169f98": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApiWithCognitoAuth" + }, + "Description": "RestApi deployment id: f67b169f98fefb4627c6065af2d5e26ca6ea4da8", + "StageName": "Stage" + } + }, + "MyFnRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ { - "Action": [ - "sts:AssumeRole" - ], - "Effect": "Allow", - "Principal": { - "Service": [ - "lambda.amazonaws.com" - ] - } + "Value": "SAM", + "Key": "lambda:createdBy" } - ] - } - } - }, - "MyApiWithCognitoAuthDeploymentf67b169f98": { - "Type": "AWS::ApiGateway::Deployment", - "Properties": { - "RestApiId": { - "Ref": "MyApiWithCognitoAuth" - }, - "Description": "RestApi deployment id: f67b169f98fefb4627c6065af2d5e26ca6ea4da8", - "StageName": "Stage" - } - }, - "MyFnRole": { - "Type": "AWS::IAM::Role", - "Properties": { - "ManagedPolicyArns": [ - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - ], - "Tags": [ - { - "Value": "SAM", - "Key": "lambda:createdBy" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] } - ], - "AssumeRolePolicyDocument": { - "Version": "2012-10-17", - "Statement": [ + } + }, + "MyApiWithCognitoAuthProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiWithCognitoAuthDeploymentf67b169f98" + }, + "RestApiId": { + "Ref": "MyApiWithCognitoAuth" + }, + "StageName": "Prod" + } + }, + "MyFn": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.handler", + "Code": { + "ZipFile": "exports.handler = async (event) => {\n return {\n statusCode: 200,\n body: JSON.stringify(event),\n headers: {}\n }\n}\n" + }, + "Role": { + "Fn::GetAtt": [ + "MyFnRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ { - "Action": [ - "sts:AssumeRole" - ], - "Effect": "Allow", - "Principal": { - "Service": [ - "lambda.amazonaws.com" - ] - } + "Value": "SAM", + "Key": "lambda:createdBy" } ] } } - }, - "MyApiWithCognitoAuthProdStage": { - "Type": "AWS::ApiGateway::Stage", - "Properties": { - "DeploymentId": { - "Ref": "MyApiWithCognitoAuthDeploymentf67b169f98" - }, - "RestApiId": { - "Ref": "MyApiWithCognitoAuth" - }, - "StageName": "Prod" - } - }, - "MyFn": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Handler": "index.handler", - "Code": { - "ZipFile": "exports.handler = async (event) => {\n return {\n statusCode: 200,\n body: JSON.stringify(event),\n headers: {}\n }\n}\n" - }, - "Role": { - "Fn::GetAtt": [ - "MyFnRole", - "Arn" - ] - }, - "Runtime": "nodejs12.x", - "Tags": [ - { - "Value": "SAM", - "Key": "lambda:createdBy" - } - ] - } } - } -} \ No newline at end of file + } \ No newline at end of file diff --git a/tests/translator/output/api_with_usageplans_intrinsics.json b/tests/translator/output/api_with_usageplans_intrinsics.json index 3c43187e7..ce2e5413d 100644 --- a/tests/translator/output/api_with_usageplans_intrinsics.json +++ b/tests/translator/output/api_with_usageplans_intrinsics.json @@ -68,7 +68,8 @@ }, "DependsOn": [ "MyApiOne" - ] + ], + "Condition": "C1" }, "MyApiTwoUsagePlan": { "Type": "AWS::ApiGateway::UsagePlan", @@ -140,7 +141,8 @@ }, "DependsOn": [ "MyApiOneUsagePlan" - ] + ], + "Condition": "C1" }, "MyFunctionTwoRole": { "Type": "AWS::IAM::Role", @@ -185,7 +187,8 @@ }, "DependsOn": [ "MyApiOneApiKey" - ] + ], + "Condition": "C1" }, "MyApiTwoApiKey": { "Type": "AWS::ApiGateway::ApiKey", diff --git a/tests/translator/output/aws-cn/api_with_auth_all_minimum.json b/tests/translator/output/aws-cn/api_with_auth_all_minimum.json index f568fdaa8..c072d8df8 100644 --- a/tests/translator/output/aws-cn/api_with_auth_all_minimum.json +++ b/tests/translator/output/aws-cn/api_with_auth_all_minimum.json @@ -71,6 +71,18 @@ }, "StageName": "Prod" } + }, + "MyApiWithNotCachedLambdaRequestAuthProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiWithNotCachedLambdaRequestAuthDeployment234e92eab4" + }, + "RestApiId": { + "Ref": "MyApiWithNotCachedLambdaRequestAuth" + }, + "StageName": "Prod" + } }, "MyApiWithLambdaTokenAuthMyLambdaTokenAuthAuthorizerPermission": { "Type": "AWS::Lambda::Permission", @@ -203,7 +215,30 @@ ] } } - }, + }, + "MyApiWithNotCachedLambdaRequestAuthMyLambdaRequestAuthAuthorizerPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/authorizers/*", + { + "__ApiId__": { + "Ref": "MyApiWithNotCachedLambdaRequestAuth" + } + } + ] + } + } + }, "MyApiWithLambdaTokenAuthDeploymenta48b731095": { "Type": "AWS::ApiGateway::Deployment", "Properties": { @@ -213,7 +248,17 @@ "Description": "RestApi deployment id: a48b7310952ed029bd212c380e89a1bd39c74eae", "StageName": "Stage" } - }, + }, + "MyApiWithNotCachedLambdaRequestAuthDeployment234e92eab4": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApiWithNotCachedLambdaRequestAuth" + }, + "Description": "RestApi deployment id: 234e92eab4e4c590ad261ddd55775c1edcc2972f", + "StageName": "Stage" + } + }, "MyFnLambdaTokenPermissionProd": { "Type": "AWS::Lambda::Permission", "Properties": { @@ -255,7 +300,28 @@ ] } } - }, + }, + "MyFnLambdaNotCachedRequestPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/not-cached-lambda-request", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithNotCachedLambdaRequestAuth" + } + } + ] + } + } + }, "MyApiWithLambdaTokenAuth": { "Type": "AWS::ApiGateway::RestApi", "Properties": { @@ -492,6 +558,72 @@ "endpointConfigurationTypes": "REGIONAL" } } + }, + "MyApiWithNotCachedLambdaRequestAuth": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/not-cached-lambda-request": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" + } + }, + "security": [ + { + "MyLambdaRequestAuth": [] + } + ], + "responses": {} + } + } + }, + "swagger": "2.0", + "securityDefinitions": { + "MyLambdaRequestAuth": { + "in": "header", + "type": "apiKey", + "name": "Unused", + "x-amazon-apigateway-authorizer": { + "type": "request", + "authorizerUri": { + "Fn::Sub": [ + "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + { + "__FunctionArn__": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + } + } + ] + }, + "authorizerResultTtlInSeconds": 0 + }, + "x-amazon-apigateway-authtype": "custom" + } + } + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } } } } diff --git a/tests/translator/output/aws-cn/api_with_swagger_authorizer_none.json b/tests/translator/output/aws-cn/api_with_swagger_authorizer_none.json index bf67b0a5a..8ed6a1d1d 100644 --- a/tests/translator/output/aws-cn/api_with_swagger_authorizer_none.json +++ b/tests/translator/output/aws-cn/api_with_swagger_authorizer_none.json @@ -1,499 +1,499 @@ { - "Resources": { - "MyFnCognitoPermissionProd": { - "Type": "AWS::Lambda::Permission", - "Properties": { - "Action": "lambda:InvokeFunction", - "Principal": "apigateway.amazonaws.com", - "FunctionName": { - "Ref": "MyFn" - }, - "SourceArn": { - "Fn::Sub": [ - "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/cognito", - { - "__Stage__": "*", - "__ApiId__": { - "Ref": "MyApiWithCognitoAuth" + "Resources": { + "MyFnCognitoPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/cognito", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithCognitoAuth" + } } - } - ] + ] + } } - } - }, - "MyApiWithCognitoAuth": { - "Type": "AWS::ApiGateway::RestApi", - "Properties": { - "Body": { - "info": { - "version": "1.0", - "title": { - "Ref": "AWS::StackName" - } - }, - "paths": { - "/cognito": { - "get": { - "x-amazon-apigateway-integration": { - "httpMethod": "POST", - "type": "aws_proxy", - "uri": { - "Fn::Sub": "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" - } + }, + "MyApiWithCognitoAuth": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/cognito": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" + } + }, + "security": [ + { + "NONE": [] + } + ], + "responses": {} + } + } + }, + "swagger": "2.0", + "securityDefinitions": { + "MyCognitoAuth": { + "in": "header", + "type": "apiKey", + "name": "Authorization", + "x-amazon-apigateway-authorizer": { + "providerARNs": [ + { + "Fn::GetAtt": [ + "MyUserPool", + "Arn" + ] + } + ], + "type": "cognito_user_pools" }, - "security": [ - { - "NONE": [] - } - ], - "responses": {} + "x-amazon-apigateway-authtype": "cognito_user_pools" } } }, - "swagger": "2.0", - "securityDefinitions": { - "MyCognitoAuth": { - "in": "header", - "type": "apiKey", - "name": "Authorization", - "x-amazon-apigateway-authorizer": { - "providerARNs": [ - { - "Fn::GetAtt": [ - "MyUserPool", - "Arn" + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } + }, + "MyApiWithLambdaRequestAuthDeploymentbad519dbd8": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApiWithLambdaRequestAuth" + }, + "Description": "RestApi deployment id: bad519dbd801b0e2c63dc6f2011f43bce33c262a", + "StageName": "Stage" + } + }, + "MyApiWithLambdaTokenAuthDeployment29918bbdc1": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApiWithLambdaTokenAuth" + }, + "Description": "RestApi deployment id: 29918bbdc180ceedbabcf34c01ca5342e8c019cd", + "StageName": "Stage" + } + }, + "MyFnLambdaTokenPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/lambda-token", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithLambdaTokenAuth" + } + } + ] + } + } + }, + "MyApiWithLambdaTokenAuthMyLambdaTokenAuthAuthorizerPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/authorizers/*", + { + "__ApiId__": { + "Ref": "MyApiWithLambdaTokenAuth" + } + } + ] + } + } + }, + "MyFnLambdaRequestPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/lambda-request", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithLambdaRequestAuth" + } + } + ] + } + } + }, + "MyApiWithLambdaTokenAuth": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/lambda-token": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" + } + }, + "security": [ + { + "NONE": [] + } + ], + "responses": {} + } + } + }, + "swagger": "2.0", + "securityDefinitions": { + "MyLambdaTokenAuth": { + "in": "header", + "type": "apiKey", + "name": "Authorization", + "x-amazon-apigateway-authorizer": { + "type": "token", + "authorizerUri": { + "Fn::Sub": [ + "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + { + "__FunctionArn__": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + } + } ] } - ], - "type": "cognito_user_pools" - }, - "x-amazon-apigateway-authtype": "cognito_user_pools" + }, + "x-amazon-apigateway-authtype": "custom" + } } + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" } - }, - "EndpointConfiguration": { - "Types": [ - "REGIONAL" - ] - }, - "Parameters": { - "endpointConfigurationTypes": "REGIONAL" } - } - }, - "MyApiWithLambdaRequestAuthDeploymentbad519dbd8": { - "Type": "AWS::ApiGateway::Deployment", - "Properties": { - "RestApiId": { - "Ref": "MyApiWithLambdaRequestAuth" - }, - "Description": "RestApi deployment id: bad519dbd801b0e2c63dc6f2011f43bce33c262a", - "StageName": "Stage" - } - }, - "MyApiWithLambdaTokenAuthDeployment29918bbdc1": { - "Type": "AWS::ApiGateway::Deployment", - "Properties": { - "RestApiId": { - "Ref": "MyApiWithLambdaTokenAuth" - }, - "Description": "RestApi deployment id: 29918bbdc180ceedbabcf34c01ca5342e8c019cd", - "StageName": "Stage" - } - }, - "MyFnLambdaTokenPermissionProd": { - "Type": "AWS::Lambda::Permission", - "Properties": { - "Action": "lambda:InvokeFunction", - "Principal": "apigateway.amazonaws.com", - "FunctionName": { - "Ref": "MyFn" - }, - "SourceArn": { - "Fn::Sub": [ - "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/lambda-token", - { - "__Stage__": "*", - "__ApiId__": { - "Ref": "MyApiWithLambdaTokenAuth" - } - } - ] + }, + "MyApiWithLambdaTokenAuthProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiWithLambdaTokenAuthDeployment29918bbdc1" + }, + "RestApiId": { + "Ref": "MyApiWithLambdaTokenAuth" + }, + "StageName": "Prod" } - } - }, - "MyApiWithLambdaTokenAuthMyLambdaTokenAuthAuthorizerPermission": { - "Type": "AWS::Lambda::Permission", - "Properties": { - "Action": "lambda:InvokeFunction", - "Principal": "apigateway.amazonaws.com", - "FunctionName": { - "Fn::GetAtt": [ - "MyAuthFn", - "Arn" - ] - }, - "SourceArn": { - "Fn::Sub": [ - "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/authorizers/*", + }, + "MyApiWithLambdaRequestAuthProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiWithLambdaRequestAuthDeploymentbad519dbd8" + }, + "RestApiId": { + "Ref": "MyApiWithLambdaRequestAuth" + }, + "StageName": "Prod" + } + }, + "MyUserPool": { + "Type": "AWS::Cognito::UserPool", + "Properties": { + "UsernameAttributes": [ + "email" + ], + "UserPoolName": "UserPoolName", + "Policies": { + "PasswordPolicy": { + "MinimumLength": 8 + } + }, + "Schema": [ { - "__ApiId__": { - "Ref": "MyApiWithLambdaTokenAuth" - } + "AttributeDataType": "String", + "Required": false, + "Name": "email" } ] } - } - }, - "MyFnLambdaRequestPermissionProd": { - "Type": "AWS::Lambda::Permission", - "Properties": { - "Action": "lambda:InvokeFunction", - "Principal": "apigateway.amazonaws.com", - "FunctionName": { - "Ref": "MyFn" - }, - "SourceArn": { - "Fn::Sub": [ - "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/lambda-request", + }, + "MyAuthFn": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.handler", + "Code": { + "ZipFile": "exports.handler = async (event) => {\n return {\n statusCode: 200,\n body: JSON.stringify(event),\n headers: {}\n }\n}\n" + }, + "Role": { + "Fn::GetAtt": [ + "MyAuthFnRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ { - "__Stage__": "*", - "__ApiId__": { - "Ref": "MyApiWithLambdaRequestAuth" - } + "Value": "SAM", + "Key": "lambda:createdBy" } ] } - } - }, - "MyApiWithLambdaTokenAuth": { - "Type": "AWS::ApiGateway::RestApi", - "Properties": { - "Body": { - "info": { - "version": "1.0", - "title": { - "Ref": "AWS::StackName" - } - }, - "paths": { - "/lambda-token": { - "get": { - "x-amazon-apigateway-integration": { - "httpMethod": "POST", - "type": "aws_proxy", - "uri": { - "Fn::Sub": "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" - } - }, - "security": [ - { - "NONE": [] - } - ], - "responses": {} + }, + "MyApiWithLambdaRequestAuth": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" } - } - }, - "swagger": "2.0", - "securityDefinitions": { - "MyLambdaTokenAuth": { - "in": "header", - "type": "apiKey", - "name": "Authorization", - "x-amazon-apigateway-authorizer": { - "type": "token", - "authorizerUri": { - "Fn::Sub": [ - "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + }, + "paths": { + "/lambda-request": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "passthroughBehavior": "when_no_match", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" + } + }, + "security": [ { - "__FunctionArn__": { - "Fn::GetAtt": [ - "MyAuthFn", - "Arn" - ] - } + "NONE": [] } - ] + ], + "responses": {} } - }, - "x-amazon-apigateway-authtype": "custom" - } - } - }, - "EndpointConfiguration": { - "Types": [ - "REGIONAL" - ] - }, - "Parameters": { - "endpointConfigurationTypes": "REGIONAL" - } - } - }, - "MyApiWithLambdaTokenAuthProdStage": { - "Type": "AWS::ApiGateway::Stage", - "Properties": { - "DeploymentId": { - "Ref": "MyApiWithLambdaTokenAuthDeployment29918bbdc1" - }, - "RestApiId": { - "Ref": "MyApiWithLambdaTokenAuth" - }, - "StageName": "Prod" - } - }, - "MyApiWithLambdaRequestAuthProdStage": { - "Type": "AWS::ApiGateway::Stage", - "Properties": { - "DeploymentId": { - "Ref": "MyApiWithLambdaRequestAuthDeploymentbad519dbd8" - }, - "RestApiId": { - "Ref": "MyApiWithLambdaRequestAuth" - }, - "StageName": "Prod" - } - }, - "MyUserPool": { - "Type": "AWS::Cognito::UserPool", - "Properties": { - "UsernameAttributes": [ - "email" - ], - "UserPoolName": "UserPoolName", - "Policies": { - "PasswordPolicy": { - "MinimumLength": 8 - } - }, - "Schema": [ - { - "AttributeDataType": "String", - "Required": false, - "Name": "email" - } - ] - } - }, - "MyAuthFn": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Handler": "index.handler", - "Code": { - "ZipFile": "exports.handler = async (event) => {\n return {\n statusCode: 200,\n body: JSON.stringify(event),\n headers: {}\n }\n}\n" - }, - "Role": { - "Fn::GetAtt": [ - "MyAuthFnRole", - "Arn" - ] - }, - "Runtime": "nodejs12.x", - "Tags": [ - { - "Value": "SAM", - "Key": "lambda:createdBy" - } - ] - } - }, - "MyApiWithLambdaRequestAuth": { - "Type": "AWS::ApiGateway::RestApi", - "Properties": { - "Body": { - "info": { - "version": "1.0", - "title": { - "Ref": "AWS::StackName" - } - }, - "paths": { - "/lambda-request": { - "get": { - "x-amazon-apigateway-integration": { - "httpMethod": "POST", - "passthroughBehavior": "when_no_match", - "type": "aws_proxy", - "uri": { - "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" + } + }, + "swagger": 2.0, + "schemes": [ + "https" + ], + "securityDefinitions": { + "MyLambdaRequestAuth": { + "in": "header", + "type": "apiKey", + "name": "Unused", + "x-amazon-apigateway-authorizer": { + "type": "request", + "identitySource": "method.request.header.Authorization1", + "authorizerUri": { + "Fn::Sub": [ + "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + { + "__FunctionArn__": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + } + } + ] } }, - "security": [ - { - "NONE": [] - } - ], - "responses": {} + "x-amazon-apigateway-authtype": "custom" } } }, - "swagger": 2.0, - "schemes": [ - "https" + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } + }, + "MyApiWithCognitoAuthDeployment77726cd3cb": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApiWithCognitoAuth" + }, + "Description": "RestApi deployment id: 77726cd3cb8eddd94a4856ca8d65ee0f39d03b2e", + "StageName": "Stage" + } + }, + "MyAuthFnRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" ], - "securityDefinitions": { - "MyLambdaRequestAuth": { - "in": "header", - "type": "apiKey", - "name": "Unused", - "x-amazon-apigateway-authorizer": { - "type": "request", - "identitySource": "method.request.header.Authorization1", - "authorizerUri": { - "Fn::Sub": [ - "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", - { - "__FunctionArn__": { - "Fn::GetAtt": [ - "MyAuthFn", - "Arn" - ] - } - } + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" ] } - }, - "x-amazon-apigateway-authtype": "custom" - } + } + ] } - }, - "EndpointConfiguration": { - "Types": [ - "REGIONAL" - ] - }, - "Parameters": { - "endpointConfigurationTypes": "REGIONAL" } - } - }, - "MyApiWithCognitoAuthDeployment77726cd3cb": { - "Type": "AWS::ApiGateway::Deployment", - "Properties": { - "RestApiId": { - "Ref": "MyApiWithCognitoAuth" - }, - "Description": "RestApi deployment id: 77726cd3cb8eddd94a4856ca8d65ee0f39d03b2e", - "StageName": "Stage" - } - }, - "MyAuthFnRole": { - "Type": "AWS::IAM::Role", - "Properties": { - "ManagedPolicyArns": [ - "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - ], - "Tags": [ - { - "Value": "SAM", - "Key": "lambda:createdBy" - } - ], - "AssumeRolePolicyDocument": { - "Version": "2012-10-17", - "Statement": [ + }, + "MyFnRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ { - "Action": [ - "sts:AssumeRole" - ], - "Effect": "Allow", - "Principal": { - "Service": [ - "lambda.amazonaws.com" - ] - } + "Value": "SAM", + "Key": "lambda:createdBy" } - ] - } - } - }, - "MyFnRole": { - "Type": "AWS::IAM::Role", - "Properties": { - "ManagedPolicyArns": [ - "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - ], - "Tags": [ - { - "Value": "SAM", - "Key": "lambda:createdBy" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] } - ], - "AssumeRolePolicyDocument": { - "Version": "2012-10-17", - "Statement": [ + } + }, + "MyApiWithCognitoAuthProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiWithCognitoAuthDeployment77726cd3cb" + }, + "RestApiId": { + "Ref": "MyApiWithCognitoAuth" + }, + "StageName": "Prod" + } + }, + "MyFn": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.handler", + "Code": { + "ZipFile": "exports.handler = async (event) => {\n return {\n statusCode: 200,\n body: JSON.stringify(event),\n headers: {}\n }\n}\n" + }, + "Role": { + "Fn::GetAtt": [ + "MyFnRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ { - "Action": [ - "sts:AssumeRole" - ], - "Effect": "Allow", - "Principal": { - "Service": [ - "lambda.amazonaws.com" - ] - } + "Value": "SAM", + "Key": "lambda:createdBy" } ] } - } - }, - "MyApiWithCognitoAuthProdStage": { - "Type": "AWS::ApiGateway::Stage", - "Properties": { - "DeploymentId": { - "Ref": "MyApiWithCognitoAuthDeployment77726cd3cb" - }, - "RestApiId": { - "Ref": "MyApiWithCognitoAuth" - }, - "StageName": "Prod" - } - }, - "MyFn": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Handler": "index.handler", - "Code": { - "ZipFile": "exports.handler = async (event) => {\n return {\n statusCode: 200,\n body: JSON.stringify(event),\n headers: {}\n }\n}\n" - }, - "Role": { - "Fn::GetAtt": [ - "MyFnRole", - "Arn" - ] - }, - "Runtime": "nodejs12.x", - "Tags": [ - { - "Value": "SAM", - "Key": "lambda:createdBy" - } - ] - } - }, - "MyApiWithLambdaRequestAuthMyLambdaRequestAuthAuthorizerPermission": { - "Type": "AWS::Lambda::Permission", - "Properties": { - "Action": "lambda:InvokeFunction", - "Principal": "apigateway.amazonaws.com", - "FunctionName": { - "Fn::GetAtt": [ - "MyAuthFn", - "Arn" - ] - }, - "SourceArn": { - "Fn::Sub": [ - "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/authorizers/*", - { - "__ApiId__": { - "Ref": "MyApiWithLambdaRequestAuth" + }, + "MyApiWithLambdaRequestAuthMyLambdaRequestAuthAuthorizerPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/authorizers/*", + { + "__ApiId__": { + "Ref": "MyApiWithLambdaRequestAuth" + } } - } - ] + ] + } } } } - } -} \ No newline at end of file + } \ No newline at end of file diff --git a/tests/translator/output/aws-cn/api_with_usageplans_intrinsics.json b/tests/translator/output/aws-cn/api_with_usageplans_intrinsics.json index a4d484013..c9a6c4230 100644 --- a/tests/translator/output/aws-cn/api_with_usageplans_intrinsics.json +++ b/tests/translator/output/aws-cn/api_with_usageplans_intrinsics.json @@ -59,7 +59,8 @@ }, "DependsOn": [ "MyApiOne" - ] + ], + "Condition": "C1" }, "MyApiTwoUsagePlan": { "Type": "AWS::ApiGateway::UsagePlan", @@ -101,7 +102,8 @@ }, "DependsOn": [ "MyApiOneApiKey" - ] + ], + "Condition": "C1" }, "MyApiTwoProdStage": { "Type": "AWS::ApiGateway::Stage", @@ -145,7 +147,8 @@ }, "DependsOn": [ "MyApiOneUsagePlan" - ] + ], + "Condition": "C1" }, "MyFunctionTwoRole": { "Type": "AWS::IAM::Role", diff --git a/tests/translator/output/aws-cn/function_event_conditions.json b/tests/translator/output/aws-cn/function_event_conditions.json index 274bbc538..1e1ee3b38 100644 --- a/tests/translator/output/aws-cn/function_event_conditions.json +++ b/tests/translator/output/aws-cn/function_event_conditions.json @@ -529,6 +529,7 @@ "Condition": "MyCondition" }, "MyAwesomeFunctionAnotherSNSWithSQSSubscriptionQueue": { + "Condition": "MyCondition", "Type": "AWS::SQS::Queue", "Properties": {} }, diff --git a/tests/translator/output/aws-cn/function_with_request_parameters.json b/tests/translator/output/aws-cn/function_with_request_parameters.json index 9cef0cf7d..d5715a354 100644 --- a/tests/translator/output/aws-cn/function_with_request_parameters.json +++ b/tests/translator/output/aws-cn/function_with_request_parameters.json @@ -53,13 +53,13 @@ } } }, - "ServerlessRestApiDeploymentc2741b5220": { + "ServerlessRestApiDeployment32042a0513": { "Type": "AWS::ApiGateway::Deployment", "Properties": { "RestApiId": { "Ref": "ServerlessRestApi" }, - "Description": "RestApi deployment id: c2741b5220c940a753e3d1e18da6763aaba1c19b", + "Description": "RestApi deployment id: 32042a0513cd1c4e5c14794b306c4de10ca5c4af", "StageName": "Stage" } }, @@ -118,7 +118,7 @@ "Type": "AWS::ApiGateway::Stage", "Properties": { "DeploymentId": { - "Ref": "ServerlessRestApiDeploymentc2741b5220" + "Ref": "ServerlessRestApiDeployment32042a0513" }, "RestApiId": { "Ref": "ServerlessRestApi" @@ -247,6 +247,12 @@ "type": "string", "name": "id", "in": "path" + }, + { + "required": false, + "type": "string", + "name": "full.type", + "in": "query" } ] } diff --git a/tests/translator/output/aws-cn/implicit_api_deletion_policy_precedence.json b/tests/translator/output/aws-cn/implicit_api_deletion_policy_precedence.json new file mode 100644 index 000000000..33a3bb123 --- /dev/null +++ b/tests/translator/output/aws-cn/implicit_api_deletion_policy_precedence.json @@ -0,0 +1,242 @@ +{ + "Resources": { + "RestApiFunction": { + "Type": "AWS::Lambda::Function", + "DeletionPolicy": "Delete", + "UpdateReplacePolicy": "Retain", + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "todo_list.zip" + }, + "Handler": "index.restapi", + "Role": { + "Fn::GetAtt": [ + "RestApiFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "RestApiFunctionRole": { + "Type": "AWS::IAM::Role", + "DeletionPolicy": "Delete", + "UpdateReplacePolicy": "Retain", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + "arn:aws-cn:iam::aws:policy/AmazonDynamoDBFullAccess" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "RestApiFunctionGetHtmlPermissionProd": { + "Type": "AWS::Lambda::Permission", + "DeletionPolicy": "Delete", + "UpdateReplacePolicy": "Retain", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Ref": "RestApiFunction" + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/*/*", + { + "__ApiId__": { + "Ref": "ServerlessRestApi" + }, + "__Stage__": "*" + } + ] + } + } + }, + "GetHtmlFunction": { + "Type": "AWS::Lambda::Function", + "DeletionPolicy": "Retain", + "UpdateReplacePolicy": "Retain", + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "todo_list.zip" + }, + "Handler": "index.gethtml", + "Role": { + "Fn::GetAtt": [ + "GetHtmlFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "GetHtmlFunctionRole": { + "Type": "AWS::IAM::Role", + "DeletionPolicy": "Retain", + "UpdateReplacePolicy": "Retain", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + "arn:aws-cn:iam::aws:policy/AmazonDynamoDBReadOnlyAccess" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "GetHtmlFunctionGetHtmlPermissionProd": { + "Type": "AWS::Lambda::Permission", + "DeletionPolicy": "Retain", + "UpdateReplacePolicy": "Retain", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Ref": "GetHtmlFunction" + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/*/{proxy++}", + { + "__ApiId__": { + "Ref": "ServerlessRestApi" + }, + "__Stage__": "*" + } + ] + } + } + }, + "ServerlessRestApi": { + "Type": "AWS::ApiGateway::RestApi", + "DeletionPolicy": "Retain", + "UpdateReplacePolicy": "Retain", + "Properties": { + "Body": { + "swagger": "2.0", + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/{proxy+}": { + "x-amazon-apigateway-any-method": { + "x-amazon-apigateway-integration": { + "type": "aws_proxy", + "httpMethod": "POST", + "uri": { + "Fn::Sub": "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${RestApiFunction.Arn}/invocations" + } + }, + "responses": {} + } + }, + "/{proxy++}": { + "x-amazon-apigateway-any-method": { + "x-amazon-apigateway-integration": { + "type": "aws_proxy", + "httpMethod": "POST", + "uri": { + "Fn::Sub": "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${GetHtmlFunction.Arn}/invocations" + } + }, + "responses": {} + } + } + } + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } + }, + "ServerlessRestApiDeployment1ec0d29ab5": { + "Type": "AWS::ApiGateway::Deployment", + "DeletionPolicy": "Retain", + "UpdateReplacePolicy": "Retain", + "Properties": { + "Description": "RestApi deployment id: 1ec0d29ab5c55018bb989df31615c170b707ae21", + "RestApiId": { + "Ref": "ServerlessRestApi" + }, + "StageName": "Stage" + } + }, + "ServerlessRestApiProdStage": { + "Type": "AWS::ApiGateway::Stage", + "DeletionPolicy": "Retain", + "UpdateReplacePolicy": "Retain", + "Properties": { + "DeploymentId": { + "Ref": "ServerlessRestApiDeployment1ec0d29ab5" + }, + "RestApiId": { + "Ref": "ServerlessRestApi" + }, + "StageName": "Prod" + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-cn/layer_deletion_policy_precedence.json b/tests/translator/output/aws-cn/layer_deletion_policy_precedence.json new file mode 100644 index 000000000..8bb610ad0 --- /dev/null +++ b/tests/translator/output/aws-cn/layer_deletion_policy_precedence.json @@ -0,0 +1,37 @@ +{ + "Resources": { + "MinimalLayer22b6609c3d": { + "Type": "AWS::Lambda::LayerVersion", + "DeletionPolicy": "Retain", + "Properties": { + "Content": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "layer.zip" + }, + "LayerName": "MinimalLayer" + } + }, + "MinimalLayer2800a44a445": { + "Type": "AWS::Lambda::LayerVersion", + "DeletionPolicy": "Delete", + "Properties": { + "Content": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "layer.zip" + }, + "LayerName": "MinimalLayer2" + } + }, + "MinimalLayer3ac07350a04": { + "Type": "AWS::Lambda::LayerVersion", + "DeletionPolicy": "Retain", + "Properties": { + "Content": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "layer.zip" + }, + "LayerName": "MinimalLayer3" + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-cn/version_deletion_policy_precedence.json b/tests/translator/output/aws-cn/version_deletion_policy_precedence.json new file mode 100644 index 000000000..1d970410a --- /dev/null +++ b/tests/translator/output/aws-cn/version_deletion_policy_precedence.json @@ -0,0 +1,163 @@ +{ + "Resources": { + "MinimalFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip" + }, + "Handler": "hello.handler", + "Role": { + "Fn::GetAtt": [ + "MinimalFunctionRole", + "Arn" + ] + }, + "Runtime": "python2.7", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MinimalFunctionVersion640128d35d": { + "Type": "AWS::Lambda::Version", + "DeletionPolicy": "Retain", + "Properties": { + "Description": "sam-testing", + "FunctionName": { + "Ref": "MinimalFunction" + } + } + }, + "MinimalFunctionAliaslive": { + "Type": "AWS::Lambda::Alias", + "Properties": { + "Name": "live", + "FunctionName": { + "Ref": "MinimalFunction" + }, + "FunctionVersion": { + "Fn::GetAtt": [ + "MinimalFunctionVersion640128d35d", + "Version" + ] + } + } + }, + "MinimalFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MinimalFunction2": { + "Type": "AWS::Lambda::Function", + "DeletionPolicy": "Delete", + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip" + }, + "Handler": "hello.handler", + "Role": { + "Fn::GetAtt": [ + "MinimalFunction2Role", + "Arn" + ] + }, + "Runtime": "python2.7", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MinimalFunction2Version640128d35d": { + "Type": "AWS::Lambda::Version", + "DeletionPolicy": "Delete", + "Properties": { + "Description": "sam-testing", + "FunctionName": { + "Ref": "MinimalFunction2" + } + } + }, + "MinimalFunction2Aliaslive": { + "Type": "AWS::Lambda::Alias", + "DeletionPolicy": "Delete", + "Properties": { + "Name": "live", + "FunctionName": { + "Ref": "MinimalFunction2" + }, + "FunctionVersion": { + "Fn::GetAtt": [ + "MinimalFunction2Version640128d35d", + "Version" + ] + } + } + }, + "MinimalFunction2Role": { + "Type": "AWS::IAM::Role", + "DeletionPolicy": "Delete", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/api_with_auth_all_minimum.json b/tests/translator/output/aws-us-gov/api_with_auth_all_minimum.json index b108f7b43..7ab9dea6e 100644 --- a/tests/translator/output/aws-us-gov/api_with_auth_all_minimum.json +++ b/tests/translator/output/aws-us-gov/api_with_auth_all_minimum.json @@ -71,6 +71,18 @@ }, "StageName": "Prod" } + }, + "MyApiWithNotCachedLambdaRequestAuthProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiWithNotCachedLambdaRequestAuthDeploymentd3b8858811" + }, + "RestApiId": { + "Ref": "MyApiWithNotCachedLambdaRequestAuth" + }, + "StageName": "Prod" + } }, "MyApiWithLambdaTokenAuthMyLambdaTokenAuthAuthorizerPermission": { "Type": "AWS::Lambda::Permission", @@ -213,7 +225,30 @@ ] } } - }, + }, + "MyApiWithNotCachedLambdaRequestAuthMyLambdaRequestAuthAuthorizerPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/authorizers/*", + { + "__ApiId__": { + "Ref": "MyApiWithNotCachedLambdaRequestAuth" + } + } + ] + } + } + }, "MyFnLambdaTokenPermissionProd": { "Type": "AWS::Lambda::Permission", "Properties": { @@ -234,7 +269,17 @@ ] } } - }, + }, + "MyApiWithNotCachedLambdaRequestAuthDeploymentd3b8858811": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApiWithNotCachedLambdaRequestAuth" + }, + "Description": "RestApi deployment id: d3b8858811d6c42be45490ba4d1ca059821cf4fd", + "StageName": "Stage" + } + }, "MyFnLambdaRequestPermissionProd": { "Type": "AWS::Lambda::Permission", "Properties": { @@ -256,6 +301,27 @@ } } }, + "MyFnLambdaNotCachedRequestPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/not-cached-lambda-request", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithNotCachedLambdaRequestAuth" + } + } + ] + } + } + }, "MyApiWithLambdaTokenAuth": { "Type": "AWS::ApiGateway::RestApi", "Properties": { @@ -492,6 +558,72 @@ "endpointConfigurationTypes": "REGIONAL" } } + }, + "MyApiWithNotCachedLambdaRequestAuth": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/not-cached-lambda-request": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" + } + }, + "security": [ + { + "MyLambdaRequestAuth": [] + } + ], + "responses": {} + } + } + }, + "swagger": "2.0", + "securityDefinitions": { + "MyLambdaRequestAuth": { + "in": "header", + "type": "apiKey", + "name": "Unused", + "x-amazon-apigateway-authorizer": { + "type": "request", + "authorizerUri": { + "Fn::Sub": [ + "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + { + "__FunctionArn__": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + } + } + ] + }, + "authorizerResultTtlInSeconds": 0 + }, + "x-amazon-apigateway-authtype": "custom" + } + } + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } } } } diff --git a/tests/translator/output/aws-us-gov/api_with_swagger_authorizer_none.json b/tests/translator/output/aws-us-gov/api_with_swagger_authorizer_none.json index 3edd2c2a6..1fe5153a1 100644 --- a/tests/translator/output/aws-us-gov/api_with_swagger_authorizer_none.json +++ b/tests/translator/output/aws-us-gov/api_with_swagger_authorizer_none.json @@ -1,499 +1,499 @@ { - "Resources": { - "MyFnCognitoPermissionProd": { - "Type": "AWS::Lambda::Permission", - "Properties": { - "Action": "lambda:InvokeFunction", - "Principal": "apigateway.amazonaws.com", - "FunctionName": { - "Ref": "MyFn" - }, - "SourceArn": { - "Fn::Sub": [ - "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/cognito", - { - "__Stage__": "*", - "__ApiId__": { - "Ref": "MyApiWithCognitoAuth" + "Resources": { + "MyFnCognitoPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/cognito", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithCognitoAuth" + } } - } - ] + ] + } } - } - }, - "MyApiWithCognitoAuth": { - "Type": "AWS::ApiGateway::RestApi", - "Properties": { - "Body": { - "info": { - "version": "1.0", - "title": { - "Ref": "AWS::StackName" - } - }, - "paths": { - "/cognito": { - "get": { - "x-amazon-apigateway-integration": { - "httpMethod": "POST", - "type": "aws_proxy", - "uri": { - "Fn::Sub": "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" - } + }, + "MyApiWithCognitoAuth": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/cognito": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" + } + }, + "security": [ + { + "NONE": [] + } + ], + "responses": {} + } + } + }, + "swagger": "2.0", + "securityDefinitions": { + "MyCognitoAuth": { + "in": "header", + "type": "apiKey", + "name": "Authorization", + "x-amazon-apigateway-authorizer": { + "providerARNs": [ + { + "Fn::GetAtt": [ + "MyUserPool", + "Arn" + ] + } + ], + "type": "cognito_user_pools" }, - "security": [ - { - "NONE": [] - } - ], - "responses": {} + "x-amazon-apigateway-authtype": "cognito_user_pools" } } }, - "swagger": "2.0", - "securityDefinitions": { - "MyCognitoAuth": { - "in": "header", - "type": "apiKey", - "name": "Authorization", - "x-amazon-apigateway-authorizer": { - "providerARNs": [ - { - "Fn::GetAtt": [ - "MyUserPool", - "Arn" + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } + }, + "MyApiWithLambdaRequestAuthDeployment9c20de6c65": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApiWithLambdaRequestAuth" + }, + "Description": "RestApi deployment id: 9c20de6c65c8aa8750d3136af13b9a69bc7d3e5e", + "StageName": "Stage" + } + }, + "MyApiWithLambdaTokenAuthDeployment4f66714fd8": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApiWithLambdaTokenAuth" + }, + "Description": "RestApi deployment id: 4f66714fd88af2798cc2462bd8ce435aa77a340c", + "StageName": "Stage" + } + }, + "MyApiWithLambdaRequestAuthMyLambdaRequestAuthAuthorizerPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/authorizers/*", + { + "__ApiId__": { + "Ref": "MyApiWithLambdaRequestAuth" + } + } + ] + } + } + }, + "MyApiWithCognitoAuthDeploymentbac15a89c4": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApiWithCognitoAuth" + }, + "Description": "RestApi deployment id: bac15a89c4ef70c7a908f93d9f39dc7ce56fa1e3", + "StageName": "Stage" + } + }, + "MyApiWithLambdaRequestAuthProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiWithLambdaRequestAuthDeployment9c20de6c65" + }, + "RestApiId": { + "Ref": "MyApiWithLambdaRequestAuth" + }, + "StageName": "Prod" + } + }, + "MyFnLambdaTokenPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/lambda-token", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithLambdaTokenAuth" + } + } + ] + } + } + }, + "MyFnLambdaRequestPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/lambda-request", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithLambdaRequestAuth" + } + } + ] + } + } + }, + "MyApiWithLambdaTokenAuth": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/lambda-token": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" + } + }, + "security": [ + { + "NONE": [] + } + ], + "responses": {} + } + } + }, + "swagger": "2.0", + "securityDefinitions": { + "MyLambdaTokenAuth": { + "in": "header", + "type": "apiKey", + "name": "Authorization", + "x-amazon-apigateway-authorizer": { + "type": "token", + "authorizerUri": { + "Fn::Sub": [ + "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + { + "__FunctionArn__": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + } + } ] } - ], - "type": "cognito_user_pools" - }, - "x-amazon-apigateway-authtype": "cognito_user_pools" + }, + "x-amazon-apigateway-authtype": "custom" + } } + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" } - }, - "EndpointConfiguration": { - "Types": [ - "REGIONAL" - ] - }, - "Parameters": { - "endpointConfigurationTypes": "REGIONAL" } - } - }, - "MyApiWithLambdaRequestAuthDeployment9c20de6c65": { - "Type": "AWS::ApiGateway::Deployment", - "Properties": { - "RestApiId": { - "Ref": "MyApiWithLambdaRequestAuth" - }, - "Description": "RestApi deployment id: 9c20de6c65c8aa8750d3136af13b9a69bc7d3e5e", - "StageName": "Stage" - } - }, - "MyApiWithLambdaTokenAuthDeployment4f66714fd8": { - "Type": "AWS::ApiGateway::Deployment", - "Properties": { - "RestApiId": { - "Ref": "MyApiWithLambdaTokenAuth" - }, - "Description": "RestApi deployment id: 4f66714fd88af2798cc2462bd8ce435aa77a340c", - "StageName": "Stage" - } - }, - "MyApiWithLambdaRequestAuthMyLambdaRequestAuthAuthorizerPermission": { - "Type": "AWS::Lambda::Permission", - "Properties": { - "Action": "lambda:InvokeFunction", - "Principal": "apigateway.amazonaws.com", - "FunctionName": { - "Fn::GetAtt": [ - "MyAuthFn", - "Arn" - ] - }, - "SourceArn": { - "Fn::Sub": [ - "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/authorizers/*", - { - "__ApiId__": { - "Ref": "MyApiWithLambdaRequestAuth" - } - } - ] + }, + "MyApiWithLambdaTokenAuthProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiWithLambdaTokenAuthDeployment4f66714fd8" + }, + "RestApiId": { + "Ref": "MyApiWithLambdaTokenAuth" + }, + "StageName": "Prod" } - } - }, - "MyApiWithCognitoAuthDeploymentbac15a89c4": { - "Type": "AWS::ApiGateway::Deployment", - "Properties": { - "RestApiId": { - "Ref": "MyApiWithCognitoAuth" - }, - "Description": "RestApi deployment id: bac15a89c4ef70c7a908f93d9f39dc7ce56fa1e3", - "StageName": "Stage" - } - }, - "MyApiWithLambdaRequestAuthProdStage": { - "Type": "AWS::ApiGateway::Stage", - "Properties": { - "DeploymentId": { - "Ref": "MyApiWithLambdaRequestAuthDeployment9c20de6c65" - }, - "RestApiId": { - "Ref": "MyApiWithLambdaRequestAuth" - }, - "StageName": "Prod" - } - }, - "MyFnLambdaTokenPermissionProd": { - "Type": "AWS::Lambda::Permission", - "Properties": { - "Action": "lambda:InvokeFunction", - "Principal": "apigateway.amazonaws.com", - "FunctionName": { - "Ref": "MyFn" - }, - "SourceArn": { - "Fn::Sub": [ - "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/lambda-token", + }, + "MyUserPool": { + "Type": "AWS::Cognito::UserPool", + "Properties": { + "UsernameAttributes": [ + "email" + ], + "UserPoolName": "UserPoolName", + "Policies": { + "PasswordPolicy": { + "MinimumLength": 8 + } + }, + "Schema": [ { - "__Stage__": "*", - "__ApiId__": { - "Ref": "MyApiWithLambdaTokenAuth" - } + "AttributeDataType": "String", + "Required": false, + "Name": "email" } ] } - } - }, - "MyFnLambdaRequestPermissionProd": { - "Type": "AWS::Lambda::Permission", - "Properties": { - "Action": "lambda:InvokeFunction", - "Principal": "apigateway.amazonaws.com", - "FunctionName": { - "Ref": "MyFn" - }, - "SourceArn": { - "Fn::Sub": [ - "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/lambda-request", + }, + "MyAuthFn": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.handler", + "Code": { + "ZipFile": "exports.handler = async (event) => {\n return {\n statusCode: 200,\n body: JSON.stringify(event),\n headers: {}\n }\n}\n" + }, + "Role": { + "Fn::GetAtt": [ + "MyAuthFnRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ { - "__Stage__": "*", - "__ApiId__": { - "Ref": "MyApiWithLambdaRequestAuth" - } + "Value": "SAM", + "Key": "lambda:createdBy" } ] } - } - }, - "MyApiWithLambdaTokenAuth": { - "Type": "AWS::ApiGateway::RestApi", - "Properties": { - "Body": { - "info": { - "version": "1.0", - "title": { - "Ref": "AWS::StackName" - } - }, - "paths": { - "/lambda-token": { - "get": { - "x-amazon-apigateway-integration": { - "httpMethod": "POST", - "type": "aws_proxy", - "uri": { - "Fn::Sub": "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" - } - }, - "security": [ - { - "NONE": [] - } - ], - "responses": {} + }, + "MyApiWithLambdaRequestAuth": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" } - } - }, - "swagger": "2.0", - "securityDefinitions": { - "MyLambdaTokenAuth": { - "in": "header", - "type": "apiKey", - "name": "Authorization", - "x-amazon-apigateway-authorizer": { - "type": "token", - "authorizerUri": { - "Fn::Sub": [ - "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + }, + "paths": { + "/lambda-request": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "passthroughBehavior": "when_no_match", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" + } + }, + "security": [ { - "__FunctionArn__": { - "Fn::GetAtt": [ - "MyAuthFn", - "Arn" - ] - } + "NONE": [] } - ] + ], + "responses": {} } - }, - "x-amazon-apigateway-authtype": "custom" + } + }, + "swagger": 2.0, + "schemes": [ + "https" + ], + "securityDefinitions": { + "MyLambdaRequestAuth": { + "in": "header", + "type": "apiKey", + "name": "Unused", + "x-amazon-apigateway-authorizer": { + "type": "request", + "identitySource": "method.request.header.Authorization1", + "authorizerUri": { + "Fn::Sub": [ + "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + { + "__FunctionArn__": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + } + } + ] + } + }, + "x-amazon-apigateway-authtype": "custom" + } } + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" } - }, - "EndpointConfiguration": { - "Types": [ - "REGIONAL" - ] - }, - "Parameters": { - "endpointConfigurationTypes": "REGIONAL" } - } - }, - "MyApiWithLambdaTokenAuthProdStage": { - "Type": "AWS::ApiGateway::Stage", - "Properties": { - "DeploymentId": { - "Ref": "MyApiWithLambdaTokenAuthDeployment4f66714fd8" - }, - "RestApiId": { - "Ref": "MyApiWithLambdaTokenAuth" - }, - "StageName": "Prod" - } - }, - "MyUserPool": { - "Type": "AWS::Cognito::UserPool", - "Properties": { - "UsernameAttributes": [ - "email" - ], - "UserPoolName": "UserPoolName", - "Policies": { - "PasswordPolicy": { - "MinimumLength": 8 - } - }, - "Schema": [ - { - "AttributeDataType": "String", - "Required": false, - "Name": "email" - } - ] - } - }, - "MyAuthFn": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Handler": "index.handler", - "Code": { - "ZipFile": "exports.handler = async (event) => {\n return {\n statusCode: 200,\n body: JSON.stringify(event),\n headers: {}\n }\n}\n" - }, - "Role": { - "Fn::GetAtt": [ - "MyAuthFnRole", - "Arn" - ] - }, - "Runtime": "nodejs12.x", - "Tags": [ - { - "Value": "SAM", - "Key": "lambda:createdBy" - } - ] - } - }, - "MyApiWithLambdaRequestAuth": { - "Type": "AWS::ApiGateway::RestApi", - "Properties": { - "Body": { - "info": { - "version": "1.0", - "title": { - "Ref": "AWS::StackName" - } + }, + "MyApiWithLambdaTokenAuthMyLambdaTokenAuthAuthorizerPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] }, - "paths": { - "/lambda-request": { - "get": { - "x-amazon-apigateway-integration": { - "httpMethod": "POST", - "passthroughBehavior": "when_no_match", - "type": "aws_proxy", - "uri": { - "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" - } - }, - "security": [ - { - "NONE": [] - } - ], - "responses": {} + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/authorizers/*", + { + "__ApiId__": { + "Ref": "MyApiWithLambdaTokenAuth" + } } + ] + } + } + }, + "MyAuthFnRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" } - }, - "swagger": 2.0, - "schemes": [ - "https" ], - "securityDefinitions": { - "MyLambdaRequestAuth": { - "in": "header", - "type": "apiKey", - "name": "Unused", - "x-amazon-apigateway-authorizer": { - "type": "request", - "identitySource": "method.request.header.Authorization1", - "authorizerUri": { - "Fn::Sub": [ - "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", - { - "__FunctionArn__": { - "Fn::GetAtt": [ - "MyAuthFn", - "Arn" - ] - } - } + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" ] } - }, - "x-amazon-apigateway-authtype": "custom" - } + } + ] } - }, - "EndpointConfiguration": { - "Types": [ - "REGIONAL" - ] - }, - "Parameters": { - "endpointConfigurationTypes": "REGIONAL" } - } - }, - "MyApiWithLambdaTokenAuthMyLambdaTokenAuthAuthorizerPermission": { - "Type": "AWS::Lambda::Permission", - "Properties": { - "Action": "lambda:InvokeFunction", - "Principal": "apigateway.amazonaws.com", - "FunctionName": { - "Fn::GetAtt": [ - "MyAuthFn", - "Arn" - ] - }, - "SourceArn": { - "Fn::Sub": [ - "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/authorizers/*", + }, + "MyFnRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ { - "__ApiId__": { - "Ref": "MyApiWithLambdaTokenAuth" - } + "Value": "SAM", + "Key": "lambda:createdBy" } - ] - } - } - }, - "MyAuthFnRole": { - "Type": "AWS::IAM::Role", - "Properties": { - "ManagedPolicyArns": [ - "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - ], - "Tags": [ - { - "Value": "SAM", - "Key": "lambda:createdBy" - } - ], - "AssumeRolePolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Action": [ - "sts:AssumeRole" - ], - "Effect": "Allow", - "Principal": { - "Service": [ - "lambda.amazonaws.com" - ] + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } } - } - ] - } - } - }, - "MyFnRole": { - "Type": "AWS::IAM::Role", - "Properties": { - "ManagedPolicyArns": [ - "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - ], - "Tags": [ - { - "Value": "SAM", - "Key": "lambda:createdBy" + ] } - ], - "AssumeRolePolicyDocument": { - "Version": "2012-10-17", - "Statement": [ + } + }, + "MyApiWithCognitoAuthProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiWithCognitoAuthDeploymentbac15a89c4" + }, + "RestApiId": { + "Ref": "MyApiWithCognitoAuth" + }, + "StageName": "Prod" + } + }, + "MyFn": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.handler", + "Code": { + "ZipFile": "exports.handler = async (event) => {\n return {\n statusCode: 200,\n body: JSON.stringify(event),\n headers: {}\n }\n}\n" + }, + "Role": { + "Fn::GetAtt": [ + "MyFnRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ { - "Action": [ - "sts:AssumeRole" - ], - "Effect": "Allow", - "Principal": { - "Service": [ - "lambda.amazonaws.com" - ] - } + "Value": "SAM", + "Key": "lambda:createdBy" } ] } } - }, - "MyApiWithCognitoAuthProdStage": { - "Type": "AWS::ApiGateway::Stage", - "Properties": { - "DeploymentId": { - "Ref": "MyApiWithCognitoAuthDeploymentbac15a89c4" - }, - "RestApiId": { - "Ref": "MyApiWithCognitoAuth" - }, - "StageName": "Prod" - } - }, - "MyFn": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Handler": "index.handler", - "Code": { - "ZipFile": "exports.handler = async (event) => {\n return {\n statusCode: 200,\n body: JSON.stringify(event),\n headers: {}\n }\n}\n" - }, - "Role": { - "Fn::GetAtt": [ - "MyFnRole", - "Arn" - ] - }, - "Runtime": "nodejs12.x", - "Tags": [ - { - "Value": "SAM", - "Key": "lambda:createdBy" - } - ] - } } - } -} \ No newline at end of file + } \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/api_with_usageplans_intrinsics.json b/tests/translator/output/aws-us-gov/api_with_usageplans_intrinsics.json index 0a60e70b8..0a9355d91 100644 --- a/tests/translator/output/aws-us-gov/api_with_usageplans_intrinsics.json +++ b/tests/translator/output/aws-us-gov/api_with_usageplans_intrinsics.json @@ -68,7 +68,8 @@ }, "DependsOn": [ "MyApiOne" - ] + ], + "Condition": "C1" }, "MyApiOneDeployment8b73115419": { "Type": "AWS::ApiGateway::Deployment", @@ -140,7 +141,8 @@ }, "DependsOn": [ "MyApiOneUsagePlan" - ] + ], + "Condition": "C1" }, "MyFunctionTwoRole": { "Type": "AWS::IAM::Role", @@ -185,7 +187,8 @@ }, "DependsOn": [ "MyApiOneApiKey" - ] + ], + "Condition": "C1" }, "MyApiTwoApiKey": { "Type": "AWS::ApiGateway::ApiKey", diff --git a/tests/translator/output/aws-us-gov/function_event_conditions.json b/tests/translator/output/aws-us-gov/function_event_conditions.json index e22637ef5..d35bfe484 100644 --- a/tests/translator/output/aws-us-gov/function_event_conditions.json +++ b/tests/translator/output/aws-us-gov/function_event_conditions.json @@ -529,6 +529,7 @@ "Condition": "MyCondition" }, "MyAwesomeFunctionAnotherSNSWithSQSSubscriptionQueue": { + "Condition": "MyCondition", "Type": "AWS::SQS::Queue", "Properties": {} }, diff --git a/tests/translator/output/aws-us-gov/function_with_request_parameters.json b/tests/translator/output/aws-us-gov/function_with_request_parameters.json index 44ff1d8f5..67345e559 100644 --- a/tests/translator/output/aws-us-gov/function_with_request_parameters.json +++ b/tests/translator/output/aws-us-gov/function_with_request_parameters.json @@ -53,13 +53,13 @@ } } }, - "ServerlessRestApiDeployment7c706bcd56": { + "ServerlessRestApiDeploymentbe3a929cf9": { "Type": "AWS::ApiGateway::Deployment", "Properties": { "RestApiId": { "Ref": "ServerlessRestApi" }, - "Description": "RestApi deployment id: 7c706bcd56e685afb5882e0219515c9413bcd13b", + "Description": "RestApi deployment id: be3a929cf90555789f2865fc4a96eb9a11ff7a81", "StageName": "Stage" } }, @@ -128,7 +128,7 @@ "Type": "AWS::ApiGateway::Stage", "Properties": { "DeploymentId": { - "Ref": "ServerlessRestApiDeployment7c706bcd56" + "Ref": "ServerlessRestApiDeploymentbe3a929cf9" }, "RestApiId": { "Ref": "ServerlessRestApi" @@ -247,6 +247,12 @@ "type": "string", "name": "id", "in": "path" + }, + { + "required": false, + "type": "string", + "name": "full.type", + "in": "query" } ] } diff --git a/tests/translator/output/aws-us-gov/implicit_api_deletion_policy_precedence.json b/tests/translator/output/aws-us-gov/implicit_api_deletion_policy_precedence.json new file mode 100644 index 000000000..434cc36b0 --- /dev/null +++ b/tests/translator/output/aws-us-gov/implicit_api_deletion_policy_precedence.json @@ -0,0 +1,242 @@ +{ + "Resources": { + "RestApiFunction": { + "Type": "AWS::Lambda::Function", + "DeletionPolicy": "Delete", + "UpdateReplacePolicy": "Retain", + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "todo_list.zip" + }, + "Handler": "index.restapi", + "Role": { + "Fn::GetAtt": [ + "RestApiFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "RestApiFunctionRole": { + "Type": "AWS::IAM::Role", + "DeletionPolicy": "Delete", + "UpdateReplacePolicy": "Retain", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + "arn:aws-us-gov:iam::aws:policy/AmazonDynamoDBFullAccess" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "RestApiFunctionGetHtmlPermissionProd": { + "Type": "AWS::Lambda::Permission", + "DeletionPolicy": "Delete", + "UpdateReplacePolicy": "Retain", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Ref": "RestApiFunction" + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/*/*", + { + "__ApiId__": { + "Ref": "ServerlessRestApi" + }, + "__Stage__": "*" + } + ] + } + } + }, + "GetHtmlFunction": { + "Type": "AWS::Lambda::Function", + "DeletionPolicy": "Retain", + "UpdateReplacePolicy": "Retain", + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "todo_list.zip" + }, + "Handler": "index.gethtml", + "Role": { + "Fn::GetAtt": [ + "GetHtmlFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "GetHtmlFunctionRole": { + "Type": "AWS::IAM::Role", + "DeletionPolicy": "Retain", + "UpdateReplacePolicy": "Retain", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + "arn:aws-us-gov:iam::aws:policy/AmazonDynamoDBReadOnlyAccess" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "GetHtmlFunctionGetHtmlPermissionProd": { + "Type": "AWS::Lambda::Permission", + "DeletionPolicy": "Retain", + "UpdateReplacePolicy": "Retain", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Ref": "GetHtmlFunction" + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/*/{proxy++}", + { + "__ApiId__": { + "Ref": "ServerlessRestApi" + }, + "__Stage__": "*" + } + ] + } + } + }, + "ServerlessRestApi": { + "Type": "AWS::ApiGateway::RestApi", + "DeletionPolicy": "Retain", + "UpdateReplacePolicy": "Retain", + "Properties": { + "Body": { + "swagger": "2.0", + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/{proxy+}": { + "x-amazon-apigateway-any-method": { + "x-amazon-apigateway-integration": { + "type": "aws_proxy", + "httpMethod": "POST", + "uri": { + "Fn::Sub": "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${RestApiFunction.Arn}/invocations" + } + }, + "responses": {} + } + }, + "/{proxy++}": { + "x-amazon-apigateway-any-method": { + "x-amazon-apigateway-integration": { + "type": "aws_proxy", + "httpMethod": "POST", + "uri": { + "Fn::Sub": "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${GetHtmlFunction.Arn}/invocations" + } + }, + "responses": {} + } + } + } + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } + }, + "ServerlessRestApiDeployment695a271271": { + "Type": "AWS::ApiGateway::Deployment", + "DeletionPolicy": "Retain", + "UpdateReplacePolicy": "Retain", + "Properties": { + "Description": "RestApi deployment id: 695a271271d114dfef4fba262c839d269473d910", + "RestApiId": { + "Ref": "ServerlessRestApi" + }, + "StageName": "Stage" + } + }, + "ServerlessRestApiProdStage": { + "Type": "AWS::ApiGateway::Stage", + "DeletionPolicy": "Retain", + "UpdateReplacePolicy": "Retain", + "Properties": { + "DeploymentId": { + "Ref": "ServerlessRestApiDeployment695a271271" + }, + "RestApiId": { + "Ref": "ServerlessRestApi" + }, + "StageName": "Prod" + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/layer_deletion_policy_precedence.json b/tests/translator/output/aws-us-gov/layer_deletion_policy_precedence.json new file mode 100644 index 000000000..8bb610ad0 --- /dev/null +++ b/tests/translator/output/aws-us-gov/layer_deletion_policy_precedence.json @@ -0,0 +1,37 @@ +{ + "Resources": { + "MinimalLayer22b6609c3d": { + "Type": "AWS::Lambda::LayerVersion", + "DeletionPolicy": "Retain", + "Properties": { + "Content": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "layer.zip" + }, + "LayerName": "MinimalLayer" + } + }, + "MinimalLayer2800a44a445": { + "Type": "AWS::Lambda::LayerVersion", + "DeletionPolicy": "Delete", + "Properties": { + "Content": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "layer.zip" + }, + "LayerName": "MinimalLayer2" + } + }, + "MinimalLayer3ac07350a04": { + "Type": "AWS::Lambda::LayerVersion", + "DeletionPolicy": "Retain", + "Properties": { + "Content": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "layer.zip" + }, + "LayerName": "MinimalLayer3" + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/version_deletion_policy_precedence.json b/tests/translator/output/aws-us-gov/version_deletion_policy_precedence.json new file mode 100644 index 000000000..0beb66af0 --- /dev/null +++ b/tests/translator/output/aws-us-gov/version_deletion_policy_precedence.json @@ -0,0 +1,163 @@ +{ + "Resources": { + "MinimalFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip" + }, + "Handler": "hello.handler", + "Role": { + "Fn::GetAtt": [ + "MinimalFunctionRole", + "Arn" + ] + }, + "Runtime": "python2.7", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MinimalFunctionVersion640128d35d": { + "Type": "AWS::Lambda::Version", + "DeletionPolicy": "Retain", + "Properties": { + "Description": "sam-testing", + "FunctionName": { + "Ref": "MinimalFunction" + } + } + }, + "MinimalFunctionAliaslive": { + "Type": "AWS::Lambda::Alias", + "Properties": { + "Name": "live", + "FunctionName": { + "Ref": "MinimalFunction" + }, + "FunctionVersion": { + "Fn::GetAtt": [ + "MinimalFunctionVersion640128d35d", + "Version" + ] + } + } + }, + "MinimalFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MinimalFunction2": { + "Type": "AWS::Lambda::Function", + "DeletionPolicy": "Delete", + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip" + }, + "Handler": "hello.handler", + "Role": { + "Fn::GetAtt": [ + "MinimalFunction2Role", + "Arn" + ] + }, + "Runtime": "python2.7", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MinimalFunction2Version640128d35d": { + "Type": "AWS::Lambda::Version", + "DeletionPolicy": "Delete", + "Properties": { + "Description": "sam-testing", + "FunctionName": { + "Ref": "MinimalFunction2" + } + } + }, + "MinimalFunction2Aliaslive": { + "Type": "AWS::Lambda::Alias", + "DeletionPolicy": "Delete", + "Properties": { + "Name": "live", + "FunctionName": { + "Ref": "MinimalFunction2" + }, + "FunctionVersion": { + "Fn::GetAtt": [ + "MinimalFunction2Version640128d35d", + "Version" + ] + } + } + }, + "MinimalFunction2Role": { + "Type": "AWS::IAM::Role", + "DeletionPolicy": "Delete", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/error_api_invalid_source_vpc_blacklist.json b/tests/translator/output/error_api_invalid_source_vpc_blacklist.json new file mode 100644 index 000000000..cc7d2188a --- /dev/null +++ b/tests/translator/output/error_api_invalid_source_vpc_blacklist.json @@ -0,0 +1,3 @@ +{ + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. SourceVpcBlacklist must be a list of strings. Use IntrinsicVpcBlacklist instead for values that use Intrinsic Functions" +} diff --git a/tests/translator/output/error_api_invalid_source_vpc_whitelist.json b/tests/translator/output/error_api_invalid_source_vpc_whitelist.json new file mode 100644 index 000000000..5ce6cf3fd --- /dev/null +++ b/tests/translator/output/error_api_invalid_source_vpc_whitelist.json @@ -0,0 +1,3 @@ +{ + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. SourceVpcWhitelist must be a list of strings. Use IntrinsicVpcWhitelist instead for values that use Intrinsic Functions" +} diff --git a/tests/translator/output/function_event_conditions.json b/tests/translator/output/function_event_conditions.json index c6de70318..4926995a9 100644 --- a/tests/translator/output/function_event_conditions.json +++ b/tests/translator/output/function_event_conditions.json @@ -529,6 +529,7 @@ "Condition": "MyCondition" }, "MyAwesomeFunctionAnotherSNSWithSQSSubscriptionQueue": { + "Condition": "MyCondition", "Type": "AWS::SQS::Queue", "Properties": {} }, diff --git a/tests/translator/output/function_with_request_parameters.json b/tests/translator/output/function_with_request_parameters.json index 51dcff6ba..940a79710 100644 --- a/tests/translator/output/function_with_request_parameters.json +++ b/tests/translator/output/function_with_request_parameters.json @@ -53,13 +53,13 @@ } } }, - "ServerlessRestApiDeployment2223b43914": { + "ServerlessRestApiDeployment104b236830": { "Type": "AWS::ApiGateway::Deployment", "Properties": { "RestApiId": { "Ref": "ServerlessRestApi" }, - "Description": "RestApi deployment id: 2223b439142974b7a3aad1381ddd39027077ce52", + "Description": "RestApi deployment id: 104b236830d26d2515909073d13fa9c58ad6db49", "StageName": "Stage" } }, @@ -118,7 +118,7 @@ "Type": "AWS::ApiGateway::Stage", "Properties": { "DeploymentId": { - "Ref": "ServerlessRestApiDeployment2223b43914" + "Ref": "ServerlessRestApiDeployment104b236830" }, "RestApiId": { "Ref": "ServerlessRestApi" @@ -239,6 +239,12 @@ "type": "string", "name": "id", "in": "path" + }, + { + "required": false, + "type": "string", + "name": "full.type", + "in": "query" } ] } @@ -272,4 +278,4 @@ } } } -} +} \ No newline at end of file diff --git a/tests/translator/output/implicit_api_deletion_policy_precedence.json b/tests/translator/output/implicit_api_deletion_policy_precedence.json new file mode 100644 index 000000000..025e04735 --- /dev/null +++ b/tests/translator/output/implicit_api_deletion_policy_precedence.json @@ -0,0 +1,234 @@ +{ + "Resources": { + "RestApiFunction": { + "Type": "AWS::Lambda::Function", + "DeletionPolicy": "Delete", + "UpdateReplacePolicy": "Retain", + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "todo_list.zip" + }, + "Handler": "index.restapi", + "Role": { + "Fn::GetAtt": [ + "RestApiFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "RestApiFunctionRole": { + "Type": "AWS::IAM::Role", + "DeletionPolicy": "Delete", + "UpdateReplacePolicy": "Retain", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + "arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "RestApiFunctionGetHtmlPermissionProd": { + "Type": "AWS::Lambda::Permission", + "DeletionPolicy": "Delete", + "UpdateReplacePolicy": "Retain", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Ref": "RestApiFunction" + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/*/*", + { + "__ApiId__": { + "Ref": "ServerlessRestApi" + }, + "__Stage__": "*" + } + ] + } + } + }, + "GetHtmlFunction": { + "Type": "AWS::Lambda::Function", + "DeletionPolicy": "Retain", + "UpdateReplacePolicy": "Retain", + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "todo_list.zip" + }, + "Handler": "index.gethtml", + "Role": { + "Fn::GetAtt": [ + "GetHtmlFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "GetHtmlFunctionRole": { + "Type": "AWS::IAM::Role", + "DeletionPolicy": "Retain", + "UpdateReplacePolicy": "Retain", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + "arn:aws:iam::aws:policy/AmazonDynamoDBReadOnlyAccess" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "GetHtmlFunctionGetHtmlPermissionProd": { + "Type": "AWS::Lambda::Permission", + "DeletionPolicy": "Retain", + "UpdateReplacePolicy": "Retain", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Ref": "GetHtmlFunction" + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/*/{proxy++}", + { + "__ApiId__": { + "Ref": "ServerlessRestApi" + }, + "__Stage__": "*" + } + ] + } + } + }, + "ServerlessRestApi": { + "Type": "AWS::ApiGateway::RestApi", + "DeletionPolicy": "Retain", + "UpdateReplacePolicy": "Retain", + "Properties": { + "Body": { + "swagger": "2.0", + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/{proxy+}": { + "x-amazon-apigateway-any-method": { + "x-amazon-apigateway-integration": { + "type": "aws_proxy", + "httpMethod": "POST", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${RestApiFunction.Arn}/invocations" + } + }, + "responses": {} + } + }, + "/{proxy++}": { + "x-amazon-apigateway-any-method": { + "x-amazon-apigateway-integration": { + "type": "aws_proxy", + "httpMethod": "POST", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${GetHtmlFunction.Arn}/invocations" + } + }, + "responses": {} + } + } + } + } + } + }, + "ServerlessRestApiDeploymentbeaf67e605": { + "Type": "AWS::ApiGateway::Deployment", + "DeletionPolicy": "Retain", + "UpdateReplacePolicy": "Retain", + "Properties": { + "Description": "RestApi deployment id: beaf67e605cdfc82f45c51fa5f8d9552af2ca0c6", + "RestApiId": { + "Ref": "ServerlessRestApi" + }, + "StageName": "Stage" + } + }, + "ServerlessRestApiProdStage": { + "Type": "AWS::ApiGateway::Stage", + "DeletionPolicy": "Retain", + "UpdateReplacePolicy": "Retain", + "Properties": { + "DeploymentId": { + "Ref": "ServerlessRestApiDeploymentbeaf67e605" + }, + "RestApiId": { + "Ref": "ServerlessRestApi" + }, + "StageName": "Prod" + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/layer_deletion_policy_precedence.json b/tests/translator/output/layer_deletion_policy_precedence.json new file mode 100644 index 000000000..8bb610ad0 --- /dev/null +++ b/tests/translator/output/layer_deletion_policy_precedence.json @@ -0,0 +1,37 @@ +{ + "Resources": { + "MinimalLayer22b6609c3d": { + "Type": "AWS::Lambda::LayerVersion", + "DeletionPolicy": "Retain", + "Properties": { + "Content": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "layer.zip" + }, + "LayerName": "MinimalLayer" + } + }, + "MinimalLayer2800a44a445": { + "Type": "AWS::Lambda::LayerVersion", + "DeletionPolicy": "Delete", + "Properties": { + "Content": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "layer.zip" + }, + "LayerName": "MinimalLayer2" + } + }, + "MinimalLayer3ac07350a04": { + "Type": "AWS::Lambda::LayerVersion", + "DeletionPolicy": "Retain", + "Properties": { + "Content": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "layer.zip" + }, + "LayerName": "MinimalLayer3" + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/version_deletion_policy_precedence.json b/tests/translator/output/version_deletion_policy_precedence.json new file mode 100644 index 000000000..36ab3d842 --- /dev/null +++ b/tests/translator/output/version_deletion_policy_precedence.json @@ -0,0 +1,163 @@ +{ + "Resources": { + "MinimalFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip" + }, + "Handler": "hello.handler", + "Role": { + "Fn::GetAtt": [ + "MinimalFunctionRole", + "Arn" + ] + }, + "Runtime": "python2.7", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MinimalFunctionVersion640128d35d": { + "Type": "AWS::Lambda::Version", + "DeletionPolicy": "Retain", + "Properties": { + "Description": "sam-testing", + "FunctionName": { + "Ref": "MinimalFunction" + } + } + }, + "MinimalFunctionAliaslive": { + "Type": "AWS::Lambda::Alias", + "Properties": { + "Name": "live", + "FunctionName": { + "Ref": "MinimalFunction" + }, + "FunctionVersion": { + "Fn::GetAtt": [ + "MinimalFunctionVersion640128d35d", + "Version" + ] + } + } + }, + "MinimalFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MinimalFunction2": { + "Type": "AWS::Lambda::Function", + "DeletionPolicy": "Delete", + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip" + }, + "Handler": "hello.handler", + "Role": { + "Fn::GetAtt": [ + "MinimalFunction2Role", + "Arn" + ] + }, + "Runtime": "python2.7", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MinimalFunction2Version640128d35d": { + "Type": "AWS::Lambda::Version", + "DeletionPolicy": "Delete", + "Properties": { + "Description": "sam-testing", + "FunctionName": { + "Ref": "MinimalFunction2" + } + } + }, + "MinimalFunction2Aliaslive": { + "Type": "AWS::Lambda::Alias", + "DeletionPolicy": "Delete", + "Properties": { + "Name": "live", + "FunctionName": { + "Ref": "MinimalFunction2" + }, + "FunctionVersion": { + "Fn::GetAtt": [ + "MinimalFunction2Version640128d35d", + "Version" + ] + } + } + }, + "MinimalFunction2Role": { + "Type": "AWS::IAM::Role", + "DeletionPolicy": "Delete", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + } + } +} \ No newline at end of file diff --git a/tests/translator/test_arn_generator.py b/tests/translator/test_arn_generator.py index 461840cdf..200f90414 100644 --- a/tests/translator/test_arn_generator.py +++ b/tests/translator/test_arn_generator.py @@ -1,13 +1,13 @@ from unittest import TestCase from parameterized import parameterized -from mock import Mock, patch +from mock import patch from samtranslator.translator.arn_generator import ArnGenerator, NoRegionFound class TestArnGenerator(TestCase): def setUp(self): - ArnGenerator.class_boto_session = None + ArnGenerator.BOTO_SESSION_REGION_NAME = None @parameterized.expand( [("us-east-1", "aws"), ("cn-east-1", "aws-cn"), ("us-gov-west-1", "aws-us-gov"), ("US-EAST-1", "aws")] @@ -23,13 +23,10 @@ def test_get_partition_name_raise_NoRegionFound(self): ArnGenerator.get_partition_name(None) def test_get_partition_name_from_boto_session(self): - boto_session_mock = Mock() - boto_session_mock.region_name = "us-east-1" - - ArnGenerator.class_boto_session = boto_session_mock + ArnGenerator.BOTO_SESSION_REGION_NAME = "us-east-1" actual = ArnGenerator.get_partition_name() self.assertEqual(actual, "aws") - ArnGenerator.class_boto_session = None + ArnGenerator.BOTO_SESSION_REGION_NAME = None diff --git a/tests/translator/test_resource_level_attributes.py b/tests/translator/test_resource_level_attributes.py new file mode 100644 index 000000000..58754fe92 --- /dev/null +++ b/tests/translator/test_resource_level_attributes.py @@ -0,0 +1,86 @@ +import itertools +from mock import patch + +from parameterized import parameterized + +from tests.plugins.application.test_serverless_app_plugin import mock_get_region +from tests.translator.test_translator import mock_sar_service_call, AbstractTestTranslator + + +class TestResourceLevelAttributes(AbstractTestTranslator): + @parameterized.expand( + itertools.product( + [ + "cognito_userpool_with_event", + "s3_with_condition", + "function_with_condition", + "basic_function", + "basic_application", + "application_with_intrinsics", + "cloudwatchevent", + "eventbridgerule", + "cloudwatchlog", + "streams", + "sqs", + "function_with_amq", + "simpletable", + "implicit_api", + "explicit_api", + "api_description", + "s3", + "sns", + "alexa_skill", + "iot_rule", + "layers_all_properties", + "unsupported_resources", + "intrinsic_functions", + "basic_function_with_tags", + "depends_on", + "function_event_conditions", + "function_with_alias", + "function_with_layers", + "global_handle_path_level_parameter", + "all_policy_templates", + "simple_table_ref_parameter_intrinsic", + "implicit_api_with_serverless_rest_api_resource", + "api_with_cors_and_conditions_no_definitionbody", + "api_with_auth_and_conditions_all_max", + "api_with_apikey_required", + "api_with_path_parameters", + "function_with_event_source_mapping", + "api_with_usageplans", + "state_machine_with_inline_definition", + "function_with_file_system_config", + "state_machine_with_permissions_boundary", + ], + [ + ("aws", "ap-southeast-1"), + ("aws-cn", "cn-north-1"), + ("aws-us-gov", "us-gov-west-1"), + ], # Run all the above tests against each of the list of partitions to test against + ) + ) + @patch( + "samtranslator.plugins.application.serverless_app_plugin.ServerlessAppPlugin._sar_service_call", + mock_sar_service_call, + ) + @patch("botocore.client.ClientEndpointBridge._check_default_region", mock_get_region) + def test_transform_with_additional_resource_level_attributes(self, testcase, partition_with_region): + partition = partition_with_region[0] + region = partition_with_region[1] + + # add resource level attributes to input resources + manifest = self._read_input(testcase) + resources = manifest.get("Resources", []) + for _, resource in resources.items(): + resource["DeletionPolicy"] = "Delete" + resource["UpdateReplacePolicy"] = "Retain" + + # add resource level attributes to expected output resources + expected = self._read_expected_output(testcase, partition) + expected_resources = expected.get("Resources", []) + for _, expected_resource in expected_resources.items(): + expected_resource["DeletionPolicy"] = "Delete" + expected_resource["UpdateReplacePolicy"] = "Retain" + + self._compare_transform(manifest, expected, partition, region) diff --git a/tests/translator/test_translator.py b/tests/translator/test_translator.py index 05948913f..5a100fe38 100644 --- a/tests/translator/test_translator.py +++ b/tests/translator/test_translator.py @@ -138,7 +138,122 @@ def mock_sar_service_call(self, service_call_function, logical_id, *args): # api and s3 location for explicit api. -class TestTranslatorEndToEnd(TestCase): +class AbstractTestTranslator(TestCase): + def _read_input(self, testcase): + manifest = yaml_parse(open(os.path.join(INPUT_FOLDER, testcase + ".yaml"), "r")) + # To uncover unicode-related bugs, convert dict to JSON string and parse JSON back to dict + return json.loads(json.dumps(manifest)) + + def _read_expected_output(self, testcase, partition): + partition_folder = partition if partition != "aws" else "" + expected_filepath = os.path.join(OUTPUT_FOLDER, partition_folder, testcase + ".json") + return json.load(open(expected_filepath, "r")) + + def _compare_transform(self, manifest, expected, partition, region): + with patch("boto3.session.Session.region_name", region): + parameter_values = get_template_parameter_values() + mock_policy_loader = MagicMock() + mock_policy_loader.load.return_value = { + "AWSLambdaBasicExecutionRole": "arn:{}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole".format( + partition + ), + "AmazonDynamoDBFullAccess": "arn:{}:iam::aws:policy/AmazonDynamoDBFullAccess".format(partition), + "AmazonDynamoDBReadOnlyAccess": "arn:{}:iam::aws:policy/AmazonDynamoDBReadOnlyAccess".format(partition), + "AWSLambdaRole": "arn:{}:iam::aws:policy/service-role/AWSLambdaRole".format(partition), + } + if partition == "aws": + mock_policy_loader.load.return_value[ + "AWSXrayWriteOnlyAccess" + ] = "arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess" + else: + mock_policy_loader.load.return_value[ + "AWSXRayDaemonWriteAccess" + ] = "arn:{}:iam::aws:policy/AWSXRayDaemonWriteAccess".format(partition) + + output_fragment = transform(manifest, parameter_values, mock_policy_loader) + + print(json.dumps(output_fragment, indent=2)) + + # Only update the deployment Logical Id hash in Py3. + if sys.version_info.major >= 3: + self._update_logical_id_hash(expected) + self._update_logical_id_hash(output_fragment) + + self.assertEqual(deep_sort_lists(output_fragment), deep_sort_lists(expected)) + + def _update_logical_id_hash(self, resources): + """ + Brute force method for updating all APIGW Deployment LogicalIds and references to a consistent hash + """ + output_resources = resources.get("Resources", {}) + deployment_logical_id_dict = {} + rest_api_to_swagger_hash = {} + dict_of_things_to_delete = {} + + # Find all RestApis in the template + for logical_id, resource_dict in output_resources.items(): + if "AWS::ApiGateway::RestApi" == resource_dict.get("Type"): + resource_properties = resource_dict.get("Properties", {}) + if "Body" in resource_properties: + self._generate_new_deployment_hash( + logical_id, resource_properties.get("Body"), rest_api_to_swagger_hash + ) + + elif "BodyS3Location" in resource_dict.get("Properties"): + self._generate_new_deployment_hash( + logical_id, resource_properties.get("BodyS3Location"), rest_api_to_swagger_hash + ) + + # Collect all APIGW Deployments LogicalIds and generate the new ones + for logical_id, resource_dict in output_resources.items(): + if "AWS::ApiGateway::Deployment" == resource_dict.get("Type"): + resource_properties = resource_dict.get("Properties", {}) + + rest_id = resource_properties.get("RestApiId").get("Ref") + + data_hash = rest_api_to_swagger_hash.get(rest_id) + + description = resource_properties.get("Description")[: -len(data_hash)] + + resource_properties["Description"] = description + data_hash + + new_logical_id = logical_id[:-10] + data_hash[:10] + + deployment_logical_id_dict[logical_id] = new_logical_id + dict_of_things_to_delete[logical_id] = (new_logical_id, resource_dict) + + # Update References to APIGW Deployments + for logical_id, resource_dict in output_resources.items(): + if "AWS::ApiGateway::Stage" == resource_dict.get("Type"): + resource_properties = resource_dict.get("Properties", {}) + + rest_id = resource_properties.get("RestApiId", {}).get("Ref", "") + + data_hash = rest_api_to_swagger_hash.get(rest_id) + + deployment_id = resource_properties.get("DeploymentId", {}).get("Ref") + new_logical_id = deployment_logical_id_dict.get(deployment_id, "")[:-10] + new_logical_id = new_logical_id + data_hash[:10] + + resource_properties.get("DeploymentId", {})["Ref"] = new_logical_id + + # To avoid mutating the template while iterating, delete only after find everything to update + for logical_id_to_remove, tuple_to_add in dict_of_things_to_delete.items(): + output_resources[tuple_to_add[0]] = tuple_to_add[1] + del output_resources[logical_id_to_remove] + + # Update any Output References in the template + for output_key, output_value in resources.get("Outputs", {}).items(): + if output_value.get("Value").get("Ref") in deployment_logical_id_dict: + output_value["Value"]["Ref"] = deployment_logical_id_dict[output_value.get("Value").get("Ref")] + + def _generate_new_deployment_hash(self, logical_id, dict_to_hash, rest_api_to_swagger_hash): + data_bytes = json.dumps(dict_to_hash, separators=(",", ":"), sort_keys=True).encode("utf8") + data_hash = hashlib.sha1(data_bytes).hexdigest() + rest_api_to_swagger_hash[logical_id] = data_hash + + +class TestTranslatorEndToEnd(AbstractTestTranslator): @parameterized.expand( itertools.product( [ @@ -223,6 +338,7 @@ class TestTranslatorEndToEnd(TestCase): "iot_rule", "layers_with_intrinsics", "layers_all_properties", + "layer_deletion_policy_precedence", "function_managed_inline_policy", "unsupported_resources", "intrinsic_functions", @@ -268,6 +384,7 @@ class TestTranslatorEndToEnd(TestCase): "simple_table_with_extra_tags", "explicit_api_with_invalid_events_config", "no_implicit_api_with_serverless_rest_api_resource", + "implicit_api_deletion_policy_precedence", "implicit_api_with_serverless_rest_api_resource", "implicit_api_with_auth_and_conditions_max", "implicit_api_with_many_conditions", @@ -315,6 +432,7 @@ class TestTranslatorEndToEnd(TestCase): "state_machine_with_xray_role", "function_with_file_system_config", "state_machine_with_permissions_boundary", + "version_deletion_policy_precedence", ], [ ("aws", "ap-southeast-1"), @@ -332,43 +450,10 @@ def test_transform_success(self, testcase, partition_with_region): partition = partition_with_region[0] region = partition_with_region[1] - manifest = yaml_parse(open(os.path.join(INPUT_FOLDER, testcase + ".yaml"), "r")) - # To uncover unicode-related bugs, convert dict to JSON string and parse JSON back to dict - manifest = json.loads(json.dumps(manifest)) - partition_folder = partition if partition != "aws" else "" - expected_filepath = os.path.join(OUTPUT_FOLDER, partition_folder, testcase + ".json") - expected = json.load(open(expected_filepath, "r")) + manifest = self._read_input(testcase) + expected = self._read_expected_output(testcase, partition) - with patch("boto3.session.Session.region_name", region): - parameter_values = get_template_parameter_values() - mock_policy_loader = MagicMock() - mock_policy_loader.load.return_value = { - "AWSLambdaBasicExecutionRole": "arn:{}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole".format( - partition - ), - "AmazonDynamoDBFullAccess": "arn:{}:iam::aws:policy/AmazonDynamoDBFullAccess".format(partition), - "AmazonDynamoDBReadOnlyAccess": "arn:{}:iam::aws:policy/AmazonDynamoDBReadOnlyAccess".format(partition), - "AWSLambdaRole": "arn:{}:iam::aws:policy/service-role/AWSLambdaRole".format(partition), - } - if partition == "aws": - mock_policy_loader.load.return_value[ - "AWSXrayWriteOnlyAccess" - ] = "arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess" - else: - mock_policy_loader.load.return_value[ - "AWSXRayDaemonWriteAccess" - ] = "arn:{}:iam::aws:policy/AWSXRayDaemonWriteAccess".format(partition) - - output_fragment = transform(manifest, parameter_values, mock_policy_loader) - - print(json.dumps(output_fragment, indent=2)) - - # Only update the deployment Logical Id hash in Py3. - if sys.version_info.major >= 3: - self._update_logical_id_hash(expected) - self._update_logical_id_hash(output_fragment) - - assert deep_sort_lists(output_fragment) == deep_sort_lists(expected) + self._compare_transform(manifest, expected, partition, region) @parameterized.expand( itertools.product( @@ -451,7 +536,7 @@ def test_transform_success_openapi3(self, testcase, partition_with_region): self._update_logical_id_hash(expected) self._update_logical_id_hash(output_fragment) - assert deep_sort_lists(output_fragment) == deep_sort_lists(expected) + self.assertEqual(deep_sort_lists(output_fragment), deep_sort_lists(expected)) @parameterized.expand( itertools.product( @@ -509,78 +594,8 @@ def test_transform_success_resource_policy(self, testcase, partition_with_region if sys.version_info.major >= 3: self._update_logical_id_hash(expected) self._update_logical_id_hash(output_fragment) - assert deep_sort_lists(output_fragment) == deep_sort_lists(expected) - - def _update_logical_id_hash(self, resources): - """ - Brute force method for updating all APIGW Deployment LogicalIds and references to a consistent hash - """ - output_resources = resources.get("Resources", {}) - deployment_logical_id_dict = {} - rest_api_to_swagger_hash = {} - dict_of_things_to_delete = {} - - # Find all RestApis in the template - for logical_id, resource_dict in output_resources.items(): - if "AWS::ApiGateway::RestApi" == resource_dict.get("Type"): - resource_properties = resource_dict.get("Properties", {}) - if "Body" in resource_properties: - self._generate_new_deployment_hash( - logical_id, resource_properties.get("Body"), rest_api_to_swagger_hash - ) - - elif "BodyS3Location" in resource_dict.get("Properties"): - self._generate_new_deployment_hash( - logical_id, resource_properties.get("BodyS3Location"), rest_api_to_swagger_hash - ) - - # Collect all APIGW Deployments LogicalIds and generate the new ones - for logical_id, resource_dict in output_resources.items(): - if "AWS::ApiGateway::Deployment" == resource_dict.get("Type"): - resource_properties = resource_dict.get("Properties", {}) - rest_id = resource_properties.get("RestApiId").get("Ref") - - data_hash = rest_api_to_swagger_hash.get(rest_id) - - description = resource_properties.get("Description")[: -len(data_hash)] - - resource_properties["Description"] = description + data_hash - - new_logical_id = logical_id[:-10] + data_hash[:10] - - deployment_logical_id_dict[logical_id] = new_logical_id - dict_of_things_to_delete[logical_id] = (new_logical_id, resource_dict) - - # Update References to APIGW Deployments - for logical_id, resource_dict in output_resources.items(): - if "AWS::ApiGateway::Stage" == resource_dict.get("Type"): - resource_properties = resource_dict.get("Properties", {}) - - rest_id = resource_properties.get("RestApiId", {}).get("Ref", "") - - data_hash = rest_api_to_swagger_hash.get(rest_id) - - deployment_id = resource_properties.get("DeploymentId", {}).get("Ref") - new_logical_id = deployment_logical_id_dict.get(deployment_id, "")[:-10] - new_logical_id = new_logical_id + data_hash[:10] - - resource_properties.get("DeploymentId", {})["Ref"] = new_logical_id - - # To avoid mutating the template while iterating, delete only after find everything to update - for logical_id_to_remove, tuple_to_add in dict_of_things_to_delete.items(): - output_resources[tuple_to_add[0]] = tuple_to_add[1] - del output_resources[logical_id_to_remove] - - # Update any Output References in the template - for output_key, output_value in resources.get("Outputs", {}).items(): - if output_value.get("Value").get("Ref") in deployment_logical_id_dict: - output_value["Value"]["Ref"] = deployment_logical_id_dict[output_value.get("Value").get("Ref")] - - def _generate_new_deployment_hash(self, logical_id, dict_to_hash, rest_api_to_swagger_hash): - data_bytes = json.dumps(dict_to_hash, separators=(",", ":"), sort_keys=True).encode("utf8") - data_hash = hashlib.sha1(data_bytes).hexdigest() - rest_api_to_swagger_hash[logical_id] = data_hash + self.assertEqual(deep_sort_lists(output_fragment), deep_sort_lists(expected)) @pytest.mark.parametrize(