From 4bddf27e45dcb31e7ae04c446d1239f2d9ce5c44 Mon Sep 17 00:00:00 2001 From: Nick Littrell Date: Tue, 19 Feb 2019 13:42:35 -0500 Subject: [PATCH 01/17] chore: use cfn pseudo parameters for aws partition in docs (#814) --- docs/safe_lambda_deployments.rst | 2 +- examples/2016-10-31/api_cognito_auth/template.yaml | 2 +- examples/2016-10-31/inline_swagger/template.yaml | 2 +- examples/2016-10-31/lambda_safe_deployments/template.yaml | 2 +- versions/2016-10-31.md | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) 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_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/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/versions/2016-10-31.md b/versions/2016-10-31.md index 8d000a324..d8432f808 100644 --- a/versions/2016-10-31.md +++ b/versions/2016-10-31.md @@ -200,7 +200,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 From a00ad1f6c2d1c072cef97a573cbb01561dbd0de8 Mon Sep 17 00:00:00 2001 From: Keeton Hodgson Date: Thu, 7 Mar 2019 12:07:38 -0800 Subject: [PATCH 02/17] fix: catch error caused by improper event configuration (#845) --- .../plugins/api/implicit_api_plugin.py | 7 ++++-- tests/plugins/api/test_implicit_api_plugin.py | 25 +++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/samtranslator/plugins/api/implicit_api_plugin.py b/samtranslator/plugins/api/implicit_api_plugin.py index a3e71d961..38b3f71d2 100644 --- a/samtranslator/plugins/api/implicit_api_plugin.py +++ b/samtranslator/plugins/api/implicit_api_plugin.py @@ -136,8 +136,11 @@ 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)) api_dict = self.api_conditions.setdefault(api_id, {}) method_conditions = api_dict.setdefault(path, {}) method_conditions[method] = condition diff --git a/tests/plugins/api/test_implicit_api_plugin.py b/tests/plugins/api/test_implicit_api_plugin.py index 393926348..67b9f7366 100644 --- a/tests/plugins/api/test_implicit_api_plugin.py +++ b/tests/plugins/api/test_implicit_api_plugin.py @@ -398,6 +398,31 @@ 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_skip_events_without_properties(self): api_events = { From 30456c5caf219654bc4ceb676bf6555f17458c8c Mon Sep 17 00:00:00 2001 From: Keeton Hodgson Date: Thu, 7 Mar 2019 12:12:31 -0800 Subject: [PATCH 03/17] fix: catch improper event type errors (#846) --- samtranslator/model/__init__.py | 4 +-- samtranslator/model/sam_resources.py | 14 +++++++--- .../error_function_invalid_event_type.yaml | 26 +++++++++++++++++++ .../error_function_invalid_event_type.json | 8 ++++++ tests/translator/test_translator.py | 1 + 5 files changed, 47 insertions(+), 6 deletions(-) create mode 100644 tests/translator/input/error_function_invalid_event_type.yaml create mode 100644 tests/translator/output/error_function_invalid_event_type.json 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/sam_resources.py b/samtranslator/model/sam_resources.py index 325f71ed9..c783d95c7 100644 --- a/samtranslator/model/sam_resources.py +++ b/samtranslator/model/sam_resources.py @@ -264,8 +264,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 +289,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 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/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/test_translator.py b/tests/translator/test_translator.py index 1c40bf9d2..29529edc5 100644 --- a/tests/translator/test_translator.py +++ b/tests/translator/test_translator.py @@ -353,6 +353,7 @@ def _generate_new_deployment_hash(self, logical_id, dict_to_hash, rest_api_to_sw 'error_cors_credentials_true_with_wildcard_origin', 'error_cors_credentials_true_without_explicit_origin', 'error_function_invalid_codeuri', + 'error_function_invalid_event_type', 'error_function_invalid_layer', 'error_function_no_codeuri', 'error_function_no_handler', From 882d97d9c060b900153103dfb0fda7f9a3d2ebf5 Mon Sep 17 00:00:00 2001 From: James Hood Date: Thu, 7 Mar 2019 13:34:05 -0800 Subject: [PATCH 04/17] fix(policy-templates): Add more actions to ServerlessRepoReadWriteAccessPolicy (#847) --- samtranslator/policy_templates_data/policy_templates.json | 4 +++- tests/translator/output/all_policy_templates.json | 4 +++- tests/translator/output/aws-cn/all_policy_templates.json | 4 +++- tests/translator/output/aws-us-gov/all_policy_templates.json | 4 +++- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/samtranslator/policy_templates_data/policy_templates.json b/samtranslator/policy_templates_data/policy_templates.json index e841772dc..89e28d50f 100644 --- a/samtranslator/policy_templates_data/policy_templates.json +++ b/samtranslator/policy_templates_data/policy_templates.json @@ -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/tests/translator/output/all_policy_templates.json b/tests/translator/output/all_policy_templates.json index 1e5a60e92..e9ddbafcc 100644 --- a/tests/translator/output/all_policy_templates.json +++ b/tests/translator/output/all_policy_templates.json @@ -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/aws-cn/all_policy_templates.json b/tests/translator/output/aws-cn/all_policy_templates.json index 77f518eec..c84682a38 100644 --- a/tests/translator/output/aws-cn/all_policy_templates.json +++ b/tests/translator/output/aws-cn/all_policy_templates.json @@ -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/all_policy_templates.json b/tests/translator/output/aws-us-gov/all_policy_templates.json index 871b55a25..174b46eb6 100644 --- a/tests/translator/output/aws-us-gov/all_policy_templates.json +++ b/tests/translator/output/aws-us-gov/all_policy_templates.json @@ -724,9 +724,11 @@ "Action": [ "serverlessrepo:CreateApplication", "serverlessrepo:CreateApplicationVersion", + "serverlessrepo:UpdateApplication", "serverlessrepo:GetApplication", "serverlessrepo:ListApplications", - "serverlessrepo:ListApplicationVersions" + "serverlessrepo:ListApplicationVersions", + "serverlessrepo:ListApplicationDependencies" ], "Resource": [ { From b4baac69e680e9370ba05d82143bf8539bd58bf6 Mon Sep 17 00:00:00 2001 From: Manvendra Singh Date: Sat, 9 Mar 2019 00:47:15 +0530 Subject: [PATCH 05/17] feat: add VersionDescription property on Serverless::Function (#835) --- samtranslator/model/sam_resources.py | 4 ++- tests/model/test_sam_resources.py | 26 ++++++++++++++++++- .../translator/input/function_with_alias.yaml | 1 + .../output/aws-cn/function_with_alias.json | 1 + .../aws-us-gov/function_with_alias.json | 1 + .../output/function_with_alias.json | 1 + versions/2016-10-31.md | 1 + 7 files changed, 33 insertions(+), 2 deletions(-) diff --git a/samtranslator/model/sam_resources.py b/samtranslator/model/sam_resources.py index c783d95c7..5b8da4fb3 100644 --- a/samtranslator/model/sam_resources.py +++ b/samtranslator/model/sam_resources.py @@ -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, @@ -365,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 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/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/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-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/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/versions/2016-10-31.md b/versions/2016-10-31.md index d8432f808..528b3c634 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 From 4339ff146286638e3894957890c670c42601d5c3 Mon Sep 17 00:00:00 2001 From: janitha09 Date: Tue, 12 Mar 2019 10:12:58 -0700 Subject: [PATCH 06/17] docs: update HOWTO.md (#831) --- HOWTO.md | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) 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: From 972b610b5d8288e945ee8db3cd71dd0e8e1fff15 Mon Sep 17 00:00:00 2001 From: Takahiro Horike Date: Sat, 16 Mar 2019 02:25:26 +0900 Subject: [PATCH 07/17] feat: add API Gateway IAM (AWS_IAM) Authorizers (#827) --- examples/2016-10-31/api_aws_iam_auth/index.js | 7 + .../2016-10-31/api_aws_iam_auth/template.yaml | 30 + samtranslator/model/api/api_generator.py | 22 +- samtranslator/model/apigateway.py | 26 +- samtranslator/model/eventsources/push.py | 47 +- samtranslator/swagger/swagger.py | 41 +- tests/swagger/test_swagger.py | 31 + .../api_with_aws_iam_auth_overrides.yaml | 86 +++ .../input/api_with_default_aws_iam_auth.yaml | 47 ++ .../input/api_with_method_aws_iam_auth.yaml | 39 ++ .../api_with_aws_iam_auth_overrides.json | 595 +++++++++++++++++ .../output/api_with_default_aws_iam_auth.json | 369 +++++++++++ .../output/api_with_method_aws_iam_auth.json | 273 ++++++++ .../api_with_aws_iam_auth_overrides.json | 603 ++++++++++++++++++ .../aws-cn/api_with_default_aws_iam_auth.json | 393 ++++++++++++ .../aws-cn/api_with_method_aws_iam_auth.json | 281 ++++++++ .../api_with_aws_iam_auth_overrides.json | 603 ++++++++++++++++++ .../api_with_default_aws_iam_auth.json | 393 ++++++++++++ .../api_with_method_aws_iam_auth.json | 281 ++++++++ tests/translator/test_translator.py | 16 +- versions/2016-10-31.md | 6 +- 21 files changed, 4139 insertions(+), 50 deletions(-) create mode 100644 examples/2016-10-31/api_aws_iam_auth/index.js create mode 100644 examples/2016-10-31/api_aws_iam_auth/template.yaml create mode 100644 tests/translator/input/api_with_aws_iam_auth_overrides.yaml create mode 100644 tests/translator/input/api_with_default_aws_iam_auth.yaml create mode 100644 tests/translator/input/api_with_method_aws_iam_auth.yaml create mode 100644 tests/translator/output/api_with_aws_iam_auth_overrides.json create mode 100644 tests/translator/output/api_with_default_aws_iam_auth.json create mode 100644 tests/translator/output/api_with_method_aws_iam_auth.json create mode 100644 tests/translator/output/aws-cn/api_with_aws_iam_auth_overrides.json create mode 100644 tests/translator/output/aws-cn/api_with_default_aws_iam_auth.json create mode 100644 tests/translator/output/aws-cn/api_with_method_aws_iam_auth.json create mode 100644 tests/translator/output/aws-us-gov/api_with_aws_iam_auth_overrides.json create mode 100644 tests/translator/output/aws-us-gov/api_with_default_aws_iam_auth.json create mode 100644 tests/translator/output/aws-us-gov/api_with_method_aws_iam_auth.json 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/samtranslator/model/api/api_generator.py b/samtranslator/model/api/api_generator.py index 9392f52b4..30238d77d 100644 --- a/samtranslator/model/api/api_generator.py +++ b/samtranslator/model/api/api_generator.py @@ -18,8 +18,8 @@ # 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) class ApiGenerator(object): @@ -266,7 +266,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,14 +275,23 @@ def _add_auth(self): # Assign the Swagger back to template self.definition_body = swagger_editor.swagger - def _get_authorizers(self, authorizers_config): + 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(): authorizers[authorizer_name] = ApiGatewayAuthorizer( @@ -294,7 +303,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 +354,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..7e8a135a1 100644 --- a/samtranslator/model/apigateway.py +++ b/samtranslator/model/apigateway.py @@ -97,7 +97,7 @@ 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 +113,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 +136,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 +221,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 +249,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..51fa80fbd 100644 --- a/samtranslator/model/eventsources/push.py +++ b/samtranslator/model/eventsources/push.py @@ -529,7 +529,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 +538,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/swagger/swagger.py b/samtranslator/swagger/swagger.py index d69040c8d..b04fb6149 100644 --- a/samtranslator/swagger/swagger.py +++ b/samtranslator/swagger/swagger.py @@ -129,7 +129,8 @@ def add_path(self, path, method=None): 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 +157,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 +179,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, @@ -409,7 +426,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 +434,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 +492,22 @@ 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) + @property def swagger(self): """ diff --git a/tests/swagger/test_swagger.py b/tests/swagger/test_swagger.py index 4c78f3115..b484380d8 100644 --- a/tests/swagger/test_swagger.py +++ b/tests/swagger/test_swagger.py @@ -327,6 +327,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.assertEquals(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.assertEquals(expected, actual) + class TestSwaggerEditor_iter_on_path(TestCase): 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_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/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_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/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_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-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_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/test_translator.py b/tests/translator/test_translator.py index 29529edc5..3bc601144 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 @@ -156,6 +156,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', @@ -877,4 +880,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 528b3c634..271c3c1b9 100644 --- a/versions/2016-10-31.md +++ b/versions/2016-10-31.md @@ -771,11 +771,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 @@ -815,7 +815,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: From 22affd657b2bd4c0689b438ccc03b80d600e9761 Mon Sep 17 00:00:00 2001 From: Lu Hong Date: Fri, 15 Mar 2019 11:06:37 -0700 Subject: [PATCH 08/17] feat: add support for using 'AWS::Region' in Ref and Sub (#855) --- samtranslator/intrinsics/actions.py | 2 +- samtranslator/translator/translator.py | 8 +++++ tests/intrinsics/test_actions.py | 17 ++++++++++ .../input/layers_with_intrinsics.yaml | 12 +++++++ .../output/aws-cn/layers_with_intrinsics.json | 24 ++++++++++++++ .../aws-us-gov/layers_with_intrinsics.json | 24 ++++++++++++++ .../output/layers_with_intrinsics.json | 24 ++++++++++++++ tests/translator/test_translator.py | 33 +++++++++++++++++++ 8 files changed, 143 insertions(+), 1 deletion(-) diff --git a/samtranslator/intrinsics/actions.py b/samtranslator/intrinsics/actions.py index 8803dddbe..391df5175 100644 --- a/samtranslator/intrinsics/actions.py +++ b/samtranslator/intrinsics/actions.py @@ -371,7 +371,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. diff --git a/samtranslator/translator/translator.py b/samtranslator/translator/translator.py index 49cf2e746..d398841d3 100644 --- a/samtranslator/translator/translator.py +++ b/samtranslator/translator/translator.py @@ -1,4 +1,5 @@ import copy +import boto3 from samtranslator.model import ResourceTypeResolver, sam_resources from samtranslator.translator.verify_logical_id import verify_unique_logical_id @@ -48,6 +49,7 @@ def translate(self, sam_template, parameter_values): # Create & Install plugins sam_plugins = prepare_plugins(self.plugins) parameter_values = self._add_default_parameter_values(sam_template, parameter_values) + parameter_values = self._add_pseudo_parameter_values(parameter_values) self.sam_parser.parse( sam_template=sam_template, @@ -205,6 +207,12 @@ def _add_default_parameter_values(self, sam_template, parameter_values): return default_values + def _add_pseudo_parameter_values(self, parameter_values): + updated_parameter_values = copy.deepcopy(parameter_values) + if 'AWS::Region' not in updated_parameter_values: + updated_parameter_values['AWS::Region'] = boto3.session.Session().region_name + return updated_parameter_values + def prepare_plugins(plugins): """ diff --git a/tests/intrinsics/test_actions.py b/tests/intrinsics/test_actions.py index ed843d3ac..57100776b 100644 --- a/tests/intrinsics/test_actions.py +++ b/tests/intrinsics/test_actions.py @@ -419,6 +419,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") 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/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/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/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_translator.py b/tests/translator/test_translator.py index 3bc601144..c14454ffe 100644 --- a/tests/translator/test_translator.py +++ b/tests/translator/test_translator.py @@ -731,6 +731,39 @@ def test_add_default_parameter_values_must_ignore_invalid_template_parameters(se sam_template, parameter_values) self.assertEqual(expected, result) + @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_parser = Parser() + translator = Translator({}, sam_parser) + result = translator._add_pseudo_parameter_values(parameter_values) + self.assertEqual(expected, result) + + @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_parser = Parser() + translator = Translator({}, sam_parser) + result = translator._add_pseudo_parameter_values(parameter_values) + self.assertEqual(expected, result) + class TestTemplateValidation(TestCase): From a51ba12fe79d379bd42b7a40d39c06db5610f775 Mon Sep 17 00:00:00 2001 From: Shakir James Date: Mon, 18 Mar 2019 15:47:59 -0400 Subject: [PATCH 09/17] fix: ElasticsearchHttpPostPolicy resource reference (#858) --- samtranslator/policy_templates_data/policy_templates.json | 2 +- tests/translator/output/all_policy_templates.json | 2 +- tests/translator/output/aws-cn/all_policy_templates.json | 2 +- tests/translator/output/aws-us-gov/all_policy_templates.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/samtranslator/policy_templates_data/policy_templates.json b/samtranslator/policy_templates_data/policy_templates.json index 89e28d50f..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" diff --git a/tests/translator/output/all_policy_templates.json b/tests/translator/output/all_policy_templates.json index e9ddbafcc..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" } diff --git a/tests/translator/output/aws-cn/all_policy_templates.json b/tests/translator/output/aws-cn/all_policy_templates.json index c84682a38..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" } 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 174b46eb6..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" } From b7c69866970368e0e7521023b714a9fee0f0caa7 Mon Sep 17 00:00:00 2001 From: Chris/0 Date: Tue, 19 Mar 2019 13:53:09 -0400 Subject: [PATCH 10/17] feat(gatewayresponses): add support for API Gateway Responses (#841) --- docs/globals.rst | 1 + .../api_gateway_responses/src/index.js | 10 + .../api_gateway_responses/template.yaml | 48 +++++ samtranslator/model/api/api_generator.py | 54 +++++- samtranslator/model/apigateway.py | 51 ++++++ samtranslator/model/sam_resources.py | 4 +- samtranslator/plugins/globals/globals.py | 3 +- samtranslator/swagger/swagger.py | 21 ++- .../input/api_with_gateway_responses.yaml | 28 +++ .../input/api_with_gateway_responses_all.yaml | 37 ++++ .../api_with_gateway_responses_implicit.yaml | 27 +++ .../api_with_gateway_responses_minimal.yaml | 21 +++ ..._gateway_responses_string_status_code.yaml | 28 +++ ...eway_responses_nonnumeric_status_code.yaml | 28 +++ ...y_responses_unknown_responseparameter.yaml | 28 +++ ...es_unknown_responseparameter_property.yaml | 29 +++ .../output/api_with_gateway_responses.json | 154 ++++++++++++++++ .../api_with_gateway_responses_all.json | 163 +++++++++++++++++ .../api_with_gateway_responses_implicit.json | 155 ++++++++++++++++ .../api_with_gateway_responses_minimal.json | 149 +++++++++++++++ ..._gateway_responses_string_status_code.json | 154 ++++++++++++++++ .../aws-cn/api_with_gateway_responses.json | 162 +++++++++++++++++ .../api_with_gateway_responses_all.json | 171 ++++++++++++++++++ .../api_with_gateway_responses_implicit.json | 163 +++++++++++++++++ .../api_with_gateway_responses_minimal.json | 157 ++++++++++++++++ ..._gateway_responses_string_status_code.json | 162 +++++++++++++++++ .../api_with_gateway_responses.json | 162 +++++++++++++++++ .../api_with_gateway_responses_all.json | 171 ++++++++++++++++++ .../api_with_gateway_responses_implicit.json | 163 +++++++++++++++++ .../api_with_gateway_responses_minimal.json | 157 ++++++++++++++++ ..._gateway_responses_string_status_code.json | 162 +++++++++++++++++ ...eway_responses_nonnumeric_status_code.json | 1 + ...y_responses_unknown_responseparameter.json | 1 + ...es_unknown_responseparameter_property.json | 1 + .../error_globals_api_with_stage_name.json | 4 +- tests/translator/test_translator.py | 10 +- versions/2016-10-31.md | 32 ++++ 37 files changed, 2861 insertions(+), 11 deletions(-) create mode 100644 examples/2016-10-31/api_gateway_responses/src/index.js create mode 100644 examples/2016-10-31/api_gateway_responses/template.yaml create mode 100644 tests/translator/input/api_with_gateway_responses.yaml create mode 100644 tests/translator/input/api_with_gateway_responses_all.yaml create mode 100644 tests/translator/input/api_with_gateway_responses_implicit.yaml create mode 100644 tests/translator/input/api_with_gateway_responses_minimal.yaml create mode 100644 tests/translator/input/api_with_gateway_responses_string_status_code.yaml create mode 100644 tests/translator/input/error_api_gateway_responses_nonnumeric_status_code.yaml create mode 100644 tests/translator/input/error_api_gateway_responses_unknown_responseparameter.yaml create mode 100644 tests/translator/input/error_api_gateway_responses_unknown_responseparameter_property.yaml create mode 100644 tests/translator/output/api_with_gateway_responses.json create mode 100644 tests/translator/output/api_with_gateway_responses_all.json create mode 100644 tests/translator/output/api_with_gateway_responses_implicit.json create mode 100644 tests/translator/output/api_with_gateway_responses_minimal.json create mode 100644 tests/translator/output/api_with_gateway_responses_string_status_code.json create mode 100644 tests/translator/output/aws-cn/api_with_gateway_responses.json create mode 100644 tests/translator/output/aws-cn/api_with_gateway_responses_all.json create mode 100644 tests/translator/output/aws-cn/api_with_gateway_responses_implicit.json create mode 100644 tests/translator/output/aws-cn/api_with_gateway_responses_minimal.json create mode 100644 tests/translator/output/aws-cn/api_with_gateway_responses_string_status_code.json create mode 100644 tests/translator/output/aws-us-gov/api_with_gateway_responses.json create mode 100644 tests/translator/output/aws-us-gov/api_with_gateway_responses_all.json create mode 100644 tests/translator/output/aws-us-gov/api_with_gateway_responses_implicit.json create mode 100644 tests/translator/output/aws-us-gov/api_with_gateway_responses_minimal.json create mode 100644 tests/translator/output/aws-us-gov/api_with_gateway_responses_string_status_code.json create mode 100644 tests/translator/output/error_api_gateway_responses_nonnumeric_status_code.json create mode 100644 tests/translator/output/error_api_gateway_responses_unknown_responseparameter.json create mode 100644 tests/translator/output/error_api_gateway_responses_unknown_responseparameter_property.json diff --git a/docs/globals.rst b/docs/globals.rst index 05f473be9..e7c8afa14 100644 --- a/docs/globals.rst +++ b/docs/globals.rst @@ -82,6 +82,7 @@ Currently, the following resources and properties are being supported: BinaryMediaTypes: MinimumCompressionSize: Cors: + GatewayResponses: AccessLogSetting: CanarySetting: TracingEnabled: diff --git a/examples/2016-10-31/api_gateway_responses/src/index.js b/examples/2016-10-31/api_gateway_responses/src/index.js new file mode 100644 index 000000000..a6fe27fff --- /dev/null +++ b/examples/2016-10-31/api_gateway_responses/src/index.js @@ -0,0 +1,10 @@ +'use strict'; +const createResponse = (statusCode, body) => ({ statusCode, body }); + +exports.get = (event, context, callback) => { + callback(null, createResponse(200, 'You will never see this.')); +}; + +exports.auth = (event, context, callback) => { + return callback('Unauthorized', null) +}; 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..0f094e558 --- /dev/null +++ b/examples/2016-10-31/api_gateway_responses/template.yaml @@ -0,0 +1,48 @@ +AWSTemplateFormatVersion: "2010-09-09" +Transform: AWS::Serverless-2016-10-31 +Description: Simple webservice deomnstrating gateway responses. + +Resources: + ExplicitApi: + Type: AWS::Serverless::Api + Properties: + Auth: + Authorizers: + Authorizer: + FunctionArn: !GetAtt AuthorizerFunction.Arn + Identity: + ValidationExpression: "^Bearer +[-0-9a-zA-Z\\._]*$" + ReauthorizeEvery: 300 + GatewayResponses: + UNAUTHORIZED: + ResponseParameters: + Headers: + Access-Control-Expose-Headers: "'WWW-Authenticate'" + Access-Control-Allow-Origin: "'*'" + WWW-Authenticate: >- + 'Bearer realm="admin"' + GetFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.get + Runtime: nodejs6.10 + CodeUri: src/ + Events: + GetResource: + Type: Api + Properties: + Path: /resource/{resourceId} + Method: get + Auth: + Authorizer: Authorizer + RestApiId: !Ref ExplicitApi + AuthorizerFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.auth + Runtime: nodejs6.10 + CodeUri: src/ +Outputs: + ApiURL: + Description: "API endpoint URL for Prod environment" + Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/resource/" diff --git a/samtranslator/model/api/api_generator.py b/samtranslator/model/api/api_generator.py index 30238d77d..3f9da6259 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 @@ -21,14 +22,16 @@ AuthProperties = namedtuple("_AuthProperties", ["Authorizers", "DefaultAuthorizer", "InvokeRole"]) AuthProperties.__new__.__defaults__ = (None, None, None) +GatewayResponseProperties = ["ResponseParameters", "ResponseTemplates", "StatusCode"] + 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() @@ -275,6 +280,49 @@ def _add_auth(self): # Assign the Swagger back to template self.definition_body = swagger_editor.swagger + 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': diff --git a/samtranslator/model/apigateway.py b/samtranslator/model/apigateway.py index 7e8a135a1..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,6 +95,55 @@ 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'] diff --git a/samtranslator/model/sam_resources.py b/samtranslator/model/sam_resources.py index 5b8da4fb3..07c050b31 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 @@ -442,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)) @@ -477,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/plugins/globals/globals.py b/samtranslator/plugins/globals/globals.py index b4cab7d7e..eb44b26e2 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 @@ -49,6 +49,7 @@ class Globals(object): "BinaryMediaTypes", "MinimumCompressionSize", "Cors", + "GatewayResponses", "AccessLogSetting", "CanarySetting", "TracingEnabled" diff --git a/samtranslator/swagger/swagger.py b/samtranslator/swagger/swagger.py index b04fb6149..d8b99e5b3 100644 --- a/samtranslator/swagger/swagger.py +++ b/samtranslator/swagger/swagger.py @@ -1,4 +1,4 @@ -import copy +import copy from six import string_types from samtranslator.model.intrinsics import ref @@ -16,6 +16,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 +34,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) @@ -386,8 +388,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): """ @@ -508,6 +510,17 @@ def set_method_authorizer(self, path, method_name, authorizer_name, authorizers, 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): """ @@ -521,6 +534,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/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/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/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/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-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/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_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/test_translator.py b/tests/translator/test_translator.py index c14454ffe..031bc2f69 100644 --- a/tests/translator/test_translator.py +++ b/tests/translator/test_translator.py @@ -170,6 +170,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', @@ -248,7 +253,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() @@ -343,6 +348,9 @@ 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_definitionuri', 'error_api_invalid_definitionbody', diff --git a/versions/2016-10-31.md b/versions/2016-10-31.md index 271c3c1b9..c487ad7cb 100644 --- a/versions/2016-10-31.md +++ b/versions/2016-10-31.md @@ -227,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. @@ -691,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 @@ -824,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 From 203bce10076024a0f98a44f46b9a1b5e902c603c Mon Sep 17 00:00:00 2001 From: Lu Hong Date: Tue, 19 Mar 2019 12:02:23 -0700 Subject: [PATCH 11/17] feat: add support for FindInMap in serverless app plugin (#856) --- samtranslator/intrinsics/actions.py | 44 ++++ .../application/serverless_app_plugin.py | 29 ++- samtranslator/translator/translator.py | 11 +- tests/intrinsics/test_actions.py | 201 +++++++++++++++++- .../application/test_serverless_app_plugin.py | 23 ++ .../input/application_with_intrinsics.yaml | 40 ++++ .../output/application_with_intrinsics.json | 70 ++++++ .../aws-cn/application_with_intrinsics.json | 70 ++++++ .../application_with_intrinsics.json | 70 ++++++ tests/translator/test_translator.py | 3 +- 10 files changed, 551 insertions(+), 10 deletions(-) create mode 100644 tests/translator/input/application_with_intrinsics.yaml create mode 100644 tests/translator/output/application_with_intrinsics.json create mode 100644 tests/translator/output/aws-cn/application_with_intrinsics.json create mode 100644 tests/translator/output/aws-us-gov/application_with_intrinsics.json diff --git a/samtranslator/intrinsics/actions.py b/samtranslator/intrinsics/actions.py index 391df5175..a70745502 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 class Action(object): @@ -503,3 +504,46 @@ 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 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/plugins/application/serverless_app_plugin.py b/samtranslator/plugins/application/serverless_app_plugin.py index 420aa72f7..b61730c9b 100644 --- a/samtranslator/plugins/application/serverless_app_plugin.py +++ b/samtranslator/plugins/application/serverless_app_plugin.py @@ -2,12 +2,15 @@ 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 +38,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 +53,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 +72,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,8 +84,11 @@ 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) + key = (app_id, semver) if key not in self._applications: try: @@ -92,6 +100,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 diff --git a/samtranslator/translator/translator.py b/samtranslator/translator/translator.py index d398841d3..f14f2d448 100644 --- a/samtranslator/translator/translator.py +++ b/samtranslator/translator/translator.py @@ -46,10 +46,10 @@ 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 """ - # Create & Install plugins - sam_plugins = prepare_plugins(self.plugins) parameter_values = self._add_default_parameter_values(sam_template, parameter_values) parameter_values = self._add_pseudo_parameter_values(parameter_values) + # Create & Install plugins + sam_plugins = prepare_plugins(self.plugins, parameter_values) self.sam_parser.parse( sam_template=sam_template, @@ -214,12 +214,13 @@ def _add_pseudo_parameter_values(self, parameter_values): return updated_parameter_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` """ @@ -234,7 +235,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 57100776b..05e9bb11b 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 class TestAction(TestCase): @@ -932,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(InvalidTemplateException): + self.ref.resolve_parameter_refs(input, {}) + + def test_value_not_list_of_length_three(self): + input = { + "Fn::FindInMap": ["a string"] + } + + with self.assertRaises(InvalidTemplateException): + 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/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/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/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/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-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/test_translator.py b/tests/translator/test_translator.py index 031bc2f69..715a07870 100644 --- a/tests/translator/test_translator.py +++ b/tests/translator/test_translator.py @@ -142,6 +142,7 @@ class TestTranslatorEndToEnd(TestCase): 'basic_function', 'basic_application', 'application_preparing_state', + 'application_with_intrinsics', 'basic_layer', 'cloudwatchevent', 'cloudwatch_logs_with_ref', @@ -896,7 +897,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() From f0e93f17b3adaa7e72d6f084721b2ce982d35973 Mon Sep 17 00:00:00 2001 From: Brett Andrews Date: Tue, 19 Mar 2019 12:02:50 -0700 Subject: [PATCH 12/17] fix: add additional validation logic (#860) --- samtranslator/model/api/api_generator.py | 4 ++ samtranslator/parser/parser.py | 7 +++ .../plugins/api/implicit_api_plugin.py | 9 ++++ tests/plugins/api/test_implicit_api_plugin.py | 48 +++++++++++++++++ .../input/error_api_invalid_auth.yaml | 10 +++- .../error_function_invalid_api_event.yaml | 52 +++++++++++++++++++ .../input/error_resource_not_dict.yaml | 6 +++ .../output/error_api_invalid_auth.json | 2 +- .../error_function_invalid_api_event.json | 8 +++ .../output/error_resource_not_dict.json | 8 +++ tests/translator/test_translator.py | 17 ++++++ 11 files changed, 169 insertions(+), 2 deletions(-) create mode 100644 tests/translator/input/error_function_invalid_api_event.yaml create mode 100644 tests/translator/input/error_resource_not_dict.yaml create mode 100644 tests/translator/output/error_function_invalid_api_event.json create mode 100644 tests/translator/output/error_resource_not_dict.json diff --git a/samtranslator/model/api/api_generator.py b/samtranslator/model/api/api_generator.py index 3f9da6259..4946f4d92 100644 --- a/samtranslator/model/api/api_generator.py +++ b/samtranslator/model/api/api_generator.py @@ -342,6 +342,10 @@ def _get_authorizers(self, authorizers_config, default_authorizer=None): "Authorizers must be a dictionary") 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, diff --git a/samtranslator/parser/parser.py b/samtranslator/parser/parser.py index 52f594dc7..a8eebfadd 100644 --- a/samtranslator/parser/parser.py +++ b/samtranslator/parser/parser.py @@ -26,4 +26,11 @@ def _validate(self, sam_template, parameter_values): raise InvalidDocumentException( [InvalidTemplateException("'Resources' section is required")]) + if (not all(isinstance(value, dict) for value in sam_template["Resources"].values())): + raise InvalidDocumentException( + [InvalidTemplateException( + "All 'Resources' must be Objects. 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 38b3f71d2..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 @@ -141,6 +142,14 @@ def _process_api_events(self, function, api_events, template, condition=None): 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/tests/plugins/api/test_implicit_api_plugin.py b/tests/plugins/api/test_implicit_api_plugin.py index 67b9f7366..4dfa09a18 100644 --- a/tests/plugins/api/test_implicit_api_plugin.py +++ b/tests/plugins/api/test_implicit_api_plugin.py @@ -423,6 +423,54 @@ def test_must_verify_expected_keys_exist(self): 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/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_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_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/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_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_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/test_translator.py b/tests/translator/test_translator.py index 715a07870..a032ede88 100644 --- a/tests/translator/test_translator.py +++ b/tests/translator/test_translator.py @@ -365,6 +365,7 @@ def _generate_new_deployment_hash(self, logical_id, dict_to_hash, rest_api_to_sw '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_event_type', 'error_function_invalid_layer', 'error_function_no_codeuri', @@ -386,6 +387,7 @@ 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_globals_is_not_dict', 'error_globals_unsupported_type', 'error_globals_unsupported_property', @@ -810,6 +812,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 From 31d9d5507bc44a1b30c2b65b5f84ff9619dce3fe Mon Sep 17 00:00:00 2001 From: Lu Hong Date: Tue, 19 Mar 2019 15:31:52 -0700 Subject: [PATCH 13/17] chore: update version to 1.11.0 (#861) --- samtranslator/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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' From 34429067b3f4f880be39738dbda025f1ebd3c275 Mon Sep 17 00:00:00 2001 From: Lu Hong Date: Fri, 22 Mar 2019 10:05:38 -0700 Subject: [PATCH 14/17] refactor: move methods that modify parameter values to a separate class (#862) --- samtranslator/public/sdk/parameter.py | 3 + samtranslator/sdk/parameter.py | 67 ++++++++++++ samtranslator/translator/translator.py | 64 +---------- tests/sdk/test_parameter.py | 136 +++++++++++++++++++++++ tests/translator/test_translator.py | 146 ------------------------- 5 files changed, 211 insertions(+), 205 deletions(-) create mode 100644 samtranslator/public/sdk/parameter.py create mode 100644 samtranslator/sdk/parameter.py create mode 100644 tests/sdk/test_parameter.py 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/translator/translator.py b/samtranslator/translator/translator.py index f14f2d448..a0043ebf1 100644 --- a/samtranslator/translator/translator.py +++ b/samtranslator/translator/translator.py @@ -1,5 +1,4 @@ import copy -import boto3 from samtranslator.model import ResourceTypeResolver, sam_resources from samtranslator.translator.verify_logical_id import verify_unique_logical_id @@ -15,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: @@ -46,8 +46,10 @@ 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 """ - parameter_values = self._add_default_parameter_values(sam_template, parameter_values) - parameter_values = self._add_pseudo_parameter_values(parameter_values) + 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) @@ -157,62 +159,6 @@ 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 _add_pseudo_parameter_values(self, parameter_values): - updated_parameter_values = copy.deepcopy(parameter_values) - if 'AWS::Region' not in updated_parameter_values: - updated_parameter_values['AWS::Region'] = boto3.session.Session().region_name - return updated_parameter_values - def prepare_plugins(plugins, parameters={}): """ 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/translator/test_translator.py b/tests/translator/test_translator.py index a032ede88..62a8bd0ea 100644 --- a/tests/translator/test_translator.py +++ b/tests/translator/test_translator.py @@ -630,152 +630,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) - - @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_parser = Parser() - translator = Translator({}, sam_parser) - result = translator._add_pseudo_parameter_values(parameter_values) - self.assertEqual(expected, result) - - @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_parser = Parser() - translator = Translator({}, sam_parser) - result = translator._add_pseudo_parameter_values(parameter_values) - self.assertEqual(expected, result) - - class TestTemplateValidation(TestCase): @patch('botocore.client.ClientEndpointBridge._check_default_region', mock_get_region) From 0fc2ca63b96fa2181b0418cd870ae8b4620575a5 Mon Sep 17 00:00:00 2001 From: William Merz Date: Fri, 22 Mar 2019 10:19:53 -0700 Subject: [PATCH 15/17] feat: add ReservedConcurrentExecutions to globals (#808) --- docs/globals.rst | 1 + samtranslator/plugins/globals/globals.py | 3 ++- tests/translator/input/globals_for_function.yaml | 2 ++ tests/translator/output/aws-cn/globals_for_function.json | 2 ++ tests/translator/output/aws-us-gov/globals_for_function.json | 2 ++ .../translator/output/error_globals_unsupported_property.json | 4 ++-- tests/translator/output/globals_for_function.json | 2 ++ 7 files changed, 13 insertions(+), 3 deletions(-) diff --git a/docs/globals.rst b/docs/globals.rst index e7c8afa14..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 diff --git a/samtranslator/plugins/globals/globals.py b/samtranslator/plugins/globals/globals.py index eb44b26e2..f74029b99 100644 --- a/samtranslator/plugins/globals/globals.py +++ b/samtranslator/plugins/globals/globals.py @@ -30,7 +30,8 @@ class Globals(object): "AutoPublishAlias", "Layers", "DeploymentPreference", - "PermissionsBoundary" + "PermissionsBoundary", + "ReservedConcurrentExecutions" ], # Everything except 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/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-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/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/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", From 2ccd9e7a2924545e1f61eaafdf366442cef60113 Mon Sep 17 00:00:00 2001 From: Brett Andrews Date: Mon, 1 Apr 2019 11:29:27 -0700 Subject: [PATCH 16/17] feat: add additional validation and update bin/sam-translate.py (#873) bin/sam-translate.py updates: * Add additional commands * Update parameters to be consistent with SAM CLI * Add additional parameters * Improve logging * Fixes Validation/Bug fixes: * fix: add validation for SAR Plugin Location Properties * fix: add validation to ensure Resource Properties is a dict * fix: add validation for invalid FindInMap and GetAtt * fix: add validation for empty StageName * fix: raise InvalidDocumentException for Swagger invalid path - Previously this was raising ValueError * fix: change ValueError to InvalidResourceException for empty Alias name --- bin/sam-translate.py | 95 ++++++++++++++++--- samtranslator/intrinsics/actions.py | 12 ++- samtranslator/model/eventsources/push.py | 4 +- samtranslator/model/sam_resources.py | 2 +- samtranslator/parser/parser.py | 18 +++- .../application/serverless_app_plugin.py | 22 ++++- samtranslator/swagger/swagger.py | 5 +- tests/intrinsics/test_actions.py | 6 +- tests/swagger/test_swagger.py | 9 +- .../input/error_api_invalid_path.yaml | 10 ++ .../input/error_api_invalid_stagename.yaml | 20 ++++ .../input/error_application_properties.yaml | 11 ++- ...ror_function_invalid_autopublishalias.yaml | 19 ++++ .../input/error_invalid_findinmap.yaml | 39 ++++++++ .../input/error_invalid_getatt.yaml | 17 ++++ .../error_resource_properties_not_dict.yaml | 4 + .../output/error_api_invalid_path.json | 3 + .../output/error_api_invalid_stagename.json | 8 ++ .../output/error_application_properties.json | 4 +- ...ror_function_invalid_autopublishalias.json | 8 ++ .../output/error_invalid_findinmap.json | 8 ++ .../output/error_invalid_getatt.json | 8 ++ .../error_resource_properties_not_dict.json | 8 ++ tests/translator/test_function_resources.py | 3 +- tests/translator/test_translator.py | 6 ++ 25 files changed, 317 insertions(+), 32 deletions(-) create mode 100644 tests/translator/input/error_api_invalid_path.yaml create mode 100644 tests/translator/input/error_api_invalid_stagename.yaml create mode 100644 tests/translator/input/error_function_invalid_autopublishalias.yaml create mode 100644 tests/translator/input/error_invalid_findinmap.yaml create mode 100644 tests/translator/input/error_invalid_getatt.yaml create mode 100644 tests/translator/input/error_resource_properties_not_dict.yaml create mode 100644 tests/translator/output/error_api_invalid_path.json create mode 100644 tests/translator/output/error_api_invalid_stagename.json create mode 100644 tests/translator/output/error_function_invalid_autopublishalias.json create mode 100644 tests/translator/output/error_invalid_findinmap.json create mode 100644 tests/translator/output/error_invalid_getatt.json create mode 100644 tests/translator/output/error_resource_properties_not_dict.json diff --git a/bin/sam-translate.py b/bin/sam-translate.py index 4b0df0973..eda75b030 100755 --- a/bin/sam-translate.py +++ b/bin/sam-translate.py @@ -5,15 +5,25 @@ 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 @@ -23,24 +33,60 @@ 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 +102,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/samtranslator/intrinsics/actions.py b/samtranslator/intrinsics/actions.py index a70745502..d535305ed 100644 --- a/samtranslator/intrinsics/actions.py +++ b/samtranslator/intrinsics/actions.py @@ -1,7 +1,7 @@ import re from six import string_types -from samtranslator.model.exceptions import InvalidTemplateException +from samtranslator.model.exceptions import InvalidTemplateException, InvalidDocumentException class Action(object): @@ -427,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 @@ -529,8 +534,9 @@ def resolve_parameter_refs(self, input_dict, parameters): # FindInMap expects an array with 3 values if not isinstance(value, list) or len(value) != 3: - raise InvalidTemplateException('Invalid FindInMap value {}. FindInMap expects an array with 3 values.' - .format(value)) + 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) diff --git a/samtranslator/model/eventsources/push.py b/samtranslator/model/eventsources/push.py index 51fa80fbd..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" diff --git a/samtranslator/model/sam_resources.py b/samtranslator/model/sam_resources.py index 07c050b31..3e6ae61c2 100644 --- a/samtranslator/model/sam_resources.py +++ b/samtranslator/model/sam_resources.py @@ -381,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()) diff --git a/samtranslator/parser/parser.py b/samtranslator/parser/parser.py index a8eebfadd..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,11 +27,24 @@ def _validate(self, sam_template, parameter_values): raise InvalidDocumentException( [InvalidTemplateException("'Resources' section is required")]) - if (not all(isinstance(value, dict) for value in sam_template["Resources"].values())): + 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/application/serverless_app_plugin.py b/samtranslator/plugins/application/serverless_app_plugin.py index b61730c9b..ac419d9ba 100644 --- a/samtranslator/plugins/application/serverless_app_plugin.py +++ b/samtranslator/plugins/application/serverless_app_plugin.py @@ -1,4 +1,5 @@ import boto3 +import json from botocore.exceptions import ClientError, EndpointConnectionError import logging from time import sleep, time @@ -86,10 +87,17 @@ def on_before_transform_template(self, template_dict): 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 @@ -211,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/swagger/swagger.py b/samtranslator/swagger/swagger.py index d8b99e5b3..f1803f33d 100644 --- a/samtranslator/swagger/swagger.py +++ b/samtranslator/swagger/swagger.py @@ -3,6 +3,7 @@ from samtranslator.model.intrinsics import ref from samtranslator.model.intrinsics import make_conditional +from samtranslator.model.exceptions import InvalidDocumentException, InvalidTemplateException class SwaggerEditor(object): @@ -124,7 +125,9 @@ 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] diff --git a/tests/intrinsics/test_actions.py b/tests/intrinsics/test_actions.py index 05e9bb11b..81dad2abe 100644 --- a/tests/intrinsics/test_actions.py +++ b/tests/intrinsics/test_actions.py @@ -2,7 +2,7 @@ from mock import patch, Mock from samtranslator.intrinsics.actions import Action, RefAction, SubAction, GetAttAction, FindInMapAction from samtranslator.intrinsics.resource_refs import SupportedResourceReferences -from samtranslator.model.exceptions import InvalidTemplateException +from samtranslator.model.exceptions import InvalidTemplateException, InvalidDocumentException class TestAction(TestCase): @@ -954,7 +954,7 @@ def test_value_not_list(self): input = { "Fn::FindInMap": "a string" } - with self.assertRaises(InvalidTemplateException): + with self.assertRaises(InvalidDocumentException): self.ref.resolve_parameter_refs(input, {}) def test_value_not_list_of_length_three(self): @@ -962,7 +962,7 @@ def test_value_not_list_of_length_three(self): "Fn::FindInMap": ["a string"] } - with self.assertRaises(InvalidTemplateException): + with self.assertRaises(InvalidDocumentException): self.ref.resolve_parameter_refs(input, {}) def test_mapping_not_string(self): diff --git a/tests/swagger/test_swagger.py b/tests/swagger/test_swagger.py index b484380d8..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): @@ -339,7 +340,7 @@ def test_must_add_credentials_to_the_integration(self): self.editor.add_lambda_integration(path, method, integration_uri, None, api_auth_config) actual = self.editor.swagger["paths"][path][method][_X_INTEGRATION]['credentials'] - self.assertEquals(expected, actual) + self.assertEqual(expected, actual) def test_must_add_credentials_to_the_integration_overrides(self): path = "/newpath" @@ -356,7 +357,7 @@ def test_must_add_credentials_to_the_integration_overrides(self): 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.assertEquals(expected, actual) + self.assertEqual(expected, actual) class TestSwaggerEditor_iter_on_path(TestCase): @@ -430,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/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_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_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_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/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_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_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_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/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 62a8bd0ea..88657b15b 100644 --- a/tests/translator/test_translator.py +++ b/tests/translator/test_translator.py @@ -353,8 +353,10 @@ def _generate_new_deployment_hash(self, logical_id, dict_to_hash, rest_api_to_sw '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', @@ -362,10 +364,13 @@ 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', @@ -388,6 +393,7 @@ def _generate_new_deployment_hash(self, logical_id, dict_to_hash, rest_api_to_sw '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', From 7b16fbba67cfd691c29074b92e4da99c415864bb Mon Sep 17 00:00:00 2001 From: Brett Andrews Date: Tue, 2 Apr 2019 14:22:56 -0700 Subject: [PATCH 17/17] docs: fix/update GatewayResponses example (#879) --- bin/sam-translate.py | 3 ++ .../api_gateway_responses/src/index.js | 10 ------ .../api_gateway_responses/template.yaml | 31 +++++-------------- 3 files changed, 11 insertions(+), 33 deletions(-) delete mode 100644 examples/2016-10-31/api_gateway_responses/src/index.js diff --git a/bin/sam-translate.py b/bin/sam-translate.py index eda75b030..2483bd4e4 100755 --- a/bin/sam-translate.py +++ b/bin/sam-translate.py @@ -28,6 +28,9 @@ 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 diff --git a/examples/2016-10-31/api_gateway_responses/src/index.js b/examples/2016-10-31/api_gateway_responses/src/index.js deleted file mode 100644 index a6fe27fff..000000000 --- a/examples/2016-10-31/api_gateway_responses/src/index.js +++ /dev/null @@ -1,10 +0,0 @@ -'use strict'; -const createResponse = (statusCode, body) => ({ statusCode, body }); - -exports.get = (event, context, callback) => { - callback(null, createResponse(200, 'You will never see this.')); -}; - -exports.auth = (event, context, callback) => { - return callback('Unauthorized', null) -}; diff --git a/examples/2016-10-31/api_gateway_responses/template.yaml b/examples/2016-10-31/api_gateway_responses/template.yaml index 0f094e558..6b0bafcdb 100644 --- a/examples/2016-10-31/api_gateway_responses/template.yaml +++ b/examples/2016-10-31/api_gateway_responses/template.yaml @@ -3,46 +3,31 @@ Transform: AWS::Serverless-2016-10-31 Description: Simple webservice deomnstrating gateway responses. Resources: - ExplicitApi: + MyApi: Type: AWS::Serverless::Api Properties: - Auth: - Authorizers: - Authorizer: - FunctionArn: !GetAtt AuthorizerFunction.Arn - Identity: - ValidationExpression: "^Bearer +[-0-9a-zA-Z\\._]*$" - ReauthorizeEvery: 300 + StageName: Prod GatewayResponses: - UNAUTHORIZED: + DEFAULT_4xx: ResponseParameters: Headers: Access-Control-Expose-Headers: "'WWW-Authenticate'" Access-Control-Allow-Origin: "'*'" - WWW-Authenticate: >- - 'Bearer realm="admin"' + GetFunction: Type: AWS::Serverless::Function Properties: Handler: index.get Runtime: nodejs6.10 - CodeUri: src/ + InlineCode: module.exports = async () => throw new Error('Check out the response headers!') Events: GetResource: Type: Api Properties: - Path: /resource/{resourceId} + Path: /error Method: get - Auth: - Authorizer: Authorizer - RestApiId: !Ref ExplicitApi - AuthorizerFunction: - Type: AWS::Serverless::Function - Properties: - Handler: index.auth - Runtime: nodejs6.10 - CodeUri: src/ + RestApiId: !Ref MyApi Outputs: ApiURL: Description: "API endpoint URL for Prod environment" - Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/resource/" + Value: !Sub "https://${MyApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/error/"