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/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..c026a9722 100644 --- a/samtranslator/model/api/api_generator.py +++ b/samtranslator/model/api/api_generator.py @@ -1,6 +1,8 @@ +import logging from collections import namedtuple + from six import string_types -from samtranslator.model.intrinsics import ref, fnGetAtt +from samtranslator.model.intrinsics import ref, fnGetAtt, make_or_condition from samtranslator.model.apigateway import ( ApiGatewayDeployment, ApiGatewayRestApi, @@ -14,7 +16,7 @@ ApiGatewayApiKey, ) from samtranslator.model.route53 import Route53RecordSetGroup -from samtranslator.model.exceptions import InvalidResourceException +from samtranslator.model.exceptions import InvalidResourceException, InvalidTemplateException from samtranslator.model.s3_utils.uri_parser import parse_s3_uri from samtranslator.region_configuration import RegionConfiguration from samtranslator.swagger.swagger import SwaggerEditor @@ -24,6 +26,9 @@ from samtranslator.translator.arn_generator import ArnGenerator from samtranslator.model.tags.resource_tagging import get_tag_list +LOG = logging.getLogger(__name__) +LOG.setLevel(logging.INFO) + _CORS_WILDCARD = "'*'" CorsProperties = namedtuple( "_CorsProperties", ["AllowMethods", "AllowHeaders", "AllowOrigin", "MaxAge", "AllowCredentials"] @@ -52,12 +57,100 @@ 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 + """ + + SHARED_USAGE_PLAN_CONDITION_NAME = "SharedUsagePlanCondition" + + def __init__(self): + self.usage_plan_shared = False + self.stage_keys_shared = list() + self.api_stages_shared = list() + self.depends_on_shared = list() + + # shared resource level attributes + self.conditions = set() + self.any_api_without_condition = False + self.deletion_policy = None + self.update_replace_policy = None + + def get_combined_resource_attributes(self, resource_attributes, conditions): + """ + This method returns a dictionary which combines 'DeletionPolicy', 'UpdateReplacePolicy' and 'Condition' + values of API definitions that could be used in Shared Usage Plan resources. + + Parameters + ---------- + resource_attributes: Dict[str] + A dictionary of resource level attributes of the API resource + conditions: Dict[str] + Conditions section of the template + """ + self._set_deletion_policy(resource_attributes.get("DeletionPolicy")) + self._set_update_replace_policy(resource_attributes.get("UpdateReplacePolicy")) + self._set_condition(resource_attributes.get("Condition"), conditions) + + combined_resource_attributes = dict() + if self.deletion_policy: + combined_resource_attributes["DeletionPolicy"] = self.deletion_policy + if self.update_replace_policy: + combined_resource_attributes["UpdateReplacePolicy"] = self.update_replace_policy + # do not set Condition if any of the API resource does not have Condition in it + if self.conditions and not self.any_api_without_condition: + combined_resource_attributes["Condition"] = SharedApiUsagePlan.SHARED_USAGE_PLAN_CONDITION_NAME + + return combined_resource_attributes + + def _set_deletion_policy(self, deletion_policy): + if deletion_policy: + if self.deletion_policy: + # update only if new deletion policy is Retain + if deletion_policy == "Retain": + self.deletion_policy = deletion_policy + else: + self.deletion_policy = deletion_policy + + def _set_update_replace_policy(self, update_replace_policy): + if update_replace_policy: + if self.update_replace_policy: + # if new value is Retain or + # new value is retain and current value is Delete then update its value + if (update_replace_policy == "Retain") or ( + update_replace_policy == "Snapshot" and self.update_replace_policy == "Delete" + ): + self.update_replace_policy = update_replace_policy + else: + self.update_replace_policy = update_replace_policy + + def _set_condition(self, condition, template_conditions): + # if there are any API without condition, then skip + if self.any_api_without_condition: + return + if condition and condition not in self.conditions: + + if template_conditions is None: + raise InvalidTemplateException( + "Can't have condition without having 'Conditions' section in the template" + ) + + if self.conditions: + self.conditions.add(condition) + or_condition = make_or_condition(self.conditions) + template_conditions[SharedApiUsagePlan.SHARED_USAGE_PLAN_CONDITION_NAME] = or_condition + else: + self.conditions.add(condition) + template_conditions[SharedApiUsagePlan.SHARED_USAGE_PLAN_CONDITION_NAME] = condition + elif condition is None: + self.any_api_without_condition = True + if template_conditions and SharedApiUsagePlan.SHARED_USAGE_PLAN_CONDITION_NAME in template_conditions: + del template_conditions[SharedApiUsagePlan.SHARED_USAGE_PLAN_CONDITION_NAME] + + +class ApiGenerator(object): def __init__( self, logical_id, @@ -69,6 +162,8 @@ def __init__( definition_uri, name, stage_name, + shared_api_usage_plan, + template_conditions, tags=None, endpoint_configuration=None, method_settings=None, @@ -134,6 +229,8 @@ def __init__( self.models = models self.domain = domain self.description = description + self.shared_api_usage_plan = shared_api_usage_plan + self.template_conditions = template_conditions def _construct_rest_api(self): """Constructs and returns the ApiGateway RestApi. @@ -617,7 +714,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 +731,23 @@ 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.shared_api_usage_plan.get_combined_resource_attributes( + self.passthrough_resource_attributes, self.template_conditions + ), ) 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 +773,31 @@ 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.shared_api_usage_plan.get_combined_resource_attributes( + self.passthrough_resource_attributes, self.template_conditions + ), + ) 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.Enabled = True stage_keys = list() stage_key = dict() @@ -700,12 +817,20 @@ def _construct_usage_plan_key(self, usage_plan_logical_id, create_usage_plan, ap if create_usage_plan == "SHARED": # create a mapping between api key and the usage plan usage_plan_key_logical_id = "ServerlessUsagePlanKey" + resource_attributes = self.shared_api_usage_plan.get_combined_resource_attributes( + self.passthrough_resource_attributes, self.template_conditions + ) # for create_usage_plan = "PER_API" else: # create a mapping between api key and the usage plan usage_plan_key_logical_id = self.logical_id + "UsagePlanKey" + resource_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 = ApiGatewayUsagePlanKey( + logical_id=usage_plan_key_logical_id, + depends_on=[api_key.logical_id], + attributes=resource_attributes, + ) 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/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..de0f99637 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,9 +952,12 @@ 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) resources.append(CognitoUserPool.from_dict(userpool_id, userpool)) diff --git a/samtranslator/model/sam_resources.py b/samtranslator/model/sam_resources.py index a3a9a6d17..516d5b3de 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,8 @@ 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") + template_conditions = kwargs.get("conditions") api_generator = ApiGenerator( self.logical_id, @@ -868,6 +874,8 @@ def to_cloudformation(self, **kwargs): self.DefinitionUri, self.Name, self.StageName, + shared_api_usage_plan, + template_conditions, tags=self.Tags, endpoint_configuration=self.EndpointConfiguration, method_settings=self.MethodSettings, @@ -1160,15 +1168,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 +1222,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/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/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/translator/translator.py b/samtranslator/translator/translator.py index d4ca78068..b7449e02e 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 ( @@ -111,6 +112,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 +132,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) 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/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/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/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_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/api_with_usageplans_shared_attributes_three.yaml b/tests/translator/input/api_with_usageplans_shared_attributes_three.yaml new file mode 100644 index 000000000..aed811ca0 --- /dev/null +++ b/tests/translator/input/api_with_usageplans_shared_attributes_three.yaml @@ -0,0 +1,102 @@ +Globals: + Api: + Auth: + ApiKeyRequired: true + UsagePlan: + CreateUsagePlan: SHARED + +Conditions: + C1: + Fn::Equals: + - test + - test + C2: + Fn::Equals: + - test + - test + +Resources: + MyApiOne: + Type: AWS::Serverless::Api + Condition: C1 + UpdateReplacePolicy: Delete + Properties: + StageName: Prod + + MyApiTwo: + Type: AWS::Serverless::Api + Condition: C2 + UpdateReplacePolicy: Snapshot + Properties: + StageName: Prod + + MyApiThree: + Type: AWS::Serverless::Api + Properties: + StageName: Prod + + MyFunctionOne: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + InlineCode: | + exports.handler = async (event) => { + return { + statusCode: 200, + body: JSON.stringify(event), + headers: {} + } + } + Events: + ApiKey: + Type: Api + Properties: + RestApiId: + Ref: MyApiOne + Method: get + Path: /path/one + + MyFunctionTwo: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + InlineCode: | + exports.handler = async (event) => { + return { + statusCode: 200, + body: JSON.stringify(event), + headers: {} + } + } + Events: + ApiKey: + Type: Api + Properties: + RestApiId: + Ref: MyApiTwo + Method: get + Path: /path/two + + MyFunctionThree: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + InlineCode: | + exports.handler = async (event) => { + return { + statusCode: 200, + body: JSON.stringify(event), + headers: {} + } + } + Events: + ApiKey: + Type: Api + Properties: + RestApiId: + Ref: MyApiThree + Method: get + Path: /path/three \ No newline at end of file diff --git a/tests/translator/input/api_with_usageplans_shared_attributes_two.yaml b/tests/translator/input/api_with_usageplans_shared_attributes_two.yaml new file mode 100644 index 000000000..36c5bab65 --- /dev/null +++ b/tests/translator/input/api_with_usageplans_shared_attributes_two.yaml @@ -0,0 +1,75 @@ +Globals: + Api: + Auth: + ApiKeyRequired: true + UsagePlan: + CreateUsagePlan: SHARED + +Conditions: + C1: + Fn::Equals: + - test + - test + C2: + Fn::Equals: + - test + - test + +Resources: + MyApiOne: + Type: AWS::Serverless::Api + DeletionPolicy: Delete + Condition: C1 + Properties: + StageName: Prod + + MyApiTwo: + Type: AWS::Serverless::Api + DeletionPolicy: Retain + Condition: C2 + Properties: + StageName: Prod + + MyFunctionOne: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + InlineCode: | + exports.handler = async (event) => { + return { + statusCode: 200, + body: JSON.stringify(event), + headers: {} + } + } + Events: + ApiKey: + Type: Api + Properties: + RestApiId: + Ref: MyApiOne + Method: get + Path: /path/one + + MyFunctionTwo: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + InlineCode: | + exports.handler = async (event) => { + return { + statusCode: 200, + body: JSON.stringify(event), + headers: {} + } + } + Events: + ApiKey: + Type: Api + Properties: + RestApiId: + Ref: MyApiTwo + Method: get + Path: /path/two \ No newline at end of file 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_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/api_with_usageplans_shared_attributes_three.json b/tests/translator/output/api_with_usageplans_shared_attributes_three.json new file mode 100644 index 000000000..7d6b336e1 --- /dev/null +++ b/tests/translator/output/api_with_usageplans_shared_attributes_three.json @@ -0,0 +1,524 @@ +{ + "Conditions": { + "C1": { + "Fn::Equals": [ + "test", + "test" + ] + }, + "C2": { + "Fn::Equals": [ + "test", + "test" + ] + } + }, + "Resources": { + "MyFunctionOne": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = async (event) => {\n return {\n statusCode: 200,\n body: JSON.stringify(event),\n headers: {}\n }\n}\n" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyFunctionOneRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MyFunctionOneRole": { + "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" + } + ] + } + }, + "MyFunctionOneApiKeyPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Ref": "MyFunctionOne" + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/path/one", + { + "__ApiId__": { + "Ref": "MyApiOne" + }, + "__Stage__": "*" + } + ] + } + } + }, + "MyFunctionTwo": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = async (event) => {\n return {\n statusCode: 200,\n body: JSON.stringify(event),\n headers: {}\n }\n}\n" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyFunctionTwoRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MyFunctionTwoRole": { + "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" + } + ] + } + }, + "MyFunctionTwoApiKeyPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Ref": "MyFunctionTwo" + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/path/two", + { + "__ApiId__": { + "Ref": "MyApiTwo" + }, + "__Stage__": "*" + } + ] + } + } + }, + "MyFunctionThree": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = async (event) => {\n return {\n statusCode: 200,\n body: JSON.stringify(event),\n headers: {}\n }\n}\n" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyFunctionThreeRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MyFunctionThreeRole": { + "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" + } + ] + } + }, + "MyFunctionThreeApiKeyPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Ref": "MyFunctionThree" + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/path/three", + { + "__ApiId__": { + "Ref": "MyApiThree" + }, + "__Stage__": "*" + } + ] + } + } + }, + "MyApiOne": { + "Type": "AWS::ApiGateway::RestApi", + "Condition": "C1", + "UpdateReplacePolicy": "Delete", + "Properties": { + "Body": { + "swagger": "2.0", + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/path/one": { + "get": { + "x-amazon-apigateway-integration": { + "type": "aws_proxy", + "httpMethod": "POST", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunctionOne.Arn}/invocations" + } + }, + "responses": {}, + "security": [ + { + "api_key": [] + } + ] + } + } + }, + "securityDefinitions": { + "api_key": { + "type": "apiKey", + "name": "x-api-key", + "in": "header" + } + } + } + } + }, + "MyApiOneDeployment46fb22a429": { + "Type": "AWS::ApiGateway::Deployment", + "Condition": "C1", + "UpdateReplacePolicy": "Delete", + "Properties": { + "Description": "RestApi deployment id: 46fb22a42926db6f64e09966936d074ec6bb9392", + "RestApiId": { + "Ref": "MyApiOne" + }, + "StageName": "Stage" + } + }, + "MyApiOneProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Condition": "C1", + "UpdateReplacePolicy": "Delete", + "Properties": { + "DeploymentId": { + "Ref": "MyApiOneDeployment46fb22a429" + }, + "RestApiId": { + "Ref": "MyApiOne" + }, + "StageName": "Prod" + } + }, + "ServerlessUsagePlan": { + "Type": "AWS::ApiGateway::UsagePlan", + "DependsOn": [ + "MyApiOne", + "MyApiTwo", + "MyApiThree" + ], + "UpdateReplacePolicy": "Snapshot", + "Properties": { + "ApiStages": [ + { + "ApiId": { + "Ref": "MyApiOne" + }, + "Stage": { + "Ref": "MyApiOneProdStage" + } + }, + { + "ApiId": { + "Ref": "MyApiTwo" + }, + "Stage": { + "Ref": "MyApiTwoProdStage" + } + }, + { + "ApiId": { + "Ref": "MyApiThree" + }, + "Stage": { + "Ref": "MyApiThreeProdStage" + } + } + ] + } + }, + "ServerlessApiKey": { + "Type": "AWS::ApiGateway::ApiKey", + "DependsOn": [ + "ServerlessUsagePlan" + ], + "UpdateReplacePolicy": "Snapshot", + "Properties": { + "Enabled": true, + "StageKeys": [ + { + "RestApiId": { + "Ref": "MyApiOne" + }, + "StageName": { + "Ref": "MyApiOneProdStage" + } + }, + { + "RestApiId": { + "Ref": "MyApiTwo" + }, + "StageName": { + "Ref": "MyApiTwoProdStage" + } + }, + { + "RestApiId": { + "Ref": "MyApiThree" + }, + "StageName": { + "Ref": "MyApiThreeProdStage" + } + } + ] + } + }, + "ServerlessUsagePlanKey": { + "Type": "AWS::ApiGateway::UsagePlanKey", + "DependsOn": [ + "ServerlessApiKey" + ], + "UpdateReplacePolicy": "Snapshot", + "Properties": { + "KeyId": { + "Ref": "ServerlessApiKey" + }, + "KeyType": "API_KEY", + "UsagePlanId": { + "Ref": "ServerlessUsagePlan" + } + } + }, + "MyApiTwo": { + "Type": "AWS::ApiGateway::RestApi", + "Condition": "C2", + "UpdateReplacePolicy": "Snapshot", + "Properties": { + "Body": { + "swagger": "2.0", + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/path/two": { + "get": { + "x-amazon-apigateway-integration": { + "type": "aws_proxy", + "httpMethod": "POST", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunctionTwo.Arn}/invocations" + } + }, + "responses": {}, + "security": [ + { + "api_key": [] + } + ] + } + } + }, + "securityDefinitions": { + "api_key": { + "type": "apiKey", + "name": "x-api-key", + "in": "header" + } + } + } + } + }, + "MyApiTwoDeploymente9d97923b9": { + "Type": "AWS::ApiGateway::Deployment", + "Condition": "C2", + "UpdateReplacePolicy": "Snapshot", + "Properties": { + "Description": "RestApi deployment id: e9d97923b94d0801cd85a8970b3c3f84aa274003", + "RestApiId": { + "Ref": "MyApiTwo" + }, + "StageName": "Stage" + } + }, + "MyApiTwoProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Condition": "C2", + "UpdateReplacePolicy": "Snapshot", + "Properties": { + "DeploymentId": { + "Ref": "MyApiTwoDeploymente9d97923b9" + }, + "RestApiId": { + "Ref": "MyApiTwo" + }, + "StageName": "Prod" + } + }, + "MyApiThree": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "swagger": "2.0", + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/path/three": { + "get": { + "x-amazon-apigateway-integration": { + "type": "aws_proxy", + "httpMethod": "POST", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunctionThree.Arn}/invocations" + } + }, + "responses": {}, + "security": [ + { + "api_key": [] + } + ] + } + } + }, + "securityDefinitions": { + "api_key": { + "type": "apiKey", + "name": "x-api-key", + "in": "header" + } + } + } + } + }, + "MyApiThreeDeployment1d9cff47dc": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "Description": "RestApi deployment id: 1d9cff47dc9b822750c668c73b4534022483de6d", + "RestApiId": { + "Ref": "MyApiThree" + }, + "StageName": "Stage" + } + }, + "MyApiThreeProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiThreeDeployment1d9cff47dc" + }, + "RestApiId": { + "Ref": "MyApiThree" + }, + "StageName": "Prod" + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/api_with_usageplans_shared_attributes_two.json b/tests/translator/output/api_with_usageplans_shared_attributes_two.json new file mode 100644 index 000000000..3412bb041 --- /dev/null +++ b/tests/translator/output/api_with_usageplans_shared_attributes_two.json @@ -0,0 +1,385 @@ +{ + "Conditions": { + "C1": { + "Fn::Equals": [ + "test", + "test" + ] + }, + "C2": { + "Fn::Equals": [ + "test", + "test" + ] + }, + "SharedUsagePlanCondition": { + "Fn::Or": [ + { + "Condition": "C2" + }, + { + "Condition": "C1" + } + ] + } + }, + "Resources": { + "MyFunctionOne": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = async (event) => {\n return {\n statusCode: 200,\n body: JSON.stringify(event),\n headers: {}\n }\n}\n" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyFunctionOneRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MyFunctionOneRole": { + "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" + } + ] + } + }, + "MyFunctionOneApiKeyPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Ref": "MyFunctionOne" + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/path/one", + { + "__ApiId__": { + "Ref": "MyApiOne" + }, + "__Stage__": "*" + } + ] + } + } + }, + "MyFunctionTwo": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = async (event) => {\n return {\n statusCode: 200,\n body: JSON.stringify(event),\n headers: {}\n }\n}\n" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyFunctionTwoRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MyFunctionTwoRole": { + "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" + } + ] + } + }, + "MyFunctionTwoApiKeyPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Ref": "MyFunctionTwo" + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/path/two", + { + "__ApiId__": { + "Ref": "MyApiTwo" + }, + "__Stage__": "*" + } + ] + } + } + }, + "MyApiOne": { + "Type": "AWS::ApiGateway::RestApi", + "DeletionPolicy": "Delete", + "Condition": "C1", + "Properties": { + "Body": { + "swagger": "2.0", + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/path/one": { + "get": { + "x-amazon-apigateway-integration": { + "type": "aws_proxy", + "httpMethod": "POST", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunctionOne.Arn}/invocations" + } + }, + "responses": {}, + "security": [ + { + "api_key": [] + } + ] + } + } + }, + "securityDefinitions": { + "api_key": { + "type": "apiKey", + "name": "x-api-key", + "in": "header" + } + } + } + } + }, + "MyApiOneDeployment46fb22a429": { + "Type": "AWS::ApiGateway::Deployment", + "Condition": "C1", + "DeletionPolicy": "Delete", + "Properties": { + "Description": "RestApi deployment id: 46fb22a42926db6f64e09966936d074ec6bb9392", + "RestApiId": { + "Ref": "MyApiOne" + }, + "StageName": "Stage" + } + }, + "MyApiOneProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Condition": "C1", + "DeletionPolicy": "Delete", + "Properties": { + "DeploymentId": { + "Ref": "MyApiOneDeployment46fb22a429" + }, + "RestApiId": { + "Ref": "MyApiOne" + }, + "StageName": "Prod" + } + }, + "ServerlessUsagePlan": { + "Type": "AWS::ApiGateway::UsagePlan", + "DependsOn": [ + "MyApiOne", + "MyApiTwo" + ], + "Condition": "SharedUsagePlanCondition", + "DeletionPolicy": "Retain", + "Properties": { + "ApiStages": [ + { + "ApiId": { + "Ref": "MyApiOne" + }, + "Stage": { + "Ref": "MyApiOneProdStage" + } + }, + { + "ApiId": { + "Ref": "MyApiTwo" + }, + "Stage": { + "Ref": "MyApiTwoProdStage" + } + } + ] + } + }, + "ServerlessApiKey": { + "Type": "AWS::ApiGateway::ApiKey", + "DependsOn": [ + "ServerlessUsagePlan" + ], + "Condition": "SharedUsagePlanCondition", + "DeletionPolicy": "Retain", + "Properties": { + "Enabled": true, + "StageKeys": [ + { + "RestApiId": { + "Ref": "MyApiOne" + }, + "StageName": { + "Ref": "MyApiOneProdStage" + } + }, + { + "RestApiId": { + "Ref": "MyApiTwo" + }, + "StageName": { + "Ref": "MyApiTwoProdStage" + } + } + ] + } + }, + "ServerlessUsagePlanKey": { + "Type": "AWS::ApiGateway::UsagePlanKey", + "DependsOn": [ + "ServerlessApiKey" + ], + "Condition": "SharedUsagePlanCondition", + "DeletionPolicy": "Retain", + "Properties": { + "KeyId": { + "Ref": "ServerlessApiKey" + }, + "KeyType": "API_KEY", + "UsagePlanId": { + "Ref": "ServerlessUsagePlan" + } + } + }, + "MyApiTwo": { + "Type": "AWS::ApiGateway::RestApi", + "DeletionPolicy": "Retain", + "Condition": "C2", + "Properties": { + "Body": { + "swagger": "2.0", + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/path/two": { + "get": { + "x-amazon-apigateway-integration": { + "type": "aws_proxy", + "httpMethod": "POST", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunctionTwo.Arn}/invocations" + } + }, + "responses": {}, + "security": [ + { + "api_key": [] + } + ] + } + } + }, + "securityDefinitions": { + "api_key": { + "type": "apiKey", + "name": "x-api-key", + "in": "header" + } + } + } + } + }, + "MyApiTwoDeploymente9d97923b9": { + "Type": "AWS::ApiGateway::Deployment", + "Condition": "C2", + "DeletionPolicy": "Retain", + "Properties": { + "Description": "RestApi deployment id: e9d97923b94d0801cd85a8970b3c3f84aa274003", + "RestApiId": { + "Ref": "MyApiTwo" + }, + "StageName": "Stage" + } + }, + "MyApiTwoProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Condition": "C2", + "DeletionPolicy": "Retain", + "Properties": { + "DeploymentId": { + "Ref": "MyApiTwoDeploymente9d97923b9" + }, + "RestApiId": { + "Ref": "MyApiTwo" + }, + "StageName": "Prod" + } + } + } +} \ No newline at end of file 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/api_with_usageplans_shared_attributes_three.json b/tests/translator/output/aws-cn/api_with_usageplans_shared_attributes_three.json new file mode 100644 index 000000000..3f37e5bf0 --- /dev/null +++ b/tests/translator/output/aws-cn/api_with_usageplans_shared_attributes_three.json @@ -0,0 +1,548 @@ +{ + "Conditions": { + "C1": { + "Fn::Equals": [ + "test", + "test" + ] + }, + "C2": { + "Fn::Equals": [ + "test", + "test" + ] + } + }, + "Resources": { + "MyFunctionOne": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = async (event) => {\n return {\n statusCode: 200,\n body: JSON.stringify(event),\n headers: {}\n }\n}\n" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyFunctionOneRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MyFunctionOneRole": { + "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" + } + ] + } + }, + "MyFunctionOneApiKeyPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Ref": "MyFunctionOne" + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/path/one", + { + "__ApiId__": { + "Ref": "MyApiOne" + }, + "__Stage__": "*" + } + ] + } + } + }, + "MyFunctionTwo": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = async (event) => {\n return {\n statusCode: 200,\n body: JSON.stringify(event),\n headers: {}\n }\n}\n" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyFunctionTwoRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MyFunctionTwoRole": { + "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" + } + ] + } + }, + "MyFunctionTwoApiKeyPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Ref": "MyFunctionTwo" + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/path/two", + { + "__ApiId__": { + "Ref": "MyApiTwo" + }, + "__Stage__": "*" + } + ] + } + } + }, + "MyFunctionThree": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = async (event) => {\n return {\n statusCode: 200,\n body: JSON.stringify(event),\n headers: {}\n }\n}\n" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyFunctionThreeRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MyFunctionThreeRole": { + "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" + } + ] + } + }, + "MyFunctionThreeApiKeyPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Ref": "MyFunctionThree" + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/path/three", + { + "__ApiId__": { + "Ref": "MyApiThree" + }, + "__Stage__": "*" + } + ] + } + } + }, + "MyApiOne": { + "Type": "AWS::ApiGateway::RestApi", + "Condition": "C1", + "UpdateReplacePolicy": "Delete", + "Properties": { + "Body": { + "swagger": "2.0", + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/path/one": { + "get": { + "x-amazon-apigateway-integration": { + "type": "aws_proxy", + "httpMethod": "POST", + "uri": { + "Fn::Sub": "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunctionOne.Arn}/invocations" + } + }, + "responses": {}, + "security": [ + { + "api_key": [] + } + ] + } + } + }, + "securityDefinitions": { + "api_key": { + "type": "apiKey", + "name": "x-api-key", + "in": "header" + } + } + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + } + } + }, + "MyApiOneDeployment7997029260": { + "Type": "AWS::ApiGateway::Deployment", + "Condition": "C1", + "UpdateReplacePolicy": "Delete", + "Properties": { + "Description": "RestApi deployment id: 79970292604071da8105ffd8503f82af32b30550", + "RestApiId": { + "Ref": "MyApiOne" + }, + "StageName": "Stage" + } + }, + "MyApiOneProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Condition": "C1", + "UpdateReplacePolicy": "Delete", + "Properties": { + "DeploymentId": { + "Ref": "MyApiOneDeployment7997029260" + }, + "RestApiId": { + "Ref": "MyApiOne" + }, + "StageName": "Prod" + } + }, + "ServerlessUsagePlan": { + "Type": "AWS::ApiGateway::UsagePlan", + "DependsOn": [ + "MyApiOne", + "MyApiTwo", + "MyApiThree" + ], + "UpdateReplacePolicy": "Snapshot", + "Properties": { + "ApiStages": [ + { + "ApiId": { + "Ref": "MyApiOne" + }, + "Stage": { + "Ref": "MyApiOneProdStage" + } + }, + { + "ApiId": { + "Ref": "MyApiTwo" + }, + "Stage": { + "Ref": "MyApiTwoProdStage" + } + }, + { + "ApiId": { + "Ref": "MyApiThree" + }, + "Stage": { + "Ref": "MyApiThreeProdStage" + } + } + ] + } + }, + "ServerlessApiKey": { + "Type": "AWS::ApiGateway::ApiKey", + "DependsOn": [ + "ServerlessUsagePlan" + ], + "UpdateReplacePolicy": "Snapshot", + "Properties": { + "Enabled": true, + "StageKeys": [ + { + "RestApiId": { + "Ref": "MyApiOne" + }, + "StageName": { + "Ref": "MyApiOneProdStage" + } + }, + { + "RestApiId": { + "Ref": "MyApiTwo" + }, + "StageName": { + "Ref": "MyApiTwoProdStage" + } + }, + { + "RestApiId": { + "Ref": "MyApiThree" + }, + "StageName": { + "Ref": "MyApiThreeProdStage" + } + } + ] + } + }, + "ServerlessUsagePlanKey": { + "Type": "AWS::ApiGateway::UsagePlanKey", + "DependsOn": [ + "ServerlessApiKey" + ], + "UpdateReplacePolicy": "Snapshot", + "Properties": { + "KeyId": { + "Ref": "ServerlessApiKey" + }, + "KeyType": "API_KEY", + "UsagePlanId": { + "Ref": "ServerlessUsagePlan" + } + } + }, + "MyApiTwo": { + "Type": "AWS::ApiGateway::RestApi", + "Condition": "C2", + "UpdateReplacePolicy": "Snapshot", + "Properties": { + "Body": { + "swagger": "2.0", + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/path/two": { + "get": { + "x-amazon-apigateway-integration": { + "type": "aws_proxy", + "httpMethod": "POST", + "uri": { + "Fn::Sub": "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunctionTwo.Arn}/invocations" + } + }, + "responses": {}, + "security": [ + { + "api_key": [] + } + ] + } + } + }, + "securityDefinitions": { + "api_key": { + "type": "apiKey", + "name": "x-api-key", + "in": "header" + } + } + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + } + } + }, + "MyApiTwoDeployment03730b64c4": { + "Type": "AWS::ApiGateway::Deployment", + "Condition": "C2", + "UpdateReplacePolicy": "Snapshot", + "Properties": { + "Description": "RestApi deployment id: 03730b64c486cc490deefb3b8225244b0fe85d34", + "RestApiId": { + "Ref": "MyApiTwo" + }, + "StageName": "Stage" + } + }, + "MyApiTwoProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Condition": "C2", + "UpdateReplacePolicy": "Snapshot", + "Properties": { + "DeploymentId": { + "Ref": "MyApiTwoDeployment03730b64c4" + }, + "RestApiId": { + "Ref": "MyApiTwo" + }, + "StageName": "Prod" + } + }, + "MyApiThree": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "swagger": "2.0", + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/path/three": { + "get": { + "x-amazon-apigateway-integration": { + "type": "aws_proxy", + "httpMethod": "POST", + "uri": { + "Fn::Sub": "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunctionThree.Arn}/invocations" + } + }, + "responses": {}, + "security": [ + { + "api_key": [] + } + ] + } + } + }, + "securityDefinitions": { + "api_key": { + "type": "apiKey", + "name": "x-api-key", + "in": "header" + } + } + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + } + } + }, + "MyApiThreeDeploymentfa9f73f027": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "Description": "RestApi deployment id: fa9f73f0272017527c24cc93cc4440dd4476b9f4", + "RestApiId": { + "Ref": "MyApiThree" + }, + "StageName": "Stage" + } + }, + "MyApiThreeProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiThreeDeploymentfa9f73f027" + }, + "RestApiId": { + "Ref": "MyApiThree" + }, + "StageName": "Prod" + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-cn/api_with_usageplans_shared_attributes_two.json b/tests/translator/output/aws-cn/api_with_usageplans_shared_attributes_two.json new file mode 100644 index 000000000..bf555befa --- /dev/null +++ b/tests/translator/output/aws-cn/api_with_usageplans_shared_attributes_two.json @@ -0,0 +1,401 @@ +{ + "Conditions": { + "C1": { + "Fn::Equals": [ + "test", + "test" + ] + }, + "C2": { + "Fn::Equals": [ + "test", + "test" + ] + }, + "SharedUsagePlanCondition": { + "Fn::Or": [ + { + "Condition": "C1" + }, + { + "Condition": "C2" + } + ] + } + }, + "Resources": { + "MyFunctionOne": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = async (event) => {\n return {\n statusCode: 200,\n body: JSON.stringify(event),\n headers: {}\n }\n}\n" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyFunctionOneRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MyFunctionOneRole": { + "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" + } + ] + } + }, + "MyFunctionOneApiKeyPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Ref": "MyFunctionOne" + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/path/one", + { + "__ApiId__": { + "Ref": "MyApiOne" + }, + "__Stage__": "*" + } + ] + } + } + }, + "MyFunctionTwo": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = async (event) => {\n return {\n statusCode: 200,\n body: JSON.stringify(event),\n headers: {}\n }\n}\n" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyFunctionTwoRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MyFunctionTwoRole": { + "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" + } + ] + } + }, + "MyFunctionTwoApiKeyPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Ref": "MyFunctionTwo" + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/path/two", + { + "__ApiId__": { + "Ref": "MyApiTwo" + }, + "__Stage__": "*" + } + ] + } + } + }, + "MyApiOne": { + "Type": "AWS::ApiGateway::RestApi", + "DeletionPolicy": "Delete", + "Condition": "C1", + "Properties": { + "Body": { + "swagger": "2.0", + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/path/one": { + "get": { + "x-amazon-apigateway-integration": { + "type": "aws_proxy", + "httpMethod": "POST", + "uri": { + "Fn::Sub": "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunctionOne.Arn}/invocations" + } + }, + "responses": {}, + "security": [ + { + "api_key": [] + } + ] + } + } + }, + "securityDefinitions": { + "api_key": { + "type": "apiKey", + "name": "x-api-key", + "in": "header" + } + } + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + } + } + }, + "MyApiOneDeployment7997029260": { + "Type": "AWS::ApiGateway::Deployment", + "Condition": "C1", + "DeletionPolicy": "Delete", + "Properties": { + "Description": "RestApi deployment id: 79970292604071da8105ffd8503f82af32b30550", + "RestApiId": { + "Ref": "MyApiOne" + }, + "StageName": "Stage" + } + }, + "MyApiOneProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Condition": "C1", + "DeletionPolicy": "Delete", + "Properties": { + "DeploymentId": { + "Ref": "MyApiOneDeployment7997029260" + }, + "RestApiId": { + "Ref": "MyApiOne" + }, + "StageName": "Prod" + } + }, + "ServerlessUsagePlan": { + "Type": "AWS::ApiGateway::UsagePlan", + "DependsOn": [ + "MyApiOne", + "MyApiTwo" + ], + "Condition": "SharedUsagePlanCondition", + "DeletionPolicy": "Retain", + "Properties": { + "ApiStages": [ + { + "ApiId": { + "Ref": "MyApiOne" + }, + "Stage": { + "Ref": "MyApiOneProdStage" + } + }, + { + "ApiId": { + "Ref": "MyApiTwo" + }, + "Stage": { + "Ref": "MyApiTwoProdStage" + } + } + ] + } + }, + "ServerlessApiKey": { + "Type": "AWS::ApiGateway::ApiKey", + "DependsOn": [ + "ServerlessUsagePlan" + ], + "Condition": "SharedUsagePlanCondition", + "DeletionPolicy": "Retain", + "Properties": { + "Enabled": true, + "StageKeys": [ + { + "RestApiId": { + "Ref": "MyApiOne" + }, + "StageName": { + "Ref": "MyApiOneProdStage" + } + }, + { + "RestApiId": { + "Ref": "MyApiTwo" + }, + "StageName": { + "Ref": "MyApiTwoProdStage" + } + } + ] + } + }, + "ServerlessUsagePlanKey": { + "Type": "AWS::ApiGateway::UsagePlanKey", + "DependsOn": [ + "ServerlessApiKey" + ], + "Condition": "SharedUsagePlanCondition", + "DeletionPolicy": "Retain", + "Properties": { + "KeyId": { + "Ref": "ServerlessApiKey" + }, + "KeyType": "API_KEY", + "UsagePlanId": { + "Ref": "ServerlessUsagePlan" + } + } + }, + "MyApiTwo": { + "Type": "AWS::ApiGateway::RestApi", + "DeletionPolicy": "Retain", + "Condition": "C2", + "Properties": { + "Body": { + "swagger": "2.0", + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/path/two": { + "get": { + "x-amazon-apigateway-integration": { + "type": "aws_proxy", + "httpMethod": "POST", + "uri": { + "Fn::Sub": "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunctionTwo.Arn}/invocations" + } + }, + "responses": {}, + "security": [ + { + "api_key": [] + } + ] + } + } + }, + "securityDefinitions": { + "api_key": { + "type": "apiKey", + "name": "x-api-key", + "in": "header" + } + } + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + } + } + }, + "MyApiTwoDeployment03730b64c4": { + "Type": "AWS::ApiGateway::Deployment", + "Condition": "C2", + "DeletionPolicy": "Retain", + "Properties": { + "Description": "RestApi deployment id: 03730b64c486cc490deefb3b8225244b0fe85d34", + "RestApiId": { + "Ref": "MyApiTwo" + }, + "StageName": "Stage" + } + }, + "MyApiTwoProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Condition": "C2", + "DeletionPolicy": "Retain", + "Properties": { + "DeploymentId": { + "Ref": "MyApiTwoDeployment03730b64c4" + }, + "RestApiId": { + "Ref": "MyApiTwo" + }, + "StageName": "Prod" + } + } + } +} \ No newline at end of file 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/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_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/api_with_usageplans_shared_attributes_three.json b/tests/translator/output/aws-us-gov/api_with_usageplans_shared_attributes_three.json new file mode 100644 index 000000000..26b69c39b --- /dev/null +++ b/tests/translator/output/aws-us-gov/api_with_usageplans_shared_attributes_three.json @@ -0,0 +1,548 @@ +{ + "Conditions": { + "C1": { + "Fn::Equals": [ + "test", + "test" + ] + }, + "C2": { + "Fn::Equals": [ + "test", + "test" + ] + } + }, + "Resources": { + "MyFunctionOne": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = async (event) => {\n return {\n statusCode: 200,\n body: JSON.stringify(event),\n headers: {}\n }\n}\n" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyFunctionOneRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MyFunctionOneRole": { + "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" + } + ] + } + }, + "MyFunctionOneApiKeyPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Ref": "MyFunctionOne" + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/path/one", + { + "__ApiId__": { + "Ref": "MyApiOne" + }, + "__Stage__": "*" + } + ] + } + } + }, + "MyFunctionTwo": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = async (event) => {\n return {\n statusCode: 200,\n body: JSON.stringify(event),\n headers: {}\n }\n}\n" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyFunctionTwoRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MyFunctionTwoRole": { + "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" + } + ] + } + }, + "MyFunctionTwoApiKeyPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Ref": "MyFunctionTwo" + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/path/two", + { + "__ApiId__": { + "Ref": "MyApiTwo" + }, + "__Stage__": "*" + } + ] + } + } + }, + "MyFunctionThree": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = async (event) => {\n return {\n statusCode: 200,\n body: JSON.stringify(event),\n headers: {}\n }\n}\n" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyFunctionThreeRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MyFunctionThreeRole": { + "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" + } + ] + } + }, + "MyFunctionThreeApiKeyPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Ref": "MyFunctionThree" + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/path/three", + { + "__ApiId__": { + "Ref": "MyApiThree" + }, + "__Stage__": "*" + } + ] + } + } + }, + "MyApiOne": { + "Type": "AWS::ApiGateway::RestApi", + "Condition": "C1", + "UpdateReplacePolicy": "Delete", + "Properties": { + "Body": { + "swagger": "2.0", + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/path/one": { + "get": { + "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/${MyFunctionOne.Arn}/invocations" + } + }, + "responses": {}, + "security": [ + { + "api_key": [] + } + ] + } + } + }, + "securityDefinitions": { + "api_key": { + "type": "apiKey", + "name": "x-api-key", + "in": "header" + } + } + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + } + } + }, + "MyApiOneDeploymentdccbc5fda1": { + "Type": "AWS::ApiGateway::Deployment", + "Condition": "C1", + "UpdateReplacePolicy": "Delete", + "Properties": { + "Description": "RestApi deployment id: dccbc5fda163e1abe712073ffacdcc47776a5a09", + "RestApiId": { + "Ref": "MyApiOne" + }, + "StageName": "Stage" + } + }, + "MyApiOneProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Condition": "C1", + "UpdateReplacePolicy": "Delete", + "Properties": { + "DeploymentId": { + "Ref": "MyApiOneDeploymentdccbc5fda1" + }, + "RestApiId": { + "Ref": "MyApiOne" + }, + "StageName": "Prod" + } + }, + "ServerlessUsagePlan": { + "Type": "AWS::ApiGateway::UsagePlan", + "DependsOn": [ + "MyApiOne", + "MyApiTwo", + "MyApiThree" + ], + "UpdateReplacePolicy": "Snapshot", + "Properties": { + "ApiStages": [ + { + "ApiId": { + "Ref": "MyApiOne" + }, + "Stage": { + "Ref": "MyApiOneProdStage" + } + }, + { + "ApiId": { + "Ref": "MyApiTwo" + }, + "Stage": { + "Ref": "MyApiTwoProdStage" + } + }, + { + "ApiId": { + "Ref": "MyApiThree" + }, + "Stage": { + "Ref": "MyApiThreeProdStage" + } + } + ] + } + }, + "ServerlessApiKey": { + "Type": "AWS::ApiGateway::ApiKey", + "DependsOn": [ + "ServerlessUsagePlan" + ], + "UpdateReplacePolicy": "Snapshot", + "Properties": { + "Enabled": true, + "StageKeys": [ + { + "RestApiId": { + "Ref": "MyApiOne" + }, + "StageName": { + "Ref": "MyApiOneProdStage" + } + }, + { + "RestApiId": { + "Ref": "MyApiTwo" + }, + "StageName": { + "Ref": "MyApiTwoProdStage" + } + }, + { + "RestApiId": { + "Ref": "MyApiThree" + }, + "StageName": { + "Ref": "MyApiThreeProdStage" + } + } + ] + } + }, + "ServerlessUsagePlanKey": { + "Type": "AWS::ApiGateway::UsagePlanKey", + "DependsOn": [ + "ServerlessApiKey" + ], + "UpdateReplacePolicy": "Snapshot", + "Properties": { + "KeyId": { + "Ref": "ServerlessApiKey" + }, + "KeyType": "API_KEY", + "UsagePlanId": { + "Ref": "ServerlessUsagePlan" + } + } + }, + "MyApiTwo": { + "Type": "AWS::ApiGateway::RestApi", + "Condition": "C2", + "UpdateReplacePolicy": "Snapshot", + "Properties": { + "Body": { + "swagger": "2.0", + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/path/two": { + "get": { + "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/${MyFunctionTwo.Arn}/invocations" + } + }, + "responses": {}, + "security": [ + { + "api_key": [] + } + ] + } + } + }, + "securityDefinitions": { + "api_key": { + "type": "apiKey", + "name": "x-api-key", + "in": "header" + } + } + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + } + } + }, + "MyApiTwoDeployment0e45b81469": { + "Type": "AWS::ApiGateway::Deployment", + "Condition": "C2", + "UpdateReplacePolicy": "Snapshot", + "Properties": { + "Description": "RestApi deployment id: 0e45b814691166a59217a088512ee30710a12369", + "RestApiId": { + "Ref": "MyApiTwo" + }, + "StageName": "Stage" + } + }, + "MyApiTwoProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Condition": "C2", + "UpdateReplacePolicy": "Snapshot", + "Properties": { + "DeploymentId": { + "Ref": "MyApiTwoDeployment0e45b81469" + }, + "RestApiId": { + "Ref": "MyApiTwo" + }, + "StageName": "Prod" + } + }, + "MyApiThree": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "swagger": "2.0", + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/path/three": { + "get": { + "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/${MyFunctionThree.Arn}/invocations" + } + }, + "responses": {}, + "security": [ + { + "api_key": [] + } + ] + } + } + }, + "securityDefinitions": { + "api_key": { + "type": "apiKey", + "name": "x-api-key", + "in": "header" + } + } + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + } + } + }, + "MyApiThreeDeployment5206882d23": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "Description": "RestApi deployment id: 5206882d23d2cf7913f0fffea98644f959b433f2", + "RestApiId": { + "Ref": "MyApiThree" + }, + "StageName": "Stage" + } + }, + "MyApiThreeProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiThreeDeployment5206882d23" + }, + "RestApiId": { + "Ref": "MyApiThree" + }, + "StageName": "Prod" + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/api_with_usageplans_shared_attributes_two.json b/tests/translator/output/aws-us-gov/api_with_usageplans_shared_attributes_two.json new file mode 100644 index 000000000..346ef6a12 --- /dev/null +++ b/tests/translator/output/aws-us-gov/api_with_usageplans_shared_attributes_two.json @@ -0,0 +1,401 @@ +{ + "Conditions": { + "C1": { + "Fn::Equals": [ + "test", + "test" + ] + }, + "C2": { + "Fn::Equals": [ + "test", + "test" + ] + }, + "SharedUsagePlanCondition": { + "Fn::Or": [ + { + "Condition": "C1" + }, + { + "Condition": "C2" + } + ] + } + }, + "Resources": { + "MyFunctionOne": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = async (event) => {\n return {\n statusCode: 200,\n body: JSON.stringify(event),\n headers: {}\n }\n}\n" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyFunctionOneRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MyFunctionOneRole": { + "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" + } + ] + } + }, + "MyFunctionOneApiKeyPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Ref": "MyFunctionOne" + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/path/one", + { + "__ApiId__": { + "Ref": "MyApiOne" + }, + "__Stage__": "*" + } + ] + } + } + }, + "MyFunctionTwo": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = async (event) => {\n return {\n statusCode: 200,\n body: JSON.stringify(event),\n headers: {}\n }\n}\n" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyFunctionTwoRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MyFunctionTwoRole": { + "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" + } + ] + } + }, + "MyFunctionTwoApiKeyPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Ref": "MyFunctionTwo" + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/path/two", + { + "__ApiId__": { + "Ref": "MyApiTwo" + }, + "__Stage__": "*" + } + ] + } + } + }, + "MyApiOne": { + "Type": "AWS::ApiGateway::RestApi", + "DeletionPolicy": "Delete", + "Condition": "C1", + "Properties": { + "Body": { + "swagger": "2.0", + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/path/one": { + "get": { + "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/${MyFunctionOne.Arn}/invocations" + } + }, + "responses": {}, + "security": [ + { + "api_key": [] + } + ] + } + } + }, + "securityDefinitions": { + "api_key": { + "type": "apiKey", + "name": "x-api-key", + "in": "header" + } + } + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + } + } + }, + "MyApiOneDeploymentdccbc5fda1": { + "Type": "AWS::ApiGateway::Deployment", + "Condition": "C1", + "DeletionPolicy": "Delete", + "Properties": { + "Description": "RestApi deployment id: dccbc5fda163e1abe712073ffacdcc47776a5a09", + "RestApiId": { + "Ref": "MyApiOne" + }, + "StageName": "Stage" + } + }, + "MyApiOneProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Condition": "C1", + "DeletionPolicy": "Delete", + "Properties": { + "DeploymentId": { + "Ref": "MyApiOneDeploymentdccbc5fda1" + }, + "RestApiId": { + "Ref": "MyApiOne" + }, + "StageName": "Prod" + } + }, + "ServerlessUsagePlan": { + "Type": "AWS::ApiGateway::UsagePlan", + "DependsOn": [ + "MyApiOne", + "MyApiTwo" + ], + "Condition": "SharedUsagePlanCondition", + "DeletionPolicy": "Retain", + "Properties": { + "ApiStages": [ + { + "ApiId": { + "Ref": "MyApiOne" + }, + "Stage": { + "Ref": "MyApiOneProdStage" + } + }, + { + "ApiId": { + "Ref": "MyApiTwo" + }, + "Stage": { + "Ref": "MyApiTwoProdStage" + } + } + ] + } + }, + "ServerlessApiKey": { + "Type": "AWS::ApiGateway::ApiKey", + "DependsOn": [ + "ServerlessUsagePlan" + ], + "Condition": "SharedUsagePlanCondition", + "DeletionPolicy": "Retain", + "Properties": { + "Enabled": true, + "StageKeys": [ + { + "RestApiId": { + "Ref": "MyApiOne" + }, + "StageName": { + "Ref": "MyApiOneProdStage" + } + }, + { + "RestApiId": { + "Ref": "MyApiTwo" + }, + "StageName": { + "Ref": "MyApiTwoProdStage" + } + } + ] + } + }, + "ServerlessUsagePlanKey": { + "Type": "AWS::ApiGateway::UsagePlanKey", + "DependsOn": [ + "ServerlessApiKey" + ], + "Condition": "SharedUsagePlanCondition", + "DeletionPolicy": "Retain", + "Properties": { + "KeyId": { + "Ref": "ServerlessApiKey" + }, + "KeyType": "API_KEY", + "UsagePlanId": { + "Ref": "ServerlessUsagePlan" + } + } + }, + "MyApiTwo": { + "Type": "AWS::ApiGateway::RestApi", + "DeletionPolicy": "Retain", + "Condition": "C2", + "Properties": { + "Body": { + "swagger": "2.0", + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/path/two": { + "get": { + "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/${MyFunctionTwo.Arn}/invocations" + } + }, + "responses": {}, + "security": [ + { + "api_key": [] + } + ] + } + } + }, + "securityDefinitions": { + "api_key": { + "type": "apiKey", + "name": "x-api-key", + "in": "header" + } + } + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + } + } + }, + "MyApiTwoDeployment0e45b81469": { + "Type": "AWS::ApiGateway::Deployment", + "Condition": "C2", + "DeletionPolicy": "Retain", + "Properties": { + "Description": "RestApi deployment id: 0e45b814691166a59217a088512ee30710a12369", + "RestApiId": { + "Ref": "MyApiTwo" + }, + "StageName": "Stage" + } + }, + "MyApiTwoProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Condition": "C2", + "DeletionPolicy": "Retain", + "Properties": { + "DeploymentId": { + "Ref": "MyApiTwoDeployment0e45b81469" + }, + "RestApiId": { + "Ref": "MyApiTwo" + }, + "StageName": "Prod" + } + } + } +} \ No newline at end of file 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/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/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/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_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..3d93ab571 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", @@ -283,6 +400,8 @@ class TestTranslatorEndToEnd(TestCase): "function_with_event_dest_basic", "function_with_event_dest_conditional", "api_with_usageplans", + "api_with_usageplans_shared_attributes_two", + "api_with_usageplans_shared_attributes_three", "api_with_usageplans_intrinsics", "state_machine_with_inline_definition", "state_machine_with_tags", @@ -315,6 +434,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 +452,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 +538,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 +596,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( diff --git a/tests/unit/model/api/TestSharedApiUsagePlan.py b/tests/unit/model/api/TestSharedApiUsagePlan.py new file mode 100644 index 000000000..7c9e84699 --- /dev/null +++ b/tests/unit/model/api/TestSharedApiUsagePlan.py @@ -0,0 +1,119 @@ +from unittest import TestCase + +from parameterized import parameterized, param + +from samtranslator.model.api.api_generator import SharedApiUsagePlan +from samtranslator.model.exceptions import InvalidTemplateException + + +class TestSharedApiUsagePlan(TestCase): + def setUp(self): + self.shared_usage_plan = SharedApiUsagePlan() + + def test_values_should_be_propagated(self): + conditions = {} + actual_attributes = self.shared_usage_plan.get_combined_resource_attributes( + { + "Condition": "C1", + "DeletionPolicy": "Delete", + "UpdateReplacePolicy": "Delete", + }, + conditions, + ) + + self.assertEqual( + { + "Condition": SharedApiUsagePlan.SHARED_USAGE_PLAN_CONDITION_NAME, + "DeletionPolicy": "Delete", + "UpdateReplacePolicy": "Delete", + }, + actual_attributes, + ) + + self.assertEqual(conditions, {"SharedUsagePlanCondition": "C1"}) + + @parameterized.expand( + [ + ([None],), + ([None, "C1"],), + (["C1", None],), + (["C1", "C2"],), + (["C1", "C2", "C3"],), + ] + ) + def test_multiple_apis_with_conditions(self, api_conditions): + template_conditions = dict() + result = {} + for api_condition in api_conditions: + result = self.shared_usage_plan.get_combined_resource_attributes( + {"Condition": api_condition}, template_conditions + ) + print(f"Calling with {api_condition} result {result}") + + if None in api_conditions: + self.assertEqual({}, result) + self.assertEqual({}, template_conditions) + else: + print(template_conditions) + self.assertTrue("SharedUsagePlanCondition" in template_conditions) + self.assertEqual({"Condition": "SharedUsagePlanCondition"}, result) + combined_conditions = [ + condition.get("Condition") + for condition in template_conditions.get("SharedUsagePlanCondition", {}).get("Fn::Or") + ] + for combined_condition in combined_conditions: + self.assertTrue(combined_condition in api_conditions) + + def test_should_raise_invalid_template_when_no_conditions_section(self): + with self.assertRaises(InvalidTemplateException): + self.shared_usage_plan.get_combined_resource_attributes({"Condition": "C1"}, None) + + def test_deletion_policy_priority(self): + # first api sets it to delete + actual_attributes = self.shared_usage_plan.get_combined_resource_attributes({"DeletionPolicy": "Delete"}, {}) + self.assertEqual(actual_attributes["DeletionPolicy"], "Delete") + + # then second api sets it to Retain + actual_attributes = self.shared_usage_plan.get_combined_resource_attributes({"DeletionPolicy": "Retain"}, {}) + self.assertEqual(actual_attributes["DeletionPolicy"], "Retain") + + # if third api sets it to delete, it should keep retain value + actual_attributes = self.shared_usage_plan.get_combined_resource_attributes({"DeletionPolicy": "Delete"}, {}) + self.assertEqual(actual_attributes["DeletionPolicy"], "Retain") + + def test_update_replace_policy_priority(self): + # first api sets it to delete + actual_attributes = self.shared_usage_plan.get_combined_resource_attributes( + {"UpdateReplacePolicy": "Delete"}, {} + ) + self.assertEqual(actual_attributes["UpdateReplacePolicy"], "Delete") + + # then second api sets it to Retain + actual_attributes = self.shared_usage_plan.get_combined_resource_attributes( + {"UpdateReplacePolicy": "Snapshot"}, {} + ) + self.assertEqual(actual_attributes["UpdateReplacePolicy"], "Snapshot") + + # if third api sets it to delete, it should keep retain value + actual_attributes = self.shared_usage_plan.get_combined_resource_attributes( + {"UpdateReplacePolicy": "Delete"}, {} + ) + self.assertEqual(actual_attributes["UpdateReplacePolicy"], "Snapshot") + + # if third api sets it to delete, it should keep retain value + actual_attributes = self.shared_usage_plan.get_combined_resource_attributes( + {"UpdateReplacePolicy": "Retain"}, {} + ) + self.assertEqual(actual_attributes["UpdateReplacePolicy"], "Retain") + + # if third api sets it to delete, it should keep retain value + actual_attributes = self.shared_usage_plan.get_combined_resource_attributes( + {"UpdateReplacePolicy": "Snapshot"}, {} + ) + self.assertEqual(actual_attributes["UpdateReplacePolicy"], "Retain") + + # if third api sets it to delete, it should keep retain value + actual_attributes = self.shared_usage_plan.get_combined_resource_attributes( + {"UpdateReplacePolicy": "Delete"}, {} + ) + self.assertEqual(actual_attributes["UpdateReplacePolicy"], "Retain")