diff --git a/HOWTO.md b/HOWTO.md index 15b58d4d8..aca0e2e4a 100644 --- a/HOWTO.md +++ b/HOWTO.md @@ -14,9 +14,15 @@ resources for you. The remainder of this document explains how to write SAM templates and deploy them via AWS CloudFormation. -## Writing SAM Template +## Getting started with the SAM Template Check out the [latest specification](versions/2016-10-31.md) for details on how to write a SAM template + +You could also use the [aws-sam-cli](https://github.com/awslabs/aws-sam-cli) to get started + +```shell +$ sam init --runtime python3.7 +``` ## Packing Artifacts Before you can deploy a SAM template, you should first upload your Lambda function code zip and API's OpenAPI File to S3. Set the `CodeUri` and @@ -47,9 +53,22 @@ packaged template that can be readily deployed to CloudFormation. $ aws cloudformation package \ --template-file /path_to_template/template.yaml \ --s3-bucket bucket-name \ + --s3-prefix appname/branchname/version + --output-template-file packaged-template.yaml +``` + +Or using the aws-sam-cli + +```bash +$ sam package \ + --template-file /path_to_template/template.yaml \ + --s3-bucket bucket-name \ + --s3-prefix appname/branchname/version --output-template-file packaged-template.yaml ``` + + The packaged template will look something like this: ```YAML MyLambdaFunction: @@ -80,7 +99,16 @@ $ aws cloudformation deploy \ --capabilities CAPABILITY_IAM ``` -Refer to the [documentation](http://docs.aws.amazon.com/cli/latest/reference/cloudformation/deploy/index.html) for more details. +Or using aws-sam-cli + +```bash +$ sam deploy \ + --template-file /path_to_template/packaged-template.yaml \ + --stack-name my-new-stack + --capabilities CAPABILITY_IAM +``` + +Refer to the [cloudformation documentation](http://docs.aws.amazon.com/cli/latest/reference/cloudformation/deploy/index.html) and [samcli](https://github.com/awslabs/aws-sam-cli) for more details. ## Using Intrinsic Functions CloudFormation provides handy functions that you can use to generate values at runtime. These are called [Intrinsic Functions](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference.html). Since SAM is deployed using CloudFormation, you can use these intrinsic functions within SAM as well. Here are some examples: diff --git a/bin/sam-translate.py b/bin/sam-translate.py index 4b0df0973..2483bd4e4 100755 --- a/bin/sam-translate.py +++ b/bin/sam-translate.py @@ -5,42 +5,91 @@ Known limitations: cannot transform CodeUri pointing at local directory. Usage: - sam-translate.py --input-file=sam-template.yaml [--output-file=] + sam-translate.py --template-file=sam-template.yaml [--verbose] [--output-template=] + sam-translate.py package --template-file=sam-template.yaml --s3-bucket=my-bucket [--verbose] [--output-template=] + sam-translate.py deploy --template-file=sam-template.yaml --s3-bucket=my-bucket --capabilities=CAPABILITY_NAMED_IAM --stack-name=my-stack [--verbose] [--output-template=] Options: - --input-file= Location of SAM template to transform. - --output-file= Location to store resulting CloudFormation template [default: cfn-template.json]. + --template-file= Location of SAM template to transform [default: template.yaml]. + --output-template= Location to store resulting CloudFormation template [default: transformed-template.json]. + --s3-bucket= S3 bucket to use for SAM artifacts when using the `package` command + --capabilities= Capabilities + --stack-name= Unique name for your CloudFormation Stack + --verbose Enables verbose logging """ import json +import logging import os +import platform +import subprocess +import sys import boto3 from docopt import docopt +my_path = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, my_path + '/..') + from samtranslator.public.translator import ManagedPolicyLoader from samtranslator.translator.transform import transform from samtranslator.yaml_helper import yaml_parse from samtranslator.model.exceptions import InvalidDocumentException - +LOG = logging.getLogger(__name__) cli_options = docopt(__doc__) iam_client = boto3.client('iam') cwd = os.getcwd() +if cli_options.get('--verbose'): + logging.basicConfig(level=logging.DEBUG) +else: + logging.basicConfig() + +def execute_command(command, args): + try: + aws_cmd = 'aws' if platform.system().lower() != 'windows' else 'aws.cmd' + command_with_args = [aws_cmd, 'cloudformation', command] + list(args) + + LOG.debug("Executing command: %s", command_with_args) + + subprocess.check_call(command_with_args) + + LOG.debug("Command successful") + except subprocess.CalledProcessError as e: + # Underlying aws command will print the exception to the user + LOG.debug("Exception: %s", e) + sys.exit(e.returncode) + def get_input_output_file_paths(): - input_file_option = cli_options.get('--input-file') - output_file_option = cli_options.get('--output-file') + input_file_option = cli_options.get('--template-file') + output_file_option = cli_options.get('--output-template') input_file_path = os.path.join(cwd, input_file_option) output_file_path = os.path.join(cwd, output_file_option) return input_file_path, output_file_path -def main(): - input_file_path, output_file_path = get_input_output_file_paths() +def package(input_file_path, output_file_path): + template_file = input_file_path + package_output_template_file = input_file_path + '._sam_packaged_.yaml' + s3_bucket = cli_options.get('--s3-bucket') + args = [ + '--template-file', + template_file, + '--output-template-file', + package_output_template_file, + '--s3-bucket', + s3_bucket + ] + execute_command('package', args) + + return package_output_template_file + + +def transform_template(input_file_path, output_file_path): with open(input_file_path, 'r') as f: sam_template = yaml_parse(f) @@ -56,10 +105,37 @@ def main(): print('Wrote transformed CloudFormation template to: ' + output_file_path) except InvalidDocumentException as e: errorMessage = reduce(lambda message, error: message + ' ' + error.message, e.causes, e.message) - print(errorMessage) + LOG.error(errorMessage) errors = map(lambda cause: cause.message, e.causes) - print(errors) + LOG.error(errors) + + +def deploy(template_file): + capabilities = cli_options.get('--capabilities') + stack_name = cli_options.get('--stack-name') + args = [ + '--template-file', + template_file, + '--capabilities', + capabilities, + '--stack-name', + stack_name + ] + + execute_command('deploy', args) + + return package_output_template_file if __name__ == '__main__': - main() + input_file_path, output_file_path = get_input_output_file_paths() + + if cli_options.get('package'): + package_output_template_file = package(input_file_path, output_file_path) + transform_template(package_output_template_file, output_file_path) + elif cli_options.get('deploy'): + package_output_template_file = package(input_file_path, output_file_path) + transform_template(package_output_template_file, output_file_path) + deploy(output_file_path) + else: + transform_template(input_file_path, output_file_path) diff --git a/docs/globals.rst b/docs/globals.rst index 05f473be9..07ab036ee 100644 --- a/docs/globals.rst +++ b/docs/globals.rst @@ -68,6 +68,7 @@ Currently, the following resources and properties are being supported: AutoPublishAlias: DeploymentPreference: PermissionsBoundary: + ReservedConcurrentExecutions: Api: # Properties of AWS::Serverless::Api @@ -82,6 +83,7 @@ Currently, the following resources and properties are being supported: BinaryMediaTypes: MinimumCompressionSize: Cors: + GatewayResponses: AccessLogSetting: CanarySetting: TracingEnabled: diff --git a/docs/safe_lambda_deployments.rst b/docs/safe_lambda_deployments.rst index 73d70f86e..c6c4b1454 100644 --- a/docs/safe_lambda_deployments.rst +++ b/docs/safe_lambda_deployments.rst @@ -156,7 +156,7 @@ resource: Action: - "codedeploy:PutLifecycleEventHookExecutionStatus" Resource: - !Sub 'arn:aws:codedeploy:${AWS::Region}:${AWS::AccountId}:deploymentgroup:${ServerlessDeploymentApplication}/*' + !Sub 'arn:${AWS::Partition}:codedeploy:${AWS::Region}:${AWS::AccountId}:deploymentgroup:${ServerlessDeploymentApplication}/*' - Version: "2012-10-17" Statement: - Effect: "Allow" diff --git a/examples/2016-10-31/api_aws_iam_auth/index.js b/examples/2016-10-31/api_aws_iam_auth/index.js new file mode 100644 index 000000000..472369c6c --- /dev/null +++ b/examples/2016-10-31/api_aws_iam_auth/index.js @@ -0,0 +1,7 @@ +exports.handler = async (event) => { + return { + statusCode: 200, + body: JSON.stringify(event), + headers: {} + } +} diff --git a/examples/2016-10-31/api_aws_iam_auth/template.yaml b/examples/2016-10-31/api_aws_iam_auth/template.yaml new file mode 100644 index 000000000..935e45c71 --- /dev/null +++ b/examples/2016-10-31/api_aws_iam_auth/template.yaml @@ -0,0 +1,30 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: API Gateway with AWS IAM Authorizer +Resources: + MyApi: + Type: AWS::Serverless::Api + Properties: + StageName: Prod + Auth: + DefaultAuthorizer: AWS_IAM + InvokeRole: CALLER_CREDENTIALS + + MyFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: . + Handler: index.handler + Runtime: nodejs8.10 + Events: + GetRoot: + Type: Api + Properties: + RestApiId: !Ref MyApi + Path: / + Method: get + +Outputs: + ApiURL: + Description: "API URL" + Value: !Sub 'https://${MyApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/' diff --git a/examples/2016-10-31/api_cognito_auth/template.yaml b/examples/2016-10-31/api_cognito_auth/template.yaml index 60e4badd1..b1dc1dacc 100644 --- a/examples/2016-10-31/api_cognito_auth/template.yaml +++ b/examples/2016-10-31/api_cognito_auth/template.yaml @@ -131,7 +131,7 @@ Resources: Action: lambda:InvokeFunction FunctionName: !GetAtt PreSignupLambdaFunction.Arn Principal: cognito-idp.amazonaws.com - SourceArn: !Sub 'arn:aws:cognito-idp:${AWS::Region}:${AWS::AccountId}:userpool/${MyCognitoUserPool}' + SourceArn: !Sub 'arn:${AWS::Partition}:cognito-idp:${AWS::Region}:${AWS::AccountId}:userpool/${MyCognitoUserPool}' # TODO: Add a CognitoUserPool Event Source to SAM to create this permission for you. # Events: # CognitoUserPoolPreSignup: diff --git a/examples/2016-10-31/api_gateway_responses/template.yaml b/examples/2016-10-31/api_gateway_responses/template.yaml new file mode 100644 index 000000000..6b0bafcdb --- /dev/null +++ b/examples/2016-10-31/api_gateway_responses/template.yaml @@ -0,0 +1,33 @@ +AWSTemplateFormatVersion: "2010-09-09" +Transform: AWS::Serverless-2016-10-31 +Description: Simple webservice deomnstrating gateway responses. + +Resources: + MyApi: + Type: AWS::Serverless::Api + Properties: + StageName: Prod + GatewayResponses: + DEFAULT_4xx: + ResponseParameters: + Headers: + Access-Control-Expose-Headers: "'WWW-Authenticate'" + Access-Control-Allow-Origin: "'*'" + + GetFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.get + Runtime: nodejs6.10 + InlineCode: module.exports = async () => throw new Error('Check out the response headers!') + Events: + GetResource: + Type: Api + Properties: + Path: /error + Method: get + RestApiId: !Ref MyApi +Outputs: + ApiURL: + Description: "API endpoint URL for Prod environment" + Value: !Sub "https://${MyApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/error/" diff --git a/examples/2016-10-31/inline_swagger/template.yaml b/examples/2016-10-31/inline_swagger/template.yaml index aeba9d4ad..3339eafee 100644 --- a/examples/2016-10-31/inline_swagger/template.yaml +++ b/examples/2016-10-31/inline_swagger/template.yaml @@ -18,7 +18,7 @@ Resources: httpMethod: POST type: aws_proxy uri: - Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyLambdaFunction.Arn}/invocations + Fn::Sub: arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyLambdaFunction.Arn}/invocations responses: {} diff --git a/examples/2016-10-31/lambda_safe_deployments/template.yaml b/examples/2016-10-31/lambda_safe_deployments/template.yaml index ebb0e44ab..586824c28 100644 --- a/examples/2016-10-31/lambda_safe_deployments/template.yaml +++ b/examples/2016-10-31/lambda_safe_deployments/template.yaml @@ -28,7 +28,7 @@ Resources: Action: - "codedeploy:PutLifecycleEventHookExecutionStatus" Resource: - !Sub 'arn:aws:codedeploy:${AWS::Region}:${AWS::AccountId}:deploymentgroup:${ServerlessDeploymentApplication}/*' + !Sub 'arn:${AWS::Partition}:codedeploy:${AWS::Region}:${AWS::AccountId}:deploymentgroup:${ServerlessDeploymentApplication}/*' - Version: "2012-10-17" Statement: - Effect: "Allow" diff --git a/samtranslator/__init__.py b/samtranslator/__init__.py index 52af183e5..da77e85c6 100644 --- a/samtranslator/__init__.py +++ b/samtranslator/__init__.py @@ -1 +1 @@ -__version__ = '1.10.0' +__version__ = '1.11.0' diff --git a/samtranslator/intrinsics/actions.py b/samtranslator/intrinsics/actions.py index 8803dddbe..d535305ed 100644 --- a/samtranslator/intrinsics/actions.py +++ b/samtranslator/intrinsics/actions.py @@ -1,6 +1,7 @@ import re from six import string_types +from samtranslator.model.exceptions import InvalidTemplateException, InvalidDocumentException class Action(object): @@ -371,7 +372,7 @@ def handler_method(full_ref, ref_value): """ # RegExp to find pattern "${logicalId.property}" and return the word inside bracket - logical_id_regex = '[A-Za-z0-9\.]+' + logical_id_regex = '[A-Za-z0-9\.]+|AWS::[A-Z][A-Za-z]*' ref_pattern = re.compile(r'\$\{(' + logical_id_regex + ')\}') # Find all the pattern, and call the handler to decide how to substitute them. @@ -426,6 +427,11 @@ def resolve_resource_refs(self, input_dict, supported_resource_refs): if not isinstance(value, list) or len(value) < 2: return input_dict + if (not all(isinstance(entry, string_types) for entry in value)): + raise InvalidDocumentException( + [InvalidTemplateException('Invalid GetAtt value {}. GetAtt expects an array with 2 strings.' + .format(value))]) + # Value of GetAtt is an array. It can contain any number of elements, with first being the LogicalId of # resource and rest being the attributes. In a SAM template, a reference to a resource can be used in the # first parameter. However tools like AWS CLI might break them down as well. So let's just concatenate @@ -503,3 +509,47 @@ def _get_resolved_dictionary(self, input_dict, key, resolved_value, remaining): input_dict[key] = [resolved_value] + remaining return input_dict + + +class FindInMapAction(Action): + """ + This action can't be used along with other actions. + """ + intrinsic_name = "Fn::FindInMap" + + def resolve_parameter_refs(self, input_dict, parameters): + """ + Recursively resolves "Fn::FindInMap"references that are present in the mappings and returns the value. + If it is not in mappings, this method simply returns the input unchanged. + + :param input_dict: Dictionary representing the FindInMap function. Must contain only one key and it + should be "Fn::FindInMap". + + :param parameters: Dictionary of mappings from the SAM template + """ + if not self.can_handle(input_dict): + return input_dict + + value = input_dict[self.intrinsic_name] + + # FindInMap expects an array with 3 values + if not isinstance(value, list) or len(value) != 3: + raise InvalidDocumentException( + [InvalidTemplateException('Invalid FindInMap value {}. FindInMap expects an array with 3 values.' + .format(value))]) + + map_name = self.resolve_parameter_refs(value[0], parameters) + top_level_key = self.resolve_parameter_refs(value[1], parameters) + second_level_key = self.resolve_parameter_refs(value[2], parameters) + + if not isinstance(map_name, string_types) or \ + not isinstance(top_level_key, string_types) or \ + not isinstance(second_level_key, string_types): + return input_dict + + if map_name not in parameters or \ + top_level_key not in parameters[map_name] or \ + second_level_key not in parameters[map_name][top_level_key]: + return input_dict + + return parameters[map_name][top_level_key][second_level_key] diff --git a/samtranslator/model/__init__.py b/samtranslator/model/__init__.py index c93efcf77..0cb7c2a09 100644 --- a/samtranslator/model/__init__.py +++ b/samtranslator/model/__init__.py @@ -474,8 +474,8 @@ def resolve_resource_type(self, resource_dict): :rtype: class """ if not self.can_resolve(resource_dict): - raise TypeError("Resource dict has missing or invalid value for key Type. Resource Dict is: " + - str(resource_dict)) + raise TypeError("Resource dict has missing or invalid value for key Type. Event Type is: {}.".format( + resource_dict.get('Type'))) if resource_dict['Type'] not in self.resource_types: raise TypeError("Invalid resource type {resource_type}".format(resource_type=resource_dict['Type'])) return self.resource_types[resource_dict['Type']] diff --git a/samtranslator/model/api/api_generator.py b/samtranslator/model/api/api_generator.py index 9392f52b4..4946f4d92 100644 --- a/samtranslator/model/api/api_generator.py +++ b/samtranslator/model/api/api_generator.py @@ -3,7 +3,8 @@ from samtranslator.model.intrinsics import ref from samtranslator.model.apigateway import (ApiGatewayDeployment, ApiGatewayRestApi, - ApiGatewayStage, ApiGatewayAuthorizer) + ApiGatewayStage, ApiGatewayAuthorizer, + ApiGatewayResponse) from samtranslator.model.exceptions import InvalidResourceException from samtranslator.model.s3_utils.uri_parser import parse_s3_uri from samtranslator.region_configuration import RegionConfiguration @@ -18,8 +19,10 @@ # Default the Cors Properties to '*' wildcard and False AllowCredentials. Other properties are actually Optional CorsProperties.__new__.__defaults__ = (None, None, _CORS_WILDCARD, None, False) -AuthProperties = namedtuple("_AuthProperties", ["Authorizers", "DefaultAuthorizer"]) -AuthProperties.__new__.__defaults__ = (None, None) +AuthProperties = namedtuple("_AuthProperties", ["Authorizers", "DefaultAuthorizer", "InvokeRole"]) +AuthProperties.__new__.__defaults__ = (None, None, None) + +GatewayResponseProperties = ["ResponseParameters", "ResponseTemplates", "StatusCode"] class ApiGenerator(object): @@ -27,8 +30,8 @@ class ApiGenerator(object): def __init__(self, logical_id, cache_cluster_enabled, cache_cluster_size, variables, depends_on, definition_body, definition_uri, name, stage_name, endpoint_configuration=None, method_settings=None, binary_media=None, minimum_compression_size=None, cors=None, - auth=None, access_log_setting=None, canary_setting=None, tracing_enabled=None, - resource_attributes=None, passthrough_resource_attributes=None): + auth=None, gateway_responses=None, access_log_setting=None, canary_setting=None, + tracing_enabled=None, resource_attributes=None, passthrough_resource_attributes=None): """Constructs an API Generator class that generates API Gateway resources :param logical_id: Logical id of the SAM API Resource @@ -61,6 +64,7 @@ def __init__(self, logical_id, cache_cluster_enabled, cache_cluster_size, variab self.minimum_compression_size = minimum_compression_size self.cors = cors self.auth = auth + self.gateway_responses = gateway_responses self.access_log_setting = access_log_setting self.canary_setting = canary_setting self.tracing_enabled = tracing_enabled @@ -91,6 +95,7 @@ def _construct_rest_api(self): self._add_cors() self._add_auth() + self._add_gateway_responses() if self.definition_uri: rest_api.BodyS3Location = self._construct_body_s3_dict() @@ -266,7 +271,7 @@ def _add_auth(self): "'DefinitionBody' does not contain a valid Swagger") swagger_editor = SwaggerEditor(self.definition_body) auth_properties = AuthProperties(**self.auth) - authorizers = self._get_authorizers(auth_properties.Authorizers) + authorizers = self._get_authorizers(auth_properties.Authorizers, auth_properties.DefaultAuthorizer) if authorizers: swagger_editor.add_authorizers(authorizers) @@ -275,16 +280,72 @@ def _add_auth(self): # Assign the Swagger back to template self.definition_body = swagger_editor.swagger - def _get_authorizers(self, authorizers_config): + def _add_gateway_responses(self): + """ + Add Gateway Response configuration to the Swagger file, if necessary + """ + + if not self.gateway_responses: + return + + if self.gateway_responses and not self.definition_body: + raise InvalidResourceException( + self.logical_id, "GatewayResponses works only with inline Swagger specified in " + "'DefinitionBody' property") + + # Make sure keys in the dict are recognized + for responses_key, responses_value in self.gateway_responses.items(): + for response_key in responses_value.keys(): + if response_key not in GatewayResponseProperties: + raise InvalidResourceException( + self.logical_id, + "Invalid property '{}' in 'GatewayResponses' property '{}'".format(response_key, responses_key)) + + if not SwaggerEditor.is_valid(self.definition_body): + raise InvalidResourceException( + self.logical_id, "Unable to add Auth configuration because " + "'DefinitionBody' does not contain a valid Swagger") + + swagger_editor = SwaggerEditor(self.definition_body) + + gateway_responses = {} + for response_type, response in self.gateway_responses.items(): + gateway_responses[response_type] = ApiGatewayResponse( + api_logical_id=self.logical_id, + response_parameters=response.get('ResponseParameters', {}), + response_templates=response.get('ResponseTemplates', {}), + status_code=response.get('StatusCode', None) + ) + + if gateway_responses: + swagger_editor.add_gateway_responses(gateway_responses) + + # Assign the Swagger back to template + self.definition_body = swagger_editor.swagger + + def _get_authorizers(self, authorizers_config, default_authorizer=None): + authorizers = {} + if default_authorizer == 'AWS_IAM': + authorizers[default_authorizer] = ApiGatewayAuthorizer( + api_logical_id=self.logical_id, + name=default_authorizer, + is_aws_iam_authorizer=True + ) + if not authorizers_config: + if 'AWS_IAM' in authorizers: + return authorizers return None if not isinstance(authorizers_config, dict): raise InvalidResourceException(self.logical_id, "Authorizers must be a dictionary") - authorizers = {} for authorizer_name, authorizer in authorizers_config.items(): + if not isinstance(authorizer, dict): + raise InvalidResourceException(self.logical_id, + "Authorizer %s must be a dictionary." % (authorizer_name)) + authorizers[authorizer_name] = ApiGatewayAuthorizer( api_logical_id=self.logical_id, name=authorizer_name, @@ -294,7 +355,6 @@ def _get_authorizers(self, authorizers_config): function_payload_type=authorizer.get('FunctionPayloadType'), function_invoke_role=authorizer.get('FunctionInvokeRole') ) - return authorizers def _get_permission(self, authorizer_name, authorizer_lambda_function_arn): @@ -346,7 +406,7 @@ def _set_default_authorizer(self, swagger_editor, authorizers, default_authorize if not default_authorizer: return - if not authorizers.get(default_authorizer): + if not authorizers.get(default_authorizer) and default_authorizer != 'AWS_IAM': raise InvalidResourceException(self.logical_id, "Unable to set DefaultAuthorizer because '" + default_authorizer + "' was not defined in 'Authorizers'") diff --git a/samtranslator/model/apigateway.py b/samtranslator/model/apigateway.py index 0ce73950f..c59983e19 100644 --- a/samtranslator/model/apigateway.py +++ b/samtranslator/model/apigateway.py @@ -1,3 +1,5 @@ +from re import match + from samtranslator.model import PropertyType, Resource from samtranslator.model.exceptions import InvalidResourceException from samtranslator.model.types import is_type, one_of, is_str @@ -93,11 +95,60 @@ def make_auto_deployable(self, stage, swagger=None): stage.update_deployment_ref(self.logical_id) +class ApiGatewayResponse(object): + ResponseParameterProperties = ["Headers", "Paths", "QueryStrings"] + + def __init__(self, api_logical_id=None, response_parameters=None, response_templates=None, status_code=None): + if response_parameters: + for response_parameter_key in response_parameters.keys(): + if response_parameter_key not in ApiGatewayResponse.ResponseParameterProperties: + raise InvalidResourceException( + api_logical_id, + "Invalid gateway response parameter '{}'".format(response_parameter_key)) + + status_code_str = self._status_code_string(status_code) + # status_code must look like a status code, if present. Let's not be judgmental; just check 0-999. + if status_code and not match(r'^[0-9]{1,3}$', status_code_str): + raise InvalidResourceException(api_logical_id, "Property 'StatusCode' must be numeric") + + self.api_logical_id = api_logical_id + self.response_parameters = response_parameters or {} + self.response_templates = response_templates or {} + self.status_code = status_code_str + + def generate_swagger(self): + swagger = { + "responseParameters": self._add_prefixes(self.response_parameters), + "responseTemplates": self.response_templates + } + + # Prevent "null" being written. + if self.status_code: + swagger["statusCode"] = self.status_code + + return swagger + + def _add_prefixes(self, response_parameters): + GATEWAY_RESPONSE_PREFIX = 'gatewayresponse.' + prefixed_parameters = {} + for key, value in response_parameters.get('Headers', {}).items(): + prefixed_parameters[GATEWAY_RESPONSE_PREFIX + 'header.' + key] = value + for key, value in response_parameters.get('Paths', {}).items(): + prefixed_parameters[GATEWAY_RESPONSE_PREFIX + 'path.' + key] = value + for key, value in response_parameters.get('QueryStrings', {}).items(): + prefixed_parameters[GATEWAY_RESPONSE_PREFIX + 'querystring.' + key] = value + + return prefixed_parameters + + def _status_code_string(self, status_code): + return None if status_code is None else str(status_code) + + class ApiGatewayAuthorizer(object): _VALID_FUNCTION_PAYLOAD_TYPES = [None, 'TOKEN', 'REQUEST'] def __init__(self, api_logical_id=None, name=None, user_pool_arn=None, function_arn=None, identity=None, - function_payload_type=None, function_invoke_role=None): + function_payload_type=None, function_invoke_role=None, is_aws_iam_authorizer=False): if function_payload_type not in ApiGatewayAuthorizer._VALID_FUNCTION_PAYLOAD_TYPES: raise InvalidResourceException(api_logical_id, name + " Authorizer has invalid " "'FunctionPayloadType': " + function_payload_type) @@ -113,6 +164,7 @@ def __init__(self, api_logical_id=None, name=None, user_pool_arn=None, function_ self.identity = identity self.function_payload_type = function_payload_type self.function_invoke_role = function_invoke_role + self.is_aws_iam_authorizer = is_aws_iam_authorizer def _is_missing_identity_source(self, identity): if not identity: @@ -135,16 +187,19 @@ def generate_swagger(self): "type": "apiKey", "name": self._get_swagger_header_name(), "in": "header", - "x-amazon-apigateway-authtype": self._get_swagger_authtype(), - "x-amazon-apigateway-authorizer": { - "type": self._get_swagger_authorizer_type() - } + "x-amazon-apigateway-authtype": self._get_swagger_authtype() } if authorizer_type == 'COGNITO_USER_POOLS': - swagger[APIGATEWAY_AUTHORIZER_KEY]['providerARNs'] = self._get_user_pool_arn_array() + swagger[APIGATEWAY_AUTHORIZER_KEY] = { + 'type': self._get_swagger_authorizer_type(), + 'providerARNs': self._get_user_pool_arn_array() + } elif authorizer_type == 'LAMBDA': + swagger[APIGATEWAY_AUTHORIZER_KEY] = { + 'type': self._get_swagger_authorizer_type() + } partition = ArnGenerator.get_partition_name() resource = 'lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations' authorizer_uri = fnSub(ArnGenerator.generate_arn(partition=partition, service='apigateway', @@ -217,6 +272,9 @@ def _get_swagger_header_name(self): return self._get_identity_header() def _get_type(self): + if self.is_aws_iam_authorizer: + return 'AWS_IAM' + if self.user_pool_arn: return 'COGNITO_USER_POOLS' @@ -242,8 +300,13 @@ def _get_function_invoke_role(self): def _get_swagger_authtype(self): authorizer_type = self._get_type() + if authorizer_type == 'AWS_IAM': + return 'awsSigv4' + + if authorizer_type == 'COGNITO_USER_POOLS': + return 'cognito_user_pools' - return 'cognito_user_pools' if authorizer_type == 'COGNITO_USER_POOLS' else 'custom' + return 'custom' def _get_function_payload_type(self): return 'TOKEN' if not self.function_payload_type else self.function_payload_type diff --git a/samtranslator/model/eventsources/push.py b/samtranslator/model/eventsources/push.py index b53a2e13c..d07cf3c78 100644 --- a/samtranslator/model/eventsources/push.py +++ b/samtranslator/model/eventsources/push.py @@ -12,7 +12,7 @@ from samtranslator.model.events import EventsRule from samtranslator.model.iot import IotTopicRule from samtranslator.translator.arn_generator import ArnGenerator -from samtranslator.model.exceptions import InvalidEventException +from samtranslator.model.exceptions import InvalidEventException, InvalidResourceException from samtranslator.swagger.swagger import SwaggerEditor CONDITION = 'Condition' @@ -419,6 +419,8 @@ def resources_to_link(self, resources): # Stage could be a intrinsic, in which case leave the suffix to default value if isinstance(permitted_stage, string_types): + if not permitted_stage: + raise InvalidResourceException(rest_api_id, 'StageName cannot be empty.') stage_suffix = permitted_stage else: stage_suffix = "Stage" @@ -529,7 +531,7 @@ def _add_swagger_integration(self, api, function): if CONDITION in function.resource_attributes: condition = function.resource_attributes[CONDITION] - editor.add_lambda_integration(self.Path, self.Method, uri, condition=condition) + editor.add_lambda_integration(self.Path, self.Method, uri, self.Auth, api.get('Auth'), condition=condition) if self.Auth: method_authorizer = self.Auth.get('Authorizer') @@ -538,28 +540,29 @@ def _add_swagger_integration(self, api, function): api_auth = api.get('Auth') api_authorizers = api_auth and api_auth.get('Authorizers') - if not api_authorizers: - raise InvalidEventException( - self.relative_id, - 'Unable to set Authorizer [{authorizer}] on API method [{method}] for path [{path}] because ' - 'the related API does not define any Authorizers.'.format( - authorizer=method_authorizer, method=self.Method, path=self.Path)) - - if method_authorizer != 'NONE' and not api_authorizers.get(method_authorizer): - raise InvalidEventException( - self.relative_id, - 'Unable to set Authorizer [{authorizer}] on API method [{method}] for path [{path}] because it ' - 'wasn\'t defined in the API\'s Authorizers.'.format( - authorizer=method_authorizer, method=self.Method, path=self.Path)) - - if method_authorizer == 'NONE' and not api_auth.get('DefaultAuthorizer'): - raise InvalidEventException( - self.relative_id, - 'Unable to set Authorizer on API method [{method}] for path [{path}] because \'NONE\' ' - 'is only a valid value when a DefaultAuthorizer on the API is specified.'.format( - method=self.Method, path=self.Path)) - - editor.add_auth_to_method(api=api, path=self.Path, method_name=self.Method, auth=self.Auth) + if method_authorizer != 'AWS_IAM': + if not api_authorizers: + raise InvalidEventException( + self.relative_id, + 'Unable to set Authorizer [{authorizer}] on API method [{method}] for path [{path}] ' + 'because the related API does not define any Authorizers.'.format( + authorizer=method_authorizer, method=self.Method, path=self.Path)) + + if method_authorizer != 'NONE' and not api_authorizers.get(method_authorizer): + raise InvalidEventException( + self.relative_id, + 'Unable to set Authorizer [{authorizer}] on API method [{method}] for path [{path}] ' + 'because it wasn\'t defined in the API\'s Authorizers.'.format( + authorizer=method_authorizer, method=self.Method, path=self.Path)) + + if method_authorizer == 'NONE' and not api_auth.get('DefaultAuthorizer'): + raise InvalidEventException( + self.relative_id, + 'Unable to set Authorizer on API method [{method}] for path [{path}] because \'NONE\' ' + 'is only a valid value when a DefaultAuthorizer on the API is specified.'.format( + method=self.Method, path=self.Path)) + + editor.add_auth_to_method(api=api, path=self.Path, method_name=self.Method, auth=self.Auth) api["DefinitionBody"] = editor.swagger diff --git a/samtranslator/model/sam_resources.py b/samtranslator/model/sam_resources.py index 325f71ed9..3e6ae61c2 100644 --- a/samtranslator/model/sam_resources.py +++ b/samtranslator/model/sam_resources.py @@ -1,4 +1,4 @@ -""" SAM macro definitions """ +""" SAM macro definitions """ from six import string_types import samtranslator.model.eventsources @@ -53,7 +53,8 @@ class SamFunction(SamResourceMacro): 'Layers': PropertyType(False, list_of(one_of(is_str(), is_type(dict)))), # Intrinsic functions in value of Alias property are not supported, yet - 'AutoPublishAlias': PropertyType(False, one_of(is_str())) + 'AutoPublishAlias': PropertyType(False, one_of(is_str())), + 'VersionDescription': PropertyType(False, is_str()) } event_resolver = ResourceTypeResolver(samtranslator.model.eventsources, samtranslator.model.eventsources.pull, samtranslator.model.eventsources.push, @@ -264,8 +265,11 @@ def _event_resources_to_link(self, resources): event_resources = {} if self.Events: for logical_id, event_dict in self.Events.items(): - event_source = self.event_resolver.resolve_resource_type(event_dict).from_dict( - self.logical_id + logical_id, event_dict, logical_id) + try: + event_source = self.event_resolver.resolve_resource_type(event_dict).from_dict( + self.logical_id + logical_id, event_dict, logical_id) + except TypeError as e: + raise InvalidEventException(logical_id, "{}".format(e)) event_resources[logical_id] = event_source.resources_to_link(resources) return event_resources @@ -286,8 +290,11 @@ def _generate_event_resources(self, lambda_function, execution_role, event_resou resources = [] if self.Events: for logical_id, event_dict in self.Events.items(): - eventsource = self.event_resolver.resolve_resource_type(event_dict).from_dict( - lambda_function.logical_id + logical_id, event_dict, logical_id) + try: + eventsource = self.event_resolver.resolve_resource_type(event_dict).from_dict( + lambda_function.logical_id + logical_id, event_dict, logical_id) + except TypeError as e: + raise InvalidEventException(logical_id, "{}".format(e)) kwargs = { # When Alias is provided, connect all event sources to the alias and *not* the function @@ -359,6 +366,7 @@ def _construct_version(self, function, intrinsics_resolver): lambda_version = LambdaVersion(logical_id=logical_id, attributes=attributes) lambda_version.FunctionName = function.get_runtime_attr('name') + lambda_version.Description = self.VersionDescription return lambda_version @@ -373,7 +381,7 @@ def _construct_alias(self, name, function, version): """ if not name: - raise ValueError("Alias name is required to create an alias") + raise InvalidResourceException(self.logical_id, "Alias name is required to create an alias") logical_id = "{id}Alias{suffix}".format(id=function.logical_id, suffix=name) alias = LambdaAlias(logical_id=logical_id, attributes=self.get_passthrough_resource_attributes()) @@ -434,6 +442,7 @@ class SamApi(SamResourceMacro): 'MinimumCompressionSize': PropertyType(False, is_type(int)), 'Cors': PropertyType(False, one_of(is_str(), is_type(dict))), 'Auth': PropertyType(False, is_type(dict)), + 'GatewayResponses': PropertyType(False, is_type(dict)), 'AccessLogSetting': PropertyType(False, is_type(dict)), 'CanarySetting': PropertyType(False, is_type(dict)), 'TracingEnabled': PropertyType(False, is_type(bool)) @@ -469,6 +478,7 @@ def to_cloudformation(self, **kwargs): minimum_compression_size=self.MinimumCompressionSize, cors=self.Cors, auth=self.Auth, + gateway_responses=self.GatewayResponses, access_log_setting=self.AccessLogSetting, canary_setting=self.CanarySetting, tracing_enabled=self.TracingEnabled, diff --git a/samtranslator/parser/parser.py b/samtranslator/parser/parser.py index 52f594dc7..2672ae645 100644 --- a/samtranslator/parser/parser.py +++ b/samtranslator/parser/parser.py @@ -1,6 +1,7 @@ -from samtranslator.model.exceptions import InvalidDocumentException, InvalidTemplateException +from samtranslator.model.exceptions import InvalidDocumentException, InvalidTemplateException, InvalidResourceException from samtranslator.validator.validator import SamTemplateValidator from samtranslator.plugins import LifeCycleEvents +from samtranslator.public.sdk.template import SamTemplate class Parser: @@ -26,4 +27,24 @@ def _validate(self, sam_template, parameter_values): raise InvalidDocumentException( [InvalidTemplateException("'Resources' section is required")]) + if (not all(isinstance(sam_resource, dict) for sam_resource in sam_template["Resources"].values())): + raise InvalidDocumentException( + [InvalidTemplateException( + "All 'Resources' must be Objects. If you're using YAML, this may be an " + "indentation issue." + )]) + + sam_template_instance = SamTemplate(sam_template) + + for resource_logical_id, sam_resource in sam_template_instance.iterate(): + # NOTE: Properties isn't required for SimpleTable, so we can't check + # `not isinstance(sam_resources.get("Properties"), dict)` as this would be a breaking change. + # sam_resource.properties defaults to {} in SamTemplate init + if (not isinstance(sam_resource.properties, dict)): + raise InvalidDocumentException( + [InvalidResourceException(resource_logical_id, + "All 'Resources' must be Objects and have a 'Properties' Object. If " + "you're using YAML, this may be an indentation issue." + )]) + SamTemplateValidator.validate(sam_template) diff --git a/samtranslator/plugins/api/implicit_api_plugin.py b/samtranslator/plugins/api/implicit_api_plugin.py index a3e71d961..194ac6a16 100644 --- a/samtranslator/plugins/api/implicit_api_plugin.py +++ b/samtranslator/plugins/api/implicit_api_plugin.py @@ -1,4 +1,5 @@ import copy +import six from samtranslator.model.naming import GeneratedLogicalId from samtranslator.model.intrinsics import make_combined_condition @@ -136,8 +137,19 @@ def _process_api_events(self, function, api_events, template, condition=None): self._add_implicit_api_id_if_necessary(event_properties) api_id = self._get_api_id(event_properties) - path = event_properties["Path"] - method = event_properties["Method"] + try: + path = event_properties["Path"] + method = event_properties["Method"] + except KeyError as e: + raise InvalidEventException(logicalId, "Event is missing key {}.".format(e)) + + if (not isinstance(path, six.string_types)): + raise InvalidEventException(logicalId, + "Api Event must have a String specified for 'Path'.") + if (not isinstance(method, six.string_types)): + raise InvalidEventException(logicalId, + "Api Event must have a String specified for 'Method'.") + api_dict = self.api_conditions.setdefault(api_id, {}) method_conditions = api_dict.setdefault(path, {}) method_conditions[method] = condition diff --git a/samtranslator/plugins/application/serverless_app_plugin.py b/samtranslator/plugins/application/serverless_app_plugin.py index 420aa72f7..ac419d9ba 100644 --- a/samtranslator/plugins/application/serverless_app_plugin.py +++ b/samtranslator/plugins/application/serverless_app_plugin.py @@ -1,13 +1,17 @@ import boto3 +import json from botocore.exceptions import ClientError, EndpointConnectionError import logging from time import sleep, time +import copy from samtranslator.model.exceptions import InvalidResourceException from samtranslator.plugins import BasePlugin from samtranslator.plugins.exceptions import InvalidPluginException from samtranslator.public.sdk.resource import SamResourceType from samtranslator.public.sdk.template import SamTemplate +from samtranslator.intrinsics.resolver import IntrinsicsResolver +from samtranslator.intrinsics.actions import FindInMapAction class ServerlessAppPlugin(BasePlugin): @@ -35,7 +39,7 @@ class ServerlessAppPlugin(BasePlugin): LOCATION_KEY = 'Location' TEMPLATE_URL_KEY = 'TemplateUrl' - def __init__(self, sar_client=None, wait_for_template_active_status=False, validate_only=False): + def __init__(self, sar_client=None, wait_for_template_active_status=False, validate_only=False, parameters={}): """ Initialize the plugin. @@ -50,6 +54,7 @@ def __init__(self, sar_client=None, wait_for_template_active_status=False, valid self._sar_client = sar_client self._wait_for_template_active_status = wait_for_template_active_status self._validate_only = validate_only + self._parameters = parameters # make sure the flag combination makes sense if self._validate_only is True and self._wait_for_template_active_status is True: @@ -68,6 +73,7 @@ def on_before_transform_template(self, template_dict): :return: Nothing """ template = SamTemplate(template_dict) + intrinsic_resolvers = self._get_intrinsic_resolvers(template_dict.get('Mappings', {})) service_call = None if self._validate_only: @@ -79,9 +85,19 @@ def on_before_transform_template(self, template_dict): # Handle these cases in the on_before_transform_resource event continue - app_id = app.properties[self.LOCATION_KEY].get(self.APPLICATION_ID_KEY) - semver = app.properties[self.LOCATION_KEY].get(self.SEMANTIC_VERSION_KEY) + app_id = self._replace_value(app.properties[self.LOCATION_KEY], + self.APPLICATION_ID_KEY, intrinsic_resolvers) + + semver = self._replace_value(app.properties[self.LOCATION_KEY], + self.SEMANTIC_VERSION_KEY, intrinsic_resolvers) + + if isinstance(app_id, dict) or isinstance(semver, dict): + key = (json.dumps(app_id), json.dumps(semver)) + self._applications[key] = False + continue + key = (app_id, semver) + if key not in self._applications: try: # Lazy initialization of the client- create it when it is needed @@ -92,6 +108,21 @@ def on_before_transform_template(self, template_dict): # Catch all InvalidResourceExceptions, raise those in the before_resource_transform target. self._applications[key] = e + def _replace_value(self, input_dict, key, intrinsic_resolvers): + value = self._resolve_location_value(input_dict.get(key), intrinsic_resolvers) + input_dict[key] = value + return value + + def _get_intrinsic_resolvers(self, mappings): + return [IntrinsicsResolver(self._parameters), + IntrinsicsResolver(mappings, {FindInMapAction.intrinsic_name: FindInMapAction()})] + + def _resolve_location_value(self, value, intrinsic_resolvers): + resolved_value = copy.deepcopy(value) + for intrinsic_resolver in intrinsic_resolvers: + resolved_value = intrinsic_resolver.resolve_parameter_refs(resolved_value) + return resolved_value + def _can_process_application(self, app): """ Determines whether or not the on_before_transform_template event can process this application @@ -188,11 +219,23 @@ def on_before_transform_resource(self, logical_id, resource_type, resource_prope [self.APPLICATION_ID_KEY, self.SEMANTIC_VERSION_KEY]) app_id = resource_properties[self.LOCATION_KEY].get(self.APPLICATION_ID_KEY) + if not app_id: raise InvalidResourceException(logical_id, "Property 'ApplicationId' cannot be blank.") + + if isinstance(app_id, dict): + raise InvalidResourceException(logical_id, "Property 'ApplicationId' cannot be resolved. Only FindInMap " + "and Ref intrinsic functions are supported.") + semver = resource_properties[self.LOCATION_KEY].get(self.SEMANTIC_VERSION_KEY) + if not semver: - raise InvalidResourceException(logical_id, "Property 'SemanticVersion cannot be blank.") + raise InvalidResourceException(logical_id, "Property 'SemanticVersion' cannot be blank.") + + if isinstance(semver, dict): + raise InvalidResourceException(logical_id, "Property 'SemanticVersion' cannot be resolved. Only FindInMap " + "and Ref intrinsic functions are supported.") + key = (app_id, semver) # Throw any resource exceptions saved from the before_transform_template event diff --git a/samtranslator/plugins/globals/globals.py b/samtranslator/plugins/globals/globals.py index b4cab7d7e..f74029b99 100644 --- a/samtranslator/plugins/globals/globals.py +++ b/samtranslator/plugins/globals/globals.py @@ -1,4 +1,4 @@ -from samtranslator.public.sdk.resource import SamResourceType +from samtranslator.public.sdk.resource import SamResourceType from samtranslator.public.intrinsics import is_intrinsics @@ -30,7 +30,8 @@ class Globals(object): "AutoPublishAlias", "Layers", "DeploymentPreference", - "PermissionsBoundary" + "PermissionsBoundary", + "ReservedConcurrentExecutions" ], # Everything except @@ -49,6 +50,7 @@ class Globals(object): "BinaryMediaTypes", "MinimumCompressionSize", "Cors", + "GatewayResponses", "AccessLogSetting", "CanarySetting", "TracingEnabled" diff --git a/samtranslator/policy_templates_data/policy_templates.json b/samtranslator/policy_templates_data/policy_templates.json index e841772dc..10e833d86 100644 --- a/samtranslator/policy_templates_data/policy_templates.json +++ b/samtranslator/policy_templates_data/policy_templates.json @@ -261,7 +261,7 @@ ], "Resource": { "Fn::Sub": [ - "arn:${AWS::Partition}:es:${AWS::Region}:${AWS::AccountId}:domain/${domainName}", + "arn:${AWS::Partition}:es:${AWS::Region}:${AWS::AccountId}:domain/${domainName}/*", { "domainName": { "Ref": "DomainName" @@ -927,9 +927,11 @@ "Action": [ "serverlessrepo:CreateApplication", "serverlessrepo:CreateApplicationVersion", + "serverlessrepo:UpdateApplication", "serverlessrepo:GetApplication", "serverlessrepo:ListApplications", - "serverlessrepo:ListApplicationVersions" + "serverlessrepo:ListApplicationVersions", + "serverlessrepo:ListApplicationDependencies" ], "Resource": [ { diff --git a/samtranslator/public/sdk/parameter.py b/samtranslator/public/sdk/parameter.py new file mode 100644 index 000000000..442e90b40 --- /dev/null +++ b/samtranslator/public/sdk/parameter.py @@ -0,0 +1,3 @@ +# flake8: noqa + +from samtranslator.sdk.parameter import SamParameterValues \ No newline at end of file diff --git a/samtranslator/sdk/parameter.py b/samtranslator/sdk/parameter.py new file mode 100644 index 000000000..350e46331 --- /dev/null +++ b/samtranslator/sdk/parameter.py @@ -0,0 +1,67 @@ +import boto3 +import copy + + +class SamParameterValues(object): + """ + Class representing SAM parameter values. + """ + + def __init__(self, parameter_values): + """ + Initialize the object given the parameter values as a dictionary + + :param dict parameter_values: Parameter value dictionary containing parameter name & value + """ + + self.parameter_values = copy.deepcopy(parameter_values) + + def add_default_parameter_values(self, sam_template): + """ + Method to read default values for template parameters and merge with user supplied values. + + Example: + If the template contains the following parameters defined + + Parameters: + Param1: + Type: String + Default: default_value + Param2: + Type: String + Default: default_value + + And, the user explicitly provided the following parameter values: + + { + Param2: "new value" + } + + then, this method will grab default value for Param1 and return the following result: + + { + Param1: "default_value", + Param2: "new value" + } + + + :param dict sam_template: SAM template + :param dict parameter_values: Dictionary of parameter values provided by the user + :return dict: Merged parameter values + """ + + parameter_definition = sam_template.get("Parameters", None) + if not parameter_definition or not isinstance(parameter_definition, dict): + return self.parameter_values + + for param_name, value in parameter_definition.items(): + if param_name not in self.parameter_values and isinstance(value, dict) and "Default" in value: + self.parameter_values[param_name] = value["Default"] + + def add_pseudo_parameter_values(self): + """ + Add pseudo parameter values + :return: parameter values that have pseudo parameter in it + """ + if 'AWS::Region' not in self.parameter_values: + self.parameter_values['AWS::Region'] = boto3.session.Session().region_name diff --git a/samtranslator/swagger/swagger.py b/samtranslator/swagger/swagger.py index d69040c8d..f1803f33d 100644 --- a/samtranslator/swagger/swagger.py +++ b/samtranslator/swagger/swagger.py @@ -1,8 +1,9 @@ -import copy +import copy from six import string_types from samtranslator.model.intrinsics import ref from samtranslator.model.intrinsics import make_conditional +from samtranslator.model.exceptions import InvalidDocumentException, InvalidTemplateException class SwaggerEditor(object): @@ -16,6 +17,7 @@ class SwaggerEditor(object): _OPTIONS_METHOD = "options" _X_APIGW_INTEGRATION = 'x-amazon-apigateway-integration' _CONDITIONAL_IF = "Fn::If" + _X_APIGW_GATEWAY_RESPONSES = 'x-amazon-apigateway-gateway-responses' _X_ANY_METHOD = 'x-amazon-apigateway-any-method' def __init__(self, doc): @@ -33,6 +35,7 @@ def __init__(self, doc): self._doc = copy.deepcopy(doc) self.paths = self._doc["paths"] self.security_definitions = self._doc.get("securityDefinitions", {}) + self.gateway_responses = self._doc.get(self._X_APIGW_GATEWAY_RESPONSES, {}) def get_path(self, path): path_dict = self.paths.get(path) @@ -122,14 +125,17 @@ def add_path(self, path, method=None): if not isinstance(path_dict, dict): # Either customers has provided us an invalid Swagger, or this class has messed it somehow - raise ValueError("Value of '{}' path must be a dictionary according to Swagger spec".format(path)) + raise InvalidDocumentException( + [InvalidTemplateException("Value of '{}' path must be a dictionary according to Swagger spec." + .format(path))]) if self._CONDITIONAL_IF in path_dict: path_dict = path_dict[self._CONDITIONAL_IF][1] path_dict.setdefault(method, {}) - def add_lambda_integration(self, path, method, integration_uri, condition=None): + def add_lambda_integration(self, path, method, integration_uri, + method_auth_config=None, api_auth_config=None, condition=None): """ Adds aws_proxy APIGW integration to the given path+method. @@ -156,6 +162,15 @@ def add_lambda_integration(self, path, method, integration_uri, condition=None): 'uri': integration_uri } + method_auth_config = method_auth_config or {} + api_auth_config = api_auth_config or {} + if method_auth_config.get('Authorizer') == 'AWS_IAM' \ + or api_auth_config.get('DefaultAuthorizer') == 'AWS_IAM' and not method_auth_config: + self.paths[path][method][self._X_APIGW_INTEGRATION]['credentials'] = self._generate_integration_credentials( + method_invoke_role=method_auth_config.get('InvokeRole'), + api_invoke_role=api_auth_config.get('InvokeRole') + ) + # If 'responses' key is *not* present, add it with an empty dict as value path_dict[method].setdefault('responses', {}) @@ -169,6 +184,13 @@ def make_path_conditional(self, path, condition): """ self.paths[path] = make_conditional(condition, self.paths[path]) + def _generate_integration_credentials(self, method_invoke_role=None, api_invoke_role=None): + return self._get_invoke_role(method_invoke_role or api_invoke_role) + + def _get_invoke_role(self, invoke_role): + CALLER_CREDENTIALS_ARN = 'arn:aws:iam::*:user/*' + return invoke_role if invoke_role and invoke_role != 'CALLER_CREDENTIALS' else CALLER_CREDENTIALS_ARN + def iter_on_path(self): """ Yields all the paths available in the Swagger. As a caller, if you add new paths to Swagger while iterating, @@ -369,8 +391,8 @@ def add_authorizers(self, authorizers): """ self.security_definitions = self.security_definitions or {} - for authorizerName, authorizer in authorizers.items(): - self.security_definitions[authorizerName] = authorizer.generate_swagger() + for authorizer_name, authorizer in authorizers.items(): + self.security_definitions[authorizer_name] = authorizer.generate_swagger() def set_path_default_authorizer(self, path, default_authorizer, authorizers): """ @@ -409,7 +431,6 @@ def add_auth_to_method(self, path, method_name, auth, api): def set_method_authorizer(self, path, method_name, authorizer_name, authorizers, default_authorizer, is_default=False): normalized_method_name = self._normalize_method_name(method_name) - # It is possible that the method could have two definitions in a Fn::If block. for method_definition in self.get_method_contents(self.get_path(path)[normalized_method_name]): @@ -418,7 +439,10 @@ def set_method_authorizer(self, path, method_name, authorizer_name, authorizers, continue existing_security = method_definition.get('security', []) # TEST: [{'sigv4': []}, {'api_key': []}]) - authorizer_names = set(authorizers.keys()) + authorizer_list = ['AWS_IAM'] + if authorizers: + authorizer_list.extend(authorizers.keys()) + authorizer_names = set(authorizer_list) existing_non_authorizer_security = [] existing_authorizer_security = [] @@ -473,6 +497,33 @@ def set_method_authorizer(self, path, method_name, authorizer_name, authorizers, if security: method_definition['security'] = security + # The first element of the method_definition['security'] should be AWS_IAM + # because authorizer_list = ['AWS_IAM'] is hardcoded above + if 'AWS_IAM' in method_definition['security'][0]: + aws_iam_security_definition = { + 'AWS_IAM': { + 'x-amazon-apigateway-authtype': 'awsSigv4', + 'type': 'apiKey', + 'name': 'Authorization', + 'in': 'header' + } + } + if not self.security_definitions: + self.security_definitions = aws_iam_security_definition + elif 'AWS_IAM' not in self.security_definitions: + self.security_definitions.update(aws_iam_security_definition) + + def add_gateway_responses(self, gateway_responses): + """ + Add Gateway Response definitions to Swagger. + + :param dict gateway_responses: Dictionary of GatewayResponse configuration which gets translated. + """ + self.gateway_responses = self.gateway_responses or {} + + for response_type, response in gateway_responses.items(): + self.gateway_responses[response_type] = response.generate_swagger() + @property def swagger(self): """ @@ -486,6 +537,8 @@ def swagger(self): if self.security_definitions: self._doc["securityDefinitions"] = self.security_definitions + if self.gateway_responses: + self._doc[self._X_APIGW_GATEWAY_RESPONSES] = self.gateway_responses return copy.deepcopy(self._doc) diff --git a/samtranslator/translator/translator.py b/samtranslator/translator/translator.py index 49cf2e746..a0043ebf1 100644 --- a/samtranslator/translator/translator.py +++ b/samtranslator/translator/translator.py @@ -14,6 +14,7 @@ from samtranslator.plugins.globals.globals_plugin import GlobalsPlugin from samtranslator.plugins.policies.policy_templates_plugin import PolicyTemplatesForFunctionPlugin from samtranslator.policy_template_processor.processor import PolicyTemplatesProcessor +from samtranslator.sdk.parameter import SamParameterValues class Translator: @@ -45,9 +46,12 @@ def translate(self, sam_template, parameter_values): :returns: a copy of the template with SAM resources replaced with the corresponding CloudFormation, which may \ be dumped into a valid CloudFormation JSON or YAML template """ + sam_parameter_values = SamParameterValues(parameter_values) + sam_parameter_values.add_default_parameter_values(sam_template) + sam_parameter_values.add_pseudo_parameter_values() + parameter_values = sam_parameter_values.parameter_values # Create & Install plugins - sam_plugins = prepare_plugins(self.plugins) - parameter_values = self._add_default_parameter_values(sam_template, parameter_values) + sam_plugins = prepare_plugins(self.plugins, parameter_values) self.sam_parser.parse( sam_template=sam_template, @@ -155,63 +159,14 @@ def _get_resources_to_iterate(self, sam_template, macro_resolver): return functions + apis + others - # Ideally this should belong to a separate class called "Parameters" or something that knows how to manage - # parameters. An instance of this class should be passed as input to the Translate class. - def _add_default_parameter_values(self, sam_template, parameter_values): - """ - Method to read default values for template parameters and merge with user supplied values. - - Example: - If the template contains the following parameters defined - - Parameters: - Param1: - Type: String - Default: default_value - Param2: - Type: String - Default: default_value - - And, the user explicitly provided the following parameter values: - - { - Param2: "new value" - } - - then, this method will grab default value for Param1 and return the following result: - - { - Param1: "default_value", - Param2: "new value" - } - - - :param dict sam_template: SAM template - :param dict parameter_values: Dictionary of parameter values provided by the user - :return dict: Merged parameter values - """ - - parameter_definition = sam_template.get("Parameters", None) - if not parameter_definition or not isinstance(parameter_definition, dict): - return parameter_values - - default_values = {} - for param_name, value in parameter_definition.items(): - if isinstance(value, dict) and "Default" in value: - default_values[param_name] = value["Default"] - - # Any explicitly provided value must override the default - default_values.update(parameter_values) - - return default_values - -def prepare_plugins(plugins): +def prepare_plugins(plugins, parameters={}): """ Creates & returns a plugins object with the given list of plugins installed. In addition to the given plugins, we will also install a few "required" plugins that are necessary to provide complete support for SAM template spec. - :param list of samtranslator.plugins.BasePlugin plugins: List of plugins to install + :param plugins: list of samtranslator.plugins.BasePlugin plugins: List of plugins to install + :param parameters: Dictionary of parameter values :return samtranslator.plugins.SamPlugins: Instance of `SamPlugins` """ @@ -226,7 +181,7 @@ def prepare_plugins(plugins): # If a ServerlessAppPlugin does not yet exist, create one and add to the beginning of the required plugins list. if not any(isinstance(plugin, ServerlessAppPlugin) for plugin in plugins): - required_plugins.insert(0, ServerlessAppPlugin()) + required_plugins.insert(0, ServerlessAppPlugin(parameters=parameters)) # Execute customer's plugins first before running SAM plugins. It is very important to retain this order because # other plugins will be dependent on this ordering. diff --git a/tests/intrinsics/test_actions.py b/tests/intrinsics/test_actions.py index ed843d3ac..81dad2abe 100644 --- a/tests/intrinsics/test_actions.py +++ b/tests/intrinsics/test_actions.py @@ -1,7 +1,8 @@ from unittest import TestCase from mock import patch, Mock -from samtranslator.intrinsics.actions import Action, RefAction, SubAction, GetAttAction +from samtranslator.intrinsics.actions import Action, RefAction, SubAction, GetAttAction, FindInMapAction from samtranslator.intrinsics.resource_refs import SupportedResourceReferences +from samtranslator.model.exceptions import InvalidTemplateException, InvalidDocumentException class TestAction(TestCase): @@ -419,6 +420,23 @@ def test_sub_all_refs_with_dict_input(self): self.assertEqual(expected, result) + def test_sub_all_refs_with_pseudo_parameters(self): + parameters = { + "key1": "value1", + "AWS::Region": "ap-southeast-1" + } + input = { + "Fn::Sub": "hello ${AWS::Region} ${key1}" + } + expected = { + "Fn::Sub": "hello ap-southeast-1 value1" + } + + sub = SubAction() + result = sub.resolve_parameter_refs(input, parameters) + + self.assertEqual(expected, result) + class TestSubInternalMethods(TestCase): @patch.object(SubAction, "_sub_all_refs") @@ -915,3 +933,201 @@ def test_return_value_if_cannot_handle(self, can_handle_mock): getatt = GetAttAction() can_handle_mock.return_value = False # Simulate failure to handle the input. Result should be same as input self.assertEqual(expected, getatt.resolve_resource_id_refs(input, self.supported_resource_id_refs)) + + +class TestFindInMapCanResolveParameterRefs(TestCase): + + def setUp(self): + self.ref = FindInMapAction() + + @patch.object(FindInMapAction, "can_handle") + def test_cannot_handle(self, can_handle_mock): + input = { + "Fn::FindInMap": ["a", "b", "c"] + } + can_handle_mock.return_value = False + output = self.ref.resolve_parameter_refs(input, {}) + + self.assertEqual(input, output) + + def test_value_not_list(self): + input = { + "Fn::FindInMap": "a string" + } + with self.assertRaises(InvalidDocumentException): + self.ref.resolve_parameter_refs(input, {}) + + def test_value_not_list_of_length_three(self): + input = { + "Fn::FindInMap": ["a string"] + } + + with self.assertRaises(InvalidDocumentException): + self.ref.resolve_parameter_refs(input, {}) + + def test_mapping_not_string(self): + mappings = { + "MapA":{ + "TopKey1": { + "SecondKey2": "value3" + }, + "TopKey2": { + "SecondKey1": "value4" + } + } + } + input = { + "Fn::FindInMap": [["MapA"], "TopKey2", "SecondKey1"] + } + output = self.ref.resolve_parameter_refs(input, mappings) + + self.assertEqual(input, output) + + def test_top_level_key_not_string(self): + mappings = { + "MapA":{ + "TopKey1": { + "SecondKey2": "value3" + }, + "TopKey2": { + "SecondKey1": "value4" + } + } + } + input = { + "Fn::FindInMap": ["MapA", ["TopKey2"], "SecondKey1"] + } + output = self.ref.resolve_parameter_refs(input, mappings) + + self.assertEqual(input, output) + + def test_second_level_key_not_string(self): + mappings = { + "MapA":{ + "TopKey1": { + "SecondKey2": "value3" + }, + "TopKey2": { + "SecondKey1": "value4" + } + } + } + input = { + "Fn::FindInMap": ["MapA", "TopKey1", ["SecondKey2"]] + } + output = self.ref.resolve_parameter_refs(input, mappings) + + self.assertEqual(input, output) + + def test_mapping_not_found(self): + mappings = { + "MapA":{ + "TopKey1": { + "SecondKey2": "value3" + }, + "TopKey2": { + "SecondKey1": "value4" + } + } + } + input = { + "Fn::FindInMap": ["MapB", "TopKey2", "SecondKey1"] + } + output = self.ref.resolve_parameter_refs(input, mappings) + + self.assertEqual(input, output) + + def test_top_level_key_not_found(self): + mappings = { + "MapA":{ + "TopKey1": { + "SecondKey2": "value3" + }, + "TopKey2": { + "SecondKey1": "value4" + } + } + } + input = { + "Fn::FindInMap": ["MapA", "TopKey3", "SecondKey1"] + } + output = self.ref.resolve_parameter_refs(input, mappings) + + self.assertEqual(input, output) + + def test_second_level_key_not_found(self): + mappings = { + "MapA":{ + "TopKey1": { + "SecondKey2": "value3" + }, + "TopKey2": { + "SecondKey1": "value4" + } + } + } + input = { + "Fn::FindInMap": ["MapA", "TopKey1", "SecondKey1"] + } + output = self.ref.resolve_parameter_refs(input, mappings) + + self.assertEqual(input, output) + + def test_one_level_find_in_mappings(self): + mappings = { + "MapA":{ + "TopKey1": { + "SecondKey2": "value3" + }, + "TopKey2": { + "SecondKey1": "value4" + } + } + } + input = { + "Fn::FindInMap": ["MapA", "TopKey2", "SecondKey1"] + } + expected = "value4" + output = self.ref.resolve_parameter_refs(input, mappings) + + self.assertEqual(expected, output) + + def test_nested_find_in_mappings(self): + mappings = { + "MapA":{ + "TopKey1": { + "SecondKey2": "value3" + }, + "TopKey2": { + "SecondKey1": "TopKey1" + } + } + } + input = { + "Fn::FindInMap": ["MapA", {"Fn::FindInMap": ["MapA", "TopKey2", "SecondKey1"]}, "SecondKey2"] + } + expected = "value3" + output = self.ref.resolve_parameter_refs(input, mappings) + + self.assertEqual(expected, output) + + def test_nested_find_in_multiple_mappings(self): + mappings = { + "MapA":{ + "ATopKey1": { + "ASecondKey2": "value3" + } + }, + "MapB": { + "BTopKey1": { + "BSecondKey2": "ATopKey1" + } + } + } + input = { + "Fn::FindInMap": ["MapA", {"Fn::FindInMap": ["MapB", "BTopKey1", "BSecondKey2"]}, "ASecondKey2"] + } + expected = "value3" + output = self.ref.resolve_parameter_refs(input, mappings) + + self.assertEqual(expected, output) diff --git a/tests/model/test_sam_resources.py b/tests/model/test_sam_resources.py index 6641885b9..73cfdd263 100644 --- a/tests/model/test_sam_resources.py +++ b/tests/model/test_sam_resources.py @@ -4,7 +4,7 @@ from samtranslator.intrinsics.resolver import IntrinsicsResolver from samtranslator.model import InvalidResourceException -from samtranslator.model.lambda_ import LambdaFunction +from samtranslator.model.lambda_ import LambdaFunction, LambdaVersion from samtranslator.model.sam_resources import SamFunction @@ -22,6 +22,7 @@ def test_with_code_uri(self): function = SamFunction("foo") function.CodeUri = "s3://foobar/foo.zip" + cfnResources = function.to_cloudformation(**self.kwargs) generatedFunctionList = [x for x in cfnResources if isinstance(x, LambdaFunction)] self.assertEqual(generatedFunctionList.__len__(), 1) @@ -30,6 +31,7 @@ def test_with_code_uri(self): "S3Bucket": "foobar", }) + @patch('boto3.session.Session.region_name', 'ap-southeast-1') def test_with_zip_file(self): function = SamFunction("foo") @@ -46,3 +48,25 @@ def test_with_no_code_uri_or_zipfile(self): function = SamFunction("foo") with pytest.raises(InvalidResourceException): function.to_cloudformation(**self.kwargs) + +class TestVersionDescription(TestCase): + kwargs = { + 'intrinsics_resolver': IntrinsicsResolver({}), + 'event_resources': [], + 'managed_policy_map': { + "foo": "bar" + } + } + + @patch('boto3.session.Session.region_name', 'ap-southeast-1') + def test_with_version_description(self): + function = SamFunction("foo") + test_description = "foobar" + + function.CodeUri = "s3://foobar/foo.zip" + function.VersionDescription = test_description + function.AutoPublishAlias = "live" + + cfnResources = function.to_cloudformation(**self.kwargs) + generateFunctionVersion = [x for x in cfnResources if isinstance(x, LambdaVersion)] + self.assertEqual(generateFunctionVersion[0].Description, test_description) diff --git a/tests/plugins/api/test_implicit_api_plugin.py b/tests/plugins/api/test_implicit_api_plugin.py index 393926348..4dfa09a18 100644 --- a/tests/plugins/api/test_implicit_api_plugin.py +++ b/tests/plugins/api/test_implicit_api_plugin.py @@ -398,6 +398,79 @@ def test_must_work_with_api_events(self): function_events_mock.update.assert_called_with(api_events) + def test_must_verify_expected_keys_exist(self): + + api_events = { + "Api1": { + "Type": "Api", + "Properties": { + "Path": "/", + "Methid": "POST" + } + } + } + + template = Mock() + function_events_mock = Mock() + function = SamResource({ + "Type": SamResourceType.Function.value, + "Properties": { + "Events": function_events_mock + } + }) + function_events_mock.update = Mock() + + with self.assertRaises(InvalidEventException) as context: + self.plugin._process_api_events(function, api_events, template) + + def test_must_verify_method_is_string(self): + api_events = { + "Api1": { + "Type": "Api", + "Properties": { + "Path": "/", + "Method": ["POST"] + } + } + } + + template = Mock() + function_events_mock = Mock() + function = SamResource({ + "Type": SamResourceType.Function.value, + "Properties": { + "Events": function_events_mock + } + }) + function_events_mock.update = Mock() + + with self.assertRaises(InvalidEventException) as context: + self.plugin._process_api_events(function, api_events, template) + + def test_must_verify_path_is_string(self): + api_events = { + "Api1": { + "Type": "Api", + "Properties": { + "Path": ["/"], + "Method": "POST" + } + } + } + + template = Mock() + function_events_mock = Mock() + function = SamResource({ + "Type": SamResourceType.Function.value, + "Properties": { + "Events": function_events_mock + } + }) + function_events_mock.update = Mock() + + with self.assertRaises(InvalidEventException) as context: + self.plugin._process_api_events(function, api_events, template) + def test_must_skip_events_without_properties(self): api_events = { diff --git a/tests/plugins/application/test_serverless_app_plugin.py b/tests/plugins/application/test_serverless_app_plugin.py index a991f7382..c38c12d6d 100644 --- a/tests/plugins/application/test_serverless_app_plugin.py +++ b/tests/plugins/application/test_serverless_app_plugin.py @@ -92,6 +92,12 @@ def test_plugin_invalid_configuration_raises_exception(self): with self.assertRaises(InvalidPluginException): plugin = ServerlessAppPlugin(wait_for_template_active_status=True, validate_only=True) + @patch('botocore.client.ClientEndpointBridge._check_default_region', mock_get_region) + def test_plugin_accepts_parameters(self): + parameters = {"a":"b"} + self.plugin = ServerlessAppPlugin(parameters=parameters) + self.assertEqual(self.plugin._parameters, parameters) + class TestServerlessAppPlugin_on_before_transform_template_translate(TestCase): @@ -194,6 +200,23 @@ def test_sar_service_calls(self): response = self.plugin._sar_service_call(service_call_lambda, logical_id, app_id, semver) self.assertEqual(app_id, response['ApplicationId']) + def test_resolve_intrinsics(self): + self.plugin = ServerlessAppPlugin(parameters={"AWS::Region": "us-east-1"}) + mappings = { + "MapA":{ + "us-east-1": { + "SecondLevelKey1": "value1" + } + } + } + input = { + "Fn::FindInMap": ["MapA", {"Ref": "AWS::Region"}, "SecondLevelKey1"] + } + intrinsic_resolvers = self.plugin._get_intrinsic_resolvers(mappings) + output = self.plugin._resolve_location_value(input, intrinsic_resolvers) + + self.assertEqual("value1", output) + class ApplicationResource(object): def __init__(self, app_id='app_id', semver='1.3.5'): diff --git a/tests/sdk/test_parameter.py b/tests/sdk/test_parameter.py new file mode 100644 index 000000000..d2eade038 --- /dev/null +++ b/tests/sdk/test_parameter.py @@ -0,0 +1,136 @@ +from parameterized import parameterized, param + +import pytest +from unittest import TestCase +from samtranslator.sdk.parameter import SamParameterValues +from mock import patch + +class TestSAMParameterValues(TestCase): + + def test_add_default_parameter_values_must_merge(self): + parameter_values = { + "Param1": "value1" + } + + sam_template = { + "Parameters": { + "Param2": { + "Type": "String", + "Default": "template default" + } + } + } + + expected = { + "Param1": "value1", + "Param2": "template default" + } + + sam_parameter_values = SamParameterValues(parameter_values) + sam_parameter_values.add_default_parameter_values(sam_template) + self.assertEqual(expected, sam_parameter_values.parameter_values) + + def test_add_default_parameter_values_must_override_user_specified_values(self): + parameter_values = { + "Param1": "value1" + } + + sam_template = { + "Parameters": { + "Param1": { + "Type": "String", + "Default": "template default" + } + } + } + + expected = { + "Param1": "value1" + } + + sam_parameter_values = SamParameterValues(parameter_values) + sam_parameter_values.add_default_parameter_values(sam_template) + self.assertEqual(expected, sam_parameter_values.parameter_values) + + def test_add_default_parameter_values_must_skip_params_without_defaults(self): + parameter_values = { + "Param1": "value1" + } + + sam_template = { + "Parameters": { + "Param1": { + "Type": "String" + }, + "Param2": { + "Type": "String" + } + } + } + + expected = { + "Param1": "value1" + } + + sam_parameter_values = SamParameterValues(parameter_values) + sam_parameter_values.add_default_parameter_values(sam_template) + self.assertEqual(expected, sam_parameter_values.parameter_values) + + + @parameterized.expand([ + # Array + param(["1", "2"]), + + # String + param("something"), + + # Some other non-parameter looking dictionary + param({"Param1": {"Foo": "Bar"}}), + + param(None) + ]) + def test_add_default_parameter_values_must_ignore_invalid_template_parameters(self, template_parameters): + parameter_values = { + "Param1": "value1" + } + + expected = { + "Param1": "value1" + } + + sam_template = { + "Parameters": template_parameters + } + + sam_parameter_values = SamParameterValues(parameter_values) + sam_parameter_values.add_default_parameter_values(sam_template) + self.assertEqual(expected, sam_parameter_values.parameter_values) + + @patch('boto3.session.Session.region_name', 'ap-southeast-1') + def test_add_pseudo_parameter_values_aws_region(self): + parameter_values = { + "Param1": "value1" + } + + expected = { + "Param1": "value1", + "AWS::Region": "ap-southeast-1" + } + + sam_parameter_values = SamParameterValues(parameter_values) + sam_parameter_values.add_pseudo_parameter_values() + self.assertEqual(expected, sam_parameter_values.parameter_values) + + @patch('boto3.session.Session.region_name', 'ap-southeast-1') + def test_add_pseudo_parameter_values_aws_region_not_override(self): + parameter_values = { + "AWS::Region": "value1" + } + + expected = { + "AWS::Region": "value1" + } + + sam_parameter_values = SamParameterValues(parameter_values) + sam_parameter_values.add_pseudo_parameter_values() + self.assertEqual(expected, sam_parameter_values.parameter_values) diff --git a/tests/swagger/test_swagger.py b/tests/swagger/test_swagger.py index 4c78f3115..388209213 100644 --- a/tests/swagger/test_swagger.py +++ b/tests/swagger/test_swagger.py @@ -5,6 +5,7 @@ from parameterized import parameterized, param from samtranslator.swagger.swagger import SwaggerEditor +from samtranslator.model.exceptions import InvalidDocumentException _X_INTEGRATION = "x-amazon-apigateway-integration" _X_ANY_METHOD = 'x-amazon-apigateway-any-method' @@ -193,7 +194,7 @@ def test_must_raise_non_dict_path_values(self): path = "/badpath" method = "get" - with self.assertRaises(ValueError): + with self.assertRaises(InvalidDocumentException): self.editor.add_path(path, method) def test_must_skip_existing_path(self): @@ -327,6 +328,37 @@ def test_must_raise_on_existing_integration(self): with self.assertRaises(ValueError): self.editor.add_lambda_integration("/bar", "get", "integrationUri") + def test_must_add_credentials_to_the_integration(self): + path = "/newpath" + method = "get" + integration_uri = "something" + expected = 'arn:aws:iam::*:user/*' + api_auth_config = { + "DefaultAuthorizer": "AWS_IAM", + "InvokeRole": "CALLER_CREDENTIALS" + } + + self.editor.add_lambda_integration(path, method, integration_uri, None, api_auth_config) + actual = self.editor.swagger["paths"][path][method][_X_INTEGRATION]['credentials'] + self.assertEqual(expected, actual) + + def test_must_add_credentials_to_the_integration_overrides(self): + path = "/newpath" + method = "get" + integration_uri = "something" + expected = 'arn:aws:iam::*:role/xxxxxx' + api_auth_config = { + "DefaultAuthorizer": "MyAuth", + } + method_auth_config = { + "Authorizer": "AWS_IAM", + "InvokeRole": "arn:aws:iam::*:role/xxxxxx" + } + + self.editor.add_lambda_integration(path, method, integration_uri, method_auth_config, api_auth_config) + actual = self.editor.swagger["paths"][path][method][_X_INTEGRATION]['credentials'] + self.assertEqual(expected, actual) + class TestSwaggerEditor_iter_on_path(TestCase): @@ -399,7 +431,7 @@ def test_must_skip_existing_path(self): def test_must_fail_with_bad_values_for_path(self): path = "/bad" - with self.assertRaises(ValueError): + with self.assertRaises(InvalidDocumentException): self.editor.add_cors(path, "origins", "headers", "methods") def test_must_fail_for_invalid_allowed_origin(self): diff --git a/tests/translator/input/api_with_aws_iam_auth_overrides.yaml b/tests/translator/input/api_with_aws_iam_auth_overrides.yaml new file mode 100644 index 000000000..f9cd47765 --- /dev/null +++ b/tests/translator/input/api_with_aws_iam_auth_overrides.yaml @@ -0,0 +1,86 @@ +Resources: + MyApiWithAwsIamAuth: + Type: "AWS::Serverless::Api" + Properties: + StageName: Prod + Auth: + DefaultAuthorizer: AWS_IAM + Authorizers: + MyCognitoAuth: + UserPoolArn: arn:aws:cognito-idp:xxxxxxxxx + InvokeRole: arn:aws:iam::123:role/AUTH_AWS_IAM + MyFunctionMyCognitoAuth: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://bucket/key + Handler: index.handler + Runtime: nodejs8.10 + Events: + API1: + Type: Api + Properties: + RestApiId: !Ref MyApiWithAwsIamAuth + Method: get + Path: /MyFunctionMyCognitoAuth + Auth: + Authorizer: MyCognitoAuth + MyFunctionWithoutAuth: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://bucket/key + Handler: index.handler + Runtime: nodejs8.10 + Events: + API2: + Type: Api + Properties: + RestApiId: !Ref MyApiWithAwsIamAuth + Method: get + Path: /MyFunctionWithoutAuth + MyFunctionNoneAuth: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://bucket/key + Handler: index.handler + Runtime: nodejs8.10 + Events: + API3: + Type: Api + Properties: + RestApiId: !Ref MyApiWithAwsIamAuth + Method: get + Path: /MyFunctionNoneAuth + Auth: + Authorizer: NONE + MyFunctionDefaultInvokeRole: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://bucket/key + Handler: index.handler + Runtime: nodejs8.10 + Events: + API3: + Type: Api + Properties: + RestApiId: !Ref MyApiWithAwsIamAuth + Method: get + Path: /MyFunctionDefaultInvokeRole + Auth: + Authorizer: AWS_IAM + InvokeRole: CALLER_CREDENTIALS + MyFunctionCustomInvokeRole: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://bucket/key + Handler: index.handler + Runtime: nodejs8.10 + Events: + API3: + Type: Api + Properties: + RestApiId: !Ref MyApiWithAwsIamAuth + Method: get + Path: /MyFunctionCustomInvokeRole + Auth: + Authorizer: AWS_IAM + InvokeRole: arn:aws:iam::456::role/something-else diff --git a/tests/translator/input/api_with_default_aws_iam_auth.yaml b/tests/translator/input/api_with_default_aws_iam_auth.yaml new file mode 100644 index 000000000..1ab0f6600 --- /dev/null +++ b/tests/translator/input/api_with_default_aws_iam_auth.yaml @@ -0,0 +1,47 @@ +Resources: + MyApiWithAwsIamAuthAndDefaultInvokeRole: + Type: "AWS::Serverless::Api" + Properties: + StageName: Prod + Auth: + DefaultAuthorizer: AWS_IAM + InvokeRole: CALLER_CREDENTIALS + MyApiWithAwsIamAuthAndCustomInvokeRole: + Type: "AWS::Serverless::Api" + Properties: + StageName: Prod + Auth: + DefaultAuthorizer: AWS_IAM + InvokeRole: rn:aws:iam::123:role/AUTH_AWS_IAM + MyApiWithAwsIamAuth: + Type: "AWS::Serverless::Api" + Properties: + StageName: Prod + Auth: + DefaultAuthorizer: AWS_IAM + + MyFunctionWithAwsIamAuth: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://bucket/key + Handler: index.handler + Runtime: nodejs8.10 + Events: + MyApiWithAwsIamAuth: + Type: Api + Properties: + RestApiId: !Ref MyApiWithAwsIamAuth + Path: / + Method: get + MyApiWithAwsIamAuthAndCustomInvokeRole: + Type: Api + Properties: + RestApiId: !Ref MyApiWithAwsIamAuthAndCustomInvokeRole + Path: / + Method: post + MyApiWithAwsIamAuthAndDefaultInvokeRole: + Type: Api + Properties: + RestApiId: !Ref MyApiWithAwsIamAuthAndDefaultInvokeRole + Path: / + Method: put diff --git a/tests/translator/input/api_with_gateway_responses.yaml b/tests/translator/input/api_with_gateway_responses.yaml new file mode 100644 index 000000000..1bb772aa9 --- /dev/null +++ b/tests/translator/input/api_with_gateway_responses.yaml @@ -0,0 +1,28 @@ +Resources: + Function: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://sam-demo-bucket/member_portal.zip + Handler: index.gethtml + Runtime: nodejs4.3 + Events: + GetHtml: + Type: Api + Properties: + Path: / + Method: get + RestApiId: !Ref ExplicitApi + + ExplicitApi: + Type: AWS::Serverless::Api + Properties: + StageName: Prod + GatewayResponses: + UNAUTHORIZED: + StatusCode: 401 + ResponseParameters: + Headers: + Access-Control-Expose-Headers: "'WWW-Authenticate'" + Access-Control-Allow-Origin: "'*'" + WWW-Authenticate: >- + 'Bearer realm="admin"' diff --git a/tests/translator/input/api_with_gateway_responses_all.yaml b/tests/translator/input/api_with_gateway_responses_all.yaml new file mode 100644 index 000000000..4a7bdfec8 --- /dev/null +++ b/tests/translator/input/api_with_gateway_responses_all.yaml @@ -0,0 +1,37 @@ +Resources: + Function: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://sam-demo-bucket/member_portal.zip + Handler: index.gethtml + Runtime: nodejs4.3 + Events: + GetHtml: + Type: Api + Properties: + Path: / + Method: get + RestApiId: !Ref ExplicitApi + + ExplicitApi: + Type: AWS::Serverless::Api + Properties: + StageName: Prod + GatewayResponses: + UNAUTHORIZED: + StatusCode: 401 + ResponseParameters: + Headers: + Access-Control-Expose-Headers: "'WWW-Authenticate'" + Access-Control-Allow-Origin: "'*'" + WWW-Authenticate: >- + 'Bearer realm="admin"' + Paths: + PathKey: "'path-value'" + QueryStrings: + QueryStringKey: "'query-string-value'" + QUOTA_EXCEEDED: + StatusCode: 429 + ResponseParameters: + Headers: + Retry-After: "'31536000'" diff --git a/tests/translator/input/api_with_gateway_responses_implicit.yaml b/tests/translator/input/api_with_gateway_responses_implicit.yaml new file mode 100644 index 000000000..1bd754068 --- /dev/null +++ b/tests/translator/input/api_with_gateway_responses_implicit.yaml @@ -0,0 +1,27 @@ +Globals: + Api: + Name: "some api" + GatewayResponses: + UNAUTHORIZED: + StatusCode: 401 + ResponseParameters: + Headers: + Access-Control-Expose-Headers: "'WWW-Authenticate'" + Access-Control-Allow-Origin: "'*'" + WWW-Authenticate: >- + 'Bearer realm="admin"' + + +Resources: + Function: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://sam-demo-bucket/member_portal.zip + Handler: index.gethtml + Runtime: nodejs4.3 + Events: + GetHtml: + Type: Api + Properties: + Path: / + Method: get \ No newline at end of file diff --git a/tests/translator/input/api_with_gateway_responses_minimal.yaml b/tests/translator/input/api_with_gateway_responses_minimal.yaml new file mode 100644 index 000000000..e74053bd7 --- /dev/null +++ b/tests/translator/input/api_with_gateway_responses_minimal.yaml @@ -0,0 +1,21 @@ +Resources: + Function: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://sam-demo-bucket/member_portal.zip + Handler: index.gethtml + Runtime: nodejs4.3 + Events: + GetHtml: + Type: Api + Properties: + Path: / + Method: get + RestApiId: !Ref ExplicitApi + + ExplicitApi: + Type: AWS::Serverless::Api + Properties: + StageName: Prod + GatewayResponses: + UNAUTHORIZED: {} diff --git a/tests/translator/input/api_with_gateway_responses_string_status_code.yaml b/tests/translator/input/api_with_gateway_responses_string_status_code.yaml new file mode 100644 index 000000000..22d807c7c --- /dev/null +++ b/tests/translator/input/api_with_gateway_responses_string_status_code.yaml @@ -0,0 +1,28 @@ +Resources: + Function: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://sam-demo-bucket/member_portal.zip + Handler: index.gethtml + Runtime: nodejs4.3 + Events: + GetHtml: + Type: Api + Properties: + Path: / + Method: get + RestApiId: !Ref ExplicitApi + + ExplicitApi: + Type: AWS::Serverless::Api + Properties: + StageName: Prod + GatewayResponses: + UNAUTHORIZED: + StatusCode: '401' + ResponseParameters: + Headers: + Access-Control-Expose-Headers: "'WWW-Authenticate'" + Access-Control-Allow-Origin: "'*'" + WWW-Authenticate: >- + 'Bearer realm="admin"' diff --git a/tests/translator/input/api_with_method_aws_iam_auth.yaml b/tests/translator/input/api_with_method_aws_iam_auth.yaml new file mode 100644 index 000000000..4becdb90a --- /dev/null +++ b/tests/translator/input/api_with_method_aws_iam_auth.yaml @@ -0,0 +1,39 @@ +Resources: + MyApiWithoutAuth: + Type: "AWS::Serverless::Api" + Properties: + StageName: Prod + + MyFunctionWithAwsIamAuth: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://bucket/key + Handler: index.handler + Runtime: nodejs8.10 + Events: + MyApiWithAwsIamAuth: + Type: Api + Properties: + RestApiId: !Ref MyApiWithoutAuth + Path: / + Method: get + Auth: + Authorizer: AWS_IAM + MyApiWithAwsIamAuthAndCustomInvokeRole: + Type: Api + Properties: + RestApiId: !Ref MyApiWithoutAuth + Path: / + Method: post + Auth: + Authorizer: AWS_IAM + InvokeRole: rn:aws:iam::123:role/AUTH_AWS_IAM + MyApiWithAwsIamAuthAndDefaultInvokeRole: + Type: Api + Properties: + RestApiId: !Ref MyApiWithoutAuth + Path: / + Method: put + Auth: + Authorizer: AWS_IAM + InvokeRole: CALLER_CREDENTIALS diff --git a/tests/translator/input/application_with_intrinsics.yaml b/tests/translator/input/application_with_intrinsics.yaml new file mode 100644 index 000000000..7045a7c77 --- /dev/null +++ b/tests/translator/input/application_with_intrinsics.yaml @@ -0,0 +1,40 @@ +Parameters: + ApplicationIdParam: + Type: String + Default: arn:aws:serverlessrepo:us-east-1:123456789012:applications/hello-world + VersionParam: + Type: String + Default: 1.0.0 + +Mappings: + ApplicationLocations: + ap-southeast-1: + ApplicationId: arn:aws:serverlessrepo:ap-southeast-1:123456789012:applications/hello-world + Version: 1.0.1 + cn-north-1: + ApplicationId: arn:aws-cn:serverlessrepo:cn-north-1:123456789012:applications/hello-world + Version: 1.0.2 + us-gov-west-1: + ApplicationId: arn:aws-gov:serverlessrepo:us-gov-west-1:123456789012:applications/hello-world + Version: 1.0.3 + +Resources: + ApplicationRefParameter: + Type: 'AWS::Serverless::Application' + Properties: + Location: + ApplicationId: !Ref ApplicationIdParam + SemanticVersion: !Ref VersionParam + + ApplicationFindInMap: + Type: 'AWS::Serverless::Application' + Properties: + Location: + ApplicationId: !FindInMap + - ApplicationLocations + - !Ref 'AWS::Region' + - ApplicationId + SemanticVersion: !FindInMap + - ApplicationLocations + - !Ref 'AWS::Region' + - Version \ No newline at end of file diff --git a/tests/translator/input/error_api_gateway_responses_nonnumeric_status_code.yaml b/tests/translator/input/error_api_gateway_responses_nonnumeric_status_code.yaml new file mode 100644 index 000000000..71bbe4a9f --- /dev/null +++ b/tests/translator/input/error_api_gateway_responses_nonnumeric_status_code.yaml @@ -0,0 +1,28 @@ +Resources: + Function: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://sam-demo-bucket/member_portal.zip + Handler: index.gethtml + Runtime: nodejs4.3 + Events: + GetHtml: + Type: Api + Properties: + Path: / + Method: get + RestApiId: !Ref ExplicitApi + + ExplicitApi: + Type: AWS::Serverless::Api + Properties: + StageName: Prod + GatewayResponses: + UNAUTHORIZED: + StatusCode: four-oh-one + ResponseParameters: + Headers: + Access-Control-Expose-Headers: "'WWW-Authenticate'" + Access-Control-Allow-Origin: "'*'" + WWW-Authenticate: >- + 'Bearer realm="admin"' diff --git a/tests/translator/input/error_api_gateway_responses_unknown_responseparameter.yaml b/tests/translator/input/error_api_gateway_responses_unknown_responseparameter.yaml new file mode 100644 index 000000000..0f324163b --- /dev/null +++ b/tests/translator/input/error_api_gateway_responses_unknown_responseparameter.yaml @@ -0,0 +1,28 @@ +Resources: + Function: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://sam-demo-bucket/member_portal.zip + Handler: index.gethtml + Runtime: nodejs4.3 + Events: + GetHtml: + Type: Api + Properties: + Path: / + Method: get + RestApiId: !Ref ExplicitApi + + ExplicitApi: + Type: AWS::Serverless::Api + Properties: + StageName: Prod + GatewayResponses: + UNAUTHORIZED: + StatusCode: 401 + ResponseParameters: + Footers: + Access-Control-Expose-Headers: "'WWW-Authenticate'" + Access-Control-Allow-Origin: "'*'" + WWW-Authenticate: >- + 'Bearer realm="admin"' diff --git a/tests/translator/input/error_api_gateway_responses_unknown_responseparameter_property.yaml b/tests/translator/input/error_api_gateway_responses_unknown_responseparameter_property.yaml new file mode 100644 index 000000000..7fe8c85d2 --- /dev/null +++ b/tests/translator/input/error_api_gateway_responses_unknown_responseparameter_property.yaml @@ -0,0 +1,29 @@ +Resources: + Function: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://sam-demo-bucket/member_portal.zip + Handler: index.gethtml + Runtime: nodejs4.3 + Events: + GetHtml: + Type: Api + Properties: + Path: / + Method: get + RestApiId: !Ref ExplicitApi + + ExplicitApi: + Type: AWS::Serverless::Api + Properties: + StageName: Prod + GatewayResponses: + UNAUTHORIZED: + StatusCode: 401 + SubStatusCode: 1 + ResponseParameters: + Headers: + Access-Control-Expose-Headers: "'WWW-Authenticate'" + Access-Control-Allow-Origin: "'*'" + WWW-Authenticate: >- + 'Bearer realm="admin"' diff --git a/tests/translator/input/error_api_invalid_auth.yaml b/tests/translator/input/error_api_invalid_auth.yaml index 0d8e7f3b2..d4a4debfa 100644 --- a/tests/translator/input/error_api_invalid_auth.yaml +++ b/tests/translator/input/error_api_invalid_auth.yaml @@ -185,4 +185,12 @@ Resources: FunctionArn: 'arn:aws' FunctionPayloadType: REQUEST Identity: - ReauthorizeEvery: 10 \ No newline at end of file + ReauthorizeEvery: 10 + + AuthorizerNotDict: + Type: AWS::Serverless::Api + Properties: + StageName: Prod + Auth: + Authorizers: + MyCognitoAuthorizer: \ No newline at end of file diff --git a/tests/translator/input/error_api_invalid_path.yaml b/tests/translator/input/error_api_invalid_path.yaml new file mode 100644 index 000000000..67d5925b6 --- /dev/null +++ b/tests/translator/input/error_api_invalid_path.yaml @@ -0,0 +1,10 @@ +Resources: + ApiWithInvalidPath: + Type: AWS::Serverless::Api + Properties: + StageName: Prod + Cors: "'*'" + DefinitionBody: + swagger: 2.0 + paths: + /foo: diff --git a/tests/translator/input/error_api_invalid_stagename.yaml b/tests/translator/input/error_api_invalid_stagename.yaml new file mode 100644 index 000000000..a3c178759 --- /dev/null +++ b/tests/translator/input/error_api_invalid_stagename.yaml @@ -0,0 +1,20 @@ +Resources: + ApiWithEmptyStageName: + Type: AWS::Serverless::Api + Properties: + DefinitionBody: {} + StageName: '' + Function: + Type: AWS::Serverless::Function + Properties: + Handler: lambda.handler + CodeUri: s3://bucket/api + Runtime: nodejs8.10 + Events: + ProxyApiRoot: + Type: Api + Properties: + RestApiId: + Ref: ApiWithEmptyStageName + Path: "/" + Method: ANY diff --git a/tests/translator/input/error_application_properties.yaml b/tests/translator/input/error_application_properties.yaml index 8914e099d..2b069f90f 100644 --- a/tests/translator/input/error_application_properties.yaml +++ b/tests/translator/input/error_application_properties.yaml @@ -33,4 +33,13 @@ Resources: Properties: Location: ApplicationId: - SemanticVersion: \ No newline at end of file + SemanticVersion: + + IntrinsicProperties: + Type: 'AWS::Serverless::Application' + Properties: + Location: + ApplicationId: + Sub: foobar + SemanticVersion: + Fn::Sub: foobar \ No newline at end of file diff --git a/tests/translator/input/error_function_invalid_api_event.yaml b/tests/translator/input/error_function_invalid_api_event.yaml new file mode 100644 index 000000000..98b7aa5f0 --- /dev/null +++ b/tests/translator/input/error_function_invalid_api_event.yaml @@ -0,0 +1,52 @@ +Resources: + FunctionApiNoMethod: + Type: 'AWS::Serverless::Function' + Properties: + CodeUri: s3://sam-demo-bucket/hello.zip + Handler: hello.handler + Runtime: python2.7 + Events: + ApiEvent: + Type: Api + Properties: + method: get # NOTE: lowercase 'm' + Path: / + + FunctionApiNoPath: + Type: 'AWS::Serverless::Function' + Properties: + CodeUri: s3://sam-demo-bucket/hello.zip + Handler: hello.handler + Runtime: python2.7 + Events: + ApiEvent: + Type: Api + Properties: + Method: get + path: / # NOTE: lowercase 'm' + + FunctionApiPathArray: + Type: 'AWS::Serverless::Function' + Properties: + CodeUri: s3://sam-demo-bucket/hello.zip + Handler: hello.handler + Runtime: python2.7 + Events: + ApiEvent: + Type: Api + Properties: + Method: get + Path: [/, /users] + + FunctionApiMethodArray: + Type: 'AWS::Serverless::Function' + Properties: + CodeUri: s3://sam-demo-bucket/hello.zip + Handler: hello.handler + Runtime: python2.7 + Events: + ApiEvent: + Type: Api + Properties: + Method: [get, post] + Path: / diff --git a/tests/translator/input/error_function_invalid_autopublishalias.yaml b/tests/translator/input/error_function_invalid_autopublishalias.yaml new file mode 100644 index 000000000..4883b3421 --- /dev/null +++ b/tests/translator/input/error_function_invalid_autopublishalias.yaml @@ -0,0 +1,19 @@ +Parameters: + MyAlias: + Type: String + Default: "" +Resources: + InvalidAutoPublishAliasFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.lambda_handler + CodeUri: s3://bucket/object + Runtime: python3.7 + AutoPublishAlias: + Ref: MyAlias + Events: + Get: + Type: Api + Properties: + Path: "/path" + Method: GET diff --git a/tests/translator/input/error_function_invalid_event_type.yaml b/tests/translator/input/error_function_invalid_event_type.yaml new file mode 100644 index 000000000..322d9abb1 --- /dev/null +++ b/tests/translator/input/error_function_invalid_event_type.yaml @@ -0,0 +1,26 @@ +Resources: + FunctionApiTypeError: + Type: 'AWS::Serverless::Function' + Properties: + CodeUri: s3://sam-demo-bucket/hello.zip + Handler: hello.handler + Runtime: python2.7 + Events: + ApiEvent: + Type: API + Properties: + Method: get + Path: / + + FunctionNoEventType: + Type: 'AWS::Serverless::Function' + Properties: + CodeUri: s3://sam-demo-bucket/hello.zip + Handler: hello.handler + Runtime: python2.7 + Events: + MissingType: + Properties: + Method: get + Path: / + diff --git a/tests/translator/input/error_invalid_findinmap.yaml b/tests/translator/input/error_invalid_findinmap.yaml new file mode 100644 index 000000000..22bcda161 --- /dev/null +++ b/tests/translator/input/error_invalid_findinmap.yaml @@ -0,0 +1,39 @@ +Parameters: + ApplicationIdParam: + Type: String + Default: arn:aws:serverlessrepo:us-east-1:123456789012:applications/hello-world + VersionParam: + Type: String + Default: 1.0.0 + +Mappings: + ApplicationLocations: + ap-southeast-1: + ApplicationId: arn:aws:serverlessrepo:ap-southeast-1:123456789012:applications/hello-world + Version: 1.0.1 + cn-north-1: + ApplicationId: arn:aws-cn:serverlessrepo:cn-north-1:123456789012:applications/hello-world + Version: 1.0.2 + us-gov-west-1: + ApplicationId: arn:aws-gov:serverlessrepo:us-gov-west-1:123456789012:applications/hello-world + Version: 1.0.3 + +Resources: + ApplicationRefParameter: + Type: 'AWS::Serverless::Application' + Properties: + Location: + ApplicationId: !Ref ApplicationIdParam + SemanticVersion: !Ref VersionParam + + ApplicationFindInMap: + Type: 'AWS::Serverless::Application' + Properties: + Location: + ApplicationId: !FindInMap + - ApplicationLocations + - !Ref 'AWS::Region' + SemanticVersion: !FindInMap + - ApplicationLocations + - !Ref 'AWS::Region' + - Version \ No newline at end of file diff --git a/tests/translator/input/error_invalid_getatt.yaml b/tests/translator/input/error_invalid_getatt.yaml new file mode 100644 index 000000000..87e122688 --- /dev/null +++ b/tests/translator/input/error_invalid_getatt.yaml @@ -0,0 +1,17 @@ +Resources: + FunctionWithInvalidGetAtt: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://sam-demo-bucket/hello.zip + Handler: hello.handler + Runtime: python2.7 + Role: + Fn::GetAtt: + - Fn::Sub: Foo + - Arn + Events: + GET: + Type: Api + Properties: + Path: / + Method: GET diff --git a/tests/translator/input/error_resource_not_dict.yaml b/tests/translator/input/error_resource_not_dict.yaml new file mode 100644 index 000000000..5a5c8ec92 --- /dev/null +++ b/tests/translator/input/error_resource_not_dict.yaml @@ -0,0 +1,6 @@ +Resources: + Function: + Type: AWS::Serverless::Function + Properties: + Runtime: node6.10 + NotADit: diff --git a/tests/translator/input/error_resource_properties_not_dict.yaml b/tests/translator/input/error_resource_properties_not_dict.yaml new file mode 100644 index 000000000..af9248898 --- /dev/null +++ b/tests/translator/input/error_resource_properties_not_dict.yaml @@ -0,0 +1,4 @@ +Resources: + Function: + Type: AWS::Serverless::Function + Properties: diff --git a/tests/translator/input/function_with_alias.yaml b/tests/translator/input/function_with_alias.yaml index 462690cb6..4a2defb45 100644 --- a/tests/translator/input/function_with_alias.yaml +++ b/tests/translator/input/function_with_alias.yaml @@ -6,4 +6,5 @@ Resources: Handler: hello.handler Runtime: python2.7 AutoPublishAlias: live + VersionDescription: sam-testing diff --git a/tests/translator/input/globals_for_function.yaml b/tests/translator/input/globals_for_function.yaml index 97fd5ae30..1490bf245 100644 --- a/tests/translator/input/globals_for_function.yaml +++ b/tests/translator/input/globals_for_function.yaml @@ -19,6 +19,7 @@ Globals: PermissionsBoundary: arn:aws:1234:iam:boundary/CustomerCreatedPermissionsBoundary Layers: - !Sub arn:${AWS:Partition}:lambda:${AWS:Region}:${AWS:AccountId}:layer:MyLayer:1 + ReservedConcurrentExecutions: 50 Resources: MinimalFunction: @@ -45,4 +46,5 @@ Resources: PermissionsBoundary: arn:aws:1234:iam:boundary/OverridePermissionsBoundary Layers: - !Sub arn:${AWS:Partition}:lambda:${AWS:Region}:${AWS:AccountId}:layer:MyLayer2:2 + ReservedConcurrentExecutions: 100 diff --git a/tests/translator/input/layers_with_intrinsics.yaml b/tests/translator/input/layers_with_intrinsics.yaml index fa7b16d4c..70a9c8764 100644 --- a/tests/translator/input/layers_with_intrinsics.yaml +++ b/tests/translator/input/layers_with_intrinsics.yaml @@ -34,3 +34,15 @@ Resources: ContentUri: s3://sam-demo-bucket/layer.zip LayerName: !Sub layer-${LayerNameParam} + LayerWithRefNameIntrinsicRegion: + Type: AWS::Serverless::LayerVersion + Properties: + ContentUri: s3://sam-demo-bucket/layer.zip + LayerName: !Ref 'AWS::Region' + + LayerWithSubNameIntrinsicRegion: + Type: AWS::Serverless::LayerVersion + Properties: + ContentUri: s3://sam-demo-bucket/layer.zip + LayerName: !Sub 'layer-${AWS::Region}' + diff --git a/tests/translator/output/all_policy_templates.json b/tests/translator/output/all_policy_templates.json index 1e5a60e92..1c4b999ce 100644 --- a/tests/translator/output/all_policy_templates.json +++ b/tests/translator/output/all_policy_templates.json @@ -212,7 +212,7 @@ ], "Resource": { "Fn::Sub": [ - "arn:${AWS::Partition}:es:${AWS::Region}:${AWS::AccountId}:domain/${domainName}", + "arn:${AWS::Partition}:es:${AWS::Region}:${AWS::AccountId}:domain/${domainName}/*", { "domainName": "name" } @@ -725,9 +725,11 @@ "Action": [ "serverlessrepo:CreateApplication", "serverlessrepo:CreateApplicationVersion", + "serverlessrepo:UpdateApplication", "serverlessrepo:GetApplication", "serverlessrepo:ListApplications", - "serverlessrepo:ListApplicationVersions" + "serverlessrepo:ListApplicationVersions", + "serverlessrepo:ListApplicationDependencies" ], "Resource": [ { diff --git a/tests/translator/output/api_with_aws_iam_auth_overrides.json b/tests/translator/output/api_with_aws_iam_auth_overrides.json new file mode 100644 index 000000000..6ca104082 --- /dev/null +++ b/tests/translator/output/api_with_aws_iam_auth_overrides.json @@ -0,0 +1,595 @@ +{ + "Resources": { + "MyFunctionCustomInvokeRole": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.handler", + "Code": { + "S3Bucket": "bucket", + "S3Key": "key" + }, + "Role": { + "Fn::GetAtt": [ + "MyFunctionCustomInvokeRoleRole", + "Arn" + ] + }, + "Runtime": "nodejs8.10", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "MyFunctionNoneAuth": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.handler", + "Code": { + "S3Bucket": "bucket", + "S3Key": "key" + }, + "Role": { + "Fn::GetAtt": [ + "MyFunctionNoneAuthRole", + "Arn" + ] + }, + "Runtime": "nodejs8.10", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "MyApiWithAwsIamAuthProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiWithAwsIamAuthDeployment8a32fb1652" + }, + "RestApiId": { + "Ref": "MyApiWithAwsIamAuth" + }, + "StageName": "Prod" + } + }, + "MyFunctionWithoutAuthRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + } + } + }, + "MyFunctionCustomInvokeRoleAPI3PermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionCustomInvokeRole" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/MyFunctionCustomInvokeRole", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApiWithAwsIamAuth" + } + } + ] + } + } + }, + "MyFunctionWithoutAuthAPI2PermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionWithoutAuth" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/MyFunctionWithoutAuth", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApiWithAwsIamAuth" + } + } + ] + } + } + }, + "MyApiWithAwsIamAuthDeployment8a32fb1652": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApiWithAwsIamAuth" + }, + "Description": "RestApi deployment id: 8a32fb165218ee858b16980eed421b4f38d18f00", + "StageName": "Stage" + } + }, + "MyFunctionMyCognitoAuthAPI1PermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionMyCognitoAuth" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/MyFunctionMyCognitoAuth", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithAwsIamAuth" + } + } + ] + } + } + }, + "MyFunctionMyCognitoAuth": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.handler", + "Code": { + "S3Bucket": "bucket", + "S3Key": "key" + }, + "Role": { + "Fn::GetAtt": [ + "MyFunctionMyCognitoAuthRole", + "Arn" + ] + }, + "Runtime": "nodejs8.10", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "MyFunctionDefaultInvokeRoleAPI3PermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionDefaultInvokeRole" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/MyFunctionDefaultInvokeRole", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApiWithAwsIamAuth" + } + } + ] + } + } + }, + "MyFunctionNoneAuthRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + } + } + }, + "MyFunctionCustomInvokeRoleRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + } + } + }, + "MyFunctionDefaultInvokeRoleAPI3PermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionDefaultInvokeRole" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/MyFunctionDefaultInvokeRole", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithAwsIamAuth" + } + } + ] + } + } + }, + "MyFunctionWithoutAuthAPI2PermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionWithoutAuth" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/MyFunctionWithoutAuth", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithAwsIamAuth" + } + } + ] + } + } + }, + "MyFunctionWithoutAuth": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.handler", + "Code": { + "S3Bucket": "bucket", + "S3Key": "key" + }, + "Role": { + "Fn::GetAtt": [ + "MyFunctionWithoutAuthRole", + "Arn" + ] + }, + "Runtime": "nodejs8.10", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "MyFunctionMyCognitoAuthAPI1PermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionMyCognitoAuth" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/MyFunctionMyCognitoAuth", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApiWithAwsIamAuth" + } + } + ] + } + } + }, + "MyFunctionDefaultInvokeRoleRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + } + } + }, + "MyFunctionMyCognitoAuthRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + } + } + }, + "MyFunctionCustomInvokeRoleAPI3PermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionCustomInvokeRole" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/MyFunctionCustomInvokeRole", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithAwsIamAuth" + } + } + ] + } + } + }, + "MyFunctionDefaultInvokeRole": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.handler", + "Code": { + "S3Bucket": "bucket", + "S3Key": "key" + }, + "Role": { + "Fn::GetAtt": [ + "MyFunctionDefaultInvokeRoleRole", + "Arn" + ] + }, + "Runtime": "nodejs8.10", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "MyFunctionNoneAuthAPI3PermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionNoneAuth" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/MyFunctionNoneAuth", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithAwsIamAuth" + } + } + ] + } + } + }, + "MyApiWithAwsIamAuth": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/MyFunctionDefaultInvokeRole": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunctionDefaultInvokeRole.Arn}/invocations" + }, + "credentials": "arn:aws:iam::*:user/*" + }, + "security": [ + { + "AWS_IAM": [] + } + ], + "responses": {} + } + }, + "/MyFunctionWithoutAuth": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunctionWithoutAuth.Arn}/invocations" + }, + "credentials": "arn:aws:iam::123:role/AUTH_AWS_IAM" + }, + "security": [ + { + "AWS_IAM": [] + } + ], + "responses": {} + } + }, + "/MyFunctionNoneAuth": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunctionNoneAuth.Arn}/invocations" + } + }, + "security": [ + { + "NONE": [] + } + ], + "responses": {} + } + }, + "/MyFunctionMyCognitoAuth": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunctionMyCognitoAuth.Arn}/invocations" + } + }, + "security": [ + { + "MyCognitoAuth": [] + } + ], + "responses": {} + } + }, + "/MyFunctionCustomInvokeRole": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunctionCustomInvokeRole.Arn}/invocations" + }, + "credentials": "arn:aws:iam::456::role/something-else" + }, + "security": [ + { + "AWS_IAM": [] + } + ], + "responses": {} + } + } + }, + "swagger": "2.0", + "securityDefinitions": { + "MyCognitoAuth": { + "in": "header", + "type": "apiKey", + "name": "Authorization", + "x-amazon-apigateway-authorizer": { + "providerARNs": [ + "arn:aws:cognito-idp:xxxxxxxxx" + ], + "type": "cognito_user_pools" + }, + "x-amazon-apigateway-authtype": "cognito_user_pools" + }, + "AWS_IAM": { + "in": "header", + "type": "apiKey", + "name": "Authorization", + "x-amazon-apigateway-authtype": "awsSigv4" + } + } + } + } + }, + "MyFunctionNoneAuthAPI3PermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionNoneAuth" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/MyFunctionNoneAuth", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApiWithAwsIamAuth" + } + } + ] + } + } + } + } +} diff --git a/tests/translator/output/api_with_default_aws_iam_auth.json b/tests/translator/output/api_with_default_aws_iam_auth.json new file mode 100644 index 000000000..b7d0bb46f --- /dev/null +++ b/tests/translator/output/api_with_default_aws_iam_auth.json @@ -0,0 +1,369 @@ +{ + "Resources": { + "MyApiWithAwsIamAuthDeployment3364487a57": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApiWithAwsIamAuth" + }, + "Description": "RestApi deployment id: 3364487a57573f17976c9edc5dad6c3afe02a101", + "StageName": "Stage" + } + }, + "MyFunctionWithAwsIamAuth": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.handler", + "Code": { + "S3Bucket": "bucket", + "S3Key": "key" + }, + "Role": { + "Fn::GetAtt": [ + "MyFunctionWithAwsIamAuthRole", + "Arn" + ] + }, + "Runtime": "nodejs8.10", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "MyFunctionWithAwsIamAuthMyApiWithAwsIamAuthPermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionWithAwsIamAuth" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithAwsIamAuth" + } + } + ] + } + } + }, + "MyFunctionWithAwsIamAuthMyApiWithAwsIamAuthPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionWithAwsIamAuth" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApiWithAwsIamAuth" + } + } + ] + } + } + }, + "MyApiWithAwsIamAuthProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiWithAwsIamAuthDeployment3364487a57" + }, + "RestApiId": { + "Ref": "MyApiWithAwsIamAuth" + }, + "StageName": "Prod" + } + }, + "MyApiWithAwsIamAuthAndDefaultInvokeRoleDeployment1edb95497c": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApiWithAwsIamAuthAndDefaultInvokeRole" + }, + "Description": "RestApi deployment id: 1edb95497ceb1e912fa65281f21a8f6f1098b5f4", + "StageName": "Stage" + } + }, + "MyFunctionWithAwsIamAuthMyApiWithAwsIamAuthAndDefaultInvokeRolePermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionWithAwsIamAuth" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/PUT/", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApiWithAwsIamAuthAndDefaultInvokeRole" + } + } + ] + } + } + }, + "MyFunctionWithAwsIamAuthMyApiWithAwsIamAuthAndDefaultInvokeRolePermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionWithAwsIamAuth" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/PUT/", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithAwsIamAuthAndDefaultInvokeRole" + } + } + ] + } + } + }, + "MyApiWithAwsIamAuthAndCustomInvokeRoleDeployment61108120cf": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApiWithAwsIamAuthAndCustomInvokeRole" + }, + "Description": "RestApi deployment id: 61108120cf9f293d5cf2fc21044a94dce2ed499a", + "StageName": "Stage" + } + }, + "MyApiWithAwsIamAuthAndCustomInvokeRole": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/": { + "post": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunctionWithAwsIamAuth.Arn}/invocations" + }, + "credentials": "rn:aws:iam::123:role/AUTH_AWS_IAM" + }, + "security": [ + { + "AWS_IAM": [] + } + ], + "responses": {} + } + } + }, + "swagger": "2.0", + "securityDefinitions": { + "AWS_IAM": { + "in": "header", + "type": "apiKey", + "name": "Authorization", + "x-amazon-apigateway-authtype": "awsSigv4" + } + } + } + } + }, + "MyApiWithAwsIamAuthAndDefaultInvokeRoleProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiWithAwsIamAuthAndDefaultInvokeRoleDeployment1edb95497c" + }, + "RestApiId": { + "Ref": "MyApiWithAwsIamAuthAndDefaultInvokeRole" + }, + "StageName": "Prod" + } + }, + "MyApiWithAwsIamAuthAndCustomInvokeRoleProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiWithAwsIamAuthAndCustomInvokeRoleDeployment61108120cf" + }, + "RestApiId": { + "Ref": "MyApiWithAwsIamAuthAndCustomInvokeRole" + }, + "StageName": "Prod" + } + }, + "MyApiWithAwsIamAuthAndDefaultInvokeRole": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/": { + "put": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunctionWithAwsIamAuth.Arn}/invocations" + }, + "credentials": "arn:aws:iam::*:user/*" + }, + "security": [ + { + "AWS_IAM": [] + } + ], + "responses": {} + } + } + }, + "swagger": "2.0", + "securityDefinitions": { + "AWS_IAM": { + "in": "header", + "type": "apiKey", + "name": "Authorization", + "x-amazon-apigateway-authtype": "awsSigv4" + } + } + } + } + }, + "MyApiWithAwsIamAuth": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunctionWithAwsIamAuth.Arn}/invocations" + }, + "credentials": "arn:aws:iam::*:user/*" + }, + "security": [ + { + "AWS_IAM": [] + } + ], + "responses": {} + } + } + }, + "swagger": "2.0", + "securityDefinitions": { + "AWS_IAM": { + "in": "header", + "type": "apiKey", + "name": "Authorization", + "x-amazon-apigateway-authtype": "awsSigv4" + } + } + } + } + }, + "MyFunctionWithAwsIamAuthMyApiWithAwsIamAuthAndCustomInvokeRolePermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionWithAwsIamAuth" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/POST/", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApiWithAwsIamAuthAndCustomInvokeRole" + } + } + ] + } + } + }, + "MyFunctionWithAwsIamAuthRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + } + } + }, + "MyFunctionWithAwsIamAuthMyApiWithAwsIamAuthAndCustomInvokeRolePermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionWithAwsIamAuth" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/POST/", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithAwsIamAuthAndCustomInvokeRole" + } + } + ] + } + } + } + } +} diff --git a/tests/translator/output/api_with_gateway_responses.json b/tests/translator/output/api_with_gateway_responses.json new file mode 100644 index 000000000..357b3b2ea --- /dev/null +++ b/tests/translator/output/api_with_gateway_responses.json @@ -0,0 +1,154 @@ +{ + "Resources": { + "Function": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.gethtml", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "member_portal.zip" + }, + "Role": { + "Fn::GetAtt": [ + "FunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs4.3", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "FunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + } + } + }, + "ExplicitApi": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${Function.Arn}/invocations" + } + }, + "responses": {} + } + } + }, + "swagger": "2.0", + "x-amazon-apigateway-gateway-responses": { + "UNAUTHORIZED": { + "responseParameters": { + "gatewayresponse.header.WWW-Authenticate": "'Bearer realm=\"admin\"'", + "gatewayresponse.header.Access-Control-Expose-Headers": "'WWW-Authenticate'", + "gatewayresponse.header.Access-Control-Allow-Origin": "'*'" + }, + "responseTemplates": {}, + "statusCode": "401" + } + } + } + } + }, + "ExplicitApiDeploymentf63e40e7e8": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "ExplicitApi" + }, + "Description": "RestApi deployment id: f63e40e7e8923689c5fa5f296abf1fc5d4773cc3", + "StageName": "Stage" + } + }, + "ExplicitApiProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "ExplicitApiDeploymentf63e40e7e8" + }, + "RestApiId": { + "Ref": "ExplicitApi" + }, + "StageName": "Prod" + } + }, + "FunctionGetHtmlPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "Function" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "ExplicitApi" + } + } + ] + } + } + }, + "FunctionGetHtmlPermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "Function" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "ExplicitApi" + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/api_with_gateway_responses_all.json b/tests/translator/output/api_with_gateway_responses_all.json new file mode 100644 index 000000000..ecdd9d941 --- /dev/null +++ b/tests/translator/output/api_with_gateway_responses_all.json @@ -0,0 +1,163 @@ +{ + "Resources": { + "Function": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.gethtml", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "member_portal.zip" + }, + "Role": { + "Fn::GetAtt": [ + "FunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs4.3", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "FunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + } + } + }, + "ExplicitApi": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${Function.Arn}/invocations" + } + }, + "responses": {} + } + } + }, + "swagger": "2.0", + "x-amazon-apigateway-gateway-responses": { + "QUOTA_EXCEEDED": { + "responseParameters": { + "gatewayresponse.header.Retry-After": "'31536000'" + }, + "responseTemplates": {}, + "statusCode": "429" + }, + "UNAUTHORIZED": { + "responseParameters": { + "gatewayresponse.header.WWW-Authenticate": "'Bearer realm=\"admin\"'", + "gatewayresponse.header.Access-Control-Expose-Headers": "'WWW-Authenticate'", + "gatewayresponse.header.Access-Control-Allow-Origin": "'*'", + "gatewayresponse.path.PathKey": "'path-value'", + "gatewayresponse.querystring.QueryStringKey": "'query-string-value'" + }, + "responseTemplates": {}, + "statusCode": "401" + } + } + } + } + }, + "ExplicitApiDeploymentedbd8aa001": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "ExplicitApi" + }, + "Description": "RestApi deployment id: edbd8aa001b34e2c5fa4458827ad9f4075fb812d", + "StageName": "Stage" + } + }, + "ExplicitApiProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "ExplicitApiDeploymentedbd8aa001" + }, + "RestApiId": { + "Ref": "ExplicitApi" + }, + "StageName": "Prod" + } + }, + "FunctionGetHtmlPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "Function" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "ExplicitApi" + } + } + ] + } + } + }, + "FunctionGetHtmlPermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "Function" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "ExplicitApi" + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/api_with_gateway_responses_implicit.json b/tests/translator/output/api_with_gateway_responses_implicit.json new file mode 100644 index 000000000..7a0325187 --- /dev/null +++ b/tests/translator/output/api_with_gateway_responses_implicit.json @@ -0,0 +1,155 @@ +{ + "Resources": { + "Function": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.gethtml", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "member_portal.zip" + }, + "Role": { + "Fn::GetAtt": [ + "FunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs4.3", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "ServerlessRestApiDeploymentf63e40e7e8": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "ServerlessRestApi" + }, + "Description": "RestApi deployment id: f63e40e7e8923689c5fa5f296abf1fc5d4773cc3", + "StageName": "Stage" + } + }, + "FunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + } + } + }, + "ServerlessRestApiProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "ServerlessRestApiDeploymentf63e40e7e8" + }, + "RestApiId": { + "Ref": "ServerlessRestApi" + }, + "StageName": "Prod" + } + }, + "FunctionGetHtmlPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "Function" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "ServerlessRestApi" + } + } + ] + } + } + }, + "ServerlessRestApi": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${Function.Arn}/invocations" + } + }, + "responses": {} + } + } + }, + "swagger": "2.0", + "x-amazon-apigateway-gateway-responses": { + "UNAUTHORIZED": { + "responseParameters": { + "gatewayresponse.header.WWW-Authenticate": "'Bearer realm=\"admin\"'", + "gatewayresponse.header.Access-Control-Expose-Headers": "'WWW-Authenticate'", + "gatewayresponse.header.Access-Control-Allow-Origin": "'*'" + }, + "responseTemplates": {}, + "statusCode": "401" + } + } + }, + "Name": "some api" + } + }, + "FunctionGetHtmlPermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "Function" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "ServerlessRestApi" + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/api_with_gateway_responses_minimal.json b/tests/translator/output/api_with_gateway_responses_minimal.json new file mode 100644 index 000000000..f9bd0bfc4 --- /dev/null +++ b/tests/translator/output/api_with_gateway_responses_minimal.json @@ -0,0 +1,149 @@ +{ + "Resources": { + "Function": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.gethtml", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "member_portal.zip" + }, + "Role": { + "Fn::GetAtt": [ + "FunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs4.3", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "FunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + } + } + }, + "ExplicitApi": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${Function.Arn}/invocations" + } + }, + "responses": {} + } + } + }, + "swagger": "2.0", + "x-amazon-apigateway-gateway-responses": { + "UNAUTHORIZED": { + "responseParameters": {}, + "responseTemplates": {} + } + } + } + } + }, + "ExplicitApiDeployment43cede1065": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "ExplicitApi" + }, + "Description": "RestApi deployment id: 43cede1065157a0a0bdea5f4025a38449a7c37e6", + "StageName": "Stage" + } + }, + "ExplicitApiProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "ExplicitApiDeployment43cede1065" + }, + "RestApiId": { + "Ref": "ExplicitApi" + }, + "StageName": "Prod" + } + }, + "FunctionGetHtmlPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "Function" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "ExplicitApi" + } + } + ] + } + } + }, + "FunctionGetHtmlPermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "Function" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "ExplicitApi" + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/api_with_gateway_responses_string_status_code.json b/tests/translator/output/api_with_gateway_responses_string_status_code.json new file mode 100644 index 000000000..357b3b2ea --- /dev/null +++ b/tests/translator/output/api_with_gateway_responses_string_status_code.json @@ -0,0 +1,154 @@ +{ + "Resources": { + "Function": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.gethtml", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "member_portal.zip" + }, + "Role": { + "Fn::GetAtt": [ + "FunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs4.3", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "FunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + } + } + }, + "ExplicitApi": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${Function.Arn}/invocations" + } + }, + "responses": {} + } + } + }, + "swagger": "2.0", + "x-amazon-apigateway-gateway-responses": { + "UNAUTHORIZED": { + "responseParameters": { + "gatewayresponse.header.WWW-Authenticate": "'Bearer realm=\"admin\"'", + "gatewayresponse.header.Access-Control-Expose-Headers": "'WWW-Authenticate'", + "gatewayresponse.header.Access-Control-Allow-Origin": "'*'" + }, + "responseTemplates": {}, + "statusCode": "401" + } + } + } + } + }, + "ExplicitApiDeploymentf63e40e7e8": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "ExplicitApi" + }, + "Description": "RestApi deployment id: f63e40e7e8923689c5fa5f296abf1fc5d4773cc3", + "StageName": "Stage" + } + }, + "ExplicitApiProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "ExplicitApiDeploymentf63e40e7e8" + }, + "RestApiId": { + "Ref": "ExplicitApi" + }, + "StageName": "Prod" + } + }, + "FunctionGetHtmlPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "Function" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "ExplicitApi" + } + } + ] + } + } + }, + "FunctionGetHtmlPermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "Function" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "ExplicitApi" + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/api_with_method_aws_iam_auth.json b/tests/translator/output/api_with_method_aws_iam_auth.json new file mode 100644 index 000000000..694b7ec22 --- /dev/null +++ b/tests/translator/output/api_with_method_aws_iam_auth.json @@ -0,0 +1,273 @@ +{ + "Resources": { + "MyFunctionWithAwsIamAuthMyApiWithAwsIamAuthPermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionWithAwsIamAuth" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithoutAuth" + } + } + ] + } + } + }, + "MyFunctionWithAwsIamAuthMyApiWithAwsIamAuthPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionWithAwsIamAuth" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApiWithoutAuth" + } + } + ] + } + } + }, + "MyFunctionWithAwsIamAuthMyApiWithAwsIamAuthAndCustomInvokeRolePermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionWithAwsIamAuth" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/POST/", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApiWithoutAuth" + } + } + ] + } + } + }, + "MyApiWithoutAuth": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/": { + "put": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunctionWithAwsIamAuth.Arn}/invocations" + }, + "credentials": "arn:aws:iam::*:user/*" + }, + "security": [ + { + "AWS_IAM": [] + } + ], + "responses": {} + }, + "post": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunctionWithAwsIamAuth.Arn}/invocations" + }, + "credentials": "rn:aws:iam::123:role/AUTH_AWS_IAM" + }, + "security": [ + { + "AWS_IAM": [] + } + ], + "responses": {} + }, + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunctionWithAwsIamAuth.Arn}/invocations" + }, + "credentials": "arn:aws:iam::*:user/*" + }, + "security": [ + { + "AWS_IAM": [] + } + ], + "responses": {} + } + } + }, + "swagger": "2.0", + "securityDefinitions": { + "AWS_IAM": { + "x-amazon-apigateway-authtype": "awsSigv4", + "type": "apiKey", + "name": "Authorization", + "in": "header" + } + } + } + } + }, + "MyFunctionWithAwsIamAuthMyApiWithAwsIamAuthAndDefaultInvokeRolePermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionWithAwsIamAuth" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/PUT/", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApiWithoutAuth" + } + } + ] + } + } + }, + "MyFunctionWithAwsIamAuthMyApiWithAwsIamAuthAndDefaultInvokeRolePermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionWithAwsIamAuth" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/PUT/", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithoutAuth" + } + } + ] + } + } + }, + "MyFunctionWithAwsIamAuth": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.handler", + "Code": { + "S3Bucket": "bucket", + "S3Key": "key" + }, + "Role": { + "Fn::GetAtt": [ + "MyFunctionWithAwsIamAuthRole", + "Arn" + ] + }, + "Runtime": "nodejs8.10", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "MyApiWithoutAuthProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiWithoutAuthDeployment0cf1ab8c4c" + }, + "RestApiId": { + "Ref": "MyApiWithoutAuth" + }, + "StageName": "Prod" + } + }, + "MyApiWithoutAuthDeployment0cf1ab8c4c": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApiWithoutAuth" + }, + "Description": "RestApi deployment id: 0cf1ab8c4caae4435015b3256bce0daaa087bd5e", + "StageName": "Stage" + } + }, + "MyFunctionWithAwsIamAuthRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + } + } + }, + "MyFunctionWithAwsIamAuthMyApiWithAwsIamAuthAndCustomInvokeRolePermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionWithAwsIamAuth" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/POST/", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithoutAuth" + } + } + ] + } + } + } + } +} diff --git a/tests/translator/output/application_with_intrinsics.json b/tests/translator/output/application_with_intrinsics.json new file mode 100644 index 000000000..d9f6b1d4e --- /dev/null +++ b/tests/translator/output/application_with_intrinsics.json @@ -0,0 +1,70 @@ +{ + "Resources": { + "ApplicationRefParameter": { + "Type": "AWS::CloudFormation::Stack", + "Properties": { + "TemplateURL": "https://awsserverlessrepo-changesets-xxx.s3.amazonaws.com/signed-url", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + }, + { + "Value": "arn:aws:serverlessrepo:us-east-1:123456789012:applications/hello-world", + "Key": "serverlessrepo:applicationId" + }, + { + "Value": "1.0.0", + "Key": "serverlessrepo:semanticVersion" + } + ] + } + }, + "ApplicationFindInMap": { + "Type": "AWS::CloudFormation::Stack", + "Properties": { + "TemplateURL": "https://awsserverlessrepo-changesets-xxx.s3.amazonaws.com/signed-url", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + }, + { + "Value": "arn:aws:serverlessrepo:ap-southeast-1:123456789012:applications/hello-world", + "Key": "serverlessrepo:applicationId" + }, + { + "Value": "1.0.1", + "Key": "serverlessrepo:semanticVersion" + } + ] + } + } + }, + "Parameters": { + "ApplicationIdParam": { + "Type": "String", + "Default": "arn:aws:serverlessrepo:us-east-1:123456789012:applications/hello-world" + }, + "VersionParam": { + "Type": "String", + "Default": "1.0.0" + } + }, + "Mappings": { + "ApplicationLocations": { + "ap-southeast-1": { + "ApplicationId": "arn:aws:serverlessrepo:ap-southeast-1:123456789012:applications/hello-world", + "Version": "1.0.1" + }, + "cn-north-1": { + "ApplicationId": "arn:aws-cn:serverlessrepo:cn-north-1:123456789012:applications/hello-world", + "Version": "1.0.2" + }, + "us-gov-west-1": { + "ApplicationId": "arn:aws-gov:serverlessrepo:us-gov-west-1:123456789012:applications/hello-world", + "Version": "1.0.3" + } + } + } +} diff --git a/tests/translator/output/aws-cn/all_policy_templates.json b/tests/translator/output/aws-cn/all_policy_templates.json index 77f518eec..f908ddadf 100644 --- a/tests/translator/output/aws-cn/all_policy_templates.json +++ b/tests/translator/output/aws-cn/all_policy_templates.json @@ -211,7 +211,7 @@ ], "Resource": { "Fn::Sub": [ - "arn:${AWS::Partition}:es:${AWS::Region}:${AWS::AccountId}:domain/${domainName}", + "arn:${AWS::Partition}:es:${AWS::Region}:${AWS::AccountId}:domain/${domainName}/*", { "domainName": "name" } @@ -724,9 +724,11 @@ "Action": [ "serverlessrepo:CreateApplication", "serverlessrepo:CreateApplicationVersion", + "serverlessrepo:UpdateApplication", "serverlessrepo:GetApplication", "serverlessrepo:ListApplications", - "serverlessrepo:ListApplicationVersions" + "serverlessrepo:ListApplicationVersions", + "serverlessrepo:ListApplicationDependencies" ], "Resource": [ { diff --git a/tests/translator/output/aws-cn/api_with_aws_iam_auth_overrides.json b/tests/translator/output/aws-cn/api_with_aws_iam_auth_overrides.json new file mode 100644 index 000000000..49f1e90a4 --- /dev/null +++ b/tests/translator/output/aws-cn/api_with_aws_iam_auth_overrides.json @@ -0,0 +1,603 @@ +{ + "Resources": { + "MyFunctionCustomInvokeRole": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.handler", + "Code": { + "S3Bucket": "bucket", + "S3Key": "key" + }, + "Role": { + "Fn::GetAtt": [ + "MyFunctionCustomInvokeRoleRole", + "Arn" + ] + }, + "Runtime": "nodejs8.10", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "MyFunctionNoneAuth": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.handler", + "Code": { + "S3Bucket": "bucket", + "S3Key": "key" + }, + "Role": { + "Fn::GetAtt": [ + "MyFunctionNoneAuthRole", + "Arn" + ] + }, + "Runtime": "nodejs8.10", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "MyApiWithAwsIamAuthProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiWithAwsIamAuthDeploymented0a631915" + }, + "RestApiId": { + "Ref": "MyApiWithAwsIamAuth" + }, + "StageName": "Prod" + } + }, + "MyFunctionWithoutAuthRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + } + } + }, + "MyFunctionCustomInvokeRoleAPI3PermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionCustomInvokeRole" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/MyFunctionCustomInvokeRole", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApiWithAwsIamAuth" + } + } + ] + } + } + }, + "MyFunctionWithoutAuthAPI2PermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionWithoutAuth" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/MyFunctionWithoutAuth", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApiWithAwsIamAuth" + } + } + ] + } + } + }, + "MyFunctionMyCognitoAuthAPI1PermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionMyCognitoAuth" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/MyFunctionMyCognitoAuth", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithAwsIamAuth" + } + } + ] + } + } + }, + "MyApiWithAwsIamAuthDeploymented0a631915": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApiWithAwsIamAuth" + }, + "Description": "RestApi deployment id: ed0a631915dc4df4436f471e81cd69beb6f89603", + "StageName": "Stage" + } + }, + "MyFunctionMyCognitoAuth": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.handler", + "Code": { + "S3Bucket": "bucket", + "S3Key": "key" + }, + "Role": { + "Fn::GetAtt": [ + "MyFunctionMyCognitoAuthRole", + "Arn" + ] + }, + "Runtime": "nodejs8.10", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "MyFunctionDefaultInvokeRoleAPI3PermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionDefaultInvokeRole" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/MyFunctionDefaultInvokeRole", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApiWithAwsIamAuth" + } + } + ] + } + } + }, + "MyFunctionNoneAuthRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + } + } + }, + "MyFunctionCustomInvokeRoleRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + } + } + }, + "MyFunctionDefaultInvokeRoleAPI3PermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionDefaultInvokeRole" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/MyFunctionDefaultInvokeRole", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithAwsIamAuth" + } + } + ] + } + } + }, + "MyFunctionWithoutAuthAPI2PermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionWithoutAuth" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/MyFunctionWithoutAuth", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithAwsIamAuth" + } + } + ] + } + } + }, + "MyFunctionWithoutAuth": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.handler", + "Code": { + "S3Bucket": "bucket", + "S3Key": "key" + }, + "Role": { + "Fn::GetAtt": [ + "MyFunctionWithoutAuthRole", + "Arn" + ] + }, + "Runtime": "nodejs8.10", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "MyFunctionMyCognitoAuthAPI1PermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionMyCognitoAuth" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/MyFunctionMyCognitoAuth", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApiWithAwsIamAuth" + } + } + ] + } + } + }, + "MyFunctionDefaultInvokeRoleRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + } + } + }, + "MyFunctionMyCognitoAuthRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + } + } + }, + "MyFunctionCustomInvokeRoleAPI3PermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionCustomInvokeRole" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/MyFunctionCustomInvokeRole", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithAwsIamAuth" + } + } + ] + } + } + }, + "MyFunctionDefaultInvokeRole": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.handler", + "Code": { + "S3Bucket": "bucket", + "S3Key": "key" + }, + "Role": { + "Fn::GetAtt": [ + "MyFunctionDefaultInvokeRoleRole", + "Arn" + ] + }, + "Runtime": "nodejs8.10", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "MyFunctionNoneAuthAPI3PermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionNoneAuth" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/MyFunctionNoneAuth", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithAwsIamAuth" + } + } + ] + } + } + }, + "MyApiWithAwsIamAuth": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/MyFunctionDefaultInvokeRole": { + "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/${MyFunctionDefaultInvokeRole.Arn}/invocations" + }, + "credentials": "arn:aws:iam::*:user/*" + }, + "security": [ + { + "AWS_IAM": [] + } + ], + "responses": {} + } + }, + "/MyFunctionWithoutAuth": { + "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/${MyFunctionWithoutAuth.Arn}/invocations" + }, + "credentials": "arn:aws:iam::123:role/AUTH_AWS_IAM" + }, + "security": [ + { + "AWS_IAM": [] + } + ], + "responses": {} + } + }, + "/MyFunctionNoneAuth": { + "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/${MyFunctionNoneAuth.Arn}/invocations" + } + }, + "security": [ + { + "NONE": [] + } + ], + "responses": {} + } + }, + "/MyFunctionMyCognitoAuth": { + "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/${MyFunctionMyCognitoAuth.Arn}/invocations" + } + }, + "security": [ + { + "MyCognitoAuth": [] + } + ], + "responses": {} + } + }, + "/MyFunctionCustomInvokeRole": { + "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/${MyFunctionCustomInvokeRole.Arn}/invocations" + }, + "credentials": "arn:aws:iam::456::role/something-else" + }, + "security": [ + { + "AWS_IAM": [] + } + ], + "responses": {} + } + } + }, + "swagger": "2.0", + "securityDefinitions": { + "MyCognitoAuth": { + "in": "header", + "type": "apiKey", + "name": "Authorization", + "x-amazon-apigateway-authorizer": { + "providerARNs": [ + "arn:aws:cognito-idp:xxxxxxxxx" + ], + "type": "cognito_user_pools" + }, + "x-amazon-apigateway-authtype": "cognito_user_pools" + }, + "AWS_IAM": { + "in": "header", + "type": "apiKey", + "name": "Authorization", + "x-amazon-apigateway-authtype": "awsSigv4" + } + } + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } + }, + "MyFunctionNoneAuthAPI3PermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionNoneAuth" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/MyFunctionNoneAuth", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApiWithAwsIamAuth" + } + } + ] + } + } + } + } +} diff --git a/tests/translator/output/aws-cn/api_with_default_aws_iam_auth.json b/tests/translator/output/aws-cn/api_with_default_aws_iam_auth.json new file mode 100644 index 000000000..58fd1f46b --- /dev/null +++ b/tests/translator/output/aws-cn/api_with_default_aws_iam_auth.json @@ -0,0 +1,393 @@ +{ + "Resources": { + "MyFunctionWithAwsIamAuth": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.handler", + "Code": { + "S3Bucket": "bucket", + "S3Key": "key" + }, + "Role": { + "Fn::GetAtt": [ + "MyFunctionWithAwsIamAuthRole", + "Arn" + ] + }, + "Runtime": "nodejs8.10", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "MyApiWithAwsIamAuthAndDefaultInvokeRoleDeploymentd0103947f7": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApiWithAwsIamAuthAndDefaultInvokeRole" + }, + "Description": "RestApi deployment id: d0103947f7e2e1d52ca7afac92f5afc8339a051b", + "StageName": "Stage" + } + }, + "MyFunctionWithAwsIamAuthMyApiWithAwsIamAuthPermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionWithAwsIamAuth" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithAwsIamAuth" + } + } + ] + } + } + }, + "MyFunctionWithAwsIamAuthMyApiWithAwsIamAuthPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionWithAwsIamAuth" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApiWithAwsIamAuth" + } + } + ] + } + } + }, + "MyApiWithAwsIamAuthProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiWithAwsIamAuthDeploymentc8adfb74cf" + }, + "RestApiId": { + "Ref": "MyApiWithAwsIamAuth" + }, + "StageName": "Prod" + } + }, + "MyApiWithAwsIamAuthAndDefaultInvokeRoleProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiWithAwsIamAuthAndDefaultInvokeRoleDeploymentd0103947f7" + }, + "RestApiId": { + "Ref": "MyApiWithAwsIamAuthAndDefaultInvokeRole" + }, + "StageName": "Prod" + } + }, + "MyFunctionWithAwsIamAuthMyApiWithAwsIamAuthAndDefaultInvokeRolePermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionWithAwsIamAuth" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/PUT/", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApiWithAwsIamAuthAndDefaultInvokeRole" + } + } + ] + } + } + }, + "MyFunctionWithAwsIamAuthMyApiWithAwsIamAuthAndDefaultInvokeRolePermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionWithAwsIamAuth" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/PUT/", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithAwsIamAuthAndDefaultInvokeRole" + } + } + ] + } + } + }, + "MyApiWithAwsIamAuthDeploymentc8adfb74cf": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApiWithAwsIamAuth" + }, + "Description": "RestApi deployment id: c8adfb74cfae8b8052802a21a258ecbd5178d144", + "StageName": "Stage" + } + }, + "MyApiWithAwsIamAuthAndCustomInvokeRole": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/": { + "post": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunctionWithAwsIamAuth.Arn}/invocations" + }, + "credentials": "rn:aws:iam::123:role/AUTH_AWS_IAM" + }, + "security": [ + { + "AWS_IAM": [] + } + ], + "responses": {} + } + } + }, + "swagger": "2.0", + "securityDefinitions": { + "AWS_IAM": { + "in": "header", + "type": "apiKey", + "name": "Authorization", + "x-amazon-apigateway-authtype": "awsSigv4" + } + } + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } + }, + "MyApiWithAwsIamAuthAndCustomInvokeRoleProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiWithAwsIamAuthAndCustomInvokeRoleDeployment2a6ecd9264" + }, + "RestApiId": { + "Ref": "MyApiWithAwsIamAuthAndCustomInvokeRole" + }, + "StageName": "Prod" + } + }, + "MyApiWithAwsIamAuthAndDefaultInvokeRole": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/": { + "put": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunctionWithAwsIamAuth.Arn}/invocations" + }, + "credentials": "arn:aws:iam::*:user/*" + }, + "security": [ + { + "AWS_IAM": [] + } + ], + "responses": {} + } + } + }, + "swagger": "2.0", + "securityDefinitions": { + "AWS_IAM": { + "in": "header", + "type": "apiKey", + "name": "Authorization", + "x-amazon-apigateway-authtype": "awsSigv4" + } + } + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } + }, + "MyApiWithAwsIamAuthAndCustomInvokeRoleDeployment2a6ecd9264": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApiWithAwsIamAuthAndCustomInvokeRole" + }, + "Description": "RestApi deployment id: 2a6ecd9264d4f59054caa89a94960604594cd94f", + "StageName": "Stage" + } + }, + "MyApiWithAwsIamAuth": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/": { + "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/${MyFunctionWithAwsIamAuth.Arn}/invocations" + }, + "credentials": "arn:aws:iam::*:user/*" + }, + "security": [ + { + "AWS_IAM": [] + } + ], + "responses": {} + } + } + }, + "swagger": "2.0", + "securityDefinitions": { + "AWS_IAM": { + "in": "header", + "type": "apiKey", + "name": "Authorization", + "x-amazon-apigateway-authtype": "awsSigv4" + } + } + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } + }, + "MyFunctionWithAwsIamAuthMyApiWithAwsIamAuthAndCustomInvokeRolePermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionWithAwsIamAuth" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/POST/", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApiWithAwsIamAuthAndCustomInvokeRole" + } + } + ] + } + } + }, + "MyFunctionWithAwsIamAuthRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + } + } + }, + "MyFunctionWithAwsIamAuthMyApiWithAwsIamAuthAndCustomInvokeRolePermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionWithAwsIamAuth" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/POST/", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithAwsIamAuthAndCustomInvokeRole" + } + } + ] + } + } + } + } +} diff --git a/tests/translator/output/aws-cn/api_with_gateway_responses.json b/tests/translator/output/aws-cn/api_with_gateway_responses.json new file mode 100644 index 000000000..8d7814759 --- /dev/null +++ b/tests/translator/output/aws-cn/api_with_gateway_responses.json @@ -0,0 +1,162 @@ +{ + "Resources": { + "Function": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.gethtml", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "member_portal.zip" + }, + "Role": { + "Fn::GetAtt": [ + "FunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs4.3", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "ExplicitApiDeployment9196b651da": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "ExplicitApi" + }, + "Description": "RestApi deployment id: 9196b651da2a4564831de870a4b6cf9c6bed29b0", + "StageName": "Stage" + } + }, + "FunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + } + } + }, + "ExplicitApi": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/": { + "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/${Function.Arn}/invocations" + } + }, + "responses": {} + } + } + }, + "swagger": "2.0", + "x-amazon-apigateway-gateway-responses": { + "UNAUTHORIZED": { + "responseParameters": { + "gatewayresponse.header.WWW-Authenticate": "'Bearer realm=\"admin\"'", + "gatewayresponse.header.Access-Control-Expose-Headers": "'WWW-Authenticate'", + "gatewayresponse.header.Access-Control-Allow-Origin": "'*'" + }, + "responseTemplates": {}, + "statusCode": "401" + } + } + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } + }, + "ExplicitApiProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "ExplicitApiDeployment9196b651da" + }, + "RestApiId": { + "Ref": "ExplicitApi" + }, + "StageName": "Prod" + } + }, + "FunctionGetHtmlPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "Function" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "ExplicitApi" + } + } + ] + } + } + }, + "FunctionGetHtmlPermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "Function" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "ExplicitApi" + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-cn/api_with_gateway_responses_all.json b/tests/translator/output/aws-cn/api_with_gateway_responses_all.json new file mode 100644 index 000000000..25df8a738 --- /dev/null +++ b/tests/translator/output/aws-cn/api_with_gateway_responses_all.json @@ -0,0 +1,171 @@ +{ + "Resources": { + "Function": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.gethtml", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "member_portal.zip" + }, + "Role": { + "Fn::GetAtt": [ + "FunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs4.3", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "FunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + } + } + }, + "ExplicitApiDeploymenta27cf1b1d7": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "ExplicitApi" + }, + "Description": "RestApi deployment id: a27cf1b1d762583a4e0873d7a1eb25bea7966067", + "StageName": "Stage" + } + }, + "ExplicitApi": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/": { + "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/${Function.Arn}/invocations" + } + }, + "responses": {} + } + } + }, + "swagger": "2.0", + "x-amazon-apigateway-gateway-responses": { + "QUOTA_EXCEEDED": { + "responseParameters": { + "gatewayresponse.header.Retry-After": "'31536000'" + }, + "responseTemplates": {}, + "statusCode": "429" + }, + "UNAUTHORIZED": { + "responseParameters": { + "gatewayresponse.header.WWW-Authenticate": "'Bearer realm=\"admin\"'", + "gatewayresponse.header.Access-Control-Expose-Headers": "'WWW-Authenticate'", + "gatewayresponse.header.Access-Control-Allow-Origin": "'*'", + "gatewayresponse.path.PathKey": "'path-value'", + "gatewayresponse.querystring.QueryStringKey": "'query-string-value'" + }, + "responseTemplates": {}, + "statusCode": "401" + } + } + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } + }, + "ExplicitApiProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "ExplicitApiDeploymenta27cf1b1d7" + }, + "RestApiId": { + "Ref": "ExplicitApi" + }, + "StageName": "Prod" + } + }, + "FunctionGetHtmlPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "Function" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "ExplicitApi" + } + } + ] + } + } + }, + "FunctionGetHtmlPermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "Function" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "ExplicitApi" + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-cn/api_with_gateway_responses_implicit.json b/tests/translator/output/aws-cn/api_with_gateway_responses_implicit.json new file mode 100644 index 000000000..e9670182d --- /dev/null +++ b/tests/translator/output/aws-cn/api_with_gateway_responses_implicit.json @@ -0,0 +1,163 @@ +{ + "Resources": { + "Function": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.gethtml", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "member_portal.zip" + }, + "Role": { + "Fn::GetAtt": [ + "FunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs4.3", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "FunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + } + } + }, + "ServerlessRestApiDeployment9196b651da": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "ServerlessRestApi" + }, + "Description": "RestApi deployment id: 9196b651da2a4564831de870a4b6cf9c6bed29b0", + "StageName": "Stage" + } + }, + "ServerlessRestApiProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "ServerlessRestApiDeployment9196b651da" + }, + "RestApiId": { + "Ref": "ServerlessRestApi" + }, + "StageName": "Prod" + } + }, + "FunctionGetHtmlPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "Function" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "ServerlessRestApi" + } + } + ] + } + } + }, + "ServerlessRestApi": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/": { + "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/${Function.Arn}/invocations" + } + }, + "responses": {} + } + } + }, + "swagger": "2.0", + "x-amazon-apigateway-gateway-responses": { + "UNAUTHORIZED": { + "responseParameters": { + "gatewayresponse.header.WWW-Authenticate": "'Bearer realm=\"admin\"'", + "gatewayresponse.header.Access-Control-Expose-Headers": "'WWW-Authenticate'", + "gatewayresponse.header.Access-Control-Allow-Origin": "'*'" + }, + "responseTemplates": {}, + "statusCode": "401" + } + } + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Name": "some api", + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } + }, + "FunctionGetHtmlPermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "Function" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "ServerlessRestApi" + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-cn/api_with_gateway_responses_minimal.json b/tests/translator/output/aws-cn/api_with_gateway_responses_minimal.json new file mode 100644 index 000000000..7a92ecdeb --- /dev/null +++ b/tests/translator/output/aws-cn/api_with_gateway_responses_minimal.json @@ -0,0 +1,157 @@ +{ + "Resources": { + "Function": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.gethtml", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "member_portal.zip" + }, + "Role": { + "Fn::GetAtt": [ + "FunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs4.3", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "FunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + } + } + }, + "ExplicitApi": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/": { + "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/${Function.Arn}/invocations" + } + }, + "responses": {} + } + } + }, + "swagger": "2.0", + "x-amazon-apigateway-gateway-responses": { + "UNAUTHORIZED": { + "responseParameters": {}, + "responseTemplates": {} + } + } + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } + }, + "ExplicitApiDeployment8f2919f3e3": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "ExplicitApi" + }, + "Description": "RestApi deployment id: 8f2919f3e3e2816355b75920c4482c085c6124a2", + "StageName": "Stage" + } + }, + "ExplicitApiProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "ExplicitApiDeployment8f2919f3e3" + }, + "RestApiId": { + "Ref": "ExplicitApi" + }, + "StageName": "Prod" + } + }, + "FunctionGetHtmlPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "Function" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "ExplicitApi" + } + } + ] + } + } + }, + "FunctionGetHtmlPermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "Function" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "ExplicitApi" + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-cn/api_with_gateway_responses_string_status_code.json b/tests/translator/output/aws-cn/api_with_gateway_responses_string_status_code.json new file mode 100644 index 000000000..8d7814759 --- /dev/null +++ b/tests/translator/output/aws-cn/api_with_gateway_responses_string_status_code.json @@ -0,0 +1,162 @@ +{ + "Resources": { + "Function": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.gethtml", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "member_portal.zip" + }, + "Role": { + "Fn::GetAtt": [ + "FunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs4.3", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "ExplicitApiDeployment9196b651da": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "ExplicitApi" + }, + "Description": "RestApi deployment id: 9196b651da2a4564831de870a4b6cf9c6bed29b0", + "StageName": "Stage" + } + }, + "FunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + } + } + }, + "ExplicitApi": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/": { + "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/${Function.Arn}/invocations" + } + }, + "responses": {} + } + } + }, + "swagger": "2.0", + "x-amazon-apigateway-gateway-responses": { + "UNAUTHORIZED": { + "responseParameters": { + "gatewayresponse.header.WWW-Authenticate": "'Bearer realm=\"admin\"'", + "gatewayresponse.header.Access-Control-Expose-Headers": "'WWW-Authenticate'", + "gatewayresponse.header.Access-Control-Allow-Origin": "'*'" + }, + "responseTemplates": {}, + "statusCode": "401" + } + } + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } + }, + "ExplicitApiProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "ExplicitApiDeployment9196b651da" + }, + "RestApiId": { + "Ref": "ExplicitApi" + }, + "StageName": "Prod" + } + }, + "FunctionGetHtmlPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "Function" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "ExplicitApi" + } + } + ] + } + } + }, + "FunctionGetHtmlPermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "Function" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "ExplicitApi" + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-cn/api_with_method_aws_iam_auth.json b/tests/translator/output/aws-cn/api_with_method_aws_iam_auth.json new file mode 100644 index 000000000..f0be0f4a1 --- /dev/null +++ b/tests/translator/output/aws-cn/api_with_method_aws_iam_auth.json @@ -0,0 +1,281 @@ +{ + "Resources": { + "MyFunctionWithAwsIamAuthMyApiWithAwsIamAuthPermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionWithAwsIamAuth" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithoutAuth" + } + } + ] + } + } + }, + "MyFunctionWithAwsIamAuthMyApiWithAwsIamAuthPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionWithAwsIamAuth" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApiWithoutAuth" + } + } + ] + } + } + }, + "MyApiWithoutAuthDeployment82d56c6578": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApiWithoutAuth" + }, + "Description": "RestApi deployment id: 82d56c65786c0de97eebda5ce6fdc9561ae3ee1f", + "StageName": "Stage" + } + }, + "MyApiWithoutAuth": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/": { + "put": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunctionWithAwsIamAuth.Arn}/invocations" + }, + "credentials": "arn:aws:iam::*:user/*" + }, + "security": [ + { + "AWS_IAM": [] + } + ], + "responses": {} + }, + "post": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunctionWithAwsIamAuth.Arn}/invocations" + }, + "credentials": "rn:aws:iam::123:role/AUTH_AWS_IAM" + }, + "security": [ + { + "AWS_IAM": [] + } + ], + "responses": {} + }, + "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/${MyFunctionWithAwsIamAuth.Arn}/invocations" + }, + "credentials": "arn:aws:iam::*:user/*" + }, + "security": [ + { + "AWS_IAM": [] + } + ], + "responses": {} + } + } + }, + "swagger": "2.0", + "securityDefinitions": { + "AWS_IAM": { + "x-amazon-apigateway-authtype": "awsSigv4", + "type": "apiKey", + "name": "Authorization", + "in": "header" + } + } + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } + }, + "MyFunctionWithAwsIamAuthMyApiWithAwsIamAuthAndDefaultInvokeRolePermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionWithAwsIamAuth" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/PUT/", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApiWithoutAuth" + } + } + ] + } + } + }, + "MyFunctionWithAwsIamAuthMyApiWithAwsIamAuthAndDefaultInvokeRolePermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionWithAwsIamAuth" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/PUT/", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithoutAuth" + } + } + ] + } + } + }, + "MyFunctionWithAwsIamAuth": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.handler", + "Code": { + "S3Bucket": "bucket", + "S3Key": "key" + }, + "Role": { + "Fn::GetAtt": [ + "MyFunctionWithAwsIamAuthRole", + "Arn" + ] + }, + "Runtime": "nodejs8.10", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "MyApiWithoutAuthProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiWithoutAuthDeployment82d56c6578" + }, + "RestApiId": { + "Ref": "MyApiWithoutAuth" + }, + "StageName": "Prod" + } + }, + "MyFunctionWithAwsIamAuthMyApiWithAwsIamAuthAndCustomInvokeRolePermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionWithAwsIamAuth" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/POST/", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApiWithoutAuth" + } + } + ] + } + } + }, + "MyFunctionWithAwsIamAuthRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + } + } + }, + "MyFunctionWithAwsIamAuthMyApiWithAwsIamAuthAndCustomInvokeRolePermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionWithAwsIamAuth" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/POST/", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithoutAuth" + } + } + ] + } + } + } + } +} diff --git a/tests/translator/output/aws-cn/application_with_intrinsics.json b/tests/translator/output/aws-cn/application_with_intrinsics.json new file mode 100644 index 000000000..3cbe5aa31 --- /dev/null +++ b/tests/translator/output/aws-cn/application_with_intrinsics.json @@ -0,0 +1,70 @@ +{ + "Resources": { + "ApplicationRefParameter": { + "Type": "AWS::CloudFormation::Stack", + "Properties": { + "TemplateURL": "https://awsserverlessrepo-changesets-xxx.s3.amazonaws.com/signed-url", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + }, + { + "Value": "arn:aws:serverlessrepo:us-east-1:123456789012:applications/hello-world", + "Key": "serverlessrepo:applicationId" + }, + { + "Value": "1.0.0", + "Key": "serverlessrepo:semanticVersion" + } + ] + } + }, + "ApplicationFindInMap": { + "Type": "AWS::CloudFormation::Stack", + "Properties": { + "TemplateURL": "https://awsserverlessrepo-changesets-xxx.s3.amazonaws.com/signed-url", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + }, + { + "Value": "arn:aws-cn:serverlessrepo:cn-north-1:123456789012:applications/hello-world", + "Key": "serverlessrepo:applicationId" + }, + { + "Value": "1.0.2", + "Key": "serverlessrepo:semanticVersion" + } + ] + } + } + }, + "Parameters": { + "ApplicationIdParam": { + "Type": "String", + "Default": "arn:aws:serverlessrepo:us-east-1:123456789012:applications/hello-world" + }, + "VersionParam": { + "Type": "String", + "Default": "1.0.0" + } + }, + "Mappings": { + "ApplicationLocations": { + "ap-southeast-1": { + "ApplicationId": "arn:aws:serverlessrepo:ap-southeast-1:123456789012:applications/hello-world", + "Version": "1.0.1" + }, + "cn-north-1": { + "ApplicationId": "arn:aws-cn:serverlessrepo:cn-north-1:123456789012:applications/hello-world", + "Version": "1.0.2" + }, + "us-gov-west-1": { + "ApplicationId": "arn:aws-gov:serverlessrepo:us-gov-west-1:123456789012:applications/hello-world", + "Version": "1.0.3" + } + } + } +} diff --git a/tests/translator/output/aws-cn/function_with_alias.json b/tests/translator/output/aws-cn/function_with_alias.json index 588842ae2..a64da6577 100644 --- a/tests/translator/output/aws-cn/function_with_alias.json +++ b/tests/translator/output/aws-cn/function_with_alias.json @@ -4,6 +4,7 @@ "DeletionPolicy": "Retain", "Type": "AWS::Lambda::Version", "Properties": { + "Description": "sam-testing", "FunctionName": { "Ref": "MinimalFunction" } diff --git a/tests/translator/output/aws-cn/globals_for_function.json b/tests/translator/output/aws-cn/globals_for_function.json index e2115176f..a84c1d288 100644 --- a/tests/translator/output/aws-cn/globals_for_function.json +++ b/tests/translator/output/aws-cn/globals_for_function.json @@ -57,6 +57,7 @@ } ], "MemorySize": 512, + "ReservedConcurrentExecutions": 100, "Environment": { "Variables": { "Var1": "value1", @@ -146,6 +147,7 @@ } ], "MemorySize": 1024, + "ReservedConcurrentExecutions": 50, "Environment": { "Variables": { "Var1": "value1", diff --git a/tests/translator/output/aws-cn/layers_with_intrinsics.json b/tests/translator/output/aws-cn/layers_with_intrinsics.json index 4e0e3f20a..c8351374f 100644 --- a/tests/translator/output/aws-cn/layers_with_intrinsics.json +++ b/tests/translator/output/aws-cn/layers_with_intrinsics.json @@ -24,6 +24,30 @@ "LayerName": "SomeLayerName" } }, + "LayerWithRefNameIntrinsicRegion186db7e435": { + "DeletionPolicy": "Retain", + "Type": "AWS::Lambda::LayerVersion", + "Properties": { + "Content": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "layer.zip" + }, + "LayerName": "cn-north-1" + } + }, + "LayerWithSubNameIntrinsicRegionfbc3f9f13d": { + "DeletionPolicy": "Retain", + "Type": "AWS::Lambda::LayerVersion", + "Properties": { + "Content": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "layer.zip" + }, + "LayerName": { + "Fn::Sub": "layer-cn-north-1" + } + } + }, "LayerWithRuntimesIntrinsic1a006faa85": { "DeletionPolicy": "Retain", "Type": "AWS::Lambda::LayerVersion", diff --git a/tests/translator/output/aws-us-gov/all_policy_templates.json b/tests/translator/output/aws-us-gov/all_policy_templates.json index 871b55a25..de5379c63 100644 --- a/tests/translator/output/aws-us-gov/all_policy_templates.json +++ b/tests/translator/output/aws-us-gov/all_policy_templates.json @@ -211,7 +211,7 @@ ], "Resource": { "Fn::Sub": [ - "arn:${AWS::Partition}:es:${AWS::Region}:${AWS::AccountId}:domain/${domainName}", + "arn:${AWS::Partition}:es:${AWS::Region}:${AWS::AccountId}:domain/${domainName}/*", { "domainName": "name" } @@ -724,9 +724,11 @@ "Action": [ "serverlessrepo:CreateApplication", "serverlessrepo:CreateApplicationVersion", + "serverlessrepo:UpdateApplication", "serverlessrepo:GetApplication", "serverlessrepo:ListApplications", - "serverlessrepo:ListApplicationVersions" + "serverlessrepo:ListApplicationVersions", + "serverlessrepo:ListApplicationDependencies" ], "Resource": [ { diff --git a/tests/translator/output/aws-us-gov/api_with_aws_iam_auth_overrides.json b/tests/translator/output/aws-us-gov/api_with_aws_iam_auth_overrides.json new file mode 100644 index 000000000..cf8d21950 --- /dev/null +++ b/tests/translator/output/aws-us-gov/api_with_aws_iam_auth_overrides.json @@ -0,0 +1,603 @@ +{ + "Resources": { + "MyFunctionCustomInvokeRole": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.handler", + "Code": { + "S3Bucket": "bucket", + "S3Key": "key" + }, + "Role": { + "Fn::GetAtt": [ + "MyFunctionCustomInvokeRoleRole", + "Arn" + ] + }, + "Runtime": "nodejs8.10", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "MyFunctionNoneAuth": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.handler", + "Code": { + "S3Bucket": "bucket", + "S3Key": "key" + }, + "Role": { + "Fn::GetAtt": [ + "MyFunctionNoneAuthRole", + "Arn" + ] + }, + "Runtime": "nodejs8.10", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "MyApiWithAwsIamAuthDeploymentd6dd2d1504": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApiWithAwsIamAuth" + }, + "Description": "RestApi deployment id: d6dd2d1504ea960bdb50055b256d9292aa565e4b", + "StageName": "Stage" + } + }, + "MyApiWithAwsIamAuthProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiWithAwsIamAuthDeploymentd6dd2d1504" + }, + "RestApiId": { + "Ref": "MyApiWithAwsIamAuth" + }, + "StageName": "Prod" + } + }, + "MyFunctionWithoutAuthRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + } + } + }, + "MyFunctionCustomInvokeRoleAPI3PermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionCustomInvokeRole" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/MyFunctionCustomInvokeRole", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApiWithAwsIamAuth" + } + } + ] + } + } + }, + "MyFunctionWithoutAuthAPI2PermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionWithoutAuth" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/MyFunctionWithoutAuth", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApiWithAwsIamAuth" + } + } + ] + } + } + }, + "MyFunctionMyCognitoAuthAPI1PermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionMyCognitoAuth" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/MyFunctionMyCognitoAuth", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithAwsIamAuth" + } + } + ] + } + } + }, + "MyFunctionMyCognitoAuth": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.handler", + "Code": { + "S3Bucket": "bucket", + "S3Key": "key" + }, + "Role": { + "Fn::GetAtt": [ + "MyFunctionMyCognitoAuthRole", + "Arn" + ] + }, + "Runtime": "nodejs8.10", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "MyFunctionDefaultInvokeRoleAPI3PermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionDefaultInvokeRole" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/MyFunctionDefaultInvokeRole", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApiWithAwsIamAuth" + } + } + ] + } + } + }, + "MyFunctionNoneAuthRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + } + } + }, + "MyFunctionCustomInvokeRoleRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + } + } + }, + "MyFunctionDefaultInvokeRoleAPI3PermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionDefaultInvokeRole" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/MyFunctionDefaultInvokeRole", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithAwsIamAuth" + } + } + ] + } + } + }, + "MyFunctionWithoutAuthAPI2PermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionWithoutAuth" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/MyFunctionWithoutAuth", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithAwsIamAuth" + } + } + ] + } + } + }, + "MyFunctionWithoutAuth": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.handler", + "Code": { + "S3Bucket": "bucket", + "S3Key": "key" + }, + "Role": { + "Fn::GetAtt": [ + "MyFunctionWithoutAuthRole", + "Arn" + ] + }, + "Runtime": "nodejs8.10", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "MyFunctionMyCognitoAuthAPI1PermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionMyCognitoAuth" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/MyFunctionMyCognitoAuth", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApiWithAwsIamAuth" + } + } + ] + } + } + }, + "MyFunctionDefaultInvokeRoleRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + } + } + }, + "MyFunctionMyCognitoAuthRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + } + } + }, + "MyFunctionCustomInvokeRoleAPI3PermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionCustomInvokeRole" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/MyFunctionCustomInvokeRole", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithAwsIamAuth" + } + } + ] + } + } + }, + "MyFunctionDefaultInvokeRole": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.handler", + "Code": { + "S3Bucket": "bucket", + "S3Key": "key" + }, + "Role": { + "Fn::GetAtt": [ + "MyFunctionDefaultInvokeRoleRole", + "Arn" + ] + }, + "Runtime": "nodejs8.10", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "MyFunctionNoneAuthAPI3PermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionNoneAuth" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/MyFunctionNoneAuth", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithAwsIamAuth" + } + } + ] + } + } + }, + "MyApiWithAwsIamAuth": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/MyFunctionDefaultInvokeRole": { + "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/${MyFunctionDefaultInvokeRole.Arn}/invocations" + }, + "credentials": "arn:aws:iam::*:user/*" + }, + "security": [ + { + "AWS_IAM": [] + } + ], + "responses": {} + } + }, + "/MyFunctionWithoutAuth": { + "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/${MyFunctionWithoutAuth.Arn}/invocations" + }, + "credentials": "arn:aws:iam::123:role/AUTH_AWS_IAM" + }, + "security": [ + { + "AWS_IAM": [] + } + ], + "responses": {} + } + }, + "/MyFunctionNoneAuth": { + "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/${MyFunctionNoneAuth.Arn}/invocations" + } + }, + "security": [ + { + "NONE": [] + } + ], + "responses": {} + } + }, + "/MyFunctionMyCognitoAuth": { + "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/${MyFunctionMyCognitoAuth.Arn}/invocations" + } + }, + "security": [ + { + "MyCognitoAuth": [] + } + ], + "responses": {} + } + }, + "/MyFunctionCustomInvokeRole": { + "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/${MyFunctionCustomInvokeRole.Arn}/invocations" + }, + "credentials": "arn:aws:iam::456::role/something-else" + }, + "security": [ + { + "AWS_IAM": [] + } + ], + "responses": {} + } + } + }, + "swagger": "2.0", + "securityDefinitions": { + "MyCognitoAuth": { + "in": "header", + "type": "apiKey", + "name": "Authorization", + "x-amazon-apigateway-authorizer": { + "providerARNs": [ + "arn:aws:cognito-idp:xxxxxxxxx" + ], + "type": "cognito_user_pools" + }, + "x-amazon-apigateway-authtype": "cognito_user_pools" + }, + "AWS_IAM": { + "in": "header", + "type": "apiKey", + "name": "Authorization", + "x-amazon-apigateway-authtype": "awsSigv4" + } + } + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } + }, + "MyFunctionNoneAuthAPI3PermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionNoneAuth" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/MyFunctionNoneAuth", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApiWithAwsIamAuth" + } + } + ] + } + } + } + } +} diff --git a/tests/translator/output/aws-us-gov/api_with_default_aws_iam_auth.json b/tests/translator/output/aws-us-gov/api_with_default_aws_iam_auth.json new file mode 100644 index 000000000..2adb4725b --- /dev/null +++ b/tests/translator/output/aws-us-gov/api_with_default_aws_iam_auth.json @@ -0,0 +1,393 @@ +{ + "Resources": { + "MyFunctionWithAwsIamAuth": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.handler", + "Code": { + "S3Bucket": "bucket", + "S3Key": "key" + }, + "Role": { + "Fn::GetAtt": [ + "MyFunctionWithAwsIamAuthRole", + "Arn" + ] + }, + "Runtime": "nodejs8.10", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "MyFunctionWithAwsIamAuthMyApiWithAwsIamAuthPermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionWithAwsIamAuth" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithAwsIamAuth" + } + } + ] + } + } + }, + "MyApiWithAwsIamAuthDeploymentf9a4964a7d": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApiWithAwsIamAuth" + }, + "Description": "RestApi deployment id: f9a4964a7df5021874e5a094662f1a2443982e0a", + "StageName": "Stage" + } + }, + "MyFunctionWithAwsIamAuthMyApiWithAwsIamAuthPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionWithAwsIamAuth" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApiWithAwsIamAuth" + } + } + ] + } + } + }, + "MyApiWithAwsIamAuthProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiWithAwsIamAuthDeploymentf9a4964a7d" + }, + "RestApiId": { + "Ref": "MyApiWithAwsIamAuth" + }, + "StageName": "Prod" + } + }, + "MyApiWithAwsIamAuthAndDefaultInvokeRoleProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiWithAwsIamAuthAndDefaultInvokeRoleDeploymentce2dead7de" + }, + "RestApiId": { + "Ref": "MyApiWithAwsIamAuthAndDefaultInvokeRole" + }, + "StageName": "Prod" + } + }, + "MyFunctionWithAwsIamAuthMyApiWithAwsIamAuthAndDefaultInvokeRolePermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionWithAwsIamAuth" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/PUT/", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApiWithAwsIamAuthAndDefaultInvokeRole" + } + } + ] + } + } + }, + "MyFunctionWithAwsIamAuthMyApiWithAwsIamAuthAndDefaultInvokeRolePermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionWithAwsIamAuth" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/PUT/", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithAwsIamAuthAndDefaultInvokeRole" + } + } + ] + } + } + }, + "MyApiWithAwsIamAuthAndDefaultInvokeRoleDeploymentce2dead7de": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApiWithAwsIamAuthAndDefaultInvokeRole" + }, + "Description": "RestApi deployment id: ce2dead7ded0bb502db5bd0c105948c27bc96729", + "StageName": "Stage" + } + }, + "MyApiWithAwsIamAuthAndCustomInvokeRoleDeploymentb31aa75bb2": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApiWithAwsIamAuthAndCustomInvokeRole" + }, + "Description": "RestApi deployment id: b31aa75bb2284229d40917a9b424c8ea1cf98217", + "StageName": "Stage" + } + }, + "MyApiWithAwsIamAuthAndCustomInvokeRole": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/": { + "post": { + "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/${MyFunctionWithAwsIamAuth.Arn}/invocations" + }, + "credentials": "rn:aws:iam::123:role/AUTH_AWS_IAM" + }, + "security": [ + { + "AWS_IAM": [] + } + ], + "responses": {} + } + } + }, + "swagger": "2.0", + "securityDefinitions": { + "AWS_IAM": { + "in": "header", + "type": "apiKey", + "name": "Authorization", + "x-amazon-apigateway-authtype": "awsSigv4" + } + } + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } + }, + "MyApiWithAwsIamAuthAndCustomInvokeRoleProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiWithAwsIamAuthAndCustomInvokeRoleDeploymentb31aa75bb2" + }, + "RestApiId": { + "Ref": "MyApiWithAwsIamAuthAndCustomInvokeRole" + }, + "StageName": "Prod" + } + }, + "MyApiWithAwsIamAuthAndDefaultInvokeRole": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/": { + "put": { + "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/${MyFunctionWithAwsIamAuth.Arn}/invocations" + }, + "credentials": "arn:aws:iam::*:user/*" + }, + "security": [ + { + "AWS_IAM": [] + } + ], + "responses": {} + } + } + }, + "swagger": "2.0", + "securityDefinitions": { + "AWS_IAM": { + "in": "header", + "type": "apiKey", + "name": "Authorization", + "x-amazon-apigateway-authtype": "awsSigv4" + } + } + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } + }, + "MyApiWithAwsIamAuth": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/": { + "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/${MyFunctionWithAwsIamAuth.Arn}/invocations" + }, + "credentials": "arn:aws:iam::*:user/*" + }, + "security": [ + { + "AWS_IAM": [] + } + ], + "responses": {} + } + } + }, + "swagger": "2.0", + "securityDefinitions": { + "AWS_IAM": { + "in": "header", + "type": "apiKey", + "name": "Authorization", + "x-amazon-apigateway-authtype": "awsSigv4" + } + } + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } + }, + "MyFunctionWithAwsIamAuthMyApiWithAwsIamAuthAndCustomInvokeRolePermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionWithAwsIamAuth" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/POST/", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApiWithAwsIamAuthAndCustomInvokeRole" + } + } + ] + } + } + }, + "MyFunctionWithAwsIamAuthRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + } + } + }, + "MyFunctionWithAwsIamAuthMyApiWithAwsIamAuthAndCustomInvokeRolePermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionWithAwsIamAuth" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/POST/", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithAwsIamAuthAndCustomInvokeRole" + } + } + ] + } + } + } + } +} diff --git a/tests/translator/output/aws-us-gov/api_with_gateway_responses.json b/tests/translator/output/aws-us-gov/api_with_gateway_responses.json new file mode 100644 index 000000000..ab983dba3 --- /dev/null +++ b/tests/translator/output/aws-us-gov/api_with_gateway_responses.json @@ -0,0 +1,162 @@ +{ + "Resources": { + "Function": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.gethtml", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "member_portal.zip" + }, + "Role": { + "Fn::GetAtt": [ + "FunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs4.3", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "ExplicitApiDeployment929d2126a7": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "ExplicitApi" + }, + "Description": "RestApi deployment id: 929d2126a7a3b228757ccb4aa5e896c1bd5c76e8", + "StageName": "Stage" + } + }, + "FunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + } + } + }, + "ExplicitApi": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/": { + "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/${Function.Arn}/invocations" + } + }, + "responses": {} + } + } + }, + "swagger": "2.0", + "x-amazon-apigateway-gateway-responses": { + "UNAUTHORIZED": { + "responseParameters": { + "gatewayresponse.header.WWW-Authenticate": "'Bearer realm=\"admin\"'", + "gatewayresponse.header.Access-Control-Expose-Headers": "'WWW-Authenticate'", + "gatewayresponse.header.Access-Control-Allow-Origin": "'*'" + }, + "responseTemplates": {}, + "statusCode": "401" + } + } + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } + }, + "ExplicitApiProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "ExplicitApiDeployment929d2126a7" + }, + "RestApiId": { + "Ref": "ExplicitApi" + }, + "StageName": "Prod" + } + }, + "FunctionGetHtmlPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "Function" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "ExplicitApi" + } + } + ] + } + } + }, + "FunctionGetHtmlPermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "Function" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "ExplicitApi" + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/api_with_gateway_responses_all.json b/tests/translator/output/aws-us-gov/api_with_gateway_responses_all.json new file mode 100644 index 000000000..88c0ef6ff --- /dev/null +++ b/tests/translator/output/aws-us-gov/api_with_gateway_responses_all.json @@ -0,0 +1,171 @@ +{ + "Resources": { + "Function": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.gethtml", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "member_portal.zip" + }, + "Role": { + "Fn::GetAtt": [ + "FunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs4.3", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "ExplicitApiDeployment42329c77b0": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "ExplicitApi" + }, + "Description": "RestApi deployment id: 42329c77b0faabe3a86487f92af32b9afe0a0fa1", + "StageName": "Stage" + } + }, + "FunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + } + } + }, + "ExplicitApi": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/": { + "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/${Function.Arn}/invocations" + } + }, + "responses": {} + } + } + }, + "swagger": "2.0", + "x-amazon-apigateway-gateway-responses": { + "QUOTA_EXCEEDED": { + "responseParameters": { + "gatewayresponse.header.Retry-After": "'31536000'" + }, + "responseTemplates": {}, + "statusCode": "429" + }, + "UNAUTHORIZED": { + "responseParameters": { + "gatewayresponse.header.WWW-Authenticate": "'Bearer realm=\"admin\"'", + "gatewayresponse.header.Access-Control-Expose-Headers": "'WWW-Authenticate'", + "gatewayresponse.header.Access-Control-Allow-Origin": "'*'", + "gatewayresponse.path.PathKey": "'path-value'", + "gatewayresponse.querystring.QueryStringKey": "'query-string-value'" + }, + "responseTemplates": {}, + "statusCode": "401" + } + } + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } + }, + "ExplicitApiProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "ExplicitApiDeployment42329c77b0" + }, + "RestApiId": { + "Ref": "ExplicitApi" + }, + "StageName": "Prod" + } + }, + "FunctionGetHtmlPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "Function" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "ExplicitApi" + } + } + ] + } + } + }, + "FunctionGetHtmlPermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "Function" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "ExplicitApi" + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/api_with_gateway_responses_implicit.json b/tests/translator/output/aws-us-gov/api_with_gateway_responses_implicit.json new file mode 100644 index 000000000..35c0daca3 --- /dev/null +++ b/tests/translator/output/aws-us-gov/api_with_gateway_responses_implicit.json @@ -0,0 +1,163 @@ +{ + "Resources": { + "Function": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.gethtml", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "member_portal.zip" + }, + "Role": { + "Fn::GetAtt": [ + "FunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs4.3", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "FunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + } + } + }, + "ServerlessRestApiProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "ServerlessRestApiDeployment929d2126a7" + }, + "RestApiId": { + "Ref": "ServerlessRestApi" + }, + "StageName": "Prod" + } + }, + "ServerlessRestApiDeployment929d2126a7": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "ServerlessRestApi" + }, + "Description": "RestApi deployment id: 929d2126a7a3b228757ccb4aa5e896c1bd5c76e8", + "StageName": "Stage" + } + }, + "FunctionGetHtmlPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "Function" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "ServerlessRestApi" + } + } + ] + } + } + }, + "ServerlessRestApi": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/": { + "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/${Function.Arn}/invocations" + } + }, + "responses": {} + } + } + }, + "swagger": "2.0", + "x-amazon-apigateway-gateway-responses": { + "UNAUTHORIZED": { + "responseParameters": { + "gatewayresponse.header.WWW-Authenticate": "'Bearer realm=\"admin\"'", + "gatewayresponse.header.Access-Control-Expose-Headers": "'WWW-Authenticate'", + "gatewayresponse.header.Access-Control-Allow-Origin": "'*'" + }, + "responseTemplates": {}, + "statusCode": "401" + } + } + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Name": "some api", + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } + }, + "FunctionGetHtmlPermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "Function" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "ServerlessRestApi" + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/api_with_gateway_responses_minimal.json b/tests/translator/output/aws-us-gov/api_with_gateway_responses_minimal.json new file mode 100644 index 000000000..290a0fbf3 --- /dev/null +++ b/tests/translator/output/aws-us-gov/api_with_gateway_responses_minimal.json @@ -0,0 +1,157 @@ +{ + "Resources": { + "Function": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.gethtml", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "member_portal.zip" + }, + "Role": { + "Fn::GetAtt": [ + "FunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs4.3", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "FunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + } + } + }, + "ExplicitApi": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/": { + "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/${Function.Arn}/invocations" + } + }, + "responses": {} + } + } + }, + "swagger": "2.0", + "x-amazon-apigateway-gateway-responses": { + "UNAUTHORIZED": { + "responseParameters": {}, + "responseTemplates": {} + } + } + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } + }, + "ExplicitApiDeployment40cf320ad7": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "ExplicitApi" + }, + "Description": "RestApi deployment id: 40cf320ad73ddd78e84f215f113837bc07a54769", + "StageName": "Stage" + } + }, + "ExplicitApiProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "ExplicitApiDeployment40cf320ad7" + }, + "RestApiId": { + "Ref": "ExplicitApi" + }, + "StageName": "Prod" + } + }, + "FunctionGetHtmlPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "Function" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "ExplicitApi" + } + } + ] + } + } + }, + "FunctionGetHtmlPermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "Function" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "ExplicitApi" + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/api_with_gateway_responses_string_status_code.json b/tests/translator/output/aws-us-gov/api_with_gateway_responses_string_status_code.json new file mode 100644 index 000000000..ab983dba3 --- /dev/null +++ b/tests/translator/output/aws-us-gov/api_with_gateway_responses_string_status_code.json @@ -0,0 +1,162 @@ +{ + "Resources": { + "Function": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.gethtml", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "member_portal.zip" + }, + "Role": { + "Fn::GetAtt": [ + "FunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs4.3", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "ExplicitApiDeployment929d2126a7": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "ExplicitApi" + }, + "Description": "RestApi deployment id: 929d2126a7a3b228757ccb4aa5e896c1bd5c76e8", + "StageName": "Stage" + } + }, + "FunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + } + } + }, + "ExplicitApi": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/": { + "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/${Function.Arn}/invocations" + } + }, + "responses": {} + } + } + }, + "swagger": "2.0", + "x-amazon-apigateway-gateway-responses": { + "UNAUTHORIZED": { + "responseParameters": { + "gatewayresponse.header.WWW-Authenticate": "'Bearer realm=\"admin\"'", + "gatewayresponse.header.Access-Control-Expose-Headers": "'WWW-Authenticate'", + "gatewayresponse.header.Access-Control-Allow-Origin": "'*'" + }, + "responseTemplates": {}, + "statusCode": "401" + } + } + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } + }, + "ExplicitApiProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "ExplicitApiDeployment929d2126a7" + }, + "RestApiId": { + "Ref": "ExplicitApi" + }, + "StageName": "Prod" + } + }, + "FunctionGetHtmlPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "Function" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "ExplicitApi" + } + } + ] + } + } + }, + "FunctionGetHtmlPermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "Function" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "ExplicitApi" + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/api_with_method_aws_iam_auth.json b/tests/translator/output/aws-us-gov/api_with_method_aws_iam_auth.json new file mode 100644 index 000000000..1d2a8a6bf --- /dev/null +++ b/tests/translator/output/aws-us-gov/api_with_method_aws_iam_auth.json @@ -0,0 +1,281 @@ +{ + "Resources": { + "MyFunctionWithAwsIamAuthMyApiWithAwsIamAuthPermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionWithAwsIamAuth" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithoutAuth" + } + } + ] + } + } + }, + "MyFunctionWithAwsIamAuthMyApiWithAwsIamAuthPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionWithAwsIamAuth" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApiWithoutAuth" + } + } + ] + } + } + }, + "MyApiWithoutAuth": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/": { + "put": { + "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/${MyFunctionWithAwsIamAuth.Arn}/invocations" + }, + "credentials": "arn:aws:iam::*:user/*" + }, + "security": [ + { + "AWS_IAM": [] + } + ], + "responses": {} + }, + "post": { + "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/${MyFunctionWithAwsIamAuth.Arn}/invocations" + }, + "credentials": "rn:aws:iam::123:role/AUTH_AWS_IAM" + }, + "security": [ + { + "AWS_IAM": [] + } + ], + "responses": {} + }, + "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/${MyFunctionWithAwsIamAuth.Arn}/invocations" + }, + "credentials": "arn:aws:iam::*:user/*" + }, + "security": [ + { + "AWS_IAM": [] + } + ], + "responses": {} + } + } + }, + "swagger": "2.0", + "securityDefinitions": { + "AWS_IAM": { + "x-amazon-apigateway-authtype": "awsSigv4", + "type": "apiKey", + "name": "Authorization", + "in": "header" + } + } + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } + }, + "MyFunctionWithAwsIamAuthMyApiWithAwsIamAuthAndDefaultInvokeRolePermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionWithAwsIamAuth" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/PUT/", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApiWithoutAuth" + } + } + ] + } + } + }, + "MyFunctionWithAwsIamAuthMyApiWithAwsIamAuthAndDefaultInvokeRolePermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionWithAwsIamAuth" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/PUT/", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithoutAuth" + } + } + ] + } + } + }, + "MyFunctionWithAwsIamAuth": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.handler", + "Code": { + "S3Bucket": "bucket", + "S3Key": "key" + }, + "Role": { + "Fn::GetAtt": [ + "MyFunctionWithAwsIamAuthRole", + "Arn" + ] + }, + "Runtime": "nodejs8.10", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "MyApiWithoutAuthDeploymentd7b0de15e2": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApiWithoutAuth" + }, + "Description": "RestApi deployment id: d7b0de15e29c2d947cc5a2004fd545fec3260faf", + "StageName": "Stage" + } + }, + "MyApiWithoutAuthProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiWithoutAuthDeploymentd7b0de15e2" + }, + "RestApiId": { + "Ref": "MyApiWithoutAuth" + }, + "StageName": "Prod" + } + }, + "MyFunctionWithAwsIamAuthMyApiWithAwsIamAuthAndCustomInvokeRolePermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionWithAwsIamAuth" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/POST/", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApiWithoutAuth" + } + } + ] + } + } + }, + "MyFunctionWithAwsIamAuthRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + } + } + }, + "MyFunctionWithAwsIamAuthMyApiWithAwsIamAuthAndCustomInvokeRolePermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunctionWithAwsIamAuth" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/POST/", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithoutAuth" + } + } + ] + } + } + } + } +} diff --git a/tests/translator/output/aws-us-gov/application_with_intrinsics.json b/tests/translator/output/aws-us-gov/application_with_intrinsics.json new file mode 100644 index 000000000..7271c2629 --- /dev/null +++ b/tests/translator/output/aws-us-gov/application_with_intrinsics.json @@ -0,0 +1,70 @@ +{ + "Resources": { + "ApplicationRefParameter": { + "Type": "AWS::CloudFormation::Stack", + "Properties": { + "TemplateURL": "https://awsserverlessrepo-changesets-xxx.s3.amazonaws.com/signed-url", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + }, + { + "Value": "arn:aws:serverlessrepo:us-east-1:123456789012:applications/hello-world", + "Key": "serverlessrepo:applicationId" + }, + { + "Value": "1.0.0", + "Key": "serverlessrepo:semanticVersion" + } + ] + } + }, + "ApplicationFindInMap": { + "Type": "AWS::CloudFormation::Stack", + "Properties": { + "TemplateURL": "https://awsserverlessrepo-changesets-xxx.s3.amazonaws.com/signed-url", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + }, + { + "Value": "arn:aws-gov:serverlessrepo:us-gov-west-1:123456789012:applications/hello-world", + "Key": "serverlessrepo:applicationId" + }, + { + "Value": "1.0.3", + "Key": "serverlessrepo:semanticVersion" + } + ] + } + } + }, + "Parameters": { + "ApplicationIdParam": { + "Type": "String", + "Default": "arn:aws:serverlessrepo:us-east-1:123456789012:applications/hello-world" + }, + "VersionParam": { + "Type": "String", + "Default": "1.0.0" + } + }, + "Mappings": { + "ApplicationLocations": { + "ap-southeast-1": { + "ApplicationId": "arn:aws:serverlessrepo:ap-southeast-1:123456789012:applications/hello-world", + "Version": "1.0.1" + }, + "cn-north-1": { + "ApplicationId": "arn:aws-cn:serverlessrepo:cn-north-1:123456789012:applications/hello-world", + "Version": "1.0.2" + }, + "us-gov-west-1": { + "ApplicationId": "arn:aws-gov:serverlessrepo:us-gov-west-1:123456789012:applications/hello-world", + "Version": "1.0.3" + } + } + } +} diff --git a/tests/translator/output/aws-us-gov/function_with_alias.json b/tests/translator/output/aws-us-gov/function_with_alias.json index 6dadd4080..f1dc1e6c4 100644 --- a/tests/translator/output/aws-us-gov/function_with_alias.json +++ b/tests/translator/output/aws-us-gov/function_with_alias.json @@ -4,6 +4,7 @@ "DeletionPolicy": "Retain", "Type": "AWS::Lambda::Version", "Properties": { + "Description": "sam-testing", "FunctionName": { "Ref": "MinimalFunction" } diff --git a/tests/translator/output/aws-us-gov/globals_for_function.json b/tests/translator/output/aws-us-gov/globals_for_function.json index d00126709..bd87cc916 100644 --- a/tests/translator/output/aws-us-gov/globals_for_function.json +++ b/tests/translator/output/aws-us-gov/globals_for_function.json @@ -57,6 +57,7 @@ } ], "MemorySize": 512, + "ReservedConcurrentExecutions": 100, "Environment": { "Variables": { "Var1": "value1", @@ -146,6 +147,7 @@ } ], "MemorySize": 1024, + "ReservedConcurrentExecutions": 50, "Environment": { "Variables": { "Var1": "value1", diff --git a/tests/translator/output/aws-us-gov/layers_with_intrinsics.json b/tests/translator/output/aws-us-gov/layers_with_intrinsics.json index 4e0e3f20a..a0a46e7d7 100644 --- a/tests/translator/output/aws-us-gov/layers_with_intrinsics.json +++ b/tests/translator/output/aws-us-gov/layers_with_intrinsics.json @@ -24,6 +24,30 @@ "LayerName": "SomeLayerName" } }, + "LayerWithRefNameIntrinsicRegionad31c93c8b": { + "DeletionPolicy": "Retain", + "Type": "AWS::Lambda::LayerVersion", + "Properties": { + "Content": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "layer.zip" + }, + "LayerName": "us-gov-west-1" + } + }, + "LayerWithSubNameIntrinsicRegion5b2c74d55e": { + "DeletionPolicy": "Retain", + "Type": "AWS::Lambda::LayerVersion", + "Properties": { + "Content": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "layer.zip" + }, + "LayerName": { + "Fn::Sub": "layer-us-gov-west-1" + } + } + }, "LayerWithRuntimesIntrinsic1a006faa85": { "DeletionPolicy": "Retain", "Type": "AWS::Lambda::LayerVersion", diff --git a/tests/translator/output/error_api_gateway_responses_nonnumeric_status_code.json b/tests/translator/output/error_api_gateway_responses_nonnumeric_status_code.json new file mode 100644 index 000000000..3a404715c --- /dev/null +++ b/tests/translator/output/error_api_gateway_responses_nonnumeric_status_code.json @@ -0,0 +1 @@ +{"errorMessage":"Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [ExplicitApi] is invalid. Property 'StatusCode' must be numeric"} \ No newline at end of file diff --git a/tests/translator/output/error_api_gateway_responses_unknown_responseparameter.json b/tests/translator/output/error_api_gateway_responses_unknown_responseparameter.json new file mode 100644 index 000000000..503194edc --- /dev/null +++ b/tests/translator/output/error_api_gateway_responses_unknown_responseparameter.json @@ -0,0 +1 @@ +{"errorMessage":"Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [ExplicitApi] is invalid. Invalid gateway response parameter 'Footers'"} \ No newline at end of file diff --git a/tests/translator/output/error_api_gateway_responses_unknown_responseparameter_property.json b/tests/translator/output/error_api_gateway_responses_unknown_responseparameter_property.json new file mode 100644 index 000000000..bfcd81c34 --- /dev/null +++ b/tests/translator/output/error_api_gateway_responses_unknown_responseparameter_property.json @@ -0,0 +1 @@ +{"errorMessage":"Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [ExplicitApi] is invalid. Invalid property 'SubStatusCode' in 'GatewayResponses' property 'UNAUTHORIZED'"} \ No newline at end of file diff --git a/tests/translator/output/error_api_invalid_auth.json b/tests/translator/output/error_api_invalid_auth.json index bf11bdb6e..1ed9fb027 100644 --- a/tests/translator/output/error_api_invalid_auth.json +++ b/tests/translator/output/error_api_invalid_auth.json @@ -1,3 +1,3 @@ { - "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 14. Resource with id [AuthNotDictApi] is invalid. Type of property 'Auth' is invalid. Resource with id [AuthWithAdditionalPropertyApi] is invalid. Invalid value for 'Auth' property Resource with id [AuthWithDefinitionUriApi] is invalid. Auth works only with inline Swagger specified in 'DefinitionBody' property Resource with id [AuthWithInvalidDefinitionBodyApi] is invalid. Unable to add Auth configuration because 'DefinitionBody' does not contain a valid Swagger Resource with id [AuthWithMissingDefaultAuthorizerApi] is invalid. Unable to set DefaultAuthorizer because 'NotThere' was not defined in 'Authorizers' Resource with id [AuthorizersNotDictApi] is invalid. Authorizers must be a dictionary Resource with id [InvalidFunctionPayloadTypeApi] is invalid. MyLambdaAuthorizer Authorizer has invalid 'FunctionPayloadType': INVALID Resource with id [MissingAuthorizerFn] is invalid. Event with id [GetRoot] is invalid. Unable to set Authorizer [UnspecifiedAuthorizer] on API method [get] for path [/] because it wasn't defined in the API's Authorizers. Resource with id [NoApiAuthorizerFn] is invalid. Event with id [GetRoot] is invalid. Unable to set Authorizer [MyAuth] on API method [get] for path [/] because the related API does not define any Authorizers. Resource with id [NoAuthFn] is invalid. Event with id [GetRoot] is invalid. Unable to set Authorizer [MyAuth] on API method [get] for path [/] because the related API does not define any Authorizers. Resource with id [NoAuthorizersFn] is invalid. Event with id [GetRoot] is invalid. Unable to set Authorizer [MyAuth] on API method [get] for path [/] because the related API does not define any Authorizers. Resource with id [NoDefaultAuthorizerWithNoneFn] is invalid. Event with id [GetRoot] is invalid. Unable to set Authorizer on API method [get] for path [/] because 'NONE' is only a valid value when a DefaultAuthorizer on the API is specified. Resource with id [NoIdentityOnRequestAuthorizer] is invalid. MyLambdaRequestAuthorizer Authorizer must specify Identity with at least one of Headers, QueryStrings, StageVariables, or Context. Resource with id [NoIdentitySourceOnRequestAuthorizer] is invalid. MyLambdaRequestAuthorizer Authorizer must specify Identity with at least one of Headers, QueryStrings, StageVariables, or Context." + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 15. Resource with id [AuthNotDictApi] is invalid. Type of property 'Auth' is invalid. Resource with id [AuthWithAdditionalPropertyApi] is invalid. Invalid value for 'Auth' property Resource with id [AuthWithDefinitionUriApi] is invalid. Auth works only with inline Swagger specified in 'DefinitionBody' property Resource with id [AuthWithInvalidDefinitionBodyApi] is invalid. Unable to add Auth configuration because 'DefinitionBody' does not contain a valid Swagger Resource with id [AuthWithMissingDefaultAuthorizerApi] is invalid. Unable to set DefaultAuthorizer because 'NotThere' was not defined in 'Authorizers' Resource with id [AuthorizerNotDict] is invalid. Authorizer MyCognitoAuthorizer must be a dictionary. Resource with id [AuthorizersNotDictApi] is invalid. Authorizers must be a dictionary Resource with id [InvalidFunctionPayloadTypeApi] is invalid. MyLambdaAuthorizer Authorizer has invalid 'FunctionPayloadType': INVALID Resource with id [MissingAuthorizerFn] is invalid. Event with id [GetRoot] is invalid. Unable to set Authorizer [UnspecifiedAuthorizer] on API method [get] for path [/] because it wasn't defined in the API's Authorizers. Resource with id [NoApiAuthorizerFn] is invalid. Event with id [GetRoot] is invalid. Unable to set Authorizer [MyAuth] on API method [get] for path [/] because the related API does not define any Authorizers. Resource with id [NoAuthFn] is invalid. Event with id [GetRoot] is invalid. Unable to set Authorizer [MyAuth] on API method [get] for path [/] because the related API does not define any Authorizers. Resource with id [NoAuthorizersFn] is invalid. Event with id [GetRoot] is invalid. Unable to set Authorizer [MyAuth] on API method [get] for path [/] because the related API does not define any Authorizers. Resource with id [NoDefaultAuthorizerWithNoneFn] is invalid. Event with id [GetRoot] is invalid. Unable to set Authorizer on API method [get] for path [/] because 'NONE' is only a valid value when a DefaultAuthorizer on the API is specified. Resource with id [NoIdentityOnRequestAuthorizer] is invalid. MyLambdaRequestAuthorizer Authorizer must specify Identity with at least one of Headers, QueryStrings, StageVariables, or Context. Resource with id [NoIdentitySourceOnRequestAuthorizer] is invalid. MyLambdaRequestAuthorizer Authorizer must specify Identity with at least one of Headers, QueryStrings, StageVariables, or Context." } \ No newline at end of file diff --git a/tests/translator/output/error_api_invalid_path.json b/tests/translator/output/error_api_invalid_path.json new file mode 100644 index 000000000..2aedc0684 --- /dev/null +++ b/tests/translator/output/error_api_invalid_path.json @@ -0,0 +1,3 @@ +{ + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Structure of the SAM template is invalid. Value of '/foo' path must be a dictionary according to Swagger spec." +} \ No newline at end of file diff --git a/tests/translator/output/error_api_invalid_stagename.json b/tests/translator/output/error_api_invalid_stagename.json new file mode 100644 index 000000000..1076d671f --- /dev/null +++ b/tests/translator/output/error_api_invalid_stagename.json @@ -0,0 +1,8 @@ +{ + "errors": [ + { + "errorMessage": "Resource with id [ApiWithEmptyStageName] is invalid. StageName cannot be empty." + } + ], + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [ApiWithEmptyStageName] is invalid. StageName cannot be empty." +} \ No newline at end of file diff --git a/tests/translator/output/error_application_properties.json b/tests/translator/output/error_application_properties.json index 4bd94227b..1bf610981 100644 --- a/tests/translator/output/error_application_properties.json +++ b/tests/translator/output/error_application_properties.json @@ -1,8 +1,8 @@ { "errors": [ { - "errorMessage": "Resource with id [BlankProperties] is invalid. Property 'ApplicationId' cannot be blank. Resource with id [MissingApplicationId] is invalid. Resource is missing the required [ApplicationId] property. Resource with id [MissingLocation] is invalid. Resource is missing the required [Location] property. Resource with id [MissingSemanticVersion] is invalid. Resource is missing the required [SemanticVersion] property. Resource with id [NormalApplication] is invalid. Type of property 'ApplicationId' is invalid. Resource with id [UnsupportedProperty] is invalid. Resource is missing the required [Location] property." + "errorMessage": "Resource with id [BlankProperties] is invalid. Property 'ApplicationId' cannot be blank. Resource with id [IntrinsicProperties] is invalid. Property 'ApplicationId' cannot be resolved. Only FindInMap and Ref intrinsic functions are supported. Resource with id [MissingApplicationId] is invalid. Resource is missing the required [ApplicationId] property. Resource with id [MissingLocation] is invalid. Resource is missing the required [Location] property. Resource with id [MissingSemanticVersion] is invalid. Resource is missing the required [SemanticVersion] property. Resource with id [NormalApplication] is invalid. Type of property 'ApplicationId' is invalid. Resource with id [UnsupportedProperty] is invalid. Resource is missing the required [Location] property." } ], - "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 6. Resource with id [BlankProperties] is invalid. Property 'ApplicationId' cannot be blank. Resource with id [MissingApplicationId] is invalid. Resource is missing the required [ApplicationId] property. Resource with id [MissingLocation] is invalid. Resource is missing the required [Location] property. Resource with id [MissingSemanticVersion] is invalid. Resource is missing the required [SemanticVersion] property. Resource with id [NormalApplication] is invalid. Type of property 'ApplicationId' is invalid. Resource with id [UnsupportedProperty] is invalid. Resource is missing the required [Location] property." + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 7. Resource with id [BlankProperties] is invalid. Property 'ApplicationId' cannot be blank. Resource with id [IntrinsicProperties] is invalid. Property 'ApplicationId' cannot be resolved. Only FindInMap and Ref intrinsic functions are supported. Resource with id [MissingApplicationId] is invalid. Resource is missing the required [ApplicationId] property. Resource with id [MissingLocation] is invalid. Resource is missing the required [Location] property. Resource with id [MissingSemanticVersion] is invalid. Resource is missing the required [SemanticVersion] property. Resource with id [NormalApplication] is invalid. Type of property 'ApplicationId' is invalid. Resource with id [UnsupportedProperty] is invalid. Resource is missing the required [Location] property." } \ No newline at end of file diff --git a/tests/translator/output/error_function_invalid_api_event.json b/tests/translator/output/error_function_invalid_api_event.json new file mode 100644 index 000000000..01b123ed8 --- /dev/null +++ b/tests/translator/output/error_function_invalid_api_event.json @@ -0,0 +1,8 @@ +{ + "errors": [ + { + "errorMessage": "Resource with id [FunctionApiNoMethod] is invalid. Event with id [ApiEvent] is invalid. Event is missing key 'Path'." + } + ], + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 4. Resource with id [FunctionApiMethodArray] is invalid. Event with id [ApiEvent] is invalid. Api Event must have a String specified for 'Method'. Resource with id [FunctionApiNoMethod] is invalid. Event with id [ApiEvent] is invalid. Event is missing key 'Method'. Resource with id [FunctionApiNoPath] is invalid. Event with id [ApiEvent] is invalid. Event is missing key 'Path'. Resource with id [FunctionApiPathArray] is invalid. Event with id [ApiEvent] is invalid. Api Event must have a String specified for 'Path'." +} \ No newline at end of file diff --git a/tests/translator/output/error_function_invalid_autopublishalias.json b/tests/translator/output/error_function_invalid_autopublishalias.json new file mode 100644 index 000000000..820df472f --- /dev/null +++ b/tests/translator/output/error_function_invalid_autopublishalias.json @@ -0,0 +1,8 @@ +{ + "errors": [ + { + "errorMessage": "Resource with id [InvalidAutoPublishAliasFunction] is invalid. Alias name is required to create an alias" + } + ], + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [InvalidAutoPublishAliasFunction] is invalid. Alias name is required to create an alias" +} \ No newline at end of file diff --git a/tests/translator/output/error_function_invalid_event_type.json b/tests/translator/output/error_function_invalid_event_type.json new file mode 100644 index 000000000..0e404b5c6 --- /dev/null +++ b/tests/translator/output/error_function_invalid_event_type.json @@ -0,0 +1,8 @@ +{ + "errors": [ + { + "errorMessage": "Resource with id [FunctionApiTypeError] is invalid. Event with id [ApiEvent] is invalid. Resource dict has missing or invalid value for key Type. Event Type is: API. Resource with id [FunctionNoEventType] is invalid. Event with id [MissingType] is invalid. Resource dict has missing or invalid value for key Type. Event Type is: None." + } + ], + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 2. Resource with id [FunctionApiTypeError] is invalid. Event with id [ApiEvent] is invalid. Resource dict has missing or invalid value for key Type. Event Type is: API. Resource with id [FunctionNoEventType] is invalid. Event with id [MissingType] is invalid. Resource dict has missing or invalid value for key Type. Event Type is: None." +} diff --git a/tests/translator/output/error_globals_api_with_stage_name.json b/tests/translator/output/error_globals_api_with_stage_name.json index 937ccf2f0..899ef37ea 100644 --- a/tests/translator/output/error_globals_api_with_stage_name.json +++ b/tests/translator/output/error_globals_api_with_stage_name.json @@ -1,8 +1,8 @@ { "errors": [ { - "errorMessage": "'Globals' section is invalid. 'StageName' is not a supported property of 'Api'. Must be one of the following values - ['Auth', 'Name', 'DefinitionUri', 'CacheClusterEnabled', 'CacheClusterSize', 'Variables', 'EndpointConfiguration', 'MethodSettings', 'BinaryMediaTypes', 'Cors', 'AccessLogSetting', 'CanarySetting']" + "errorMessage": "'Globals' section is invalid. 'StageName' is not a supported property of 'Api'. Must be one of the following values - ['Auth', 'Name', 'DefinitionUri', 'CacheClusterEnabled', 'CacheClusterSize', 'Variables', 'EndpointConfiguration', 'MethodSettings', 'BinaryMediaTypes', 'Cors', 'GatewayResponses', 'AccessLogSetting', 'CanarySetting']" } ], - "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. 'Globals' section is invalid. 'StageName' is not a supported property of 'Api'. Must be one of the following values - ['Auth', 'Name', 'DefinitionUri', 'CacheClusterEnabled', 'CacheClusterSize', 'Variables', 'EndpointConfiguration', 'MethodSettings', 'BinaryMediaTypes', 'MinimumCompressionSize', 'Cors', 'AccessLogSetting', 'CanarySetting', 'TracingEnabled']" + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. 'Globals' section is invalid. 'StageName' is not a supported property of 'Api'. Must be one of the following values - ['Auth', 'Name', 'DefinitionUri', 'CacheClusterEnabled', 'CacheClusterSize', 'Variables', 'EndpointConfiguration', 'MethodSettings', 'BinaryMediaTypes', 'MinimumCompressionSize', 'Cors', 'GatewayResponses', 'AccessLogSetting', 'CanarySetting', 'TracingEnabled']" } diff --git a/tests/translator/output/error_globals_unsupported_property.json b/tests/translator/output/error_globals_unsupported_property.json index 4805f3f4e..c814d640a 100644 --- a/tests/translator/output/error_globals_unsupported_property.json +++ b/tests/translator/output/error_globals_unsupported_property.json @@ -1,8 +1,8 @@ { "errors": [ { - "errorMessage": "'Globals' section is invalid. 'SomeKey' is not a supported property of 'Function'. Must be one of the following values - ['Handler', 'Runtime', 'CodeUri', 'DeadLetterQueue', 'Description', 'MemorySize', 'Timeout', 'VpcConfig', 'Environment', 'Tags', 'Tracing', 'KmsKeyArn', 'AutoPublishAlias', 'Layers', 'DeploymentPreference', 'PermissionsBoundary']" + "errorMessage": "'Globals' section is invalid. 'SomeKey' is not a supported property of 'Function'. Must be one of the following values - ['Handler', 'Runtime', 'CodeUri', 'DeadLetterQueue', 'Description', 'MemorySize', 'Timeout', 'VpcConfig', 'Environment', 'Tags', 'Tracing', 'KmsKeyArn', 'AutoPublishAlias', 'Layers', 'DeploymentPreference', 'PermissionsBoundary', 'ReservedConcurrentExecutions']" } ], - "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. 'Globals' section is invalid. 'SomeKey' is not a supported property of 'Function'. Must be one of the following values - ['Handler', 'Runtime', 'CodeUri', 'DeadLetterQueue', 'Description', 'MemorySize', 'Timeout', 'VpcConfig', 'Environment', 'Tags', 'Tracing', 'KmsKeyArn', 'AutoPublishAlias', 'Layers', 'DeploymentPreference', 'PermissionsBoundary']" + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. 'Globals' section is invalid. 'SomeKey' is not a supported property of 'Function'. Must be one of the following values - ['Handler', 'Runtime', 'CodeUri', 'DeadLetterQueue', 'Description', 'MemorySize', 'Timeout', 'VpcConfig', 'Environment', 'Tags', 'Tracing', 'KmsKeyArn', 'AutoPublishAlias', 'Layers', 'DeploymentPreference', 'PermissionsBoundary', 'ReservedConcurrentExecutions']" } diff --git a/tests/translator/output/error_invalid_findinmap.json b/tests/translator/output/error_invalid_findinmap.json new file mode 100644 index 000000000..6332622b8 --- /dev/null +++ b/tests/translator/output/error_invalid_findinmap.json @@ -0,0 +1,8 @@ +{ + "errors": [ + { + "errorMessage": "Structure of the SAM template is invalid. Invalid FindInMap value ['ApplicationLocations', 'ap-southeast-1']. FindInMap expects an array with 3 values." + } + ], + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Structure of the SAM template is invalid. Invalid FindInMap value ['ApplicationLocations', 'ap-southeast-1']. FindInMap expects an array with 3 values." +} \ No newline at end of file diff --git a/tests/translator/output/error_invalid_getatt.json b/tests/translator/output/error_invalid_getatt.json new file mode 100644 index 000000000..ff6893d52 --- /dev/null +++ b/tests/translator/output/error_invalid_getatt.json @@ -0,0 +1,8 @@ +{ + "errors": [ + { + "errorMessage": "Structure of the SAM template is invalid. Invalid GetAtt value [{'Fn::Sub': 'Foo'}, 'Arn']. GetAtt expects an array with 2 strings." + } + ], + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Structure of the SAM template is invalid. Invalid GetAtt value [{'Fn::Sub': 'Foo'}, 'Arn']. GetAtt expects an array with 2 strings." +} \ No newline at end of file diff --git a/tests/translator/output/error_resource_not_dict.json b/tests/translator/output/error_resource_not_dict.json new file mode 100644 index 000000000..47272f87d --- /dev/null +++ b/tests/translator/output/error_resource_not_dict.json @@ -0,0 +1,8 @@ +{ + "errors": [ + { + "errorMessage": "Structure of the SAM template is invalid. All 'Resources' must be Objects. If you're using YAML, this may be an indentation issue." + } + ], + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Structure of the SAM template is invalid. All 'Resources' must be Objects. If you're using YAML, this may be an indentation issue." +} \ No newline at end of file diff --git a/tests/translator/output/error_resource_properties_not_dict.json b/tests/translator/output/error_resource_properties_not_dict.json new file mode 100644 index 000000000..a7929ef22 --- /dev/null +++ b/tests/translator/output/error_resource_properties_not_dict.json @@ -0,0 +1,8 @@ +{ + "errors": [ + { + "errorMessage": "Resource with id [Function] is invalid. All 'Resources' must be Objects and have a 'Properties' Object. If you're using YAML, this may be an indentation issue." + } + ], + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [Function] is invalid. All 'Resources' must be Objects and have a 'Properties' Object. If you're using YAML, this may be an indentation issue." +} \ No newline at end of file diff --git a/tests/translator/output/function_with_alias.json b/tests/translator/output/function_with_alias.json index e4ac30b6c..16231d245 100644 --- a/tests/translator/output/function_with_alias.json +++ b/tests/translator/output/function_with_alias.json @@ -4,6 +4,7 @@ "DeletionPolicy": "Retain", "Type": "AWS::Lambda::Version", "Properties": { + "Description": "sam-testing", "FunctionName": { "Ref": "MinimalFunction" } diff --git a/tests/translator/output/globals_for_function.json b/tests/translator/output/globals_for_function.json index 3006cce73..6d0e58931 100644 --- a/tests/translator/output/globals_for_function.json +++ b/tests/translator/output/globals_for_function.json @@ -57,6 +57,7 @@ } ], "MemorySize": 512, + "ReservedConcurrentExecutions": 100, "Environment": { "Variables": { "Var1": "value1", @@ -146,6 +147,7 @@ } ], "MemorySize": 1024, + "ReservedConcurrentExecutions": 50, "Environment": { "Variables": { "Var1": "value1", diff --git a/tests/translator/output/layers_with_intrinsics.json b/tests/translator/output/layers_with_intrinsics.json index 4e0e3f20a..79e2dea55 100644 --- a/tests/translator/output/layers_with_intrinsics.json +++ b/tests/translator/output/layers_with_intrinsics.json @@ -24,6 +24,30 @@ "LayerName": "SomeLayerName" } }, + "LayerWithRefNameIntrinsicRegion32bf7198a5": { + "DeletionPolicy": "Retain", + "Type": "AWS::Lambda::LayerVersion", + "Properties": { + "Content": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "layer.zip" + }, + "LayerName": "ap-southeast-1" + } + }, + "LayerWithSubNameIntrinsicRegiond71326de24": { + "DeletionPolicy": "Retain", + "Type": "AWS::Lambda::LayerVersion", + "Properties": { + "Content": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "layer.zip" + }, + "LayerName": { + "Fn::Sub": "layer-ap-southeast-1" + } + } + }, "LayerWithRuntimesIntrinsic1a006faa85": { "DeletionPolicy": "Retain", "Type": "AWS::Lambda::LayerVersion", diff --git a/tests/translator/test_function_resources.py b/tests/translator/test_function_resources.py index 3cdf7fe8f..a34917415 100644 --- a/tests/translator/test_function_resources.py +++ b/tests/translator/test_function_resources.py @@ -470,7 +470,7 @@ def test_alias_creation(self): def test_alias_creation_error(self): - with self.assertRaises(ValueError): + with self.assertRaises(InvalidResourceException): self.sam_func._construct_alias(None, self.lambda_func, self.lambda_version) def test_get_resolved_alias_name_must_work(self): @@ -515,7 +515,6 @@ def test_get_resolved_alias_name_must_error_if_intrinsics_are_not_resolved_with_ ex = raises_assert.exception self.assertEqual(expected_exception_msg, ex.message) - def _make_lambda_function(self, logical_id): func = LambdaFunction(logical_id) func.Code = { diff --git a/tests/translator/test_translator.py b/tests/translator/test_translator.py index 1c40bf9d2..88657b15b 100644 --- a/tests/translator/test_translator.py +++ b/tests/translator/test_translator.py @@ -119,12 +119,12 @@ def mock_sar_service_call(self, service_call_function, logical_id, *args): elif application_id == "expired": status = "EXPIRED" message = { - 'ApplicationId': args[0], - 'CreationTime': 'x', - 'ExpirationTime': 'x', - 'SemanticVersion': '1.1.1', - 'Status': status, - 'TemplateId': 'id-xx-xx', + 'ApplicationId': args[0], + 'CreationTime': 'x', + 'ExpirationTime': 'x', + 'SemanticVersion': '1.1.1', + 'Status': status, + 'TemplateId': 'id-xx-xx', 'TemplateUrl': 'https://awsserverlessrepo-changesets-xxx.s3.amazonaws.com/signed-url' } return message @@ -142,6 +142,7 @@ class TestTranslatorEndToEnd(TestCase): 'basic_function', 'basic_application', 'application_preparing_state', + 'application_with_intrinsics', 'basic_layer', 'cloudwatchevent', 'cloudwatch_logs_with_ref', @@ -156,6 +157,9 @@ class TestTranslatorEndToEnd(TestCase): 'api_with_auth_all_maximum', 'api_with_auth_all_minimum', 'api_with_auth_no_default', + 'api_with_default_aws_iam_auth', + 'api_with_method_aws_iam_auth', + 'api_with_aws_iam_auth_overrides', 'api_with_method_settings', 'api_with_binary_media_types', 'api_with_minimum_compression_size', @@ -167,6 +171,11 @@ class TestTranslatorEndToEnd(TestCase): 'api_with_cors_and_only_maxage', 'api_with_cors_and_only_credentials_false', 'api_with_cors_no_definitionbody', + 'api_with_gateway_responses', + 'api_with_gateway_responses_all', + 'api_with_gateway_responses_minimal', + 'api_with_gateway_responses_implicit', + 'api_with_gateway_responses_string_status_code', 'api_cache', 'api_with_access_log_setting', 'api_with_canary_setting', @@ -245,7 +254,7 @@ def test_transform_success(self, testcase, partition_with_region): # 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 = json.load(open(os.path.join(OUTPUT_FOLDER,partition_folder, testcase + '.json'), 'r')) + expected = json.load(open(os.path.join(OUTPUT_FOLDER, partition_folder, testcase + '.json'), 'r')) with patch('boto3.session.Session.region_name', region): parameter_values = get_template_parameter_values() @@ -340,9 +349,14 @@ def _generate_new_deployment_hash(self, logical_id, dict_to_hash, rest_api_to_sw @pytest.mark.parametrize('testcase', [ 'error_api_duplicate_methods_same_path', + 'error_api_gateway_responses_nonnumeric_status_code', + 'error_api_gateway_responses_unknown_responseparameter', + 'error_api_gateway_responses_unknown_responseparameter_property', 'error_api_invalid_auth', + 'error_api_invalid_path', 'error_api_invalid_definitionuri', 'error_api_invalid_definitionbody', + 'error_api_invalid_stagename', 'error_api_invalid_restapiid', 'error_application_properties', 'error_application_does_not_exist', @@ -350,9 +364,14 @@ def _generate_new_deployment_hash(self, logical_id, dict_to_hash, rest_api_to_sw 'error_application_preparing_timeout', 'error_cors_on_external_swagger', 'error_invalid_cors_dict', + 'error_invalid_findinmap', + 'error_invalid_getatt', 'error_cors_credentials_true_with_wildcard_origin', 'error_cors_credentials_true_without_explicit_origin', 'error_function_invalid_codeuri', + 'error_function_invalid_api_event', + 'error_function_invalid_autopublishalias', + 'error_function_invalid_event_type', 'error_function_invalid_layer', 'error_function_no_codeuri', 'error_function_no_handler', @@ -373,6 +392,8 @@ def _generate_new_deployment_hash(self, logical_id, dict_to_hash, rest_api_to_sw 'existing_permission_logical_id', 'existing_role_logical_id', 'error_invalid_template', + 'error_resource_not_dict', + 'error_resource_properties_not_dict', 'error_globals_is_not_dict', 'error_globals_unsupported_type', 'error_globals_unsupported_property', @@ -615,119 +636,6 @@ def _do_transform(self, document, parameter_values=get_template_parameter_values return output_fragment -class TestParameterValuesHandling(TestCase): - """ - Test how user-supplied parameters & default template parameter values from template get merged - """ - - def test_add_default_parameter_values_must_merge(self): - parameter_values = { - "Param1": "value1" - } - - sam_template = { - "Parameters": { - "Param2": { - "Type": "String", - "Default": "template default" - } - } - } - - expected = { - "Param1": "value1", - "Param2": "template default" - } - - sam_parser = Parser() - translator = Translator({}, sam_parser) - result = translator._add_default_parameter_values(sam_template, - parameter_values) - self.assertEqual(expected, result) - - def test_add_default_parameter_values_must_override_user_specified_values(self): - parameter_values = { - "Param1": "value1" - } - - sam_template = { - "Parameters": { - "Param1": { - "Type": "String", - "Default": "template default" - } - } - } - - expected = { - "Param1": "value1" - } - - - sam_parser = Parser() - translator = Translator({}, sam_parser) - result = translator._add_default_parameter_values(sam_template, parameter_values) - self.assertEqual(expected, result) - - def test_add_default_parameter_values_must_skip_params_without_defaults(self): - parameter_values = { - "Param1": "value1" - } - - sam_template = { - "Parameters": { - "Param1": { - "Type": "String" - }, - "Param2": { - "Type": "String" - } - } - } - - expected = { - "Param1": "value1" - } - - sam_parser = Parser() - translator = Translator({}, sam_parser) - result = translator._add_default_parameter_values(sam_template, parameter_values) - self.assertEqual(expected, result) - - - @parameterized.expand([ - # Array - param(["1", "2"]), - - # String - param("something"), - - # Some other non-parameter looking dictionary - param({"Param1": {"Foo": "Bar"}}), - - param(None) - ]) - def test_add_default_parameter_values_must_ignore_invalid_template_parameters(self, template_parameters): - parameter_values = { - "Param1": "value1" - } - - expected = { - "Param1": "value1" - } - - sam_template = { - "Parameters": template_parameters - } - - - sam_parser = Parser() - translator = Translator({}, sam_parser) - result = translator._add_default_parameter_values( - sam_template, parameter_values) - self.assertEqual(expected, result) - - class TestTemplateValidation(TestCase): @patch('botocore.client.ClientEndpointBridge._check_default_region', mock_get_region) @@ -764,6 +672,21 @@ def test_throws_when_resource_is_not_dict(self): translator = Translator({}, sam_parser) translator.translate(template, {}) + + @patch('botocore.client.ClientEndpointBridge._check_default_region', mock_get_region) + def test_throws_when_resources_not_all_dicts(self): + template = { + "Resources": { + "notadict": None, + "MyResource": {} + } + } + + with self.assertRaises(InvalidDocumentException): + sam_parser = Parser() + translator = Translator({}, sam_parser) + translator.translate(template, {}) + class TestPluginsUsage(TestCase): # Tests if plugins are properly injected into the translator @@ -851,7 +774,7 @@ def test_transform_method_must_inject_plugins_when_creating_resources(self, resource_from_dict_mock.assert_called_with("MyTable", manifest["Resources"]["MyTable"], sam_plugins=sam_plugins_object_mock) - prepare_plugins_mock.assert_called_once_with(initial_plugins) + prepare_plugins_mock.assert_called_once_with(initial_plugins, {"AWS::Region": "ap-southeast-1"}) def get_policy_mock(): mock_policy_loader = MagicMock() @@ -876,4 +799,3 @@ def get_resource_by_type(template, type): def get_exception_error_message(e): return reduce(lambda message, error: message + ' ' + error.message, e.value.causes, e.value.message) - diff --git a/versions/2016-10-31.md b/versions/2016-10-31.md index 8d000a324..c487ad7cb 100644 --- a/versions/2016-10-31.md +++ b/versions/2016-10-31.md @@ -124,6 +124,7 @@ DeadLetterQueue | `map` | [DeadLetterQueue Object](#deadletter DeploymentPreference | [DeploymentPreference Object](#deploymentpreference-object) | Settings to enable Safe Lambda Deployments. Read the [usage guide](../docs/safe_lambda_deployments.rst) for detailed information. Layers | list of `string` | List of LayerVersion ARNs that should be used by this function. The order specified here is the order that they will be imported when running the Lambda function. AutoPublishAlias | `string` | Name of the Alias. Read [AutoPublishAlias Guide](../docs/safe_lambda_deployments.rst#instant-traffic-shifting-using-lambda-aliases) for how it works +VersionDescription | `string` | A string that specifies the Description field which will be added on the new lambda version ReservedConcurrentExecutions | `integer` | The maximum of concurrent executions you want to reserve for the function. For more information see [AWS Documentation on managing concurrency](https://docs.aws.amazon.com/lambda/latest/dg/concurrent-executions.html) ##### Return values @@ -200,7 +201,7 @@ Tags: AppNameTag: ThumbnailApp DepartmentNameTag: ThumbnailDepartment Layers: - - !Sub arn:aws:lambda:${AWS::Region}:123456789012:layer:MyLayer:1 + - !Sub arn:${AWS::Partition}:lambda:${AWS::Region}:123456789012:layer:MyLayer:1 ``` #### AWS::Serverless::Api @@ -226,6 +227,7 @@ BinaryMediaTypes | List of `string` | List of MIME types that your API could re MinimumCompressionSize | `int` | Allow compression of response bodies based on client's Accept-Encoding header. Compression is triggered when response body size is greater than or equal to your configured threshold. The maximum body size threshold is 10 MB (10,485,760 Bytes). The following compression types are supported: gzip, deflate, and identity. Cors | `string` or [Cors Configuration](#cors-configuration) | Enable CORS for all your APIs. Specify the domain to allow as a string or specify a dictionary with additional [Cors Configuration](#cors-configuration). NOTE: Cors requires SAM to modify your Swagger definition. Hence it works only inline swagger defined with `DefinitionBody`. Auth | [API Auth Object](#api-auth-object) | Auth configuration for this API. Define Lambda and Cognito `Authorizers` and specify a `DefaultAuthorizer` for this API. +GatewayResponses | Map of [Gateway Response Type](https://docs.aws.amazon.com/apigateway/api-reference/resource/gateway-response/) to [Gateway Response Object](#gateway-response-object) | Configures Gateway Reponses for an API. Gateway Responses are responses returned by API Gateway, either directly or through the use of Lambda Authorizers. Keys for this object are passed through to Api Gateway, so any value supported by `GatewayResponse.responseType` is supported here. AccessLogSetting | [CloudFormation AccessLogSetting property](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-stage-accesslogsetting.html) | Configures Access Log Setting for a stage. This value is passed through to CloudFormation, so any value supported by `AccessLogSetting` is supported here. CanarySetting | [CloudFormation CanarySetting property](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-stage-canarysetting.html) | Configure a Canary Setting to a Stage of a regular deployment. This value is passed through to Cloudformation, so any value supported by `CanarySetting` is supported here. TracingEnabled | `boolean` | Indicates whether active tracing with X-Ray is enabled for the stage. @@ -690,6 +692,7 @@ Properties: - [Cors Configuration](#cors-configuration) - [API Auth Object](#api-auth-object) - [Function Auth Object](#function-auth-object) +- [Gateway Response Object](#gateway-response-object) #### S3 Location Object @@ -770,11 +773,11 @@ Cors: #### API Auth Object -Configure Auth on APIs. Define Lambda and Cognito `Authorizers` and specify a `DefaultAuthorizer`. For more information, see the documentation on [Lambda Authorizers](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-use-lambda-authorizer.html) and [Amazon Cognito User Pool Authorizers](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-integrate-with-cognito.html). +Configure Auth on APIs. Define Lambda and Cognito `Authorizers` and specify a `DefaultAuthorizer`. If you use IAM permission, only specify `AWS_IAM` to a `DefaultAuthorizer`. For more information, see the documentation on [Lambda Authorizers](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-use-lambda-authorizer.html) and [Amazon Cognito User Pool Authorizers](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-integrate-with-cognito.html) and [IAM Permissions](https://docs.aws.amazon.com/apigateway/latest/developerguide/permissions.html). ```yaml Auth: - DefaultAuthorizer: MyCognitoAuth # OPTIONAL + DefaultAuthorizer: MyCognitoAuth # OPTIONAL, if you use IAM permissions, specify AWS_IAM. Authorizers: MyCognitoAuth: UserPoolArn: !GetAtt MyCognitoUserPool.Arn # Can also accept an array @@ -814,7 +817,7 @@ Configure Auth for a specific Api+Path+Method. ```yaml Auth: - Authorizer: MyCognitoAuth # OPTIONAL + Authorizer: MyCognitoAuth # OPTIONAL, if you use IAM permissions in each functions, specify AWS_IAM. ``` If you have specified a Global Authorizer on the API and want to make a specific Function public, override with the following: @@ -823,3 +826,33 @@ If you have specified a Global Authorizer on the API and want to make a specific Auth: Authorizer: 'NONE' ``` + +#### Gateway Response Object + +Configure Gateway Responses on APIs. These are associated with the ID of a Gateway Response [response type][]. +For more information, see the documentation on [`AWS::ApiGateway::GatewayResponse`][]. + +```yaml +GatewayResponses: + UNAUTHORIZED: + StatusCode: 401 # Even though this is the default value for UNAUTHORIZED. + ResponseTemplates: + "application/json": '{ "message": $context.error.messageString }' + ResponseParameters: + Paths: + path-key: "'value'" + QueryStrings: + query-string-key: "'value'" + Headers: + Access-Control-Expose-Headers: "'WWW-Authenticate'" + Access-Control-Allow-Origin: "'*'" + WWW-Authenticate: >- + 'Bearer realm="admin"' +``` + +All properties of a Gateway Response object are optional. API Gateway has knowledge of default status codes to associate with Gateway Responses, so – for example – `StatusCode` is only used in order to override this value. + +> NOTE: API Gateway spec allows values under the `ResponseParameters` and `ResponseTemplates` properties to be templates. In order to send constant values, don't forget the additional quotes. ie. "'WWW-Authenticate'" is correct whereas "WWW-Authenticate" is wrong. + +[response type]: https://docs.aws.amazon.com/apigateway/api-reference/resource/gateway-response/ +[`AWS::ApiGateway::GatewayResponse`]: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-gatewayresponse.html